Skip to content

Commit

Permalink
Merge branch 'main' into mark-uncloneable
Browse files Browse the repository at this point in the history
  • Loading branch information
KhafraDev authored Oct 11, 2024
2 parents 959efaa + 60c07b2 commit 42ce1b0
Show file tree
Hide file tree
Showing 49 changed files with 1,059 additions and 399 deletions.
26 changes: 26 additions & 0 deletions benchmarks/fetch/webidl-is.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { bench, run, barplot } from 'mitata'
import { Headers, FormData } from '../../index.js'
import { webidl } from '../../lib/web/fetch/webidl.js'

const headers = new Headers()
const fd = new FormData()

barplot(() => {
bench('webidl.is.FormData (ok)', () => {
return webidl.is.FormData(fd)
})

bench('webidl.is.FormData (bad)', () => {
return !webidl.is.FormData(headers)
})

bench('instanceof (ok)', () => {
return fd instanceof FormData
})

bench('instanceof (bad)', () => {
return !(headers instanceof FormData)
})
})

await run()
2 changes: 2 additions & 0 deletions docs/docs/api/MockAgent.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ Extends: [`AgentOptions`](Agent.md#parameter-agentoptions)

* **agent** `Agent` (optional) - Default: `new Agent([options])` - a custom agent encapsulated by the MockAgent.

* **ignoreTrailingSlash** `boolean` (optional) - Default: `false` - set the default value for `ignoreTrailingSlash` for interceptors.

### Example - Basic MockAgent instantiation

This will instantiate the MockAgent. It will not do anything until registered as the agent to use with requests and mock interceptions are added.
Expand Down
1 change: 1 addition & 0 deletions docs/docs/api/MockPool.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ Returns: `MockInterceptor` corresponding to the input options.
* **body** `string | RegExp | (body: string) => boolean` - (optional) - a matcher for the HTTP request body.
* **headers** `Record<string, string | RegExp | (body: string) => 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<string, any> | null` - (optional) - a matcher for the HTTP request query string params. Only applies when a `string` was provided for `MockPoolInterceptOptions.path`.
* **ignoreTrailingSlash** `boolean` - (optional) - set to `true` if the matcher should also match by ignoring potential trailing slashes in `MockPoolInterceptOptions.path`.

### Return: `MockInterceptor`

Expand Down
2 changes: 0 additions & 2 deletions lib/dispatcher/client-h1.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,6 @@ class Parser {
* @param {*} llhttp
*/
constructor (client, socket, { exports }) {
assert(Number.isFinite(client[kMaxHeadersSize]) && client[kMaxHeadersSize] > 0)

this.llhttp = exports
this.ptr = this.llhttp.llhttp_alloc(constants.TYPE.RESPONSE)
this.client = client
Expand Down
21 changes: 16 additions & 5 deletions lib/dispatcher/client.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// @ts-check

'use strict'

const assert = require('node:assert')
Expand Down Expand Up @@ -60,6 +58,13 @@ const connectH2 = require('./client-h2.js')

const kClosedResolve = Symbol('kClosedResolve')

const getDefaultNodeMaxHeaderSize = http &&
http.maxHeaderSize &&
Number.isInteger(http.maxHeaderSize) &&
http.maxHeaderSize > 0
? () => http.maxHeaderSize
: () => { throw new InvalidArgumentError('http module not available or http.maxHeaderSize invalid') }

const noop = () => {}

function getPipelining (client) {
Expand Down Expand Up @@ -123,8 +128,14 @@ class Client extends DispatcherBase {
throw new InvalidArgumentError('unsupported maxKeepAliveTimeout, use keepAliveMaxTimeout instead')
}

if (maxHeaderSize != null && !Number.isFinite(maxHeaderSize)) {
throw new InvalidArgumentError('invalid maxHeaderSize')
if (maxHeaderSize != null) {
if (!Number.isInteger(maxHeaderSize) || maxHeaderSize < 1) {
throw new InvalidArgumentError('invalid maxHeaderSize')
}
} else {
// If maxHeaderSize is not provided, use the default value from the http module
// or if that is not available, throw an error.
maxHeaderSize = getDefaultNodeMaxHeaderSize()
}

if (socketPath != null && typeof socketPath !== 'string') {
Expand Down Expand Up @@ -204,7 +215,7 @@ class Client extends DispatcherBase {
this[kUrl] = util.parseOrigin(url)
this[kConnector] = connect
this[kPipelining] = pipelining != null ? pipelining : 1
this[kMaxHeadersSize] = maxHeaderSize || http.maxHeaderSize
this[kMaxHeadersSize] = maxHeaderSize
this[kKeepAliveDefaultTimeout] = keepAliveTimeout == null ? 4e3 : keepAliveTimeout
this[kKeepAliveMaxTimeout] = keepAliveMaxTimeout == null ? 600e3 : keepAliveMaxTimeout
this[kKeepAliveTimeoutThreshold] = keepAliveTimeoutThreshold == null ? 2e3 : keepAliveTimeoutThreshold
Expand Down
6 changes: 3 additions & 3 deletions lib/dispatcher/pool-base.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,9 @@ class PoolBase extends DispatcherBase {

async [kClose] () {
if (this[kQueue].isEmpty()) {
return Promise.all(this[kClients].map(c => c.close()))
await Promise.all(this[kClients].map(c => c.close()))
} else {
return new Promise((resolve) => {
await new Promise((resolve) => {
this[kClosedResolve] = resolve
})
}
Expand All @@ -130,7 +130,7 @@ class PoolBase extends DispatcherBase {
item.handler.onError(err)
}

return Promise.all(this[kClients].map(c => c.destroy(err)))
await Promise.all(this[kClients].map(c => c.destroy(err)))
}

[kDispatch] (opts, handler) {
Expand Down
9 changes: 7 additions & 2 deletions lib/mock/mock-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ const {
kOriginalClose,
kOrigin,
kOriginalDispatch,
kConnected
kConnected,
kIgnoreTrailingSlash
} = require('./mock-symbols')
const { MockInterceptor } = require('./mock-interceptor')
const Symbols = require('../core/symbols')
Expand All @@ -29,6 +30,7 @@ class MockClient extends Client {

this[kMockAgent] = opts.agent
this[kOrigin] = origin
this[kIgnoreTrailingSlash] = opts.ignoreTrailingSlash ?? false
this[kDispatches] = []
this[kConnected] = 1
this[kOriginalDispatch] = this.dispatch
Expand All @@ -46,7 +48,10 @@ class MockClient extends Client {
* Sets up the base interceptor for mocking replies from undici.
*/
intercept (opts) {
return new MockInterceptor(opts, this[kDispatches])
return new MockInterceptor(
opts && { ignoreTrailingSlash: this[kIgnoreTrailingSlash], ...opts },
this[kDispatches]
)
}

async [kClose] () {
Expand Down
10 changes: 6 additions & 4 deletions lib/mock/mock-interceptor.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ const {
kDefaultHeaders,
kDefaultTrailers,
kContentLength,
kMockDispatch
kMockDispatch,
kIgnoreTrailingSlash
} = require('./mock-symbols')
const { InvalidArgumentError } = require('../core/errors')
const { serializePathWithQuery } = require('../core/util')
Expand Down Expand Up @@ -85,6 +86,7 @@ class MockInterceptor {

this[kDispatchKey] = buildKey(opts)
this[kDispatches] = mockDispatches
this[kIgnoreTrailingSlash] = opts.ignoreTrailingSlash ?? false
this[kDefaultHeaders] = {}
this[kDefaultTrailers] = {}
this[kContentLength] = false
Expand Down Expand Up @@ -137,7 +139,7 @@ class MockInterceptor {
}

// Add usual dispatch data, but this time set the data parameter to function that will eventually provide data.
const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], wrappedDefaultsCallback)
const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], wrappedDefaultsCallback, { ignoreTrailingSlash: this[kIgnoreTrailingSlash] })
return new MockScope(newMockDispatch)
}

Expand All @@ -154,7 +156,7 @@ class MockInterceptor {

// Send in-already provided data like usual
const dispatchData = this.createMockScopeDispatchData(replyParameters)
const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], dispatchData)
const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], dispatchData, { ignoreTrailingSlash: this[kIgnoreTrailingSlash] })
return new MockScope(newMockDispatch)
}

Expand All @@ -166,7 +168,7 @@ class MockInterceptor {
throw new InvalidArgumentError('error must be defined')
}

const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], { error })
const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], { error }, { ignoreTrailingSlash: this[kIgnoreTrailingSlash] })
return new MockScope(newMockDispatch)
}

Expand Down
9 changes: 7 additions & 2 deletions lib/mock/mock-pool.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ const {
kOriginalClose,
kOrigin,
kOriginalDispatch,
kConnected
kConnected,
kIgnoreTrailingSlash
} = require('./mock-symbols')
const { MockInterceptor } = require('./mock-interceptor')
const Symbols = require('../core/symbols')
Expand All @@ -29,6 +30,7 @@ class MockPool extends Pool {

this[kMockAgent] = opts.agent
this[kOrigin] = origin
this[kIgnoreTrailingSlash] = opts.ignoreTrailingSlash ?? false
this[kDispatches] = []
this[kConnected] = 1
this[kOriginalDispatch] = this.dispatch
Expand All @@ -46,7 +48,10 @@ class MockPool extends Pool {
* Sets up the base interceptor for mocking replies from undici.
*/
intercept (opts) {
return new MockInterceptor(opts, this[kDispatches])
return new MockInterceptor(
opts && { ignoreTrailingSlash: this[kIgnoreTrailingSlash], ...opts },
this[kDispatches]
)
}

async [kClose] () {
Expand Down
3 changes: 2 additions & 1 deletion lib/mock/mock-symbols.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ module.exports = {
kIsMockActive: Symbol('is mock active'),
kNetConnect: Symbol('net connect'),
kGetNetConnect: Symbol('get net connect'),
kConnected: Symbol('connected')
kConnected: Symbol('connected'),
kIgnoreTrailingSlash: Symbol('ignore trailing slash')
}
30 changes: 27 additions & 3 deletions lib/mock/mock-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,16 @@ function getMockDispatch (mockDispatches, key) {
const basePath = key.query ? serializePathWithQuery(key.path, key.query) : key.path
const resolvedPath = typeof basePath === 'string' ? safeUrl(basePath) : basePath

const resolvedPathWithoutTrailingSlash = removeTrailingSlash(resolvedPath)

// Match path
let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path }) => matchValue(safeUrl(path), resolvedPath))
let matchedMockDispatches = mockDispatches
.filter(({ consumed }) => !consumed)
.filter(({ path, ignoreTrailingSlash }) => {
return ignoreTrailingSlash
? matchValue(removeTrailingSlash(safeUrl(path)), resolvedPathWithoutTrailingSlash)
: matchValue(safeUrl(path), resolvedPath)
})
if (matchedMockDispatches.length === 0) {
throw new MockNotMatchedError(`Mock dispatch not matched for path '${resolvedPath}'`)
}
Expand All @@ -161,8 +169,8 @@ function getMockDispatch (mockDispatches, key) {
return matchedMockDispatches[0]
}

function addMockDispatch (mockDispatches, key, data) {
const baseData = { timesInvoked: 0, times: 1, persist: false, consumed: false }
function addMockDispatch (mockDispatches, key, data, opts) {
const baseData = { timesInvoked: 0, times: 1, persist: false, consumed: false, ...opts }
const replyData = typeof data === 'function' ? { callback: data } : { ...data }
const newMockDispatch = { ...baseData, ...key, pending: true, data: { error: null, ...replyData } }
mockDispatches.push(newMockDispatch)
Expand All @@ -181,8 +189,24 @@ function deleteMockDispatch (mockDispatches, key) {
}
}

/**
* @param {string} path Path to remove trailing slash from
*/
function removeTrailingSlash (path) {
while (path.endsWith('/')) {
path = path.slice(0, -1)
}

if (path.length === 0) {
path = '/'
}

return path
}

function buildKey (opts) {
const { path, method, body, headers, query } = opts

return {
path,
method,
Expand Down
15 changes: 9 additions & 6 deletions lib/web/cache/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const { kConstruct } = require('../../core/symbols')
const { urlEquals, getFieldValues } = require('./util')
const { kEnumerableProperty, isDisturbed } = require('../../core/util')
const { webidl } = require('../fetch/webidl')
const { Response, cloneResponse, fromInnerResponse, getResponseState } = require('../fetch/response')
const { cloneResponse, fromInnerResponse, getResponseState } = require('../fetch/response')
const { Request, fromInnerRequest, getRequestState } = require('../fetch/request')
const { fetching } = require('../fetch/index')
const { urlIsHttpHttpsScheme, createDeferredPromise, readAllBytes } = require('../fetch/util')
Expand Down Expand Up @@ -269,7 +269,7 @@ class Cache {
let innerRequest = null

// 2.
if (request instanceof Request) {
if (webidl.is.Request(request)) {
innerRequest = getRequestState(request)
} else { // 3.
innerRequest = getRequestState(new Request(request))
Expand Down Expand Up @@ -401,7 +401,7 @@ class Cache {
*/
let r = null

if (request instanceof Request) {
if (webidl.is.Request(request)) {
r = getRequestState(request)

if (r.method !== 'GET' && !options.ignoreMethod) {
Expand Down Expand Up @@ -467,7 +467,7 @@ class Cache {
// 2.
if (request !== undefined) {
// 2.1
if (request instanceof Request) {
if (webidl.is.Request(request)) {
// 2.1.1
r = getRequestState(request)

Expand Down Expand Up @@ -749,7 +749,7 @@ class Cache {

// 2.
if (request !== undefined) {
if (request instanceof Request) {
if (webidl.is.Request(request)) {
// 2.1.1
r = getRequestState(request)

Expand Down Expand Up @@ -848,7 +848,10 @@ webidl.converters.MultiCacheQueryOptions = webidl.dictionaryConverter([
}
])

webidl.converters.Response = webidl.interfaceConverter(Response)
webidl.converters.Response = webidl.interfaceConverter(
webidl.is.Response,
'Response'
)

webidl.converters['sequence<RequestInfo>'] = webidl.sequenceConverter(
webidl.converters.RequestInfo
Expand Down
10 changes: 6 additions & 4 deletions lib/web/cookies/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ const { stringify } = require('./util')
const { webidl } = require('../fetch/webidl')
const { Headers } = require('../fetch/headers')

const brandChecks = webidl.brandCheckMultiple([Headers, globalThis.Headers].filter(Boolean))

/**
* @typedef {Object} Cookie
* @property {string} name
Expand All @@ -26,7 +28,7 @@ const { Headers } = require('../fetch/headers')
function getCookies (headers) {
webidl.argumentLengthCheck(arguments, 1, 'getCookies')

webidl.brandCheckMultiple(headers, [Headers, globalThis.Headers])
brandChecks(headers)

const cookie = headers.get('cookie')

Expand All @@ -53,7 +55,7 @@ function getCookies (headers) {
* @returns {void}
*/
function deleteCookie (headers, name, attributes) {
webidl.brandCheckMultiple(headers, [Headers, globalThis.Headers])
brandChecks(headers)

const prefix = 'deleteCookie'
webidl.argumentLengthCheck(arguments, 2, prefix)
Expand All @@ -78,7 +80,7 @@ function deleteCookie (headers, name, attributes) {
function getSetCookies (headers) {
webidl.argumentLengthCheck(arguments, 1, 'getSetCookies')

webidl.brandCheckMultiple(headers, [Headers, globalThis.Headers])
brandChecks(headers)

const cookies = headers.getSetCookie()

Expand All @@ -97,7 +99,7 @@ function getSetCookies (headers) {
function setCookie (headers, cookie) {
webidl.argumentLengthCheck(arguments, 2, 'setCookie')

webidl.brandCheckMultiple(headers, [Headers, globalThis.Headers])
brandChecks(headers)

cookie = webidl.converters.Cookie(cookie)

Expand Down
Loading

0 comments on commit 42ce1b0

Please sign in to comment.