diff --git a/README.md b/README.md index 50cefe6..a129eaf 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,11 @@ For HTTP headers such as Authorization, you may want Drakov to return them in th Drakov includes many headers by default: `Origin, X-Requested-With, Content-Type, Accept` when CORS is enabled. +## Ignore Headers + +In cases where strict HTTP headers matching against API blueprints is not necessary, you can use the `--ignoreHeader` argument: + +`drakov -f "../com/foo/contracts/*.md" --ignoreHeader Cookie --ignoreHeader Authorization` ## Using as a Node.js module diff --git a/lib/arguments/arguments.js b/lib/arguments/arguments.js index fccc183..f5ebaec 100644 --- a/lib/arguments/arguments.js +++ b/lib/arguments/arguments.js @@ -57,6 +57,9 @@ module.exports = { }, debugMode: { description: 'Enables DEBUG mode. Mismatch requests will be dumped' + }, + ignoreHeader: { + description: 'Ignore the HTTP header in API blueprints' } }; diff --git a/lib/content.js b/lib/content.js index f297ec4..a02e0cd 100644 --- a/lib/content.js +++ b/lib/content.js @@ -54,6 +54,15 @@ function getBodyContent(req, parseToJson){ return body; } +function areContentTypesSame(httpReq, specReq) { + var actual = getMediaTypeFromHttpReq(httpReq); + var expected = getMediaTypeFromSpecReq(specReq); + + var result = !expected || actual === expected; + logger.log('[MATCHING]'.yellow,'by request content type:', expected, 'actual:', actual, logger.stringfy(result)); + return result; +} + exports.contentTypeComparator = function(specA) { function hasContentTypeHeader(spec){ @@ -66,15 +75,6 @@ exports.contentTypeComparator = function(specA) { return !hasContentTypeHeader(specA)? 1 : -1; }; -exports.areContentTypesSame = function(httpReq, specReq) { - var actual = getMediaTypeFromHttpReq(httpReq); - var expected = getMediaTypeFromSpecReq(specReq); - - var result = !expected || actual === expected; - logger.log('[MATCHING]'.yellow,'by request content type:', expected, 'actual:', actual, logger.stringfy(result)); - return result; -}; - exports.matchesBody = function(httpReq, specReq) { if (!specReq) { return true; @@ -106,13 +106,23 @@ exports.matchesSchema = function(httpReq, specReq) { return result; }; -exports.matchesHeader = function(httpReq, specReq) { +exports.matchesHeader = function(httpReq, specReq, ignoreHeaders) { if (!specReq || !specReq.headers){ return true; } - function removeContentTypeHeader(header){ - return header.name.toLowerCase() !== 'content-type'; + ignoreHeaders = (ignoreHeaders && ignoreHeaders.map(function (header) { + return header.toLowerCase(); + })) || []; + + function shouldIgnoreHeader(headerName){ + return ignoreHeaders.indexOf(headerName.toLowerCase()) > -1; + } + + function headersForEvaluation(header) { + return header.name && + (header.name.toLowerCase() !== 'content-type' && + !shouldIgnoreHeader(header.name)); } function containsHeader( header ){ @@ -124,5 +134,6 @@ exports.matchesHeader = function(httpReq, specReq) { return result; } - return specReq.headers.filter(removeContentTypeHeader).every(containsHeader); + return specReq.headers.filter(headersForEvaluation).every(containsHeader) && + (shouldIgnoreHeader('content-type') || areContentTypesSame(httpReq, specReq)); }; diff --git a/lib/handler-filter.js b/lib/handler-filter.js index 2784c81..acea02d 100644 --- a/lib/handler-filter.js +++ b/lib/handler-filter.js @@ -1,15 +1,9 @@ var content = require('./content'); var queryComparator = require('./query-comparator'); -var filterContentType = function (req) { +var filterRequestHeader = function (req, ignoreHeaders) { return function (handler) { - return content.areContentTypesSame(req, handler.request); - }; -}; - -var filterRequestHeader = function (req) { - return function (handler) { - return content.matchesHeader(req, handler.request); + return content.matchesHeader(req, handler.request, ignoreHeaders); }; }; @@ -25,7 +19,7 @@ var filterSchema = function (req) { }; }; -exports.filterHandlers = function (req, handlers) { +exports.filterHandlers = function (req, handlers, ignoreHeaders) { if (handlers) { var filteredHandlers; @@ -39,9 +33,7 @@ exports.filterHandlers = function (req, handlers) { handlers.sort(queryComparator.queryParameterComparator); } - filteredHandlers = handlers - .filter(filterRequestHeader(req)) - .filter(filterContentType(req)); + filteredHandlers = handlers.filter(filterRequestHeader(req, ignoreHeaders)); var matchRequestBodyHandlers = filteredHandlers.filter(filterRequestBody(req)); @@ -58,4 +50,3 @@ exports.filterHandlers = function (req, handlers) { return null; }; - diff --git a/lib/middleware/index.js b/lib/middleware/index.js index 3877be7..c985e06 100644 --- a/lib/middleware/index.js +++ b/lib/middleware/index.js @@ -16,7 +16,9 @@ var bootstrapMiddleware = function(app, argv) { exports.init = function(app, argv, cb) { bootstrapMiddleware(app, argv); - var options = {sourceFiles: argv.sourceFiles, autoOptions: argv.autoOptions}; + var options = {sourceFiles: argv.sourceFiles, + autoOptions: argv.autoOptions, + ignoreHeaders: argv.ignoreHeader}; routeHandlers(options, function(err, middleware) { cb(err, middleware); }); diff --git a/lib/middleware/route-handlers.js b/lib/middleware/route-handlers.js index 58044db..b6c29a4 100644 --- a/lib/middleware/route-handlers.js +++ b/lib/middleware/route-handlers.js @@ -22,7 +22,8 @@ module.exports = function(options, cb) { var match = regex.exec(req.path); logger.log('[MATCHING]'.yellow, 'by url pattern:', urlPattern.yellow, logger.stringfy(match)); if (match) { - handler = filter.filterHandlers(req, routeMap[urlPattern].methods[req.method.toUpperCase()]); + var handlers = routeMap[urlPattern].methods[req.method.toUpperCase()]; + handler = filter.filterHandlers(req, handlers, options.ignoreHeaders); } }); diff --git a/test/api/debugger-test.js b/test/api/debugger-test.js index 7a03389..05d29df 100644 --- a/test/api/debugger-test.js +++ b/test/api/debugger-test.js @@ -37,20 +37,23 @@ describe('Debug Mode', function(){ }); it('should respond with spec response', function(done){ - var expectedResponsePayload = { - originalUrl:'/api/things-not-found', - body: {'key1':'value1','key2':2}, - method: 'POST', - headers:{'host':'localhost:3003','accept-encoding':'gzip, deflate','user-agent':'node-superagent/3.5.1','content-type':'application/json','content-length':'26','connection':'close'}, - query:{} - }; - + function matchingReturnedBody(expectedBody) { + return function (response) { + var actualBodyString = JSON.stringify(response.body.body); + var expectedBodyString = JSON.stringify(expectedBody); + if (actualBodyString !== expectedBodyString) { + throw new Error('Expected: ' + expectedBodyString + ', but got: ' + actualBodyString); + } + }; + } + + var content = { key1: 'value1', key2: 2 }; request.post('/api/things-not-found') .set('Content-type', 'application/json') - .send({ key1: 'value1', key2: 2 }) + .send(content) .expect(404) .expect('Content-type', 'application/json; charset=utf-8') - .expect(expectedResponsePayload) + .expect(matchingReturnedBody(content)) .end(helper.endCb(done)); }); diff --git a/test/api/headers-test.js b/test/api/headers-test.js index 23b3b11..9de5ef3 100644 --- a/test/api/headers-test.js +++ b/test/api/headers-test.js @@ -4,7 +4,8 @@ var request = helper.getRequest(); describe('HEADERS', function(){ before(function (done) { - helper.drakov.run({sourceFiles: 'test/example/md/headers.md'}, done); + helper.drakov.run({sourceFiles: 'test/example/md/headers.md', + ignoreHeader: ['Cookie']}, done); }); after(function (done) { @@ -32,6 +33,26 @@ describe('HEADERS', function(){ }); }); + describe('/ignore_headers', function(){ + + describe('GET', function(){ + it('should respond with HTTP 200 when header does not exist', function(done){ + request.get('/ignore_headers') + .expect(200) + .end(helper.endCb(done)); + }); + }); + + describe('GET', function(){ + it('should respond with HTTP 200 when header does not match', function(done){ + request.get('/ignore_headers') + .set('Cookie', 'key=value2') + .expect(200) + .end(helper.endCb(done)); + }); + }); + }); + describe('/things', function(){ describe('GET', function(){ diff --git a/test/example/md/headers.md b/test/example/md/headers.md index 50b34b4..7b7082b 100644 --- a/test/example/md/headers.md +++ b/test/example/md/headers.md @@ -29,10 +29,24 @@ Respond to deletion of object + Response 200 + +## Things [/ignore_headers] + +### Retrieve a dummy object [GET] +Check Cookie header + ++ Request + + + Headers + + Cookie: key=value + ++ Response 200 + ## Things [/things] ### Retrieve all the things with no header [GET] - + + Response 200 (application/json;charset=UTF-8) + Body @@ -40,15 +54,15 @@ Respond to deletion of object { "header":"absent" } - + + Request JSON Message - + + Headers content-type: application/json - -+ Response 200 + ++ Response 200 + Headers diff --git a/test/unit/content-test.js b/test/unit/content-test.js index 5b34de0..5d36c63 100644 --- a/test/unit/content-test.js +++ b/test/unit/content-test.js @@ -15,81 +15,6 @@ }); }); - describe('areContentTypesSame', function () { - - var httpReq = { - 'headers': { - 'content-type': 'application/json' - } - }; - - context('when spec does not define content-type', function () { - - var specReq = { - headers: [ - {} - ] - }; - - it('should return true', function () { - assert.equal(content.areContentTypesSame(httpReq, specReq), true); - }); - - it('should log to console that content type is matched', function () { - var hook = stdoutHook.setup(function (string) { - assert.equal(loadash.includes(string, 'NOT_MATCHED'), false); - }); - - content.areContentTypesSame(httpReq, specReq); - hook(); - }); - }); - - context('when headers correspond to spec', function () { - - var specReq = { - headers: [ - {name: 'Content-Type', value: 'application/json'} - ] - }; - - it('should returns true', function () { - assert.equal(content.areContentTypesSame(httpReq, specReq), true); - }); - - it('should log to console that content type is matched', function () { - var hook = stdoutHook.setup(function (string) { - assert.equal(loadash.includes(string, 'NOT_MATCHED'), false); - }); - - content.areContentTypesSame(httpReq, specReq); - hook(); - }); - }); - - context('when headers do not correspond to spec', function () { - - var specReq = { - headers: [ - {name: 'Content-Type', value: 'application/xml'} - ] - }; - - it('should returns false ', function () { - assert.equal(content.areContentTypesSame(httpReq, specReq), false); - }); - - it('should log to console that content type is not matched', function () { - var hook = stdoutHook.setup(function (string) { - assert.equal(loadash.includes(string, 'NOT_MATCHED'), true); - }); - - content.areContentTypesSame(httpReq, specReq); - hook(); - }); - }); - }); - describe('contentTypeComparator', function () { it('should return 1 when first spec does not contain content-type header', function () { var specA = { @@ -144,7 +69,7 @@ ] }; - assert.equal(content.areContentTypesSame(httpReq, specReq), false); + assert.equal(content.matchesHeader(httpReq, specReq), false); }); }); @@ -494,6 +419,97 @@ assert.equal(numberOfErrors, 1); }); }); + + context('with ignore headers', function () { + + var specReq = { + headers: [ + {name: 'Content-Type', value: 'application/xml'}, + {name: 'Cookie', value: 'key=vaue'} + ] + }; + + it('should return true', function () { + assert.equal(content.matchesHeader(httpReq, specReq, ['Content-Type', 'Cookie']), true); + }); + + }); + + + describe('regarding to content type', function () { + + var httpReq = { + 'headers': { + 'content-type': 'application/json' + } + }; + + context('when spec does not define content-type', function () { + + var specReq = { + headers: [ + {} + ] + }; + + it('should return true', function () { + assert.equal(content.matchesHeader(httpReq, specReq), true); + }); + + it('should log to console that content type is matched', function () { + var hook = stdoutHook.setup(function (string) { + assert.equal(loadash.includes(string, 'NOT_MATCHED'), false); + }); + + content.matchesHeader(httpReq, specReq); + hook(); + }); + }); + + context('when headers correspond to spec', function () { + + var specReq = { + headers: [ + {name: 'Content-Type', value: 'application/json'} + ] + }; + + it('should returns true', function () { + assert.equal(content.matchesHeader(httpReq, specReq), true); + }); + + it('should log to console that content type is matched', function () { + var hook = stdoutHook.setup(function (string) { + assert.equal(loadash.includes(string, 'NOT_MATCHED'), false); + }); + + content.matchesHeader(httpReq, specReq); + hook(); + }); + }); + + context('when headers do not correspond to spec', function () { + + var specReq = { + headers: [ + {name: 'Content-Type', value: 'application/xml'} + ], + }; + + it('should returns false ', function () { + assert.equal(content.matchesHeader(httpReq, specReq), false); + }); + + it('should log to console that content type is not matched', function () { + var hook = stdoutHook.setup(function (string) { + assert.equal(loadash.includes(string, 'NOT_MATCHED'), true); + }); + + content.matchesHeader(httpReq, specReq); + hook(); + }); + }); + }); }); }); })();