typescript-sdk/test/helpers/http.ts
2025-12-05 23:56:12 +00:00

96 lines
3.6 KiB
TypeScript

import type http from 'node:http';
import { type Server } from 'node:http';
import type { Response } from 'express';
import { AddressInfo } from 'node:net';
import { vi } from 'vitest';
/**
* Attach a listener to an existing server on a random localhost port and return its base URL.
*/
export async function listenOnRandomPort(server: Server, host: string = '127.0.0.1'): Promise<URL> {
return new Promise<URL>(resolve => {
server.listen(0, host, () => {
const addr = server.address() as AddressInfo;
resolve(new URL(`http://${host}:${addr.port}`));
});
});
}
// =========================
// HTTP/Express mock helpers
// =========================
/**
* Create a minimal Express-like Response mock for tests.
*
* The mock supports:
* - redirect()
* - status().json().send() chaining
* - set()/header()
* - optional getRedirectUrl() helper used in some tests
*/
export function createExpressResponseMock(options: { trackRedirectUrl?: boolean } = {}): Response & {
getRedirectUrl?: () => string;
} {
let capturedRedirectUrl: string | undefined;
const res: Partial<Response> & { getRedirectUrl?: () => string } = {
redirect: vi.fn((urlOrStatus: string | number, maybeUrl?: string | number) => {
if (options.trackRedirectUrl) {
if (typeof urlOrStatus === 'string') {
capturedRedirectUrl = urlOrStatus;
} else if (typeof maybeUrl === 'string') {
capturedRedirectUrl = maybeUrl;
}
}
return res as Response;
}) as unknown as Response['redirect'],
status: vi.fn<Response['status']>().mockImplementation((_code: number) => {
// status code is ignored for now; tests assert it via jest/vitest spies
return res as Response;
}),
json: vi.fn<Response['json']>().mockImplementation((_body: unknown) => {
// body is ignored; tests usually assert via spy
return res as Response;
}),
send: vi.fn<Response['send']>().mockImplementation((_body?: unknown) => {
// body is ignored; tests usually assert via spy
return res as Response;
}),
set: vi.fn<Response['set']>().mockImplementation((_field: string, _value?: string | string[]) => {
// header value is ignored in the generic mock; tests spy on set()
return res as Response;
}),
header: vi.fn<Response['header']>().mockImplementation((_field: string, _value?: string | string[]) => {
return res as Response;
})
};
if (options.trackRedirectUrl) {
res.getRedirectUrl = () => {
if (capturedRedirectUrl === undefined) {
throw new Error('No redirect URL was captured. Ensure redirect() was called first.');
}
return capturedRedirectUrl;
};
}
return res as Response & { getRedirectUrl?: () => string };
}
/**
* Create a Node http.ServerResponse mock used for low-level transport tests.
*
* All core methods are jest/vitest fns returning `this` so that
* tests can assert on writeHead/write/on/end calls.
*/
export function createNodeServerResponseMock(): http.ServerResponse {
const res = {
writeHead: vi.fn<http.ServerResponse['writeHead']>().mockReturnThis(),
write: vi.fn<http.ServerResponse['write']>().mockReturnThis(),
on: vi.fn<http.ServerResponse['on']>().mockReturnThis(),
end: vi.fn<http.ServerResponse['end']>().mockReturnThis()
};
return res as unknown as http.ServerResponse;
}