diff --git a/TODO b/TODO index 03fa011..b07e161 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,4 @@ - create a cli -- serialization embedding - derived schema attribute for $Model: schema: { inserted_at: { diff --git a/lib/mem-server/pretender-hacks.js b/lib/mem-server/pretender-hacks.js index 54b07c4..24cccc2 100644 --- a/lib/mem-server/pretender-hacks.js +++ b/lib/mem-server/pretender-hacks.js @@ -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]; @@ -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; @@ -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]), @@ -43,17 +51,21 @@ 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); @@ -61,8 +73,8 @@ window.Pretender.prototype.handleRequest = function(request) { 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); }) @@ -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) { diff --git a/lib/mem-server/server.js b/lib/mem-server/server.js index a3c1144..f47d9ce 100644 --- a/lib/mem-server/server.js +++ b/lib/mem-server/server.js @@ -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() @@ -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); @@ -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? diff --git a/package.json b/package.json index a8be72f..a2778ef 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/test/mem-server.server.js b/test/mem-server.server.js index 23040e9..1803295 100644 --- a/test/mem-server.server.js +++ b/test/mem-server.server.js @@ -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 }); @@ -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) { @@ -253,11 +253,11 @@ 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' }); @@ -265,11 +265,11 @@ describe('MemServer.Server functionality', function() { 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' }); @@ -277,22 +277,28 @@ describe('MemServer.Server functionality', function() { 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 }); } }); } @@ -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' } }); @@ -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(); @@ -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) => { @@ -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`, ` diff --git a/yarn.lock b/yarn.lock index a67bec1..e28362e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"