Skip to content

Commit

Permalink
initial working MemServer.Server. More tests coming soon.
Browse files Browse the repository at this point in the history
  • Loading branch information
izelnakri committed Oct 29, 2017
1 parent ac5b491 commit 40f52b2
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 59 deletions.
1 change: 0 additions & 1 deletion TODO
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
- create a cli
- serialization embedding
- derived schema attribute for $Model:
schema: {
inserted_at: {
Expand Down
47 changes: 27 additions & 20 deletions lib/mem-server/pretender-hacks.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import qs from 'qs';

// HACK START: Pretender Request Parameter Type Casting Hack: Because types are important.
window.Pretender.prototype._handlerFor = function(verb, url, request) {
var registry = this.hosts.forURL(url)[verb];

var matches = registry.recognize(window.Pretender.parseURL(url).fullpath);
var match = matches ? matches[0] : null;

if (match) {
request.headers = request.requestHeaders;
request.params = Object.keys(match.params).reduce((result, key) => {
var value = match.params[key];

Expand All @@ -15,6 +19,12 @@ window.Pretender.prototype._handlerFor = function(verb, url, request) {

return Object.assign(result, { [key]: parseInt(value, 10) || value });
}, {});

if (request.requestBody && request.requestHeaders['Content-Type'] === 'application/json') {
request.params = Object.assign(request.params, JSON.parse(request.requestBody));
} else {
request.params = Object.assign(request.params, qs.parse(request.requestBody ));
}
}

return match;
Expand All @@ -28,11 +38,9 @@ window.Pretender.prototype.handleRequest = function(request) {
var path = request.url;
var handler = this._handlerFor(verb, path, request);

console.log('HANDLER REQUST IS CALLED');
console.log('handler:');
console.log(handler);
var _handleRequest = function(result) {
var statusCode, headers, body;

if (Array.isArray(result) && result.length === 3) {
statusCode = result[0],
headers = pretender.prepareHeaders(result[1]),
Expand All @@ -43,26 +51,30 @@ window.Pretender.prototype.handleRequest = function(request) {
pretender.handledRequest(verb, path, request);
})
} else if (!result) {
if (verb === 'DELETE') {
headers = pretender.prepareHeaders({ 'Content-Type': 'application/json' });
headers = pretender.prepareHeaders({ 'Content-Type': 'application/json' });

if (verb === 'DELETE') {
return pretender.handleResponse(request, async, function() {
request.respond(204, headers, pretender.prepareBody({}, headers));
request.respond(204, headers, pretender.prepareBody('{}', headers));
pretender.handledRequest(verb, path, request);
})
}

throw new Error('Nothing returned by handler for ' + path +
'. Remember to return something in your route handler.');
return pretender.handleResponse(request, async, function() {
request.respond(500, headers, pretender.prepareBody({
error: 'MemServer didnt handle this route!'
}, headers));
pretender.handledRequest(verb, path, request);
});
}

var targetResult = typeof result === 'string' ? result : JSON.stringify(result);

statusCode = getDefaultStatusCode(verb);
headers = pretender.prepareHeaders({ 'Content-Type': 'application/json' });
body = pretender.prepareBody(targetResult, headers);
console.log('UPCOMING RESPONSE PARAMS', statusCode, headers, body);
pretender.handleResponse(request, async, function() {

return pretender.handleResponse(request, async, function() {
request.respond(statusCode, headers, body);
pretender.handledRequest(verb, path, request);
})
Expand All @@ -73,17 +85,12 @@ window.Pretender.prototype.handleRequest = function(request) {
var async = handler.handler.async;
this.handledRequests.push(request);

try {
var result = handler.handler(request);
var result = handler.handler(request);

if (result && typeof result.then === 'function') { // `result` is a promise, resolve it
result.then(function(resolvedResult) { _handleRequest(resolvedResult); });
} else {
_handleRequest(result);
}
} catch (error) {
this.erroredRequest(verb, path, request, error);
this.resolve(request);
if (result && typeof result.then === 'function') { // `result` is a promise, resolve it
result.then(function(resolvedResult) { _handleRequest(resolvedResult); });
} else {
_handleRequest(result);
}
} else {
if (!this.disableUnhandled) {
Expand Down
7 changes: 6 additions & 1 deletion lib/mem-server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default function(options) {
console.log(chalk.red('UNHANDLED REQUEST WAS:\n'), request);
console.log(request);
}
});
}, { trackRequests: false });

// NOTE: maybe passthrough() api here
// NOTE: maybe this.resource()
Expand All @@ -46,6 +46,8 @@ export default function(options) {
function colorStatusCode(statusCode) {
if (statusCode === 200 || statusCode === 201) {
return chalk.green(statusCode);
} else if (statusCode === 404 || statusCode === 204) {
return chalk.cyan(statusCode);
}

return chalk.red(statusCode);
Expand All @@ -54,6 +56,9 @@ function colorStatusCode(statusCode) {
function getDefaultRouteHandler(verb, path) {
const paths = path.split(/\//g);
const resourceReference = paths[paths.length - 1];
console.log('resourceReference');
console.log(resourceReference);

const ResourceModel = undefined; // TODO: change this
// TODO: if resourceModel not found throw error?

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"i": "^0.3.6",
"jsdom": "^11.3.0",
"pretender": "^1.6.0",
"route-recognizer": "^0.3.3"
"route-recognizer": "^0.3.3",
"qs": "^6.5.1"
},
"devDependencies": {
"faker": "^4.1.0",
Expand Down
134 changes: 99 additions & 35 deletions test/mem-server.server.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ describe('MemServer.Server functionality', function() {
import Model from '${process.cwd()}/lib/mem-server/model';
export default Model({
findFromToken(request) {
const authorizationHeader = request.requestHeaders.Authorization;
findFromHeaderToken(headers) {
const authorizationHeader = headers.Authorization;
const token = authorizationHeader ? authorizationHeader.slice(6) : null;
return this.findBy({ authentication_token: token });
Expand Down Expand Up @@ -243,8 +243,8 @@ describe('MemServer.Server functionality', function() {
import Response from '../lib/mem-server/response';
export default function({ User, Photo }) {
this.post('/photos', (request) => {
const user = User.findFromToken(request);
this.post('/photos', ({ headers }) => {
const user = User.findFromHeaderToken(headers);
console.log('user is', user);
if (!user) {
Expand All @@ -253,46 +253,52 @@ describe('MemServer.Server functionality', function() {
const photo = Photo.insert({ user_id: user.id });
return Photo.serialize(photo);
return { photo: Photo.serializer(photo) };
});
this.get('/photos', (request) => {
const user = User.findFromToken(request);
this.get('/photos', ({ headers }) => {
const user = User.findFromHeaderToken(headers);
if (!user) {
return Response(404, { error: 'Not found' });
}
const photos = Photo.findAll({ user_id: user.id });
return Photo.serialize(photos);
return { photos: Photo.serializer(photos) };
});
this.get('/photos/:id', ({ params, headers }) => {
const user = User.findFromToken(request);
this.get('/photos/:id', ({ headers, params }) => {
const user = User.findFromHeaderToken(headers);
if (!user) {
return Response(401, { error: 'Unauthorized' });
}
const photo = Photo.findBy({ id: params.id, user_id: user.id });
return photo ? Photo.serialize(photo) : Response(404, { error: 'Not found'})
return photo ? { photo: Photo.serializer(photo) } : Response(404, { error: 'Not found'});
});
this.put('/photos/:id', ({ params, headers }) => {
const user = User.findFromToken(request);
this.put('/photos/:id', ({ headers, params }) => {
const user = User.findFromHeaderToken(headers);
if (user && Photo.findBy({ id: params.id, user_id: user.id })) {
return Photo.update(request.params);
if (!user) {
return Response(401, { error: 'Unauthorized' });
}
if (Photo.findBy({ id: params.id, user_id: user.id })) {
return { photo: Photo.update(params.photo) };
}
});
this.delete('/photos/:id', ({ params, headers }) => {
const user = User.findFromToken(request);
this.delete('/photos/:id', ({ headers, params }) => {
const user = User.findFromHeaderToken(headers);
console.log('called');
console.log(user);
if (user && Photo.findBy({ id: params.id, user_id: user.id })) {
return Photo.delete(request.params); // NOTE: what to do with this response
return Photo.delete({ id: params.id });
}
});
}
Expand All @@ -303,7 +309,7 @@ describe('MemServer.Server functionality', function() {
this.timeout(5000);

const MemServer = require('../index.js');
const { Photo, PhotoComment } = MemServer.Models;
const { Photo } = MemServer.Models;

MemServer.start();
window.$.ajaxSetup({ headers: { 'Content-Type': 'application/json' } });
Expand All @@ -319,13 +325,15 @@ describe('MemServer.Server functionality', function() {
type: 'POST', url: '/photos', headers: AJAX_AUTHORIZATION_HEADERS
}).then((data, textStatus, jqXHR) => {
assert.equal(jqXHR.status, 201);
assert.deepEqual(data, { is_public: true, name: 'Some default name', id: 4, user_id: 1 });
assert.deepEqual(data, {
photo: { is_public: true, name: 'Some default name', id: 4, user_id: 1 }
});
});
});

it('GET /resources works with custom headers and responses', async function() {
const MemServer = require('../index.js');
const { Photo, PhotoComment } = MemServer.Models;
const { Photo } = MemServer.Models;

MemServer.start();

Expand All @@ -336,7 +344,6 @@ describe('MemServer.Server functionality', function() {
assert.deepEqual(jqXHR.responseJSON, { error: 'Not found' });
});

// TODO: error here
await window.$.ajax({
type: 'GET', url: '/photos', headers: AJAX_AUTHORIZATION_HEADERS
}).then((data, textStatus, jqXHR) => {
Expand All @@ -345,19 +352,76 @@ describe('MemServer.Server functionality', function() {
});
});

// it('GET /resources/:id works with custom headers and responses', function() {
//
// });
//
// it('PUT /resources/:id works with custom headers and responses', function() {
//
// });
//
// it('DELETE /resources/:id works with custom headers and responses', function() {
//
// });
// });
});
it('GET /resources/:id works with custom headers and responses', async function() {
const MemServer = require('../index.js');
const { Photo } = MemServer.Models;

MemServer.start();

await window.$.ajax({
type: 'GET', url: '/photos/1', headers: { 'Content-Type': 'application/json' }
}).catch((jqXHR) => {
assert.equal(jqXHR.status, 401);
assert.deepEqual(jqXHR.responseJSON, { error: 'Unauthorized' });
});

await window.$.ajax({
type: 'GET', url: '/photos/1', headers: AJAX_AUTHORIZATION_HEADERS
}).then((data, textStatus, jqXHR) => {
assert.equal(jqXHR.status, 200);
assert.deepEqual(data, { photo: Photo.serializer(Photo.find(1))});
});
});

it('PUT /resources/:id works with custom headers and responses', async function() {
const MemServer = require('../index.js');
const { Photo } = MemServer.Models;

MemServer.start();

await window.$.ajax({
type: 'PUT', url: '/photos/1', headers: { 'Content-Type': 'application/json' },
data: JSON.stringify({ photo: { id: 1, name: 'Photo after edit' }})
}).catch((jqXHR) => {
assert.equal(jqXHR.status, 401);
assert.deepEqual(jqXHR.responseJSON, { error: 'Unauthorized' });
});

await window.$.ajax({
type: 'PUT', url: '/photos/1', headers: AJAX_AUTHORIZATION_HEADERS,
data: JSON.stringify({ photo: { id: 1, name: 'Photo after edit' } })
}).then((data, textStatus, jqXHR) => {
assert.equal(jqXHR.status, 200);
assert.deepEqual(data, { photo: Photo.serializer(Photo.find(1))});
assert.equal(Photo.find(1).name, 'Photo after edit');
});
});

it('DELETE /resources/:id works with custom headers and responses', async function() {
const MemServer = require('../index.js');
const { Photo } = MemServer.Models;

MemServer.start();

assert.ok(Photo.find(1), 'User id: 1 exists');

await window.$.ajax({
type: 'DELETE', url: '/photos/1', headers: { 'Content-Type': 'application/json' }
}).catch((jqXHR) => {
assert.equal(jqXHR.status, 401);
assert.deepEqual(jqXHR.responseJSON, { error: 'Unauthorized' });
assert.ok(Photo.find(1), 'User id: 1 exists');
});

await window.$.ajax({
type: 'DELETE', url: '/photos/1', headers: AJAX_AUTHORIZATION_HEADERS
}).then((data, textStatus, jqXHR) => {
assert.equal(jqXHR.status, 204);
assert.deepEqual(data, undefined);
assert.ok(!Photo.find(1), 'User id: 1 gets deleted');
});
});
});

// describe('server can process custom queryParams and responses', function() {
// fs.writeFileSync(`${process.cwd()}/memserver/server.js`, `
Expand Down
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1239,7 +1239,7 @@ pygmentize-bundled@^2.3.0:
bl "~0.4.1"
through2 "~0.2.1"

qs@~6.5.1:
qs@^6.5.1, qs@~6.5.1:
version "6.5.1"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"

Expand Down

0 comments on commit 40f52b2

Please sign in to comment.