Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use @mswjs/interceptors for mocking #2517

Merged
merged 55 commits into from
Jul 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
576b296
wip
mikicho Sep 11, 2023
94b9b90
wip
mikicho Sep 17, 2023
ef59cf6
wip
mikicho Sep 17, 2023
d7d17bd
fixes
mikicho Sep 18, 2023
855914e
fix
mikicho Sep 18, 2023
51dd0c6
wip
mikicho Sep 21, 2023
21cf9c2
more fixes
mikicho Sep 21, 2023
091d876
more fixes
mikicho Sep 23, 2023
79f5162
more fixes
mikicho Sep 24, 2023
2028d4e
more fixes
mikicho Sep 26, 2023
bba8bdb
small fix
mikicho Sep 26, 2023
5e521a1
more fixes
mikicho Sep 27, 2023
e1c3eb4
more fixes
mikicho Sep 29, 2023
e89acec
Merge remote-tracking branch 'upstream/main' into Michael/fetch-support
mikicho Oct 26, 2023
cbd31cb
wip
mikicho Dec 15, 2023
bc4327a
Merge remote-tracking branch 'upstream/main' into Michael/fetch-support
mikicho Dec 15, 2023
7df18f2
wip
mikicho Dec 16, 2023
53e3d2a
fix
mikicho Dec 17, 2023
ca9e616
fix
mikicho Dec 18, 2023
2c0e4f1
more-tests
mikicho Dec 19, 2023
bcbdd04
wip
mikicho Feb 3, 2024
f4d1b15
ci(test): use a single `test` job that we can require, independent of…
gr2m Feb 3, 2024
4ad60ba
add experimental fetch support notice (#2583)
mikicho Feb 6, 2024
8deca36
docs: add mikicho as a contributor for maintenance, code, and doc (#2…
allcontributors[bot] Feb 6, 2024
8bab28d
ci: add node 20 in ci (#2585)
VladimirChuprazov Feb 7, 2024
7e957b3
fix: remove duplicates from `activeMocks()` and `pendingMocks()` (#2356)
mbargiel Feb 17, 2024
4162fa8
fix: support literal query string (#2590)
mikicho Feb 17, 2024
1005698
chore(deps-dev): bump eslint-plugin-import from 2.29.0 to 2.29.1 (#2577)
dependabot[bot] Feb 26, 2024
08b2b09
chore(deps-dev): bump prettier from 3.1.0 to 3.2.4 (#2578)
dependabot[bot] Feb 26, 2024
81c20dd
chore(deps-dev): bump chai from 4.3.10 to 4.4.1 (#2576)
dependabot[bot] Feb 26, 2024
ba9fc42
fix: call `fs.createReadStream` lazily (#2357)
mbargiel Feb 26, 2024
cbb135d
chore(deps-dev): bump semantic-release from 22.0.6 to 23.0.2 (#2598)
dependabot[bot] Mar 2, 2024
3659e82
chore(deps-dev): bump prettier from 3.2.4 to 3.2.5 (#2596)
dependabot[bot] Mar 2, 2024
e44812b
chore(deps-dev): bump eslint from 8.56.0 to 8.57.0 (#2597)
dependabot[bot] Mar 2, 2024
ab8037d
wip
mikicho Mar 13, 2024
db0f76a
chore(deps-dev): bump eslint-plugin-mocha from 10.2.0 to 10.4.1
dependabot[bot] Apr 1, 2024
7a4badb
chore(deps-dev): bump semantic-release from 23.0.2 to 23.0.6
dependabot[bot] Apr 1, 2024
d013ed2
chore(deps-dev): bump typescript from 5.3.3 to 5.4.3
dependabot[bot] Apr 1, 2024
e7a6309
fix
mikicho Apr 13, 2024
dac5a26
fix
mikicho May 30, 2024
36f6778
fix
mikicho Jun 1, 2024
565e99a
chore(deps-dev): bump braces from 3.0.2 to 3.0.3 (#2752)
dependabot[bot] Jun 20, 2024
2b7836d
ci: exclude nodejs 10, 12 and 14 tests running on macos (#2753)
Uzlopak Jun 20, 2024
73e507d
fix
mikicho Jul 4, 2024
7c89896
fix
mikicho Jul 6, 2024
dfd069c
fix
mikicho Jul 6, 2024
cf3b522
fix
mikicho Jul 6, 2024
16453f7
Merge remote-tracking branch 'origin/main' into Michael/fetch-support
mikicho Jul 6, 2024
2746b60
fix
mikicho Jul 12, 2024
67c1481
fix
mikicho Jul 12, 2024
7baaf27
clean
mikicho Jul 12, 2024
6acc6df
fix
mikicho Jul 12, 2024
a083fdf
fix
mikicho Jul 13, 2024
7f6422d
Merge remote-tracking branch 'origin/beta' into Michael/fetch-support
mikicho Jul 13, 2024
e941ae7
fix
mikicho Jul 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/continuous-integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ jobs:
- macos-latest
- ubuntu-latest
- windows-latest

runs-on: ${{ matrix.os }}
timeout-minutes: 5

Expand Down
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package.json
package-lock.json
tsconfig.json
/.nyc_output
/coverage
/tests/browserify-public/browserify-bundle.js
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,36 @@ Nock’s changelog can be found directly in the [GitHub release notes](https://g
These are automatically created by [semantic-release](https://github.com/semantic-release/semantic-release) based on their [commit message conventions](https://semantic-release.gitbook.io/semantic-release#commit-message-format).

Migration guides are available for major versions in the [migration guides directory](https://github.com/nock/nock/tree/main/migration_guides).

// TODO: Remove this before merge:

# Breaking changes:

1. No longer support preemptive timeout for delay connection. Please use fake timers instead.

We increased our compatibility with Node.js:
1. Request (http.get/request) interception resolution is no longer sync.
2. socket.authorized now returns false. This is the case most of the time.
2. headers matcher gets non-string values.
2. socket ref/unref return this.
3. response rawHeaders no longer return arrays.
3. We no longer support undefined content-length
1. GET requests no longer may have body.
3. 204, 205, 304 responses can not have body.

# Topics to discuss
1. In this PR I tried (very poorly :sweat_smile:) to keep the changes to minimum. My next step is to remove all parts that we no longer need, as now the interception logic sits in mswjs/interceptors.
1. test: does not record requests from previous sessions
4. test: get correct filtering with scope and request headers filtering - why is this considered as correct behavior?
5. test: should be safe to call in the middle of a request
We can (should?) send cleanAll to next loop with setImmediate
6. test: socket emits connect and secureConnect - edge case (https://github.com/mswjs/interceptors/pull/515#issuecomment-2067702330)
7. test: error events on reply streams proxy to the response - what's the use case for this?

# Need to be done
1. Support fetch decompress (https://github.com/mswjs/interceptors/pull/604)
3. test: Request with `Expect: 100-continue` triggers continue event (https://github.com/mswjs/interceptors/pull/599)
5. test: socket has getPeerCertificate() method which returns a random base64 string

For me:
Why tests stuck if expect fails in req callback?
1 change: 0 additions & 1 deletion lib/back.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ function Back(fixtureName, options, nockedFn) {
}

debug('context:', context)

// If nockedFn is a function then invoke it, otherwise return a promise resolving to nockDone.
if (typeof nockedFn === 'function') {
nockedFn.call(context, nockDone)
Expand Down
125 changes: 28 additions & 97 deletions lib/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const { common: debug } = require('./debug')
const timers = require('timers')
const url = require('url')
const util = require('util')
const http = require('http')

/**
* Normalizes the request options so that it always has `host` property.
Expand Down Expand Up @@ -50,82 +51,6 @@ function isUtf8Representable(buffer) {
return reconstructedBuffer.equals(buffer)
}

// Array where all information about all the overridden requests are held.
let requestOverrides = {}

/**
* Overrides the current `request` function of `http` and `https` modules with
* our own version which intercepts issues HTTP/HTTPS requests and forwards them
* to the given `newRequest` function.
*
* @param {Function} newRequest - a function handling requests; it accepts four arguments:
* - proto - a string with the overridden module's protocol name (either `http` or `https`)
* - overriddenRequest - the overridden module's request function already bound to module's object
* - options - the options of the issued request
* - callback - the callback of the issued request
*/
function overrideRequests(newRequest) {
debug('overriding requests')
;['http', 'https'].forEach(function (proto) {
debug('- overriding request for', proto)

const moduleName = proto // 1 to 1 match of protocol and module is fortunate :)
const module = require(proto)
const overriddenRequest = module.request
const overriddenGet = module.get

if (requestOverrides[moduleName]) {
throw new Error(
`Module's request already overridden for ${moduleName} protocol.`,
)
}

// Store the properties of the overridden request so that it can be restored later on.
requestOverrides[moduleName] = {
module,
request: overriddenRequest,
get: overriddenGet,
}
// https://nodejs.org/api/http.html#http_http_request_url_options_callback
module.request = function (input, options, callback) {
return newRequest(proto, overriddenRequest.bind(module), [
input,
options,
callback,
])
}
// https://nodejs.org/api/http.html#http_http_get_options_callback
module.get = function (input, options, callback) {
const req = newRequest(proto, overriddenGet.bind(module), [
input,
options,
callback,
])
req.end()
return req
}

debug('- overridden request for', proto)
})
}

/**
* Restores `request` function of `http` and `https` modules to values they
* held before they were overridden by us.
*/
function restoreOverriddenRequests() {
debug('restoring requests')
Object.entries(requestOverrides).forEach(
([proto, { module, request, get }]) => {
debug('- restoring request for', proto)
module.request = request
module.get = get
debug('- restored request for', proto)
},
)
requestOverrides = {}
}

/**
* In WHATWG URL vernacular, this returns the origin portion of a URL.
* However, the port is not included if it's standard and not already present on the host.
Expand Down Expand Up @@ -616,6 +541,7 @@ function clearTimer(clear, ids) {
}

function removeAllTimers() {
debug('remove all timers')
clearTimer(clearTimeout, timeouts)
clearTimer(clearInterval, intervals)
clearTimer(clearImmediate, immediates)
Expand Down Expand Up @@ -649,6 +575,31 @@ function isRequestDestroyed(req) {
)
}

/**
* @param {Request} request
*/
function convertFetchRequestToClientRequest(request) {
const url = new URL(request.url);
const options = {
...urlToOptions(url),
method: request.method,
host: url.hostname,
port: url.port || (url.protocol === 'https:' ? 443 : 80),
path: url.pathname + url.search,
proto: url.protocol.slice(0, -1),
headers: Object.fromEntries(request.headers.entries())
};

// By default, Node adds a host header, but for maximum backward compatibility, we are now removing it.
// However, we need to consider leaving the header and fixing the tests.
if (options.headers.host === options.host) {
const { host, ...restHeaders } = options.headers
options.headers = restHeaders
}

return new http.ClientRequest(options);
}

/**
* Returns true if the given value is a plain object and not an Array.
* @param {*} value
Expand Down Expand Up @@ -735,24 +686,6 @@ const expand = input => {
return result
}

/**
* @param {Request} request
*/
function convertFetchRequestToOptions(request) {
const url = new URL(request.url)
const options = {
...urlToOptions(url),
method: request.method,
host: url.hostname,
port: url.port || (url.protocol === 'https:' ? 443 : 80),
path: url.pathname + url.search,
proto: url.protocol.slice(0, -1),
headers: Object.fromEntries(request.headers.entries()),
}

return options
}

module.exports = {
contentEncoding,
dataEqual,
Expand All @@ -774,14 +707,12 @@ module.exports = {
normalizeClientRequestArgs,
normalizeOrigin,
normalizeRequestOptions,
overrideRequests,
percentDecode,
percentEncode,
removeAllTimers,
restoreOverriddenRequests,
setImmediate,
setInterval,
setTimeout,
stringifyRequest,
convertFetchRequestToClientRequest: convertFetchRequestToOptions,
convertFetchRequestToClientRequest,
}
89 changes: 22 additions & 67 deletions lib/create_response.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
'use strict'

const zlib = require('node:zlib')
const { headersArrayToObject } = require('./common')
const { STATUS_CODES } = require('http')
const { pipeline, Readable } = require('node:stream')

/**
* Creates a Fetch API `Response` instance from the given
Expand All @@ -18,79 +15,37 @@ const { pipeline, Readable } = require('node:stream')
const responseStatusCodesWithoutBody = [204, 205, 304]

/**
* @param {import('node:http').IncomingMessage} message
* @param {import('http').IncomingMessage} message
*/
function createResponse(message) {
// https://github.com/Uzlopak/undici/blob/main/lib/fetch/index.js#L2031
const decoders = []
const codings =
message.headers['content-encoding']
?.toLowerCase()
.split(',')
.map(x => x.trim())
.reverse() || []
for (const coding of codings) {
if (coding === 'gzip' || coding === 'x-gzip') {
decoders.push(
zlib.createGunzip({
flush: zlib.constants.Z_SYNC_FLUSH,
finishFlush: zlib.constants.Z_SYNC_FLUSH,
}),
)
} else if (coding === 'deflate') {
decoders.push(zlib.createInflate())
} else if (coding === 'br') {
decoders.push(zlib.createBrotliDecompress())
} else {
decoders.length = 0
break
}
}

let isCanceled = false
const chunks = []
const responseBodyOrNull = responseStatusCodesWithoutBody.includes(
message.statusCode,
message.statusCode || 200
)
? null
: new ReadableStream({
start(controller) {
message.on('data', chunk => chunks.push(chunk))
message.on('end', () => {
pipeline(
Readable.from(chunks),
...decoders,
async function* (source) {
for await (const chunk of source) {
yield controller.enqueue(chunk)
}
},
error => {
if (error) {
controller.error(error)
} else if (!isCanceled) {
controller.close()
}
},
)
})

/**
* @todo Should also listen to the "error" on the message
* and forward it to the controller. Otherwise the stream
* will pend indefinitely.
*/
},
cancel() {
isCanceled = true
},
})
start(controller) {
message.on('data', (chunk) => controller.enqueue(chunk))
message.on('end', () => controller.close())
message.on('error', (error) => controller.error(error))
},
cancel() {
message.destroy()
},
})

const rawHeaders = new Headers()
for (let i = 0; i < message.rawHeaders.length; i += 2) {
rawHeaders.append(message.rawHeaders[i], message.rawHeaders[i + 1])
}

return new Response(responseBodyOrNull, {
// @mswjs/interceptors supports rawHeaders. https://github.com/mswjs/interceptors/pull/598
const response = new Response(responseBodyOrNull, {
status: message.statusCode,
statusText: STATUS_CODES[message.statusCode],
headers: headersArrayToObject(message.rawHeaders),
statusText: message.statusMessage || STATUS_CODES[message.statusCode],
headers: rawHeaders,
})

return response
}

module.exports = { createResponse }
Loading
Loading