feat(release): prepare 1.1.0 candidate
This commit is contained in:
parent
5a34f66981
commit
5205dcdfa9
736 changed files with 115667 additions and 12203 deletions
84
apps/web/e2e/web-stt-client.spec.ts
Normal file
84
apps/web/e2e/web-stt-client.spec.ts
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
import { expect, test } from '@playwright/test'
|
||||
import { transcribeWebAudio, WebSttError } from '../src/lib/web-stt-client'
|
||||
|
||||
const audio = new Blob([new Uint8Array([0x1a, 0x45, 0xdf, 0xa3])], { type: 'audio/webm;codecs=opus' })
|
||||
|
||||
test.describe('web STT fail-closed client', () => {
|
||||
test('sends one authenticated request and accepts only a strict real transcript', async () => {
|
||||
let calls = 0
|
||||
const result = await transcribeWebAudio({
|
||||
audio,
|
||||
accessToken: 'access-token',
|
||||
supabaseUrl: 'https://project.supabase.co',
|
||||
fetchImpl: async (input, init) => {
|
||||
calls += 1
|
||||
expect(String(input)).toBe('https://project.supabase.co/functions/v1/stt-proxy')
|
||||
expect(init?.headers).toEqual({ Authorization: 'Bearer access-token' })
|
||||
expect(init?.body).toBeInstanceOf(FormData)
|
||||
return new Response(JSON.stringify({
|
||||
transcript: '실제 전사 결과',
|
||||
confidence: 0.97,
|
||||
language_code: 'ko',
|
||||
duration_seconds: 1.25,
|
||||
provider: 'whisper.cpp-local',
|
||||
}), { status: 200, headers: { 'Content-Type': 'application/json' } })
|
||||
},
|
||||
})
|
||||
expect(calls).toBe(1)
|
||||
expect(result.transcript).toBe('실제 전사 결과')
|
||||
})
|
||||
|
||||
test('maps auth, quota, provider and upstream failures without a fallback request', async () => {
|
||||
for (const [status, error, code] of [
|
||||
[401, 'invalid_token', 'auth_required'],
|
||||
[429, 'quota_exceeded', 'quota_exceeded'],
|
||||
[503, 'stt_provider_unavailable', 'provider_unavailable'],
|
||||
[502, 'stt_upstream_failed', 'upstream_failed'],
|
||||
] as const) {
|
||||
let calls = 0
|
||||
const rejection = transcribeWebAudio({
|
||||
audio,
|
||||
accessToken: 'token',
|
||||
supabaseUrl: 'https://project.supabase.co',
|
||||
fetchImpl: async () => {
|
||||
calls += 1
|
||||
return new Response(JSON.stringify({ error }), { status })
|
||||
},
|
||||
})
|
||||
await expect(rejection).rejects.toMatchObject({ code })
|
||||
expect(calls).toBe(1)
|
||||
}
|
||||
})
|
||||
|
||||
test('rejects empty, oversized, wrong MIME, malformed success and insecure endpoints', async () => {
|
||||
const noFetch = async (): Promise<Response> => { throw new Error('must not fetch') }
|
||||
const invalidCalls = [
|
||||
transcribeWebAudio({ audio: new Blob([], { type: 'audio/webm' }), accessToken: 't', supabaseUrl: 'https://p.supabase.co', fetchImpl: noFetch }),
|
||||
transcribeWebAudio({ audio: new Blob([new Uint8Array(25 * 1024 * 1024 + 1)], { type: 'audio/webm' }), accessToken: 't', supabaseUrl: 'https://p.supabase.co', fetchImpl: noFetch }),
|
||||
transcribeWebAudio({ audio: new Blob(['x'], { type: 'audio/mpeg' }), accessToken: 't', supabaseUrl: 'https://p.supabase.co', fetchImpl: noFetch }),
|
||||
transcribeWebAudio({ audio, accessToken: 't', supabaseUrl: 'http://remote.example', fetchImpl: noFetch }),
|
||||
transcribeWebAudio({
|
||||
audio,
|
||||
accessToken: 't',
|
||||
supabaseUrl: 'https://p.supabase.co',
|
||||
fetchImpl: async () => new Response(JSON.stringify({ transcript: 'placeholder' }), { status: 200 }),
|
||||
}),
|
||||
]
|
||||
for (const call of invalidCalls) await expect(call).rejects.toBeInstanceOf(WebSttError)
|
||||
})
|
||||
|
||||
test('aborts the only provider request as cancelled', async () => {
|
||||
const controller = new AbortController()
|
||||
const promise = transcribeWebAudio({
|
||||
audio,
|
||||
accessToken: 'token',
|
||||
supabaseUrl: 'https://project.supabase.co',
|
||||
signal: controller.signal,
|
||||
fetchImpl: async (_input, init) => new Promise((_resolve, reject) => {
|
||||
init?.signal?.addEventListener('abort', () => reject(new DOMException('aborted', 'AbortError')), { once: true })
|
||||
}),
|
||||
})
|
||||
controller.abort()
|
||||
await expect(promise).rejects.toMatchObject({ code: 'cancelled' })
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue