/** * T005: 契约测试 IUniversalHttpClient.request() * * 这个测试将验证 IUniversalHttpClient.request() 方法的契约是否正确实现 * * 重要:按照TDD原则,这个测试现在必须失败,因为实现还不存在 */ import { describe, test, expect, beforeEach, afterEach } from '@jest/globals' // 导入接口定义(这些应该在contracts中定义的类型) interface IUniversalHttpClient { request(request: HttpClientRequest): Promise> batchRequest(requests: HttpClientRequest[]): Promise[]> registerPlatform(platform: string, adapter: IPlatformAdapter): void getHealth(): Promise close(): Promise } interface HttpClientRequest { platform: string accountId: string method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' url: string headers?: Record body?: any options?: RequestOptions } interface RequestOptions { timeout?: TimeoutConfig retry?: RetryConfig proxy?: ProxyControlOptions logSensitiveData?: boolean idempotencyKey?: string } interface TimeoutConfig { connect?: number read?: number write?: number } interface RetryConfig { maxAttempts?: number delay?: number exponentialBackoff?: boolean shouldRetry?: (error: any) => boolean } interface ProxyControlOptions { enabled?: boolean forceProxy?: any disableProxy?: boolean strategy?: 'global' | 'account' | 'force' | 'disabled' } interface HttpClientResponse { status: number statusText: string ok: boolean data: T headers: Record metadata: ResponseMetadata } interface ResponseMetadata { requestId: string duration: number retryCount: number usedProxy: boolean proxyUsed?: string timestamp: Date platform: string } interface IPlatformAdapter { readonly platform: string readonly baseUrl: string request(request: any): Promise } interface HealthStatus { status: 'healthy' | 'degraded' | 'unhealthy' platforms: Record metrics: any } // 导入实现(这应该会失败,因为实现还不存在) let UniversalHttpClient: any try { // 尝试从现有的HTTP客户端库导入 UniversalHttpClient = require('../../libs/http-client/src/index.js').UniversalHttpClient } catch (error) { // 如果从库导入失败,尝试从src目录导入 try { UniversalHttpClient = require('../../src/utils/universalHttpClient.js').UniversalHttpClient } catch (error2) { // 预期的失败:实现还不存在 UniversalHttpClient = undefined } } describe('IUniversalHttpClient.request() 契约测试', () => { let httpClient: IUniversalHttpClient let mockPlatformAdapter: IPlatformAdapter beforeEach(() => { // 创建模拟平台适配器 mockPlatformAdapter = { platform: 'test-platform', baseUrl: 'https://api.test-platform.com', request: jest.fn().mockResolvedValue({ status: 200, data: { message: 'success' }, headers: { 'content-type': 'application/json' }, metadata: { platform: 'test-platform', requestId: 'test-123', serverTime: new Date(), rateLimit: undefined, platformData: {} } }) } // 如果实现存在,创建客户端实例 if (UniversalHttpClient) { httpClient = new UniversalHttpClient() httpClient.registerPlatform('test-platform', mockPlatformAdapter) } }) afterEach(async () => { if (httpClient && httpClient.close) { await httpClient.close() } }) test('应该存在 UniversalHttpClient 类', () => { // 这个测试现在应该失败 expect(UniversalHttpClient).toBeDefined() expect(typeof UniversalHttpClient).toBe('function') }) test('应该实现 IUniversalHttpClient 接口', () => { if (!UniversalHttpClient) { expect(UniversalHttpClient).toBeDefined() // 强制失败 return } const client = new UniversalHttpClient() // 验证必需方法存在 expect(typeof client.request).toBe('function') expect(typeof client.batchRequest).toBe('function') expect(typeof client.registerPlatform).toBe('function') expect(typeof client.getHealth).toBe('function') expect(typeof client.close).toBe('function') }) test('request() 应该处理基本的GET请求', async () => { if (!httpClient) { expect(httpClient).toBeDefined() // 强制失败 return } const request: HttpClientRequest = { platform: 'test-platform', accountId: 'test-account', method: 'GET', url: '/api/v1/test' } const response = await httpClient.request(request) // 验证响应结构符合契约 expect(response).toHaveProperty('status') expect(response).toHaveProperty('statusText') expect(response).toHaveProperty('ok') expect(response).toHaveProperty('data') expect(response).toHaveProperty('headers') expect(response).toHaveProperty('metadata') // 验证元数据结构 expect(response.metadata).toHaveProperty('requestId') expect(response.metadata).toHaveProperty('duration') expect(response.metadata).toHaveProperty('retryCount') expect(response.metadata).toHaveProperty('usedProxy') expect(response.metadata).toHaveProperty('timestamp') expect(response.metadata).toHaveProperty('platform') expect(response.metadata.platform).toBe('test-platform') }) test('request() 应该处理带请求体的POST请求', async () => { if (!httpClient) { expect(httpClient).toBeDefined() // 强制失败 return } const request: HttpClientRequest = { platform: 'test-platform', accountId: 'test-account', method: 'POST', url: '/api/v1/orders', headers: { 'Content-Type': 'application/json' }, body: { symbol: 'BTC-USD', side: 'buy', amount: 0.001 } } const response = await httpClient.request(request) expect(response.status).toBe(200) expect(response.ok).toBe(true) expect(response.data).toBeDefined() }) test('request() 应该支持超时配置', async () => { if (!httpClient) { expect(httpClient).toBeDefined() // 强制失败 return } const request: HttpClientRequest = { platform: 'test-platform', accountId: 'test-account', method: 'GET', url: '/api/v1/slow-endpoint', options: { timeout: { connect: 5000, read: 30000, write: 15000 } } } // 这应该不会抛出错误(模拟快速响应) const response = await httpClient.request(request) expect(response.status).toBe(200) }) test('request() 应该支持重试配置', async () => { if (!httpClient) { expect(httpClient).toBeDefined() // 强制失败 return } const request: HttpClientRequest = { platform: 'test-platform', accountId: 'test-account', method: 'GET', url: '/api/v1/test', options: { retry: { maxAttempts: 3, delay: 1000, exponentialBackoff: true } } } const response = await httpClient.request(request) expect(response.metadata.retryCount).toBeGreaterThanOrEqual(0) }) test('request() 应该支持代理控制选项', async () => { if (!httpClient) { expect(httpClient).toBeDefined() // 强制失败 return } const request: HttpClientRequest = { platform: 'test-platform', accountId: 'test-account', method: 'GET', url: '/api/v1/test', options: { proxy: { enabled: true, strategy: 'global' } } } const response = await httpClient.request(request) expect(response.metadata).toHaveProperty('usedProxy') }) test('request() 应该支持幂等性键', async () => { if (!httpClient) { expect(httpClient).toBeDefined() // 强制失败 return } const idempotencyKey = 'test-key-' + Date.now() const request: HttpClientRequest = { platform: 'test-platform', accountId: 'test-account', method: 'POST', url: '/api/v1/orders', body: { symbol: 'BTC-USD' }, options: { idempotencyKey } } const response = await httpClient.request(request) expect(response.status).toBe(200) }) test('request() 应该在未注册平台时抛出错误', async () => { if (!httpClient) { expect(httpClient).toBeDefined() // 强制失败 return } const request: HttpClientRequest = { platform: 'unknown-platform', accountId: 'test-account', method: 'GET', url: '/api/v1/test' } await expect(httpClient.request(request)).rejects.toThrow() }) test('request() 应该在无效请求时抛出错误', async () => { if (!httpClient) { expect(httpClient).toBeDefined() // 强制失败 return } const invalidRequest = { platform: 'test-platform', // 缺少必需字段 method: 'GET' } as HttpClientRequest await expect(httpClient.request(invalidRequest)).rejects.toThrow() }) test('request() 响应应该包含正确的状态码和数据类型', async () => { if (!httpClient) { expect(httpClient).toBeDefined() // 强制失败 return } const request: HttpClientRequest = { platform: 'test-platform', accountId: 'test-account', method: 'GET', url: '/api/v1/test' } const response = await httpClient.request(request) expect(typeof response.status).toBe('number') expect(typeof response.statusText).toBe('string') expect(typeof response.ok).toBe('boolean') expect(response.headers).toEqual(expect.any(Object)) expect(response.metadata.timestamp).toBeInstanceOf(Date) expect(typeof response.metadata.duration).toBe('number') }) })