UNPKG

canned

Version:

serve canned responses to mock an api, based on files in a folder

857 lines (776 loc) 26.1 kB
"use strict"; var querystring = require("querystring") var canned = require('../index') var path = require('path') describe('canned', function () { var can, req, res beforeEach(function () { can = canned('./spec/test_responses') req = { method: 'GET' } res = { setHeader: function () {}, end: function () {} } spyOn(res, 'setHeader') }) describe('paths', function () { beforeEach(function () { var fullpath = path.resolve('./spec/test_responses') can = canned(fullpath) }) it('resolve requests when passed an absolute path', function (done) { req.url = '/a' res.end = function () { expect(res.statusCode).toBe(200) done() } can(req, res) }) }) describe('error messages', function () { var writeLog, logCan beforeEach(function () { var logger = { write: function (msg) { if (writeLog) writeLog(msg) } } logCan = canned('./spec/test_responses', { logger: logger }) }) it('displays an error for unparsable json files', function (done) { var regex = new RegExp('.*Syntax.*') writeLog = function (message) { if (regex.test(message)) { expect(message).toContain("problem sanatizing content for _invalid_syntax.get.json SyntaxError: Unexpected token I") done() } } req.url = '/invalid_syntax' logCan(req, res) }) }) describe('sanitization', function () { var writeLog, logCan var logger = { write: function (msg) { if (writeLog) writeLog(msg) } } describe('with sanitization enabled', function() { beforeEach(function () { logCan = canned('./spec/test_responses', { logger: logger }) }) it('displays an error for json containing unexpected markup', function (done) { var regex = new RegExp('.*Syntax.*') writeLog = function (message) { if (regex.test(message)) { expect(message).toContain("problem sanatizing content for _broken_sanitize.get.json SyntaxError: Unexpected token") done() } } req.url = '/broken_sanitize' logCan(req, res) }) }) describe('with sanitization disabled', function() { beforeEach(function () { logCan = canned('./spec/test_responses', { logger: logger, sanitize: false }) }) it('loads content from _broken_sanitize.get.json', function (done) { req.url = '/broken_sanitize' res.end = function (content) { expect(content).toContain('"whatAmI": "I have been copy/pasted into a WYSIWYG editor by your grandma"') done() } logCan(req, res) }) }) }) describe('status codes', function () { it('sets 404 for non resolveable request', function (done) { req.url = '/i_do_not_exist' res.end = function () { expect(res.statusCode).toBe(404) done() } can(req, res) }) it('sets 200 for resolveable requests', function (done) { req.url = '/a' res.end = function () { expect(res.statusCode).toBe(200) done() } can(req, res) }) it('sets 201 if specified in file', function (done) { req.url = '/201' res.end = function () { expect(res.statusCode).toBe(201) done() } can(req, res) }) it('sets 204 for empty file', function (done) { req.url = '/empty' res.end = function () { expect(res.statusCode).toBe(204) done() } can(req, res) }) it('sets specified status for empty file with headers set', function (done) { req.url = '/empty_with_headers' res.end = function () { expect(res.statusCode).toBe(420) done() } can(req, res) }) }) describe('content type', function () { it('sets text/plain for txt', function (done) { req.url = '/b' res.setHeader = function (name, value) { expect(value).toBe('text/plain') expect(name).toBe('Content-Type') done() } can(req, res) }) it('sets text/html for errors', function (done) { req.url = '/i_do_not_exist' res.setHeader = function (name, value) { expect(value).toBe('text/html') expect(name).toBe('Content-Type') done() } can(req, res) }) it('sets Content-type header if specified in file', function(done){ req.url = '/vendor_type' res.setHeader = function(name, value){ expect(value).toBe('application/vnd.custom+xml') expect(name).toBe('Content-Type') done() } can(req, res) }) }) describe('custom response header', function(){ it('populates custom header with single header', function(done){ req.url = '/single_custom_header' res.end = function() { expect(res.setHeader).toHaveBeenCalledWith('Header-Key', 'Header-Content') done() } can(req, res) }) it('populates custom headers with multiple headers', function(done){ req.url = '/multiple_custom_header' res.end = function() { expect(res.setHeader).toHaveBeenCalledWith('Header-Key', 'Header-Content') expect(res.setHeader).toHaveBeenCalledWith('Header-Key2', 'Header-Content2') done() } can(req, res) }) }) describe('resolve file paths', function () { it('loads index for /', function (done) { req.url = '/' res.end = function (content) { expect(content).toContain('index.get.json') done() } can(req, res) }); it('loads index for /d with d being a directory', function (done) { req.url = '/d' res.end = function (content) { expect(content).toContain('d/index.get.json') done() } can(req, res) }); it('loads index for /d/e with both being directories', function (done) { req.url = '/d/e' res.end = function (content) { expect(content).toContain('d/e/index.get.html') done() } can(req, res); }); it('loads any for /d/something', function (done) { req.url = '/d/i_am_an_id' res.end = function (content) { expect(content).toContain('d/any.get.json') done() } can(req, res) }) it('loads index from wildcard path for /d/1/', function (done) { req.url = '/d/1/' res.end = function (content) { expect(content).toContain('{"wildcard":1}') done() } can(req, res) }) it('loads named response from wildcard path for /d/1/bar', function (done) { req.url = '/d/1/bar' res.end = function (content) { expect(content).toContain('{"wildcard":"named_response"}') done() } can(req, res) }) it('loads index from real path for /d/2/', function (done) { req.url = '/d/2/' res.end = function (content) { expect(content).toContain('{"not-wildcard":1}') done() } can(req, res) }) it('looks for _file with query params', function (done) { req.url = '/multimatch_query_param?name=Superman&age=30&idontneed=everyparaminfilename' res.end = function (content) { expect(content).toContain('Superman!') done() } can(req, res) }) it('can tell different query param files a part', function (done) { req.url = '/multimatch_query_param?name=Batman&age=30&idontneed=everyparaminfilename' res.end = function (content) { expect(content).toContain('Batman!') done() } can(req, res) }) it('falls back to file without query params if one or more params dont match', function (done) { req.url = '/a?foo=bar' res.end = function (content) { expect(content).toContain('_a.get.json') done() } can(req, res) }) it('works for nested folder being not present', function (done) { req.url = '/foo/bar/baz' res.end = function () { expect(res.statusCode).toBe(404) done() } can(req, res) }) it('allows for multiple files to match via the .NUMBER extension and pick 1 by default', function (done) { req.url = '/multimatch' res.end = function (content) { var multimatch = JSON.parse(content).multimatch expect(multimatch).toBe(1) done() } can(req, res) }) it('selects json file for request with application/json accept header', function (done) { req.url = '/multiple_type'; req.headers = { accept: 'application/json' } res.end = function (content) { var jsonResponse = JSON.parse(content) expect(jsonResponse.type).toBe('json') done() } can(req, res) }) it('selects jsonld file for request with application/ld+json accept header', function (done) { req.url = '/multiple_type'; req.headers = { accept: 'application/ld+json' } res.end = function (content) { var jsonResponse = JSON.parse(content) expect(jsonResponse['@context']).toBe('http://schema.org/') done() } can(req, res) }) it('selects nt file for request with application/n-triples accept header', function (done) { req.url = '/multiple_type'; req.headers = { accept: 'application/n-triples' } res.end = function (content) { expect(content).toBe('_:b0 <http://schema.org/name> \"Jane Doe\" .') done() } can(req, res) }) it('selects csv file for request with text/csv accept header', function (done) { req.url = '/multiple_type'; req.headers = { accept: 'text/csv' } res.end = function (content) { expect(content).toBe('Jane Doe,Professor,(425) 123-4567,http://www.janedoe.com') done() } can(req, res) }) it('selects js file for request with application/javascript accept header', function (done) { req.url = '/multiple_type'; req.headers = { accept: 'application/javascript' } res.end = function (content) { expect(content).toBe('var type = \'js\';') done() } can(req, res) }) it('selects txt file for request with text/plain accept header', function (done) { req.url = '/multiple_type'; req.headers = { accept: 'text/plain' } res.end = function (content) { expect(content).toBe('text type') done() } can(req, res) }) it('selects txt file for request with text/html accept header', function (done) { req.url = '/multiple_type'; req.headers = { accept: 'text/html' } res.end = function (content) { expect(content).toBe('<type>html</type>') done() } can(req, res) }) it('selects json file when application/json is not first in the Accept header', function (done) { can = canned('./spec/test_responses', {"relaxed_accept": true}) req.url = '/d/multiple-accept-types' req.headers = { accept: 'text/html, application/json' } res.end = function (content) { expect(content).toBe('{"type":"json"}'); done() } can(req, res) }) }) describe('content modifier', function () { it('removes comments from json', function (done) { req.url = '/d/commented' res.end = function (content) { expect(content).toBe('{"no":"comments"}'); done() } can(req, res) }) it('works with http:// in json strings', function (done) { req.url = '/chartest' res.end = function (content) { expect(content).toBe('{"my_url":"http://www.mywebsite.com"}'); done() } can(req, res) }) }) describe('CORS', function () { var can = canned('./spec/test_responses', { cors: true }) it('accepts the options verb', function (done) { req.method = 'OPTIONS' req.url = '/' res.end = function (content) { // serves no content expect(content).toBe('') done() } can(req, res) }) it('sets the headers', function (done) { req.url = '/' var expectedHeaders = { 'Access-Control-Allow-Origin': "*", 'Access-Control-Allow-Headers': "X-Requested-With", 'Access-Control-Allow-Methods': "GET, POST, PUT, PATCH, DELETE, OPTIONS" } res.setHeader = function (name, value) { if (expectedHeaders[name]) { expect(expectedHeaders[name]).toBe(value) delete expectedHeaders[name] } // all expected headers have been set! if (Object.keys(expectedHeaders).length === 0) done() } can(req, res) }) it('adds custom headers from a string', function (done) { var can2 = canned('./spec/test_responses', { cors: true, cors_headers: "Authorization, Content-Type" }) req.url = '/' var expectedHeaders = { 'Access-Control-Allow-Headers': "X-Requested-With, Authorization, Content-Type" } res.setHeader = function (name, value) { if (expectedHeaders[name]) { expect(expectedHeaders[name]).toBe(value) delete expectedHeaders[name] } // all expected headers have been set! if (Object.keys(expectedHeaders).length === 0) done() } can2(req, res) }) it('adds custom headers from an array', function (done) { var can2 = canned('./spec/test_responses', { cors: true, cors_headers: ["Authorization", "Content-Type"] }) req.url = '/' var expectedHeaders = { 'Access-Control-Allow-Headers': "X-Requested-With, Authorization, Content-Type" } res.setHeader = function (name, value) { if (expectedHeaders[name]) { expect(expectedHeaders[name]).toBe(value) delete expectedHeaders[name] } // all expected headers have been set! if (Object.keys(expectedHeaders).length === 0) done() } can2(req, res) }) }) describe('variable GET responses', function () { it('should return the first JSON response body if no header match', function (done) { req.headers = {} req.url = '/multiple_responses' res.end = function (content) { expect(content).toEqual(JSON.stringify({"response":"response for abc"})) done() } can(req, res) }) it('should return the first text response body if no header match', function (done) { req.headers = {} req.url = '/multiple_responses_text' res.end = function (content) { expect(content).toEqual('response for abc') done() } can(req, res) }) it('should return the first JSON response body on header match', function (done) { req.headers = { "authorization": 'abc' } req.url = '/multiple_responses' res.end = function (content) { expect(content).toEqual(JSON.stringify({"response":"response for abc"})) done() } can(req, res) }) it('should return the first text response body on header match', function (done) { req.headers = { "authorization": 'abc' } req.url = '/multiple_responses_text' res.end = function (content) { expect(content).toEqual('response for abc') done() } can(req, res) }) it('should return the second response body on header match', function (done) { req.headers = { "authorization": '123' } req.url = '/multiple_responses' res.end = function (content) { expect(content).toEqual(JSON.stringify({"response":"response for 123"})) done() } can(req, res) }) it('should return the second response body on header match', function (done) { req.headers = { "authorization": '123' } req.url = '/multiple_responses_text' res.end = function (content) { expect(content).toEqual('response for 123') done() } can(req, res) }) it('should be able to return html', function (done) { req.headers = { "authorization": 'html' } req.url = '/multiple_responses_text' res.end = function (content) { expect(content).toEqual('<h1>response for html</h1>') done() } can(req, res) }) it('should return correct status code and the first JSON response body on header match', function (done) { req.headers = { "authorization": 'abc' } req.url = '/201_multiple_responses' res.end = function (content) { expect(res.statusCode).toBe(201) expect(content).toEqual(JSON.stringify({"response":"response for abc"})) done() } can(req, res) }) it('should return correct status code and the second response body on header match', function (done) { req.headers = { "authorization": '123' } req.url = '/201_multiple_responses' res.end = function (content) { expect(res.statusCode).toBe(201) expect(content).toEqual(JSON.stringify({"response":"response for 123"})) done() } can(req, res) }) }) describe("variable GET responses based on params", function() { var req, data beforeEach(function() { req = { method: 'GET' } }) it("should select the right response based on the GET request data", function (done) { req.url = "/multiple_get_responses?" + querystring.stringify({ foo: "bar" }) res.end = function (content) { expect(content).toEqual(JSON.stringify({"response": "response for bar"})) done() } can(req, res) }) it("should select the right response based on the GET request data", function (done) { req.url = "/multiple_get_responses?" + querystring.stringify({ foo: "apostrophe" }) res.end = function (content) { expect(content).toEqual(JSON.stringify({"response": "response with 'apostrophes'"})) done() } can(req, res) }) it("should select the right response based on the GET request data", function (done) { req.url = "/multiple_get_responses?" + querystring.stringify({ foo: "bar", index: 1 }) res.end = function (content) { expect(content).toEqual(JSON.stringify({"response": "response with index 1"})) done() } can(req, res) }) it("should select the first response with no query string", function (done) { req.url = "/multiple_get_responses" res.end = function (content) { expect(content).toEqual(JSON.stringify({"response": "response for baz"})) done() } can(req, res) }) }) describe("Issues", function () { it("#58", function(done) { req.url = "/multiple_get_responses?" + querystring.stringify({foo: "apostrophe"}) res.end = function(content) { expect(content).toEqual(JSON.stringify({"response": "response with 'apostrophes'"})) done() } can(req, res) }) it("#73", function (done) { req.url = "/multiple_get_responses?" + querystring.stringify({"foo": "bar", "index": 1}) res.end = function (content) { expect(content).toEqual(JSON.stringify({"response": "response with index 1"})) done() } can(req, res) }) it("#79", function (done) { var Canned = require('../lib/canned') var can = new Canned('./spec/test_responses', {}); var mock_text = '//! params: {"serialkey": "abc"}\r\n{\r\n"errorCode": "ERROR1"\r\n}\r\n' + '//! params: {"serialkey": "12121"}\r\n{\r\n"errorCode": "ERROR2"\r\n}'; var parsedMeta = can.parseMetaData(mock_text); expect(parsedMeta).toEqual({ request: { serialkey: 'abc' }, params: { serialkey: '12121' } }); done(); }) }) describe("variable POST responses", function() { var req, data beforeEach(function() { req = { method: 'POST', headers: {}, on: function(event, fn) { fn(data) } } }) it('should return the first response body if no payload match', function (done) { data = 'email=nobody@example.com' req.url = '/multiple_responses' res.end = function (content) { expect(content).toEqual(JSON.stringify({"response": "response for one@example.com"})) done() } can(req, res) }) it('should return the first response body if no payload match', function (done) { data = 'email=nobody@example.com' req.url = '/multiple_responses_text' res.end = function (content) { expect(content).toEqual('response for one@example.com') done() } can(req, res) }) it('should return the first response body on payload match', function (done) { data = 'email=one@example.com' req.url = '/multiple_responses' res.end = function (content) { expect(content).toEqual(JSON.stringify({"response": "response for one@example.com"})) done() } can(req, res) }) it('should return the first response JSON body on payload match', function (done) { data = '{"email":"one@example.com"}' req.url = '/multiple_responses' req.headers['content-type'] = 'application/json' res.end = function (content) { expect(content).toEqual(JSON.stringify({"response": "response for one@example.com"})) done() } can(req, res) }) it('should return the first response JSON body on payload match even if content type has charset', function (done) { data = '{"email":"one@example.com"}' req.url = '/multiple_responses' req.headers['content-type'] = 'application/json; charset=UTF-8' res.end = function (content) { expect(content).toEqual(JSON.stringify({"response": "response for one@example.com"})) done() } can(req, res) }) it('should handle request bodies containing arrays', function (done) { data = '{"email": "two@example.com","topics": [1,2]}' req.url = '/multiple_responses' req.headers['content-type'] = 'application/json; charset=UTF-8' res.end = function (content) { expect(content).toEqual(JSON.stringify({"response": "response for two@example.com topics 1,2"})) done() } can(req, res) }) it('should handle request bodies containing urls', function (done) { data = '{"url": "http://example.com"}' req.url = '/response_with_url_param' req.headers['content-type'] = 'application/json' res.end = function (content) { expect(content).toEqual(JSON.stringify({"response": "response for url in param"})) done() } can(req, res) }) it('should return the first response JSON body on payload match (because JSON body is invalid)', function (done) { data = 'bad json data' req.url = '/multiple_responses' req.headers['content-type'] = 'application/json' res.end = function (content) { expect(content).toEqual(JSON.stringify({"response": "response for one@example.com"})) done() } can(req, res) }) it('should return the first response body on payload match', function (done) { data = 'email=one@example.com' req.url = '/multiple_responses_text' res.end = function (content) { expect(content).toEqual('response for one@example.com') done() } can(req, res) }) it('should return the second response body on payload match', function (done) { data = 'email=two@example.com' req.url = '/multiple_responses' res.end = function (content) { expect(content).toEqual(JSON.stringify({"response": "response for two@example.com"})) done() } can(req, res) }) it('should return the second response JSON body on payload match', function (done) { data = '{"email":"two@example.com"}' req.url = '/multiple_responses' req.headers['content-type'] = 'application/json' res.end = function (content) { expect(content).toEqual(JSON.stringify({"response": "response for two@example.com"})) done() } can(req, res) }) it('should return the second response body on payload match', function (done) { data = 'email=two@example.com' req.url = '/multiple_responses_text' res.end = function (content) { expect(content).toEqual('response for two@example.com') done() } can(req, res) }) it('should return the second response body on xml (or really any string) payload match', function (done) { data = '<xml>b</xml>' req.url = '/multiple_responses_xml_request_body' res.end = function (content) { expect(content).toEqual('<xml>B</xml>') done() } can(req, res) }) it('should return the first response xml on header match', function (done) { data = '' req.url = '/multiple_responses_xml' req.headers = { "action": 'foo' } res.end = function (content) { expect(content).toEqual('<SOAP:Envelope><SOAP:Body><Foo Time="2015-01-22T08:30:00.000+05:30"/></SOAP:Body></SOAP:Envelope>') done() } can(req, res) }) it('should return the second response xml on header match', function (done) { data = '' req.url = '/multiple_responses_xml' req.headers = { "action": 'bar' } res.end = function (content) { expect(content).toEqual('<SOAP:Envelope><SOAP:Body><Bar Time="2015-01-22T08:30:00.000+05:30"/></SOAP:Body></SOAP:Envelope>') done() } can(req, res) }) }) })