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
112
server/supabase/functions/_shared/stt-contract.test.ts
Normal file
112
server/supabase/functions/_shared/stt-contract.test.ts
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
import {
|
||||
buildDictionaryHints,
|
||||
createDeepgramSttUrl,
|
||||
createInternalSttGatewayUrl,
|
||||
MAX_STT_AUDIO_BYTES,
|
||||
normalizeSttResult,
|
||||
SttInputError,
|
||||
validateSttAudio,
|
||||
} from './stt-contract.ts'
|
||||
|
||||
function assert(condition: boolean, message: string): asserts condition {
|
||||
if (!condition) throw new Error(message)
|
||||
}
|
||||
|
||||
Deno.test('STT input contract accepts real supported audio and preserves its type', () => {
|
||||
const audio = new Blob([new Uint8Array([1, 2, 3])], { type: 'audio/mp4' })
|
||||
const result = validateSttAudio(audio, 'ko')
|
||||
assert(result.fileName === 'audio.m4a', 'm4a filename was not preserved')
|
||||
assert(result.contentType === 'audio/mp4', 'content type was not preserved')
|
||||
assert(result.languageCode === 'ko', 'language was not normalized')
|
||||
})
|
||||
|
||||
Deno.test('STT input contract rejects empty, oversized, disguised, and invalid-language input', () => {
|
||||
const cases: Array<() => unknown> = [
|
||||
() => validateSttAudio(new Blob([], { type: 'audio/wav' }), 'ko'),
|
||||
() => validateSttAudio(
|
||||
new Blob([new Uint8Array(MAX_STT_AUDIO_BYTES + 1)], { type: 'audio/wav' }),
|
||||
'ko',
|
||||
),
|
||||
() => validateSttAudio(new Blob(['not audio'], { type: 'text/plain' }), 'ko'),
|
||||
() => validateSttAudio(new Blob(['x'], { type: 'audio/wav' }), 'ko&keyterm=forged'),
|
||||
]
|
||||
for (const run of cases) {
|
||||
let caught: unknown = null
|
||||
try {
|
||||
run()
|
||||
} catch (error) {
|
||||
caught = error
|
||||
}
|
||||
assert(caught instanceof SttInputError, 'invalid input was not rejected')
|
||||
}
|
||||
})
|
||||
|
||||
Deno.test('dictionary hints are bounded, normalized, and de-duplicated', () => {
|
||||
const hints = buildDictionaryHints([
|
||||
{ word: ' D3RO\nVoice ', pronunciation: '디쓰리로 보이스' },
|
||||
{ word: 'd3ro voice', pronunciation: 'duplicate' },
|
||||
{ word: 'Supabase', pronunciation: null },
|
||||
{ word: '', pronunciation: 'ignored' },
|
||||
])
|
||||
assert(hints.keyterms.length === 2, 'duplicate or empty terms were not removed')
|
||||
assert(hints.prompt === 'D3RO Voice (디쓰리로 보이스), Supabase', 'prompt was not normalized')
|
||||
assert(hints.prompt.length <= 900, 'prompt exceeded its provider bound')
|
||||
})
|
||||
|
||||
Deno.test('Deepgram Nova-3 uses repeated encoded keyterm parameters', () => {
|
||||
const url = new URL(createDeepgramSttUrl('ko', ['D3RO Voice', '수파베이스']))
|
||||
assert(url.searchParams.get('model') === 'nova-3', 'Nova-3 was not selected')
|
||||
assert(url.searchParams.getAll('keyterm').length === 2, 'keyterms were not repeated')
|
||||
assert(url.searchParams.getAll('keyterm')[0] === 'D3RO Voice', 'keyterm encoding changed its value')
|
||||
})
|
||||
|
||||
Deno.test('STT result contract accepts only bounded real provider output', () => {
|
||||
const result = normalizeSttResult({
|
||||
transcript: ' 실제 전사 결과 ',
|
||||
confidence: 0.97,
|
||||
language_code: 'KO',
|
||||
duration_seconds: 12.5,
|
||||
provider: 'd3ro-gateway',
|
||||
})
|
||||
assert(result.transcript === '실제 전사 결과', 'transcript was not normalized')
|
||||
assert(result.language_code === 'ko', 'language was not normalized')
|
||||
|
||||
for (const value of [
|
||||
{ ...result, transcript: ' ' },
|
||||
{ ...result, confidence: Number.NaN },
|
||||
{ ...result, confidence: 2 },
|
||||
{ ...result, language_code: 'auto' },
|
||||
{ ...result, duration_seconds: -1 },
|
||||
{ ...result, provider: 'invalid provider' },
|
||||
]) {
|
||||
let rejected = false
|
||||
try {
|
||||
normalizeSttResult(value)
|
||||
} catch {
|
||||
rejected = true
|
||||
}
|
||||
assert(rejected, 'invalid provider output was accepted')
|
||||
}
|
||||
})
|
||||
|
||||
Deno.test('internal gateway URL never leaks its service token over public HTTP', () => {
|
||||
assert(
|
||||
createInternalSttGatewayUrl('https://voice.example.com/base')
|
||||
=== 'https://voice.example.com/api/stt/internal/transcribe',
|
||||
'HTTPS gateway path is not canonical',
|
||||
)
|
||||
assert(
|
||||
createInternalSttGatewayUrl('http://host.docker.internal:5000')
|
||||
=== 'http://host.docker.internal:5000/api/stt/internal/transcribe',
|
||||
'known local Docker gateway was rejected',
|
||||
)
|
||||
for (const unsafe of ['http://public.example.com', 'ftp://voice.example.com', 'https://user:pass@voice.example.com']) {
|
||||
let rejected = false
|
||||
try {
|
||||
createInternalSttGatewayUrl(unsafe)
|
||||
} catch {
|
||||
rejected = true
|
||||
}
|
||||
assert(rejected, 'unsafe gateway origin was accepted')
|
||||
}
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue