268 lines
6.8 KiB
JavaScript
268 lines
6.8 KiB
JavaScript
/**
|
|
* 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])}`,
|
|
);
|
|
}
|
|
}
|
|
}
|