From 70799aca7f733acbf2be806801f131510cffd399 Mon Sep 17 00:00:00 2001 From: Khafra <42794878+KhafraDev@users.noreply.github.com> Date: Wed, 6 Apr 2022 06:49:45 -0400 Subject: [PATCH] Fix mock bugs (#1323) * fix(MockAgent): allow lowercase `method` * fix(MockAgent): set statusText properly * add test for bad statusCode * skip test on v14 and lower * code cov --- lib/mock/mock-interceptor.js | 3 ++ lib/mock/mock-utils.js | 77 +++++++++++++++++++++++++++++++++++- test/mock-agent.js | 36 +++++++++++++++++ test/mock-client.js | 18 ++++++++- test/mock-utils.js | 22 ++++++++++- 5 files changed, 153 insertions(+), 3 deletions(-) diff --git a/lib/mock/mock-interceptor.js b/lib/mock/mock-interceptor.js index 699bec41287..5af8b1083f0 100644 --- a/lib/mock/mock-interceptor.js +++ b/lib/mock/mock-interceptor.js @@ -74,6 +74,9 @@ class MockInterceptor { const parsedURL = new URL(opts.path, 'data://') opts.path = parsedURL.pathname + parsedURL.search } + if (typeof opts.method === 'string') { + opts.method = opts.method.toUpperCase() + } this[kDispatchKey] = buildKey(opts) this[kDispatches] = mockDispatches diff --git a/lib/mock/mock-utils.js b/lib/mock/mock-utils.js index 8bd4df51a09..65e21c6bf5c 100644 --- a/lib/mock/mock-utils.js +++ b/lib/mock/mock-utils.js @@ -140,6 +140,80 @@ function generateKeyValues (data) { return Object.entries(data).reduce((keyValuePairs, [key, value]) => [...keyValuePairs, key, value], []) } +/** + * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status + * @param {number} statusCode + */ +function getStatusText (statusCode) { + switch (statusCode) { + case 100: return 'Continue' + case 101: return 'Switching Protocols' + case 102: return 'Processing' + case 103: return 'Early Hints' + case 200: return 'OK' + case 201: return 'Created' + case 202: return 'Accepted' + case 203: return 'Non-Authoritative Information' + case 204: return 'No Content' + case 205: return 'Reset Content' + case 206: return 'Partial Content' + case 207: return 'Multi-Status' + case 208: return 'Already Reported' + case 226: return 'IM Used' + case 300: return 'Multiple Choice' + case 301: return 'Moved Permanently' + case 302: return 'Found' + case 303: return 'See Other' + case 304: return 'Not Modified' + case 305: return 'Use Proxy' + case 306: return 'unused' + case 307: return 'Temporary Redirect' + case 308: return 'Permanent Redirect' + case 400: return 'Bad Request' + case 401: return 'Unauthorized' + case 402: return 'Payment Required' + case 403: return 'Forbidden' + case 404: return 'Not Found' + case 405: return 'Method Not Allowed' + case 406: return 'Not Acceptable' + case 407: return 'Proxy Authentication Required' + case 408: return 'Request Timeout' + case 409: return 'Conflict' + case 410: return 'Gone' + case 411: return 'Length Required' + case 412: return 'Precondition Failed' + case 413: return 'Payload Too Large' + case 414: return 'URI Too Large' + case 415: return 'Unsupported Media Type' + case 416: return 'Range Not Satisfiable' + case 417: return 'Expectation Failed' + case 418: return 'I\'m a teapot' + case 421: return 'Misdirected Request' + case 422: return 'Unprocessable Entity' + case 423: return 'Locked' + case 424: return 'Failed Dependency' + case 425: return 'Too Early' + case 426: return 'Upgrade Required' + case 428: return 'Precondition Required' + case 429: return 'Too Many Requests' + case 431: return 'Request Header Fields Too Large' + case 451: return 'Unavailable For Legal Reasons' + case 500: return 'Internal Server Error' + case 501: return 'Not Implemented' + case 502: return 'Bad Gateway' + case 503: return 'Service Unavailable' + case 504: return 'Gateway Timeout' + case 505: return 'HTTP Version Not Supported' + case 506: return 'Variant Also Negotiates' + case 507: return 'Insufficient Storage' + case 508: return 'Loop Detected' + case 510: return 'Not Extended' + case 511: return 'Network Authentication Required' + default: + throw new ReferenceError(`Unknown status code "${statusCode}"!`) + } +} + async function getResponse (body) { const buffers = [] for await (const data of body) { @@ -197,7 +271,7 @@ function mockDispatch (opts, handler) { const responseHeaders = generateKeyValues(headers) const responseTrailers = generateKeyValues(trailers) - handler.onHeaders(statusCode, responseHeaders, resume) + handler.onHeaders(statusCode, responseHeaders, resume, getStatusText(statusCode)) handler.onData(Buffer.from(responseData)) handler.onComplete(responseTrailers) deleteMockDispatch(mockDispatches, key) @@ -264,6 +338,7 @@ module.exports = { generateKeyValues, matchValue, getResponse, + getStatusText, mockDispatch, buildMockDispatch, checkNetConnect, diff --git a/test/mock-agent.js b/test/mock-agent.js index 3c4edbf81b9..00e6f1e693d 100644 --- a/test/mock-agent.js +++ b/test/mock-agent.js @@ -13,6 +13,8 @@ const { kAgent } = require('../lib/mock/mock-symbols') const Dispatcher = require('../lib/dispatcher') const { MockNotMatchedError } = require('../lib/mock/mock-errors') +const nodeMajor = Number(process.versions.node.split('.')[0]) + test('MockAgent - constructor', t => { t.plan(5) @@ -2396,3 +2398,37 @@ test('MockAgent - clients are not garbage collected', async (t) => { t.equal(results.size, 1) t.ok(results.has(200)) }) + +// https://github.com/nodejs/undici/issues/1321 +test('MockAgent - using fetch yields correct statusText', { skip: nodeMajor < 16 }, async (t) => { + const { fetch } = require('..') + + const mockAgent = new MockAgent() + mockAgent.disableNetConnect() + setGlobalDispatcher(mockAgent) + t.teardown(mockAgent.close.bind(mockAgent)) + + const mockPool = mockAgent.get('http://localhost:3000') + + mockPool.intercept({ + path: '/statusText', + method: 'GET' + }).reply(200, 'Body') + + const { status, statusText } = await fetch('http://localhost:3000/statusText') + + t.equal(status, 200) + t.equal(statusText, 'OK') + + mockPool.intercept({ + path: '/badStatusText', + method: 'GET' + }).reply(420, 'Everyday') + + await t.rejects( + fetch('http://localhost:3000/badStatusText'), + TypeError + ) + + t.end() +}) diff --git a/test/mock-client.js b/test/mock-client.js index e5a89af3d4c..3700954c5c2 100644 --- a/test/mock-client.js +++ b/test/mock-client.js @@ -125,7 +125,7 @@ test('MockClient - intercept should return a MockInterceptor', (t) => { }) test('MockClient - intercept validation', (t) => { - t.plan(3) + t.plan(4) t.test('it should error if no options specified in the intercept', t => { t.plan(1) @@ -155,6 +155,22 @@ test('MockClient - intercept validation', (t) => { 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) => { diff --git a/test/mock-utils.js b/test/mock-utils.js index 296143aa319..3e6637e7989 100644 --- a/test/mock-utils.js +++ b/test/mock-utils.js @@ -5,7 +5,8 @@ const { MockNotMatchedError } = require('../lib/mock/mock-errors') const { deleteMockDispatch, getMockDispatch, - getResponseData + getResponseData, + getStatusText } = require('../lib/mock/mock-utils') test('deleteMockDispatch - should do nothing if not able to find mock dispatch', (t) => { @@ -109,3 +110,22 @@ test('getResponseData', (t) => { t.ok(Buffer.isBuffer(responseData)) }) }) + +test('getStatusText', (t) => { + for (const statusCode of [ + 100, 101, 102, 103, 200, 201, 202, 203, + 204, 205, 206, 207, 208, 226, 300, 301, + 302, 303, 304, 305, 306, 307, 308, 400, + 401, 402, 403, 404, 405, 406, 407, 408, + 409, 410, 411, 412, 413, 414, 415, 416, + 417, 418, 421, 422, 423, 424, 425, 426, + 428, 429, 431, 451, 500, 501, 502, 503, + 504, 505, 506, 507, 508, 510, 511 + ]) { + t.ok(getStatusText(statusCode)) + } + + t.throws(() => getStatusText(420), ReferenceError) + + t.end() +})