Skip to content

Commit

Permalink
Merge branch 'main' into fix/fetch-resp-set-url-list
Browse files Browse the repository at this point in the history
  • Loading branch information
mikicho authored Feb 2, 2025
2 parents 8fdce78 + 51924bd commit 888cf36
Show file tree
Hide file tree
Showing 11 changed files with 114 additions and 28 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@mswjs/interceptors",
"description": "Low-level HTTP/HTTPS/XHR/fetch request interception library.",
"version": "0.37.3",
"version": "0.37.6",
"main": "./lib/node/index.js",
"module": "./lib/node/index.mjs",
"types": "./lib/node/index.d.ts",
Expand Down
5 changes: 2 additions & 3 deletions src/interceptors/ClientRequest/MockHttpSocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { MockSocket } from '../Socket/MockSocket'
import type { NormalizedSocketWriteArgs } from '../Socket/utils/normalizeSocketWriteArgs'
import { isPropertyAccessible } from '../../utils/isPropertyAccessible'
import { baseUrlFromConnectionOptions } from '../Socket/utils/baseUrlFromConnectionOptions'
import { parseRawHeaders } from '../Socket/utils/parseRawHeaders'
import { createServerErrorResponse } from '../../utils/responseUtils'
import { createRequestId } from '../../createRequestId'
import { getRawFetchHeaders } from './utils/recordRawHeaders'
Expand Down Expand Up @@ -473,7 +472,7 @@ export class MockHttpSocket extends MockSocket {

const url = new URL(path, this.baseUrl)
const method = this.connectionOptions.method?.toUpperCase() || 'GET'
const headers = parseRawHeaders(rawHeaders)
const headers = FetchResponse.parseRawHeaders(rawHeaders)
const canHaveBody = method !== 'GET' && method !== 'HEAD'

// Translate the basic authorization in the URL to the request header.
Expand Down Expand Up @@ -566,7 +565,7 @@ export class MockHttpSocket extends MockSocket {
status,
statusText
) => {
const headers = parseRawHeaders(rawHeaders)
const headers = FetchResponse.parseRawHeaders(rawHeaders)

const response = new FetchResponse(
/**
Expand Down
37 changes: 27 additions & 10 deletions src/interceptors/ClientRequest/agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {

declare module 'node:http' {
interface Agent {
options?: http.AgentOptions
createConnection(options: any, callback: any): net.Socket
}
}
Expand All @@ -31,17 +32,25 @@ export class MockAgent extends http.Agent {
this.onResponse = options.onResponse
}

public createConnection(options: any, callback: any) {
public createConnection(options: any, callback: any): net.Socket {
const createConnection =
(this.customAgent instanceof http.Agent &&
this.customAgent.createConnection) ||
super.createConnection
this.customAgent instanceof http.Agent
? this.customAgent.createConnection
: super.createConnection

const createConnectionOptions =
this.customAgent instanceof http.Agent
? {
...options,
...this.customAgent.options,
}
: options

const socket = new MockHttpSocket({
connectionOptions: options,
createConnection: createConnection.bind(
this.customAgent || this,
options,
createConnectionOptions,
callback
),
onRequest: this.onRequest.bind(this),
Expand All @@ -64,17 +73,25 @@ export class MockHttpsAgent extends https.Agent {
this.onResponse = options.onResponse
}

public createConnection(options: any, callback: any) {
public createConnection(options: any, callback: any): net.Socket {
const createConnection =
(this.customAgent instanceof https.Agent &&
this.customAgent.createConnection) ||
super.createConnection
this.customAgent instanceof https.Agent
? this.customAgent.createConnection
: super.createConnection

const createConnectionOptions =
this.customAgent instanceof https.Agent
? {
...options,
...this.customAgent.options,
}
: options

const socket = new MockHttpSocket({
connectionOptions: options,
createConnection: createConnection.bind(
this.customAgent || this,
options,
createConnectionOptions,
callback
),
onRequest: this.onRequest.bind(this),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,50 @@ it('sets fallback Agent based on the URL protocol', () => {
expect(agent).toHaveProperty('protocol', url.protocol)
})

it('preserves `requestUnauthorized` option set to undefined', () => {
const [, options] = normalizeClientRequestArgs('https:', [
'https://github.com',
{ rejectUnauthorized: undefined },
])

expect(options.rejectUnauthorized).toBe(undefined)
expect((options.agent as HttpsAgent).options.rejectUnauthorized).toBe(
undefined
)
})

it('preserves `requestUnauthorized` option set to true', () => {
const [, options] = normalizeClientRequestArgs('https:', [
'https://github.com',
{ rejectUnauthorized: true },
])

expect(options.rejectUnauthorized).toBe(true)
expect((options.agent as HttpsAgent).options.rejectUnauthorized).toBe(true)
})

it('preserves `requestUnauthorized` option set to false', () => {
const [, options] = normalizeClientRequestArgs('https:', [
'https://github.com',
{ rejectUnauthorized: false },
])

expect(options.rejectUnauthorized).toBe(false)
expect((options.agent as HttpsAgent).options.rejectUnauthorized).toBe(false)
})

it('does not add `rejectUnauthorized` value if not set', () => {
const agent = new HttpsAgent()
const [, options] = normalizeClientRequestArgs('https:', [
'https://github.com',
])

expect(options).not.toHaveProperty('rejectUnauthorized')
expect((options.agent as HttpsAgent).options).not.toHaveProperty(
'rejectUnauthorized'
)
})

it('does not set any fallback Agent given "agent: false" option', () => {
const [, options] = normalizeClientRequestArgs('https:', [
'https://github.com',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,10 @@ export function normalizeClientRequestArgs(
const agent =
options.protocol === 'https:'
? new HttpsAgent({
rejectUnauthorized: options.rejectUnauthorized,
// Any other value other than false is considered as true, so we don't add this property if undefined.
...('rejectUnauthorized' in options && {
rejectUnauthorized: options.rejectUnauthorized,
}),
})
: new HttpAgent()

Expand Down
15 changes: 15 additions & 0 deletions src/interceptors/ClientRequest/utils/recordRawHeaders.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,3 +241,18 @@ it('does not throw on using Headers before recording', () => {
request.headers.set('X-My-Header', '1')
expect(getRawFetchHeaders(request.headers)).toEqual([['X-My-Header', '1']])
})

/**
* @see https://github.com/mswjs/interceptors/issues/681
*/
it('isolates headers between different headers instances', async () => {
recordRawFetchHeaders()
const original = new Headers()
const firstClone = new Headers(original)
firstClone.set('Content-Type', 'application/json')
const secondClone = new Headers(original)

expect(original.get('Content-Type')).toBeNull()
expect(firstClone.get('Content-Type')).toBe('application/json')
expect(secondClone.get('Content-Type')).toBeNull()
})
9 changes: 8 additions & 1 deletion src/interceptors/ClientRequest/utils/recordRawHeaders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,14 @@ export function recordRawFetchHeaders() {
[Reflect.get(headersInit, kRawHeaders)],
newTarget
)
ensureRawHeadersSymbol(headers, Reflect.get(headersInit, kRawHeaders))
ensureRawHeadersSymbol(headers, [
/**
* @note Spread the retrieved headers to clone them.
* This prevents multiple Headers instances from pointing
* at the same internal "rawHeaders" array.
*/
...Reflect.get(headersInit, kRawHeaders),
])
return headers
}

Expand Down
10 changes: 0 additions & 10 deletions src/interceptors/Socket/utils/parseRawHeaders.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/interceptors/XMLHttpRequest/utils/createEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export function createEvent(
target: XMLHttpRequest | XMLHttpRequestUpload,
type: string,
init?: ProgressEventInit
): EventPolyfill {
): EventPolyfill | ProgressEvent {
const progressEvents = [
'error',
'progress',
Expand Down
11 changes: 11 additions & 0 deletions src/utils/fetchUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@ export class FetchResponse extends Response {
}
}

/**
* Parses the given raw HTTP headers into a Fetch API `Headers` instance.
*/
static parseRawHeaders(rawHeaders: Array<string>): Headers {
const headers = new Headers()
for (let line = 0; line < rawHeaders.length; line += 2) {
headers.append(rawHeaders[line], rawHeaders[line + 1])
}
return headers
}

constructor(body?: BodyInit | null, init: FetchResponseInit = {}) {
const status = init.status ?? 200
const safeStatus = FetchResponse.isConfigurableStatusCode(status)
Expand Down
2 changes: 1 addition & 1 deletion test/modules/http/compliance/http-custom-agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ it('preserves the context of the "createConnection" function in a custom http ag
const request = http.get(httpServer.http.url('/resource'), { agent })
await waitForClientRequest(request)

const [context] = createConnectionContextSpy.mock.calls[0]
const [context] = createConnectionContextSpy.mock.calls[0] || []
expect(context.constructor.name).toBe('CustomHttpAgent')
})

Expand Down

0 comments on commit 888cf36

Please sign in to comment.