/** * HTTP Mock 工具 * 用于在测试中模拟 HTTP 请求和响应 */ import { EventEmitter } from 'events'; import { vi } from 'vitest'; /** * Mock HTTP 响应 */ export class MockResponse extends EventEmitter { constructor(statusCode = 200, data = {}) { super(); this.statusCode = statusCode; this.data = data; this.headers = {}; } emit(event, ...args) { // 异步触发事件,模拟真实网络延迟 setImmediate(() => super.emit(event, ...args)); } simulateResponse() { // 如果 data 已经是字符串,直接使用;否则进行 JSON 序列化 const responseData = typeof this.data === 'string' ? this.data : JSON.stringify(this.data); this.emit('data', responseData); this.emit('end'); } simulateError(error) { this.emit('error', error); } simulateTimeout() { this.emit('timeout'); } setHeader(name, value) { this.headers[name] = value; } getHeader(name) { return this.headers[name]; } } /** * Mock HTTP 请求 */ export class MockRequest extends EventEmitter { constructor() { super(); this.written = []; this.ended = false; this.destroyed = false; this.headers = {}; } write(data) { this.written.push(data); return true; } end(data) { if (data) { this.write(data); } this.ended = true; this.emit('end'); } destroy() { this.destroyed = true; this.emit('close'); } setTimeout(timeout, callback) { setTimeout(() => { if (!this.ended && !this.destroyed) { this.emit('timeout'); if (callback) callback(); } }, timeout); } setHeader(name, value) { this.headers[name] = value; } getHeader(name) { return this.headers[name]; } getWrittenData() { return this.written.join(''); } } /** * 创建模拟的 HTTPS 模块用于测试 * * @typedef {object} HttpsMockModule * @property {import('vitest').Mock} request - 模拟的 HTTPS 请求方法 * @property {function(): Array} getRequests - 获取所有已发送的请求列表 * @property {function(number, any): void} addResponse - 添加预定义的响应 * @property {function(Error): void} addErrorResponse - 添加错误响应 * @property {function(): void} addTimeoutResponse - 添加超时响应 * @property {function(any): void} mockRequestOnce - 为单次请求设置响应 * @property {function(): void} reset - 重置所有请求和响应状态 * @property {function(): object} getLastRequest - 获取最后一次请求的详细信息 * @property {function(): number} getRequestCount - 获取已发送请求的数量 * * @returns {HttpsMockModule} 模拟的 HTTPS 模块对象 */ export function createHttpsMock() { const requests = []; const responses = []; const mockHttps = { request: vi.fn((options, callback) => { const req = new MockRequest(); const res = responses.shift() || new MockResponse(); requests.push({ options, req, res }); // 异步调用回调 setImmediate(() => { if (callback) callback(res); res.simulateResponse(); }); return req; }), // 测试辅助方法 getRequests: () => requests, addResponse: (statusCode, data) => { responses.push(new MockResponse(statusCode, data)); }, addErrorResponse: error => { const res = new MockResponse(); responses.push(res); setImmediate(() => res.simulateError(error)); }, addTimeoutResponse: () => { const res = new MockResponse(); responses.push(res); setImmediate(() => res.simulateTimeout()); }, // 添加单次请求响应 mockRequestOnce: responseData => { if (typeof responseData === 'object' && responseData.statusCode !== undefined) { // 如果传入的是包含 statusCode 的对象 responses.push( new MockResponse(responseData.statusCode, responseData.data || responseData), ); } else { // 如果传入的是普通响应数据,默认使用 200 状态码 responses.push(new MockResponse(200, responseData)); } }, reset: () => { requests.length = 0; responses.length = 0; mockHttps.request.mockClear(); }, // 获取最后一次请求的详细信息 getLastRequest: () => { return requests[requests.length - 1]; }, // 获取请求数量 getRequestCount: () => requests.length, }; return mockHttps; } /** * 创建简单的 HTTP Mock * * @param {number|object} statusCodeOrResponse - 状态码或包含 statusCode 和 data 的响应对象 * @param {object} data - 响应数据(当第一个参数是状态码时使用) * @returns {object} Mock 对象 */ export function createSimpleHttpMock(statusCodeOrResponse = 200, data = {}) { const mockHttps = createHttpsMock(); if (typeof statusCodeOrResponse === 'object' && statusCodeOrResponse !== null) { // 如果传入的是响应对象,检查是否有 statusCode 和 data 属性 if (statusCodeOrResponse.statusCode && statusCodeOrResponse.data) { mockHttps.addResponse(statusCodeOrResponse.statusCode, statusCodeOrResponse.data); } else { // 如果只是数据对象,默认使用 200 状态码 mockHttps.addResponse(200, statusCodeOrResponse); } } else { // 传统的调用方式:状态码和数据分开传递 mockHttps.addResponse(statusCodeOrResponse, data); } return mockHttps; } /** * 创建网络错误 Mock * * @param {string} errorCode - 错误代码 * @returns {object} Mock 对象 */ export function createNetworkErrorMock(errorCode = 'ECONNREFUSED') { const mockHttps = createHttpsMock(); const error = new Error(`Network error: ${errorCode}`); error.code = errorCode; mockHttps.addErrorResponse(error); return mockHttps; } /** * 创建超时 Mock * * @returns {object} Mock 对象 */ export function createTimeoutMock() { const mockHttps = createHttpsMock(); mockHttps.addTimeoutResponse(); return mockHttps; } /** * 验证请求选项 * * @param {object} request - 请求对象 * @param {object} expectedOptions - 期望的选项 */ export function assertRequestOptions(request, expectedOptions) { const { options } = request; for (const [key, value] of Object.entries(expectedOptions)) { if (options[key] !== value) { throw new Error(`Expected ${key} to be ${value}, but got ${options[key]}`); } } } /** * 验证请求体 * * @param {object} request - 请求对象 * @param {object} expectedBody - 期望的请求体 */ export function assertRequestBody(request, expectedBody) { const actualBody = JSON.parse(request.req.getWrittenData()); for (const [key, value] of Object.entries(expectedBody)) { if (JSON.stringify(actualBody[key]) !== JSON.stringify(value)) { throw new Error( `Expected ${key} to be ${JSON.stringify(value)}, but got ${JSON.stringify(actualBody[key])}`, ); } } }