import { describe, expect, it } from 'vitest'; import { DEFAULT_OPENAI, OpenAiTranscriber, redact } from '../src/core/openai'; import type { HttpRequest } from '../src/core/ports'; import { FakeHttp } from './fakes'; const KEY = 'sk-test-123'; describe('redact', () => { it('replaces every occurrence of the key', () => { expect(redact(`a ${KEY} b ${KEY}`, { apiKey: KEY })).toBe('a *** b ***'); }); it('masks any Bearer token', () => { expect(redact('Authorization: Bearer abc.def-1', { apiKey: KEY })).toBe('Authorization: Bearer ***'); }); it('leaves the message alone for an empty key', () => { expect(redact('HTTP 401: nope', { apiKey: '' })).toBe('HTTP 401: nope'); }); it('cuts at the multipart boundary and trims trailing ": "', () => { const b = '----voice-note-0000'; expect(redact(`HTTP 400: echo --${b}\r\nContent-Disposition: form-data`, { apiKey: KEY, boundary: b })).toBe( 'HTTP 400: echo', ); expect(redact(`HTTP 400: --${b}\r\n`, { apiKey: KEY, boundary: b })).toBe('HTTP 400'); }); it('cuts at Content-Disposition regardless of case', () => { expect(redact('HTTP 400: got content-disposition: form-data; name="file"', { apiKey: KEY })).toBe('HTTP 400: got'); }); it('caps the result at 300 chars', () => { expect(redact('x'.repeat(500), { apiKey: KEY })).toHaveLength(300); }); it('replaces the key before capping, so no key fragment survives the cut', () => { const out = redact(`${'y'.repeat(295)}${KEY}`, { apiKey: KEY }); expect(out).toBe(`${'y'.repeat(295)}***`); expect(out).not.toContain('sk-'); }); }); describe('secrets never reach the reason (end to end)', () => { const audio = () => new Uint8Array([1, 2, 3]).buffer; const run = async (reply: ConstructorParameters[0]) => { const http = new FakeHttp(reply); const t = new OpenAiTranscriber(http, () => ({ ...DEFAULT_OPENAI, apiKey: KEY }), () => 0); const result = await t.transcribe(audio(), 'audio/mp4', new AbortController().signal); if (result.ok) throw new Error('expected an error'); return { message: result.message, req: http.requests[0] }; }; const echo = (status: number) => (req: HttpRequest) => ({ status, text: new TextDecoder().decode(req.body) }); it('401 JSON echoing the key and Authorization header', async () => { const body = JSON.stringify({ error: { message: `Incorrect API key provided: ${KEY}. Authorization: Bearer ${KEY}` }, }); const { message } = await run(() => ({ status: 401, text: body })); expect(message).toBe('HTTP 401: Incorrect API key provided: ***. Authorization: Bearer ***'); }); it('401 plain text echoing the Authorization header', async () => { const { message } = await run(() => ({ status: 401, text: `Authorization: Bearer ${KEY}` })); expect(message).not.toContain(KEY); expect(message).toBe('HTTP 401: Authorization: Bearer ***'); }); it('network exception mentioning the key', async () => { const { message } = await run(() => Promise.reject(new Error(`connect failed for Bearer ${KEY} at https://x/?key=${KEY}`)), ); expect(message).not.toContain(KEY); expect(message).toBe('ัะตั‚ัŒ: connect failed for Bearer *** at https://x/?key=***'); }); it.each([200, 400, 401, 500])('service echoing the request body with status %i', async (status) => { const { message, req } = await run(echo(status)); const boundary = /boundary=(.+)$/.exec(req.contentType)![1]; expect(message).not.toContain(KEY); expect(message).not.toContain(boundary); expect(message.toLowerCase()).not.toContain('content-disposition'); }); });