Skip to content

Commit

Permalink
Fix mock bugs (nodejs#1323)
Browse files Browse the repository at this point in the history
* fix(MockAgent): allow lowercase `method`

* fix(MockAgent): set statusText properly

* add test for bad statusCode

* skip test on v14 and lower

* code cov
  • Loading branch information
KhafraDev authored and metcoder95 committed Dec 26, 2022
1 parent 2c6ada2 commit c871849
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 3 deletions.
3 changes: 3 additions & 0 deletions lib/mock/mock-interceptor.js
Expand Up @@ -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
Expand Down
77 changes: 76 additions & 1 deletion lib/mock/mock-utils.js
Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -264,6 +338,7 @@ module.exports = {
generateKeyValues,
matchValue,
getResponse,
getStatusText,
mockDispatch,
buildMockDispatch,
checkNetConnect,
Expand Down
36 changes: 36 additions & 0 deletions test/mock-agent.js
Expand Up @@ -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)

Expand Down Expand Up @@ -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()
})
18 changes: 17 additions & 1 deletion test/mock-client.js
Expand Up @@ -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)
Expand Down Expand Up @@ -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) => {
Expand Down
22 changes: 21 additions & 1 deletion test/mock-utils.js
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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()
})

0 comments on commit c871849

Please sign in to comment.