openaiTestHelper.ts
1 /** 2 * MSW-based OpenAI API mock server for testing 3 * Provides realistic OpenAI API responses for comprehensive testing 4 */ 5 6 import { http, HttpResponse } from 'msw'; 7 import { 8 ChatCompletion, 9 ChatCompletionCreateParams, 10 CreateEmbeddingResponse, 11 EmbeddingCreateParams, 12 } from 'openai/resources/index'; 13 import { ResponseCreateParams, Response } from 'openai/resources/responses/responses'; 14 import { setupServer } from 'msw/node'; 15 16 /** 17 * Create a realistic chat completion response 18 */ 19 function createChatCompletionResponse(request: ChatCompletionCreateParams): ChatCompletion { 20 const timestamp = Math.floor(Date.now() / 1000); 21 const requestId = `chatcmpl-${Math.random().toString(36).substring(2, 15)}`; 22 23 return { 24 id: requestId, 25 object: 'chat.completion', 26 created: timestamp, 27 model: request.model, 28 choices: [ 29 { 30 index: 0, 31 message: { 32 role: 'assistant', 33 content: 'Test response content', 34 refusal: null, 35 }, 36 finish_reason: 'stop', 37 logprobs: null, 38 }, 39 ], 40 usage: { 41 prompt_tokens: 100, 42 completion_tokens: 200, 43 total_tokens: 300, 44 }, 45 }; 46 } 47 48 /** 49 * Create a mock response for Responses API 50 */ 51 function createResponsesResponse(request: ResponseCreateParams): Response { 52 return { 53 id: 'responses-123', 54 object: 'response', 55 model: request.model || '', 56 output: [ 57 { 58 id: 'response-123', 59 content: [ 60 { 61 type: 'output_text', 62 text: 'Dummy output', 63 annotations: [], 64 }, 65 ], 66 role: 'assistant', 67 status: 'completed', 68 type: 'message', 69 }, 70 ], 71 usage: { 72 input_tokens: 36, 73 output_tokens: 87, 74 total_tokens: 123, 75 input_tokens_details: { 76 cached_tokens: 0, 77 }, 78 output_tokens_details: { 79 reasoning_tokens: 0, 80 }, 81 }, 82 created_at: 123, 83 output_text: 'Dummy output', 84 error: null, 85 incomplete_details: null, 86 instructions: null, 87 metadata: null, 88 parallel_tool_calls: false, 89 temperature: 0.5, 90 tools: [], 91 top_p: 1, 92 tool_choice: 'auto', 93 }; 94 } 95 96 /** 97 * Create a mock response for Embeddings API 98 */ 99 function createEmbeddingResponse(request: EmbeddingCreateParams): CreateEmbeddingResponse { 100 const inputs = Array.isArray(request.input) ? request.input : [request.input]; 101 102 return { 103 object: 'list', 104 data: inputs.map((_, index) => ({ 105 object: 'embedding', 106 index, 107 embedding: Array(1536) 108 .fill(0) 109 .map(() => Math.random() * 0.1 - 0.05), 110 })), 111 model: request.model, 112 usage: { 113 prompt_tokens: inputs.length * 10, 114 total_tokens: inputs.length * 10, 115 }, 116 }; 117 } 118 119 /** 120 * Main MSW handlers for OpenAI API endpoints 121 */ 122 export const openAIMockHandlers = [ 123 http.post('https://api.openai.com/v1/chat/completions', async ({ request }) => { 124 const body = (await request.json()) as ChatCompletionCreateParams; 125 return HttpResponse.json(createChatCompletionResponse(body)); 126 }), 127 http.post('https://api.openai.com/v1/responses', async ({ request }) => { 128 const body = (await request.json()) as ResponseCreateParams; 129 return HttpResponse.json(createResponsesResponse(body)); 130 }), 131 http.post('https://api.openai.com/v1/embeddings', async ({ request }) => { 132 const body = (await request.json()) as EmbeddingCreateParams; 133 return HttpResponse.json(createEmbeddingResponse(body)); 134 }), 135 ]; 136 137 export const openAIMswServer = setupServer(...openAIMockHandlers); 138 139 export function useMockOpenAIServer(): void { 140 beforeAll(() => openAIMswServer.listen()); 141 afterEach(() => openAIMswServer.resetHandlers()); 142 afterAll(() => openAIMswServer.close()); 143 }