From 55fabfd7baa6a66c7f0460e4ccc3e1296cf85ff7 Mon Sep 17 00:00:00 2001 From: Kyle Warneck Date: Fri, 8 Nov 2019 14:05:29 -0800 Subject: [PATCH 1/4] #67 Accept wildcards for parameters --- mockserver.js | 92 +++++++++++++++---- .../wildcard-params/GET--foo=bar&buz=__.mock | 3 + test/mockserver.js | 11 ++- 3 files changed, 89 insertions(+), 17 deletions(-) create mode 100644 test/mocks/wildcard-params/GET--foo=bar&buz=__.mock diff --git a/mockserver.js b/mockserver.js index 2b780b0..853b81e 100644 --- a/mockserver.js +++ b/mockserver.js @@ -25,7 +25,7 @@ function parseStatus(header) { * Parses an HTTP header, splitting * by colon. */ -const parseHeader = function (header, context, request) { +const parseHeader = function(header, context, request) { header = header.split(': '); return { key: normalizeHeader(header[0]), value: parseValue(header[1], context, request) }; @@ -283,27 +283,87 @@ function getBody(req, callback) { } function getMockedContent(path, prefix, body, query) { - const mockName = prefix + (getBodyOrQueryString(body, query) || '') + '.mock'; - const mockFile = join(mockserver.directory, path, mockName); - let content; + // Check for an exact match + const exactName = prefix + (getBodyOrQueryString(body, query) || '') + '.mock'; + let content = handleMatch(path, exactName, fs.existsSync); - try { - content = fs.readFileSync(mockFile, { encoding: 'utf8' }); - if (mockserver.verbose) { - console.log( - 'Reading from ' + mockFile.yellow + ' file: ' + 'Matched'.green - ); + // Compare params without regard to order + if (!content && query && !body) { + content = testForQuery(path, prefix, query, false); + + // Compare params without regard to order and allow wildcards + if (!content) { + content = testForQuery(path, prefix, query, true); } - } catch (err) { - if (mockserver.verbose) { - console.log( - 'Reading from ' + mockFile.yellow + ' file: ' + 'Not matched'.red + } + + // fallback option (e.g. GET.mock). ignores body and query + if (!content) { + const fallbackName = prefix + '.mock'; + content = handleMatch(path, fallbackName, fs.existsSync); + } + + return content; +} + +function testForQuery(path, prefix, query, allowWildcards) { + // Find all files in the directory + return fs + .readdirSync(join(mockserver.directory, path)) + .filter(possibleFile => possibleFile.startsWith(prefix) && possibleFile.endsWith('.mock')) + .filter(possibleFile => possibleFile.match(/--[\s\S]*__/)) + .reduce((prev, possibleFile) => { + if (prev) { + return prev; + } + + let isMatch = true; + //get params from file + const paramMap = queryStringToMap(query); + const possibleFileParamMap = queryStringToMap( + possibleFile.replace('.mock', '').split('--')[1] ); + + for (const key in paramMap) { + if (!isMatch) { + continue; + } + isMatch = + possibleFileParamMap[key] === paramMap[key] || + (allowWildcards && possibleFileParamMap[key] === '__'); + } + + return handleMatch(path, possibleFile, isMatch); + }, undefined); +} + +function queryStringToMap(query) { + const result = {}; + query.split('&').forEach(param => { + const [key, val] = param.split('='); + result[key] = val; + }); + return result; +} + +function handleMatch(path, fileName, isMatchOrTest) { + const mockFile = join(mockserver.directory, path, fileName); + + let isMatch = isMatchOrTest; + if (typeof isMatchOrTest === 'function') { + isMatch = isMatchOrTest(mockFile); + } + + if (isMatch) { + if (mockserver.verbose) { + console.log('Reading from ' + mockFile.yellow + ' file: ' + 'Matched'.green); } - content = (body || query) && getMockedContent(path, prefix); + return fs.readFileSync(mockFile, { encoding: 'utf8' }); } - return content; + if (mockserver.verbose) { + console.log('Reading from ' + mockFile.yellow + ' file: ' + 'Not matched'.red); + } } function getContentFromPermutations(path, method, body, query, permutations) { diff --git a/test/mocks/wildcard-params/GET--foo=bar&buz=__.mock b/test/mocks/wildcard-params/GET--foo=bar&buz=__.mock new file mode 100644 index 0000000..de9a291 --- /dev/null +++ b/test/mocks/wildcard-params/GET--foo=bar&buz=__.mock @@ -0,0 +1,3 @@ +HTTP/1.1 200 OK + +wildcard-params diff --git a/test/mockserver.js b/test/mockserver.js index 4ff6870..a812367 100644 --- a/test/mockserver.js +++ b/test/mockserver.js @@ -108,7 +108,7 @@ describe('mockserver', function() { it('should combine the identical headers names', function() { processRequest('/multiple-headers-same-name/', 'GET'); - + assert.equal(res.headers['Set-Cookie'].length, 3); }) @@ -431,6 +431,15 @@ describe('mockserver', function() { assert.equal(res.status, 404); }); }); + + describe("wildcard params", function() { + it("matches a file with wildcards as query params", function() { + processRequest("/wildcard-params?foo=bar&buz=baz", "GET"); + + assert.equal(res.status, 200); + }); + }); + describe('.getResponseDelay', function() { it('should return a value greater than zero when valid', function() { const ownValueHeaders = [ From 0b9cbc2d78b38b993e103da9521263c11a313caa Mon Sep 17 00:00:00 2001 From: Kyle Warneck Date: Fri, 8 Nov 2019 14:26:14 -0800 Subject: [PATCH 2/4] Add docs for wildcarded query params and rearrange tests for clarity --- README.md | 27 +++++++++++++++++++++++++-- test/mockserver.js | 34 ++++++++++++++++++++-------------- 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 3b5ef9e..e6e79d6 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,7 @@ Response-Delay: 5000 The delay value is expected in milliseconds, if not set for a given file there will be no delay. -## Query string parameters and POST body +## Query string parameters In order to support query string parameters in the mocked files, replace all occurrences of `?` with `--`, then append the entire string to the end of the file. @@ -188,6 +188,29 @@ test/GET--a=b&c=d--.mock (This has been introduced to overcome issues in file naming on windows) +Query parameters can be passed in any order. For example, +``` +GET /hello?a=b&c=d +GET /hello?c=d&a=b +``` +both match the file `hello/GET--a=b&c=d`. + +You can specify a wildcard for a query param by including `__` in place of a value in the file name. +``` +GET /hello?a=b&c=d + +matches +hello/GET--a=b&c=__ +``` + +In the event that there are multiple files that match the provided query params pattern, mockserver selects files in the following order: +* Files with params in the same order (no wildcards) +* Files with params in any order (no wildcards) +* Files with params in any order and wild cards + + +## Query string parameters and POST body + To combine custom headers and query parameters, simply add the headers _then_ add the parameters: ``` @@ -295,7 +318,7 @@ Content-Type: application/json; charset=utf-8 Access-Control-Allow-Origin: * { - "Random": "Content" + "Random": "Content" } ``` diff --git a/test/mockserver.js b/test/mockserver.js index a812367..708bb7d 100644 --- a/test/mockserver.js +++ b/test/mockserver.js @@ -143,18 +143,6 @@ describe('mockserver', function() { assert.equal(res.body, 'multi-level url'); }); - it('should be able to handle GET parameters', function() { - processRequest('/test?a=b', 'GET'); - - assert.equal(res.status, 200); - }); - - it('should default to GET.mock if no matching parameter file is found', function() { - processRequest('/test?a=c', 'GET'); - - assert.equal(res.status, 200); - }); - it('should be able track custom headers', function() { mockserver.headers = ['authorization']; @@ -432,12 +420,30 @@ describe('mockserver', function() { }); }); - describe("wildcard params", function() { - it("matches a file with wildcards as query params", function() { + describe("query string parameters", function() { + it('should be able to handle GET parameters', function() { + processRequest('/test?a=b', 'GET'); + + assert.equal(res.status, 200); + }); + + it("should handle a file with wildcards as query params", function() { processRequest("/wildcard-params?foo=bar&buz=baz", "GET"); assert.equal(res.status, 200); }); + + it("should handle a request regardless of the order of the params in the query string", function() { + processRequest("/wildcard-params?buz=baz&foo=bar", "GET"); + + assert.equal(res.status, 200); + }); + + it('should default to GET.mock if no matching parameter file is found', function() { + processRequest('/test?a=c', 'GET'); + + assert.equal(res.status, 200); + }); }); describe('.getResponseDelay', function() { From 2a572b16168e5eebb76cb293e25a93173ddf6739 Mon Sep 17 00:00:00 2001 From: Kyle Warneck Date: Fri, 8 Nov 2019 14:44:39 -0800 Subject: [PATCH 3/4] Fix bug with post body and query param in same request --- mockserver.js | 20 +++++-- .../mocks/return-200/POST_Hello=123--a=b.mock | 4 ++ .../return-200/POST_Hello=456--c=__.mock | 4 ++ test/mockserver.js | 56 +++++++++++++++++-- 4 files changed, 74 insertions(+), 10 deletions(-) create mode 100644 test/mocks/return-200/POST_Hello=123--a=b.mock create mode 100644 test/mocks/return-200/POST_Hello=456--c=__.mock diff --git a/mockserver.js b/mockserver.js index 853b81e..a7e0e28 100644 --- a/mockserver.js +++ b/mockserver.js @@ -247,6 +247,10 @@ function getDirectoriesRecursive(srcpath) { * GET--query=string&hello=hella.mock */ function getBodyOrQueryString(body, query) { + if (body && query) { + return '_'+ body + '--' + query; + } + if (query) { return '--' + query; } @@ -288,12 +292,12 @@ function getMockedContent(path, prefix, body, query) { let content = handleMatch(path, exactName, fs.existsSync); // Compare params without regard to order - if (!content && query && !body) { - content = testForQuery(path, prefix, query, false); + if (!content && query) { + content = testForQuery(path, prefix, body, query, false); // Compare params without regard to order and allow wildcards if (!content) { - content = testForQuery(path, prefix, query, true); + content = testForQuery(path, prefix, body, query, true); } } @@ -306,11 +310,17 @@ function getMockedContent(path, prefix, body, query) { return content; } -function testForQuery(path, prefix, query, allowWildcards) { +function testForQuery(path, prefix, body, query, allowWildcards) { // Find all files in the directory return fs .readdirSync(join(mockserver.directory, path)) - .filter(possibleFile => possibleFile.startsWith(prefix) && possibleFile.endsWith('.mock')) + .filter(possibleFile => { + if (body) { + return possibleFile.startsWith(prefix + '_' + body) && possibleFile.endsWith('.mock'); + } + + return possibleFile.startsWith(prefix) && possibleFile.endsWith('.mock'); + }) .filter(possibleFile => possibleFile.match(/--[\s\S]*__/)) .reduce((prev, possibleFile) => { if (prev) { diff --git a/test/mocks/return-200/POST_Hello=123--a=b.mock b/test/mocks/return-200/POST_Hello=123--a=b.mock new file mode 100644 index 0000000..0878d04 --- /dev/null +++ b/test/mocks/return-200/POST_Hello=123--a=b.mock @@ -0,0 +1,4 @@ +HTTP/1.1 200 OK +Content-Type: text/xml; charset=utf-8 + +Hella \ No newline at end of file diff --git a/test/mocks/return-200/POST_Hello=456--c=__.mock b/test/mocks/return-200/POST_Hello=456--c=__.mock new file mode 100644 index 0000000..3a3133b --- /dev/null +++ b/test/mocks/return-200/POST_Hello=456--c=__.mock @@ -0,0 +1,4 @@ +HTTP/1.1 200 OK +Content-Type: text/xml; charset=utf-8 + +Hello!!! \ No newline at end of file diff --git a/test/mockserver.js b/test/mockserver.js index 708bb7d..99dfb12 100644 --- a/test/mockserver.js +++ b/test/mockserver.js @@ -420,30 +420,76 @@ describe('mockserver', function() { }); }); - describe("query string parameters", function() { + describe('query string parameters', function() { it('should be able to handle GET parameters', function() { processRequest('/test?a=b', 'GET'); assert.equal(res.status, 200); }); - it("should handle a file with wildcards as query params", function() { - processRequest("/wildcard-params?foo=bar&buz=baz", "GET"); + it('should handle a file with wildcards as query params', function() { + processRequest('/wildcard-params?foo=bar&buz=baz', 'GET'); assert.equal(res.status, 200); }); - it("should handle a request regardless of the order of the params in the query string", function() { - processRequest("/wildcard-params?buz=baz&foo=bar", "GET"); + it('should handle a request regardless of the order of the params in the query string', function() { + processRequest('/wildcard-params?buz=baz&foo=bar', 'GET'); assert.equal(res.status, 200); }); + it('should not handle requests with extra params in the query string', function() { + processRequest('/wildcard-params?buz=baz&foo=bar&biz=bak', 'GET'); + + assert.equal(res.status, 404); + }); + it('should default to GET.mock if no matching parameter file is found', function() { processRequest('/test?a=c', 'GET'); assert.equal(res.status, 200); }); + + it('should be able to include POST bodies and query params', function(done) { + const req = new MockReq({ + method: 'POST', + url: '/return-200?a=b', + headers: { + Accept: 'text/plain' + } + }); + req.write('Hello=123'); + req.end(); + + mockserver(mocksDirectory, verbose)(req, res); + + req.on('end', function() { + assert.equal(res.body, 'Hella'); + assert.equal(res.status, 200); + done(); + }); + }); + + it('should be able to include POST bodies and query params with wildcards', function(done) { + const req = new MockReq({ + method: 'POST', + url: '/return-200?c=d', + headers: { + Accept: 'text/plain' + } + }); + req.write('Hello=456'); + req.end(); + + mockserver(mocksDirectory, verbose)(req, res); + + req.on('end', function() { + assert.equal(res.body, 'Hello!!!'); + assert.equal(res.status, 200); + done(); + }); + }); }); describe('.getResponseDelay', function() { From 9b648a5618bc0b100e0ae3fd4805c740594cdba8 Mon Sep 17 00:00:00 2001 From: Kyle Warneck Date: Fri, 8 Nov 2019 14:54:19 -0800 Subject: [PATCH 4/4] Add test to prove we prefer exact matches over wildcard matches --- test/mocks/wildcard-params/GET--foo=bar&buz=bak.mock | 3 +++ test/mockserver.js | 7 +++++++ 2 files changed, 10 insertions(+) create mode 100644 test/mocks/wildcard-params/GET--foo=bar&buz=bak.mock diff --git a/test/mocks/wildcard-params/GET--foo=bar&buz=bak.mock b/test/mocks/wildcard-params/GET--foo=bar&buz=bak.mock new file mode 100644 index 0000000..fa016a6 --- /dev/null +++ b/test/mocks/wildcard-params/GET--foo=bar&buz=bak.mock @@ -0,0 +1,3 @@ +HTTP/1.1 200 OK + +exact match \ No newline at end of file diff --git a/test/mockserver.js b/test/mockserver.js index 99dfb12..531932c 100644 --- a/test/mockserver.js +++ b/test/mockserver.js @@ -433,6 +433,13 @@ describe('mockserver', function() { assert.equal(res.status, 200); }); + it('should prefer exact matches over wildcard matches', function () { + processRequest('/wildcard-params?foo=bar&buz=bak', 'GET'); + + assert.equal(res.status, 200); + assert.equal(res.body, 'exact match'); + }) + it('should handle a request regardless of the order of the params in the query string', function() { processRequest('/wildcard-params?buz=baz&foo=bar', 'GET');