test: live AITUNNEL integration test, skipped without AITUNNEL_API_KEY

Refs #5

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKHeGuAYh4W1jNdjMConZa
This commit is contained in:
Andrey Epifancev 2026-09-28 00:54:20 +04:00
parent 812ace7f3c
commit 87826bc427

View File

@ -0,0 +1,39 @@
import { readFileSync } from 'node:fs';
import { describe, expect, it } from 'vitest';
import { DEFAULT_OPENAI, OpenAiTranscriber } from '../../src/core/openai';
import type { HttpClient } from '../../src/core/ports';
// Ключ — только из окружения, никогда в репозитории.
const KEY = process.env.AITUNNEL_API_KEY ?? '';
// Под Node нет requestUrl: тот же порт на fetch. contentType уходит заголовком.
const fetchHttp: HttpClient = {
async post(req) {
const res = await fetch(req.url, {
method: 'POST',
headers: { ...req.headers, 'Content-Type': req.contentType },
body: req.body,
});
return { status: res.status, text: await res.text() };
},
};
// Характерные слова эталона tests/fixtures/test-01.txt.
const WORDS = ['тестовая', 'запись', 'проверки', 'распознавания', 'речи', 'whisper'];
describe.skipIf(!KEY)('AITUNNEL gpt-4o-transcribe (live)', () => {
it('transcribes test-01.m4a', { timeout: 120_000 }, async () => {
const audio = new Uint8Array(readFileSync(new URL('../fixtures/test-01.m4a', import.meta.url))).buffer;
const reference = readFileSync(new URL('../fixtures/test-01.txt', import.meta.url), 'utf8').toLowerCase();
for (const word of WORDS) expect(reference).toContain(word);
const t = new OpenAiTranscriber(fetchHttp, () => ({ ...DEFAULT_OPENAI, apiKey: KEY }), Math.random);
const result = await t.transcribe(audio, 'audio/mp4', new AbortController().signal);
expect(result).toMatchObject({ ok: true });
const text = result.ok ? result.text.toLowerCase() : '';
expect(text.trim()).not.toBe('');
const hits = WORDS.filter((w) => text.includes(w));
expect(hits.length, `распознано: ${text}`).toBeGreaterThanOrEqual(4);
});
});