merge: integrate remote learning module safely
This commit is contained in:
86
tests/unit/learning-speech-client.test.ts
Normal file
86
tests/unit/learning-speech-client.test.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { createLearningSpeechClient } from '@electron/services/learning-speech-client';
|
||||
|
||||
describe('Learning speech client', () => {
|
||||
it('keeps authentication in Main and forwards bounded multipart audio', async () => {
|
||||
const fetchImpl = vi.fn<typeof fetch>().mockResolvedValue(new Response(
|
||||
JSON.stringify({ text: '什么是变量', model: 'stt' }),
|
||||
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
||||
));
|
||||
const client = createLearningSpeechClient({
|
||||
fetchImpl,
|
||||
getAccessToken: vi.fn().mockResolvedValue('main-owned-token'),
|
||||
apiBaseUrl: 'https://square.example',
|
||||
});
|
||||
|
||||
await expect(client.transcribe({
|
||||
audio: new Uint8Array([1, 2, 3]),
|
||||
fileName: 'voice.webm',
|
||||
mimeType: 'audio/webm',
|
||||
language: 'zh-CN',
|
||||
})).resolves.toEqual({ text: '什么是变量' });
|
||||
|
||||
const [url, init] = fetchImpl.mock.calls[0];
|
||||
expect(url).toBe('https://square.example/api/speech/transcriptions');
|
||||
expect(init?.headers).toEqual({ Authorization: 'Bearer main-owned-token' });
|
||||
const form = init?.body as FormData;
|
||||
expect(form.get('language')).toBe('zh-CN');
|
||||
const file = form.get('audio') as File;
|
||||
expect(file.name).toBe('voice.webm');
|
||||
expect(new Uint8Array(await file.arrayBuffer())).toEqual(new Uint8Array([1, 2, 3]));
|
||||
});
|
||||
|
||||
it('projects only transcript text and never forwards upstream error detail', async () => {
|
||||
const fetchImpl = vi.fn<typeof fetch>()
|
||||
.mockResolvedValueOnce(new Response(JSON.stringify({ text: ' 安全答案 ', model: 'secret-model', token: 'secret' }), { status: 200 }))
|
||||
.mockResolvedValueOnce(new Response(JSON.stringify({ detail: 'provider key invalid at internal-host' }), { status: 502 }));
|
||||
const client = createLearningSpeechClient({
|
||||
fetchImpl,
|
||||
getAccessToken: vi.fn().mockResolvedValue('token'),
|
||||
apiBaseUrl: 'https://square.example',
|
||||
});
|
||||
|
||||
await expect(client.transcribe({ audio: new Uint8Array([1]), fileName: 'a.webm', mimeType: 'audio/webm' }))
|
||||
.resolves.toEqual({ text: '安全答案' });
|
||||
await expect(client.transcribe({ audio: new Uint8Array([1]), fileName: 'a.webm', mimeType: 'audio/webm' }))
|
||||
.rejects.toThrow('语音识别失败');
|
||||
});
|
||||
|
||||
it('does not upload audio after its captured account changes during token lookup', async () => {
|
||||
let resolveToken!: (token: string) => void;
|
||||
const fetchImpl = vi.fn<typeof fetch>();
|
||||
const client = createLearningSpeechClient({
|
||||
fetchImpl,
|
||||
getAccessToken: vi.fn(() => new Promise((resolve) => { resolveToken = resolve; })),
|
||||
apiBaseUrl: 'https://square.example',
|
||||
});
|
||||
let current = true;
|
||||
const result = client.transcribe(
|
||||
{ audio: new Uint8Array([1]), fileName: 'a.webm', mimeType: 'audio/webm' },
|
||||
() => { if (!current) throw new Error('account changed'); },
|
||||
);
|
||||
current = false;
|
||||
resolveToken('new-account-token');
|
||||
await expect(result).rejects.toThrow('account changed');
|
||||
expect(fetchImpl).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not retry a 401 after its captured account changes during refresh', async () => {
|
||||
let resolveRefresh!: (token: string) => void;
|
||||
const getAccessToken = vi.fn()
|
||||
.mockResolvedValueOnce('old-token')
|
||||
.mockReturnValueOnce(new Promise((resolve) => { resolveRefresh = resolve; }));
|
||||
const fetchImpl = vi.fn<typeof fetch>().mockResolvedValueOnce(new Response(null, { status: 401 }));
|
||||
const client = createLearningSpeechClient({ fetchImpl, getAccessToken, apiBaseUrl: 'https://square.example' });
|
||||
let current = true;
|
||||
const result = client.transcribe(
|
||||
{ audio: new Uint8Array([1]), fileName: 'a.webm', mimeType: 'audio/webm' },
|
||||
() => { if (!current) throw new Error('account changed'); },
|
||||
);
|
||||
await vi.waitFor(() => expect(getAccessToken).toHaveBeenCalledTimes(2));
|
||||
current = false;
|
||||
resolveRefresh('new-account-token');
|
||||
await expect(result).rejects.toThrow('account changed');
|
||||
expect(fetchImpl).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user