diff options
Diffstat (limited to '')
-rw-r--r-- | test/mock-client.js | 446 |
1 files changed, 446 insertions, 0 deletions
diff --git a/test/mock-client.js b/test/mock-client.js new file mode 100644 index 0000000..ef0600e --- /dev/null +++ b/test/mock-client.js @@ -0,0 +1,446 @@ +'use strict' + +const { test } = require('tap') +const { createServer } = require('http') +const { promisify } = require('util') +const { MockAgent, MockClient, setGlobalDispatcher, request } = require('..') +const { kUrl } = require('../lib/core/symbols') +const { kDispatches } = require('../lib/mock/mock-symbols') +const { InvalidArgumentError } = require('../lib/core/errors') +const { MockInterceptor } = require('../lib/mock/mock-interceptor') +const { getResponse } = require('../lib/mock/mock-utils') +const Dispatcher = require('../lib/dispatcher') + +test('MockClient - constructor', t => { + t.plan(3) + + t.test('fails if opts.agent does not implement `get` method', t => { + t.plan(1) + t.throws(() => new MockClient('http://localhost:9999', { agent: { get: 'not a function' } }), InvalidArgumentError) + }) + + t.test('sets agent', t => { + t.plan(1) + t.doesNotThrow(() => new MockClient('http://localhost:9999', { agent: new MockAgent({ connections: 1 }) })) + }) + + t.test('should implement the Dispatcher API', t => { + t.plan(1) + + const mockClient = new MockClient('http://localhost:9999', { agent: new MockAgent({ connections: 1 }) }) + t.type(mockClient, Dispatcher) + }) +}) + +test('MockClient - dispatch', t => { + t.plan(2) + + t.test('should handle a single interceptor', (t) => { + t.plan(1) + + const baseUrl = 'http://localhost:9999' + + const mockAgent = new MockAgent({ connections: 1 }) + t.teardown(mockAgent.close.bind(mockAgent)) + + const mockClient = mockAgent.get(baseUrl) + + this[kUrl] = new URL('http://localhost:9999') + mockClient[kDispatches] = [ + { + path: '/foo', + method: 'GET', + data: { + statusCode: 200, + data: 'hello', + headers: {}, + trailers: {}, + error: null + } + } + ] + + t.doesNotThrow(() => mockClient.dispatch({ + path: '/foo', + method: 'GET' + }, { + onHeaders: (_statusCode, _headers, resume) => resume(), + onData: () => {}, + onComplete: () => {} + })) + }) + + t.test('should directly throw error from mockDispatch function if error is not a MockNotMatchedError', (t) => { + t.plan(1) + + const baseUrl = 'http://localhost:9999' + + const mockAgent = new MockAgent({ connections: 1 }) + t.teardown(mockAgent.close.bind(mockAgent)) + + const mockClient = mockAgent.get(baseUrl) + + this[kUrl] = new URL('http://localhost:9999') + mockClient[kDispatches] = [ + { + path: '/foo', + method: 'GET', + data: { + statusCode: 200, + data: 'hello', + headers: {}, + trailers: {}, + error: null + } + } + ] + + t.throws(() => mockClient.dispatch({ + path: '/foo', + method: 'GET' + }, { + onHeaders: (_statusCode, _headers, resume) => { throw new Error('kaboom') }, + onData: () => {}, + onComplete: () => {} + }), new Error('kaboom')) + }) +}) + +test('MockClient - intercept should return a MockInterceptor', (t) => { + t.plan(1) + + const baseUrl = 'http://localhost:9999' + + const mockAgent = new MockAgent({ connections: 1 }) + t.teardown(mockAgent.close.bind(mockAgent)) + + const mockClient = mockAgent.get(baseUrl) + + const interceptor = mockClient.intercept({ + path: '/foo', + method: 'GET' + }) + + t.type(interceptor, MockInterceptor) +}) + +test('MockClient - intercept validation', (t) => { + t.plan(4) + + t.test('it should error if no options specified in the intercept', t => { + t.plan(1) + const mockAgent = new MockAgent({ connections: 1 }) + t.teardown(mockAgent.close.bind(mockAgent)) + + const mockClient = mockAgent.get('http://localhost:9999') + + t.throws(() => mockClient.intercept(), new InvalidArgumentError('opts must be an object')) + }) + + t.test('it should error if no path specified in the intercept', t => { + t.plan(1) + const mockAgent = new MockAgent({ connections: 1 }) + t.teardown(mockAgent.close.bind(mockAgent)) + + const mockClient = mockAgent.get('http://localhost:9999') + + t.throws(() => mockClient.intercept({}), new InvalidArgumentError('opts.path must be defined')) + }) + + t.test('it should default to GET if no method specified in the intercept', t => { + t.plan(1) + const mockAgent = new MockAgent({ connections: 1 }) + t.teardown(mockAgent.close.bind(mockAgent)) + + const mockClient = mockAgent.get('http://localhost:9999') + t.doesNotThrow(() => mockClient.intercept({ path: '/foo' })) + }) + + t.test('it should uppercase the method - https://github.com/nodejs/undici/issues/1320', t => { + t.plan(1) + + const mockAgent = new MockAgent() + const mockClient = mockAgent.get('http://localhost:3000') + + t.teardown(mockAgent.close.bind(mockAgent)) + + mockClient.intercept({ + path: '/test', + method: 'patch' + }).reply(200, 'Hello!') + + t.equal(mockClient[kDispatches][0].method, 'PATCH') + }) +}) + +test('MockClient - close should run without error', async (t) => { + t.plan(1) + + const baseUrl = 'http://localhost:9999' + + const mockAgent = new MockAgent({ connections: 1 }) + t.teardown(mockAgent.close.bind(mockAgent)) + + const mockClient = mockAgent.get(baseUrl) + mockClient[kDispatches] = [ + { + path: '/foo', + method: 'GET', + data: { + statusCode: 200, + data: 'hello', + headers: {}, + trailers: {}, + error: null + } + } + ] + + await t.resolves(mockClient.close()) +}) + +test('MockClient - should be able to set as globalDispatcher', 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) + + mockClient.intercept({ + path: '/foo', + method: 'GET' + }).reply(200, 'hello') + + const { statusCode, body } = await request(`${baseUrl}/foo`, { + method: 'GET' + }) + t.equal(statusCode, 200) + + const response = await getResponse(body) + 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 intercept query params regardless of key ordering', 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, + limit: 100, + ordering: [false, true] + } + + mockClient.intercept({ + path: '/foo', + query: { + ordering: query.ordering, + pageNum: query.pageNum, + limit: query.limit + }, + 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) + + 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) + + mockClient.intercept({ + path: '/foo', + method: 'GET' + }).reply(200, 'hello') + + const { statusCode, body } = await request(`${baseUrl}/foo`, { + method: 'GET', + dispatcher: mockClient + }) + t.equal(statusCode, 200) + + const response = await getResponse(body) + t.same(response, 'hello') +}) + +test('MockClient - basic intercept with MockClient.request', async (t) => { + t.plan(5) + + 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) + + mockClient.intercept({ + path: '/foo?hello=there&see=ya', + method: 'POST', + body: 'form1=data1&form2=data2' + }).reply(200, { foo: 'bar' }, { + headers: { 'content-type': 'application/json' }, + trailers: { 'Content-MD5': 'test' } + }) + + const { statusCode, headers, trailers, body } = await mockClient.request({ + origin: baseUrl, + path: '/foo?hello=there&see=ya', + method: 'POST', + body: 'form1=data1&form2=data2' + }) + t.equal(statusCode, 200) + t.equal(headers['content-type'], 'application/json') + t.same(trailers, { 'content-md5': 'test' }) + + const jsonResponse = JSON.parse(await getResponse(body)) + t.same(jsonResponse, { + foo: 'bar' + }) +}) |