From c5f152d4335b512c00298638758f30588336a1ae Mon Sep 17 00:00:00 2001 From: Igor Savin Date: Wed, 25 May 2022 21:42:32 +0300 Subject: [PATCH 1/5] Add support for request params in mock client --- lib/mock/mock-interceptor.js | 11 +++-- lib/mock/mock-utils.js | 12 ++++-- test/mock-client.js | 81 ++++++++++++++++++++++++++++++++++++ types/mock-interceptor.d.ts | 2 + 4 files changed, 99 insertions(+), 7 deletions(-) diff --git a/lib/mock/mock-interceptor.js b/lib/mock/mock-interceptor.js index c89ba0d29df..b51e6f2df3f 100644 --- a/lib/mock/mock-interceptor.js +++ b/lib/mock/mock-interceptor.js @@ -10,6 +10,7 @@ const { kMockDispatch } = require('./mock-symbols') const { InvalidArgumentError } = require('../core/errors') +const { buildURL } = require('../core/util') /** * Defines the scope API for an interceptor reply @@ -70,9 +71,13 @@ class MockInterceptor { // As per RFC 3986, clients are not supposed to send URI // fragments to servers when they retrieve a document, if (typeof opts.path === 'string') { - // Matches https://github.com/nodejs/undici/blob/main/lib/fetch/index.js#L1811 - const parsedURL = new URL(opts.path, 'data://') - opts.path = parsedURL.pathname + parsedURL.search + if (opts.query) { + opts.path = buildURL(opts.path, opts.query) + } else { + // Matches https://github.com/nodejs/undici/blob/main/lib/fetch/index.js#L1811 + const parsedURL = new URL(opts.path, 'data://') + opts.path = parsedURL.pathname + parsedURL.search + } } if (typeof opts.method === 'string') { opts.method = opts.method.toUpperCase() diff --git a/lib/mock/mock-utils.js b/lib/mock/mock-utils.js index a9ab94f36ae..412b91eecfe 100644 --- a/lib/mock/mock-utils.js +++ b/lib/mock/mock-utils.js @@ -8,6 +8,7 @@ const { kOrigin, kGetNetConnect } = require('./mock-symbols') +const { buildURL } = require('../core/util') function matchValue (match, value) { if (typeof match === 'string') { @@ -98,10 +99,12 @@ function getResponseData (data) { } function getMockDispatch (mockDispatches, key) { + const resolvedPath = key.query ? buildURL(key.path, key.query) : key.path + // Match path - let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path }) => matchValue(path, key.path)) + let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path }) => matchValue(path, resolvedPath)) if (matchedMockDispatches.length === 0) { - throw new MockNotMatchedError(`Mock dispatch not matched for path '${key.path}'`) + throw new MockNotMatchedError(`Mock dispatch not matched for path '${resolvedPath}'`) } // Match method @@ -146,12 +149,13 @@ function deleteMockDispatch (mockDispatches, key) { } function buildKey (opts) { - const { path, method, body, headers } = opts + const { path, method, body, headers, query } = opts return { path, method, body, - headers + headers, + query, } } diff --git a/test/mock-client.js b/test/mock-client.js index 3700954c5c2..db197ec6788 100644 --- a/test/mock-client.js +++ b/test/mock-client.js @@ -235,6 +235,87 @@ test('MockClient - should be able to set as globalDispatcher', async (t) => { t.same(response, 'hello') }) +test('MockClient - should support query params', async (t) => { + t.plan(3) + + const server = createServer((req, res) => { + res.setHeader('content-type', 'text/plain') + res.end('should not be called') + t.fail('should not be called') + t.end() + }) + t.teardown(server.close.bind(server)) + + await promisify(server.listen.bind(server))(0) + + const baseUrl = `http://localhost:${server.address().port}` + + const mockAgent = new MockAgent({ connections: 1 }) + t.teardown(mockAgent.close.bind(mockAgent)) + + const mockClient = mockAgent.get(baseUrl) + t.type(mockClient, MockClient) + setGlobalDispatcher(mockClient) + + const query = { + pageNum: 1 + } + mockClient.intercept({ + path: '/foo', + query, + method: 'GET' + }).reply(200, 'hello') + + const { statusCode, body } = await request(`${baseUrl}/foo`, { + method: 'GET', + query + }) + t.equal(statusCode, 200) + + const response = await getResponse(body) + t.same(response, 'hello') +}) + +test('MockClient - should intercept query params with hardcoded path', async (t) => { + t.plan(3) + + const server = createServer((req, res) => { + res.setHeader('content-type', 'text/plain') + res.end('should not be called') + t.fail('should not be called') + t.end() + }) + t.teardown(server.close.bind(server)) + + await promisify(server.listen.bind(server))(0) + + const baseUrl = `http://localhost:${server.address().port}` + + const mockAgent = new MockAgent({ connections: 1 }) + t.teardown(mockAgent.close.bind(mockAgent)) + + const mockClient = mockAgent.get(baseUrl) + t.type(mockClient, MockClient) + setGlobalDispatcher(mockClient) + + const query = { + pageNum: 1 + } + mockClient.intercept({ + path: '/foo?pageNum=1', + method: 'GET' + }).reply(200, 'hello') + + const { statusCode, body } = await request(`${baseUrl}/foo`, { + method: 'GET', + query + }) + t.equal(statusCode, 200) + + const response = await getResponse(body) + t.same(response, 'hello') +}) + test('MockClient - should be able to use as a local dispatcher', async (t) => { t.plan(3) diff --git a/types/mock-interceptor.d.ts b/types/mock-interceptor.d.ts index a6a1f1cc8e8..b7a2b339c04 100644 --- a/types/mock-interceptor.d.ts +++ b/types/mock-interceptor.d.ts @@ -50,6 +50,8 @@ declare namespace MockInterceptor { body?: string | RegExp | ((body: string) => boolean); /** Headers to intercept on. */ headers?: Record boolean)> | ((headers: Record) => boolean); + /** Query params to intercept on */ + query?: Record; } export interface MockDispatch extends Options { times: number | null; From 00b22b536214fc80a33580ce89f5e30f90ba74e7 Mon Sep 17 00:00:00 2001 From: Igor Savin Date: Wed, 25 May 2022 21:45:42 +0300 Subject: [PATCH 2/5] Fix linting --- lib/mock/mock-utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mock/mock-utils.js b/lib/mock/mock-utils.js index 412b91eecfe..14bfcb11879 100644 --- a/lib/mock/mock-utils.js +++ b/lib/mock/mock-utils.js @@ -155,7 +155,7 @@ function buildKey (opts) { method, body, headers, - query, + query } } From b13630dab9edcb1f165dd9c213dd72515ac70a5e Mon Sep 17 00:00:00 2001 From: Igor Savin Date: Wed, 25 May 2022 21:50:59 +0300 Subject: [PATCH 3/5] Fix failing test --- test/mock-interceptor-unused-assertions.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/mock-interceptor-unused-assertions.js b/test/mock-interceptor-unused-assertions.js index 7ad62904866..bfa2275777f 100644 --- a/test/mock-interceptor-unused-assertions.js +++ b/test/mock-interceptor-unused-assertions.js @@ -204,6 +204,7 @@ test('returns unused interceptors', t => { path: '/', method: 'GET', body: undefined, + query: undefined, headers: undefined, data: { error: null, From 64a8f978e95a491fcc040bacf9330c566ec5247c Mon Sep 17 00:00:00 2001 From: Igor Savin Date: Thu, 26 May 2022 11:06:46 +0300 Subject: [PATCH 4/5] Update docs --- docs/api/MockPool.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/api/MockPool.md b/docs/api/MockPool.md index 29667f3177b..87fde1ddd99 100644 --- a/docs/api/MockPool.md +++ b/docs/api/MockPool.md @@ -57,6 +57,7 @@ Returns: `MockInterceptor` corresponding to the input options. * **method** `string | RegExp | (method: string) => boolean` - a matcher for the HTTP request method. * **body** `string | RegExp | (body: string) => boolean` - (optional) - a matcher for the HTTP request body. * **headers** `Record boolean`> - (optional) - a matcher for the HTTP request headers. To be intercepted, a request must match all defined headers. Extra headers not defined here may (or may not) be included in the request and do not affect the interception in any way. +* **query** `Record | null` - (optional) - a matcher for the HTTP request query string params. ### Return: `MockInterceptor` From 72d032d217f6e92eb36972dd8dc7a62f91a9c7a1 Mon Sep 17 00:00:00 2001 From: Igor Savin Date: Thu, 26 May 2022 14:16:47 +0300 Subject: [PATCH 5/5] Add type test --- test/types/mock-client.test-d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/types/mock-client.test-d.ts b/test/types/mock-client.test-d.ts index 84461b98021..9e92b8ec1ec 100644 --- a/test/types/mock-client.test-d.ts +++ b/test/types/mock-client.test-d.ts @@ -8,6 +8,7 @@ import { MockInterceptor } from '../../types/mock-interceptor' // intercept expectAssignable(mockClient.intercept({ path: '', method: 'GET' })) expectAssignable(mockClient.intercept({ path: '', method: 'GET', body: '', headers: { 'User-Agent': '' } })) + expectAssignable(mockClient.intercept({ path: '', method: 'GET', query: { id: 1 } })) expectAssignable(mockClient.intercept({ path: new RegExp(''), method: new RegExp(''), body: new RegExp(''), headers: { 'User-Agent': new RegExp('') } })) expectAssignable(mockClient.intercept({ path: (path) => {