feat(release): prepare 1.1.0 candidate

This commit is contained in:
Yun Chan 2026-08-29 18:33:45 +09:00
parent 5a34f66981
commit 5205dcdfa9
736 changed files with 115667 additions and 12203 deletions

View 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')
}
})