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
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
||||
import { Buffer } from 'node:buffer'
|
||||
import { ErrorCode, D3ROError } from '@d3ro/core/errors'
|
||||
import { ErrorCode } from '@d3ro/core/errors'
|
||||
import type { STTProviderType, STTProviderConfig } from '@d3ro/core/types'
|
||||
import { getSTTManager, resetSTTManagerForTests } from '../../src/main/services/stt/STTManager'
|
||||
import { pcmToWav, createProbeWav } from '../../src/main/services/stt/audio-utils'
|
||||
|
|
@ -26,6 +26,33 @@ function makePcmBuffer(durationSeconds = 1.0, sampleRate = 16000): Buffer {
|
|||
return buf
|
||||
}
|
||||
|
||||
function makeCloudDriver(authenticated = true): D3ROCloudDriver {
|
||||
return new D3ROCloudDriver({
|
||||
getAccessToken: async () => authenticated ? 'user.jwt.token' : null,
|
||||
getSupabaseUrl: () => 'https://project.supabase.co',
|
||||
getAnonKey: () => 'anon-key',
|
||||
})
|
||||
}
|
||||
|
||||
function cloudPayload(
|
||||
transcript: string,
|
||||
overrides: Partial<{
|
||||
confidence: number
|
||||
language_code: string
|
||||
duration_seconds: number
|
||||
provider: string
|
||||
}> = {},
|
||||
) {
|
||||
return {
|
||||
transcript,
|
||||
confidence: 0.98,
|
||||
language_code: 'ko',
|
||||
duration_seconds: 1,
|
||||
provider: 'groq',
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
describe('Complex User Journeys & Multi-Provider STT Orchestration (105 Scenarios)', () => {
|
||||
beforeEach(() => {
|
||||
initInMemoryConfig()
|
||||
|
|
@ -153,20 +180,14 @@ describe('Complex User Journeys & Multi-Provider STT Orchestration (105 Scenario
|
|||
// =========================================================================
|
||||
describe('Category 2: Multi-Provider Cloud Drivers Dispatch', () => {
|
||||
it('16. D3ROCloudDriver sends audio to D3RO Cloud Gateway and returns structured transcript', async () => {
|
||||
const driver = new D3ROCloudDriver()
|
||||
const driver = makeCloudDriver()
|
||||
const mockAudio = makePcmBuffer(1.0)
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({
|
||||
text: 'D3RO Cloud Gateway transcription success.',
|
||||
language: 'ko',
|
||||
durationSeconds: 1.0,
|
||||
provider: 'groq',
|
||||
latencyMs: 120,
|
||||
}),
|
||||
json: async () => cloudPayload('D3RO Cloud Gateway transcription success.'),
|
||||
})
|
||||
)
|
||||
|
||||
|
|
@ -178,18 +199,14 @@ describe('Complex User Journeys & Multi-Provider STT Orchestration (105 Scenario
|
|||
})
|
||||
|
||||
it('17. D3ROCloudDriver testConnection returns success and latency on 200 OK', async () => {
|
||||
const driver = new D3ROCloudDriver()
|
||||
const driver = makeCloudDriver()
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ success: true, latencyMs: 45, message: 'Connected' }),
|
||||
})
|
||||
vi.fn().mockResolvedValue({ ok: false, status: 400 })
|
||||
)
|
||||
const testRes = await driver.testConnection({ baseUrl: 'http://localhost:5000' })
|
||||
expect(testRes.success).toBe(true)
|
||||
expect(testRes.latencyMs).toBe(45)
|
||||
expect(testRes.message).toContain('인증 경로 준비됨')
|
||||
})
|
||||
|
||||
it('18. GroqDriver sends multipart request with Authorization Bearer header', async () => {
|
||||
|
|
@ -340,14 +357,14 @@ describe('Complex User Journeys & Multi-Provider STT Orchestration (105 Scenario
|
|||
})
|
||||
|
||||
it('26. Driver testConnection handles network socket timeout gracefully', async () => {
|
||||
const driver = new D3ROCloudDriver()
|
||||
const driver = makeCloudDriver()
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn().mockRejectedValue(new Error('Connection timed out after 10000ms'))
|
||||
)
|
||||
const res = await driver.testConnection({ baseUrl: 'http://unreachable-host:9999' })
|
||||
expect(res.success).toBe(false)
|
||||
expect(res.message).toContain('Connection timed out')
|
||||
expect(res.message).toContain('연결 또는 인증에 실패')
|
||||
})
|
||||
|
||||
it('27. DeepgramDriver handles smart formatting and punctuation options', async () => {
|
||||
|
|
@ -811,24 +828,21 @@ describe('Complex User Journeys & Multi-Provider STT Orchestration (105 Scenario
|
|||
})
|
||||
|
||||
it('58. Success on primary cloud driver does NOT invoke local fallback', async () => {
|
||||
const mgr = getSTTManager()
|
||||
mgr.setProvider('d3ro-cloud')
|
||||
mgr.setProviderConfig('d3ro-cloud', { baseUrl: 'http://localhost:5000' })
|
||||
configSet('sttFallbackToLocal', true)
|
||||
const driver = makeCloudDriver()
|
||||
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ text: 'Primary Cloud Success' }),
|
||||
json: async () => cloudPayload('Primary Cloud Success'),
|
||||
})
|
||||
)
|
||||
|
||||
const { getLocalSTTService } = await import('../../src/main/services/LocalSTTService')
|
||||
const localSpy = vi.spyOn(getLocalSTTService(), 'transcribe')
|
||||
|
||||
const res = await mgr.transcribe(makePcmBuffer(1.0))
|
||||
const res = await driver.transcribe(makePcmBuffer(1.0))
|
||||
expect(res.text).toBe('Primary Cloud Success')
|
||||
expect(localSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
|
@ -882,13 +896,13 @@ describe('Complex User Journeys & Multi-Provider STT Orchestration (105 Scenario
|
|||
// =========================================================================
|
||||
describe('Category 5: Concurrency, Quotas & Stress Workflows', () => {
|
||||
it('61. Parallel execution of 5 audio transcriptions completes without race condition', async () => {
|
||||
const driver = new D3ROCloudDriver()
|
||||
const driver = makeCloudDriver()
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn().mockImplementation(async () => ({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ text: 'Parallel transcription result', durationSeconds: 1.0 }),
|
||||
json: async () => cloudPayload('Parallel transcription result'),
|
||||
}))
|
||||
)
|
||||
|
||||
|
|
@ -900,16 +914,16 @@ describe('Complex User Journeys & Multi-Provider STT Orchestration (105 Scenario
|
|||
})
|
||||
|
||||
it('62. High-concurrency burst (10 parallel requests) maintains data integrity', async () => {
|
||||
const driver = new D3ROCloudDriver()
|
||||
const driver = makeCloudDriver()
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn().mockImplementation(async (_, opts) => {
|
||||
const body = opts.body as FormData
|
||||
const file = body.get('file') as Blob
|
||||
const file = body.get('audio') as Blob
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ text: `Transcribed ${file.size} bytes`, durationSeconds: 1.0 }),
|
||||
json: async () => cloudPayload(`Transcribed ${file.size} bytes`),
|
||||
}
|
||||
})
|
||||
)
|
||||
|
|
@ -942,7 +956,7 @@ describe('Complex User Journeys & Multi-Provider STT Orchestration (105 Scenario
|
|||
})
|
||||
|
||||
it('64. Long audio transcription processing time measurement is strictly positive', async () => {
|
||||
const driver = new D3ROCloudDriver()
|
||||
const driver = makeCloudDriver()
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn().mockImplementation(async () => {
|
||||
|
|
@ -950,7 +964,7 @@ describe('Complex User Journeys & Multi-Provider STT Orchestration (105 Scenario
|
|||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ text: 'Measured latency', durationSeconds: 2.0 }),
|
||||
json: async () => cloudPayload('Measured latency', { duration_seconds: 2 }),
|
||||
}
|
||||
})
|
||||
)
|
||||
|
|
@ -959,30 +973,35 @@ describe('Complex User Journeys & Multi-Provider STT Orchestration (105 Scenario
|
|||
expect(result.processingTime).toBeGreaterThanOrEqual(15)
|
||||
})
|
||||
|
||||
it('65. Audio duration estimation fallback when API omits duration', async () => {
|
||||
const driver = new D3ROCloudDriver()
|
||||
it('65. Missing server duration is rejected instead of inventing metadata', async () => {
|
||||
const driver = makeCloudDriver()
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ text: 'No duration in payload' }),
|
||||
json: async () => ({
|
||||
transcript: 'No duration in payload',
|
||||
confidence: 0.98,
|
||||
language_code: 'ko',
|
||||
provider: 'groq',
|
||||
}),
|
||||
})
|
||||
)
|
||||
|
||||
const pcm1Sec = makePcmBuffer(1.0, 16000)
|
||||
const result = await driver.transcribe(pcm1Sec, {}, { baseUrl: 'http://localhost:5000' })
|
||||
expect(result.duration).toBe(1) // 32000 bytes / 2 / 16000 = 1 sec
|
||||
await expect(driver.transcribe(makePcmBuffer(1.0, 16000))).rejects.toMatchObject({
|
||||
code: ErrorCode.STTTranscriptionFailed,
|
||||
})
|
||||
})
|
||||
|
||||
it('66. Multiple sequential requests with varying sample rates succeed', async () => {
|
||||
const driver = new D3ROCloudDriver()
|
||||
const driver = makeCloudDriver()
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ text: 'Sample rate OK' }),
|
||||
json: async () => cloudPayload('Sample rate OK'),
|
||||
})
|
||||
)
|
||||
|
||||
|
|
@ -1005,13 +1024,13 @@ describe('Complex User Journeys & Multi-Provider STT Orchestration (105 Scenario
|
|||
})
|
||||
|
||||
it('69. Zero-byte audio buffer rejected with STTNoAudioData error', async () => {
|
||||
const driver = new D3ROCloudDriver()
|
||||
const driver = makeCloudDriver()
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ text: '' }),
|
||||
json: async () => cloudPayload(''),
|
||||
})
|
||||
)
|
||||
await expect(driver.transcribe(Buffer.alloc(0), {}, { baseUrl: 'http://localhost:5000' })).rejects.toMatchObject({
|
||||
|
|
@ -1020,13 +1039,13 @@ describe('Complex User Journeys & Multi-Provider STT Orchestration (105 Scenario
|
|||
})
|
||||
|
||||
it('70. Whitespace-only transcription response rejected with STTNoAudioData error', async () => {
|
||||
const driver = new D3ROCloudDriver()
|
||||
const driver = makeCloudDriver()
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ text: '\n\t \r ' }),
|
||||
json: async () => cloudPayload('\n\t \r '),
|
||||
})
|
||||
)
|
||||
await expect(driver.transcribe(makePcmBuffer(1.0), {}, { baseUrl: 'http://localhost:5000' })).rejects.toMatchObject({
|
||||
|
|
@ -1035,14 +1054,14 @@ describe('Complex User Journeys & Multi-Provider STT Orchestration (105 Scenario
|
|||
})
|
||||
|
||||
it('71. Large 1-minute audio buffer (1.92MB PCM) handles memory without truncation', async () => {
|
||||
const driver = new D3ROCloudDriver()
|
||||
const driver = makeCloudDriver()
|
||||
const bigPcm = Buffer.alloc(16000 * 2 * 60) // 1.92 MB
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ text: '1 minute transcription complete', durationSeconds: 60.0 }),
|
||||
json: async () => cloudPayload('1 minute transcription complete', { duration_seconds: 60 }),
|
||||
})
|
||||
)
|
||||
const res = await driver.transcribe(bigPcm, {}, { baseUrl: 'http://localhost:5000' })
|
||||
|
|
@ -1075,32 +1094,32 @@ describe('Complex User Journeys & Multi-Provider STT Orchestration (105 Scenario
|
|||
expect(fetchSpy.mock.calls[0][1].headers.Authorization).toBeUndefined()
|
||||
})
|
||||
|
||||
it('74. D3ROCloudDriver uses cloudAuthToken from ConfigService if not specified in config', async () => {
|
||||
configSet('cloudAuthToken', 'jwt_user_session_token_12345')
|
||||
const driver = new D3ROCloudDriver()
|
||||
it('74. D3ROCloudDriver uses the latest authenticated Supabase session token', async () => {
|
||||
const driver = makeCloudDriver()
|
||||
const fetchSpy = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ text: 'Token injected' }),
|
||||
json: async () => cloudPayload('Token injected'),
|
||||
})
|
||||
vi.stubGlobal('fetch', fetchSpy)
|
||||
|
||||
await driver.transcribe(makePcmBuffer(1.0), {}, { baseUrl: 'http://localhost:5000' })
|
||||
expect(fetchSpy.mock.calls[0][1].headers.Authorization).toBe('Bearer jwt_user_session_token_12345')
|
||||
expect(fetchSpy.mock.calls[0][1].headers.Authorization).toBe('Bearer user.jwt.token')
|
||||
expect(fetchSpy.mock.calls[0][1].headers.apikey).toBe('anon-key')
|
||||
})
|
||||
|
||||
it('75. D3ROCloudDriver prioritizes apiKey passed in config over cloudAuthToken', async () => {
|
||||
configSet('cloudAuthToken', 'jwt_user_session_token_12345')
|
||||
const driver = new D3ROCloudDriver()
|
||||
it('75. D3ROCloudDriver ignores user-supplied provider tokens and base URLs', async () => {
|
||||
const driver = makeCloudDriver()
|
||||
const fetchSpy = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ text: 'Explicit token used' }),
|
||||
json: async () => cloudPayload('Server session used'),
|
||||
})
|
||||
vi.stubGlobal('fetch', fetchSpy)
|
||||
|
||||
await driver.transcribe(makePcmBuffer(1.0), {}, { baseUrl: 'http://localhost:5000', apiKey: 'explicit_token' })
|
||||
expect(fetchSpy.mock.calls[0][1].headers.Authorization).toBe('Bearer explicit_token')
|
||||
expect(fetchSpy.mock.calls[0][1].headers.Authorization).toBe('Bearer user.jwt.token')
|
||||
expect(fetchSpy.mock.calls[0][0]).toBe('https://project.supabase.co/functions/v1/stt-proxy')
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -1109,67 +1128,67 @@ describe('Complex User Journeys & Multi-Provider STT Orchestration (105 Scenario
|
|||
// =========================================================================
|
||||
describe('Category 6: Domain Vocabulary & Action Pipelines', () => {
|
||||
it('76. Korean language code "ko" is properly injected into form data', async () => {
|
||||
const driver = new D3ROCloudDriver()
|
||||
const driver = makeCloudDriver()
|
||||
const fetchSpy = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ text: '한국어 전사', language: 'ko' }),
|
||||
json: async () => cloudPayload('한국어 전사', { language_code: 'ko' }),
|
||||
})
|
||||
vi.stubGlobal('fetch', fetchSpy)
|
||||
|
||||
await driver.transcribe(makePcmBuffer(1.0), { language: 'ko' }, { baseUrl: 'http://localhost:5000' })
|
||||
const formData = fetchSpy.mock.calls[0][1].body as FormData
|
||||
expect(formData.get('language')).toBe('ko')
|
||||
expect(formData.get('language_code')).toBe('ko')
|
||||
})
|
||||
|
||||
it('77. English language code "en" is injected into form data', async () => {
|
||||
const driver = new D3ROCloudDriver()
|
||||
const driver = makeCloudDriver()
|
||||
const fetchSpy = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ text: 'English transcript', language: 'en' }),
|
||||
json: async () => cloudPayload('English transcript', { language_code: 'en' }),
|
||||
})
|
||||
vi.stubGlobal('fetch', fetchSpy)
|
||||
|
||||
await driver.transcribe(makePcmBuffer(1.0), { language: 'en' }, { baseUrl: 'http://localhost:5000' })
|
||||
const formData = fetchSpy.mock.calls[0][1].body as FormData
|
||||
expect(formData.get('language')).toBe('en')
|
||||
expect(formData.get('language_code')).toBe('en')
|
||||
})
|
||||
|
||||
it('78. Japanese language code "ja" is injected into form data', async () => {
|
||||
const driver = new D3ROCloudDriver()
|
||||
const driver = makeCloudDriver()
|
||||
const fetchSpy = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ text: '日本語の文字起こし', language: 'ja' }),
|
||||
json: async () => cloudPayload('日本語の文字起こし', { language_code: 'ja' }),
|
||||
})
|
||||
vi.stubGlobal('fetch', fetchSpy)
|
||||
|
||||
await driver.transcribe(makePcmBuffer(1.0), { language: 'ja' }, { baseUrl: 'http://localhost:5000' })
|
||||
const formData = fetchSpy.mock.calls[0][1].body as FormData
|
||||
expect(formData.get('language')).toBe('ja')
|
||||
expect(formData.get('language_code')).toBe('ja')
|
||||
})
|
||||
|
||||
it('79. Auto language detection "auto" does not constrain language parameter', async () => {
|
||||
const driver = new D3ROCloudDriver()
|
||||
const driver = makeCloudDriver()
|
||||
const fetchSpy = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ text: 'Auto detected text', language: 'ko' }),
|
||||
json: async () => cloudPayload('Auto detected text'),
|
||||
})
|
||||
vi.stubGlobal('fetch', fetchSpy)
|
||||
|
||||
await driver.transcribe(makePcmBuffer(1.0), { language: 'auto' }, { baseUrl: 'http://localhost:5000' })
|
||||
const formData = fetchSpy.mock.calls[0][1].body as FormData
|
||||
expect(formData.get('language')).toBeNull()
|
||||
expect(formData.get('language_code')).toBeNull()
|
||||
})
|
||||
|
||||
it('80. Domain vocabulary prompt injected into initialPrompt parameter', async () => {
|
||||
const driver = new D3ROCloudDriver()
|
||||
it('80. Domain vocabulary stays server-managed and is not client-injected', async () => {
|
||||
const driver = makeCloudDriver()
|
||||
const fetchSpy = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ text: '쿠버네티스, 리액트, D3RO 전사 완료' }),
|
||||
json: async () => cloudPayload('쿠버네티스, 리액트, D3RO 전사 완료'),
|
||||
})
|
||||
vi.stubGlobal('fetch', fetchSpy)
|
||||
|
||||
|
|
@ -1179,21 +1198,21 @@ describe('Complex User Journeys & Multi-Provider STT Orchestration (105 Scenario
|
|||
{ baseUrl: 'http://localhost:5000' }
|
||||
)
|
||||
const formData = fetchSpy.mock.calls[0][1].body as FormData
|
||||
expect(formData.get('prompt')).toBe('Kubernetes, React, Next.js, D3RO Voice')
|
||||
expect(formData.get('prompt')).toBeNull()
|
||||
})
|
||||
|
||||
it('81. Custom model selection overrides default model in request', async () => {
|
||||
const driver = new D3ROCloudDriver()
|
||||
it('81. Managed STT model stays server-selected despite client config', async () => {
|
||||
const driver = makeCloudDriver()
|
||||
const fetchSpy = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ text: 'Model override OK' }),
|
||||
json: async () => cloudPayload('Server model OK'),
|
||||
})
|
||||
vi.stubGlobal('fetch', fetchSpy)
|
||||
|
||||
await driver.transcribe(makePcmBuffer(1.0), {}, { baseUrl: 'http://localhost:5000', modelId: 'whisper-large-v3-turbo' })
|
||||
const formData = fetchSpy.mock.calls[0][1].body as FormData
|
||||
expect(formData.get('model')).toBe('whisper-large-v3-turbo')
|
||||
expect(formData.get('model')).toBeNull()
|
||||
})
|
||||
|
||||
it('82. Medical vocabulary prompt recognition flow', async () => {
|
||||
|
|
@ -1256,13 +1275,13 @@ describe('Complex User Journeys & Multi-Provider STT Orchestration (105 Scenario
|
|||
})
|
||||
|
||||
it('85. Punctuation formatting preserved in final output', async () => {
|
||||
const driver = new D3ROCloudDriver()
|
||||
const driver = makeCloudDriver()
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ text: '안녕하세요? 오늘 회의는 오후 2시입니다!' }),
|
||||
json: async () => cloudPayload('안녕하세요? 오늘 회의는 오후 2시입니다!'),
|
||||
})
|
||||
)
|
||||
|
||||
|
|
@ -1271,13 +1290,13 @@ describe('Complex User Journeys & Multi-Provider STT Orchestration (105 Scenario
|
|||
})
|
||||
|
||||
it('86. Number and currency formatting preserved in transcript', async () => {
|
||||
const driver = new D3ROCloudDriver()
|
||||
const driver = makeCloudDriver()
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ text: '총 결제 금액은 45,000원입니다.' }),
|
||||
json: async () => cloudPayload('총 결제 금액은 45,000원입니다.'),
|
||||
})
|
||||
)
|
||||
|
||||
|
|
@ -1307,13 +1326,13 @@ describe('Complex User Journeys & Multi-Provider STT Orchestration (105 Scenario
|
|||
})
|
||||
|
||||
it('88. Non-standard special symbols in transcript do not break string serialization', async () => {
|
||||
const driver = new D3ROCloudDriver()
|
||||
const driver = makeCloudDriver()
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ text: '특수문자 테스트: <>&"\'!@#$%^&*()' }),
|
||||
json: async () => cloudPayload('특수문자 테스트: <>&"\'!@#$%^&*()'),
|
||||
})
|
||||
)
|
||||
|
||||
|
|
@ -1322,11 +1341,11 @@ describe('Complex User Journeys & Multi-Provider STT Orchestration (105 Scenario
|
|||
})
|
||||
|
||||
it('89. Empty prompt does not insert "prompt" key in form data', async () => {
|
||||
const driver = new D3ROCloudDriver()
|
||||
const driver = makeCloudDriver()
|
||||
const fetchSpy = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ text: 'Clean prompt' }),
|
||||
json: async () => cloudPayload('Clean prompt'),
|
||||
})
|
||||
vi.stubGlobal('fetch', fetchSpy)
|
||||
|
||||
|
|
@ -1383,17 +1402,17 @@ describe('Complex User Journeys & Multi-Provider STT Orchestration (105 Scenario
|
|||
expect(calculatedCost).toBeCloseTo(0.00000417, 7)
|
||||
})
|
||||
|
||||
it('95. API Base URL trailing slashes are trimmed cleanly without double-slash paths', async () => {
|
||||
const driver = new D3ROCloudDriver()
|
||||
it('95. User-supplied API Base URL cannot replace the Supabase Edge origin', async () => {
|
||||
const driver = makeCloudDriver()
|
||||
const fetchSpy = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ text: 'URL normalized' }),
|
||||
json: async () => cloudPayload('URL normalized'),
|
||||
})
|
||||
vi.stubGlobal('fetch', fetchSpy)
|
||||
|
||||
await driver.transcribe(makePcmBuffer(1.0), {}, { baseUrl: 'http://localhost:5000///' })
|
||||
expect(fetchSpy.mock.calls[0][0]).toBe('http://localhost:5000/api/stt/transcribe')
|
||||
expect(fetchSpy.mock.calls[0][0]).toBe('https://project.supabase.co/functions/v1/stt-proxy')
|
||||
})
|
||||
|
||||
it('96. Custom Driver base URL without protocol defaults or handles cleanly', async () => {
|
||||
|
|
@ -1466,28 +1485,21 @@ describe('Complex User Journeys & Multi-Provider STT Orchestration (105 Scenario
|
|||
})
|
||||
|
||||
it('105. Full End-to-End lifecycle simulation: Config -> Audio capture -> Cloud STT -> Result delivery', async () => {
|
||||
const mgr = getSTTManager()
|
||||
mgr.setProvider('d3ro-cloud')
|
||||
mgr.setProviderConfig('d3ro-cloud', { baseUrl: 'http://localhost:5000', apiKey: 'user_jwt' })
|
||||
const driver = makeCloudDriver()
|
||||
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({
|
||||
text: 'D3RO 음성 인식이 완료되었습니다.',
|
||||
language: 'ko',
|
||||
durationSeconds: 3.5,
|
||||
provider: 'groq',
|
||||
latencyMs: 140,
|
||||
cost: 0.000029,
|
||||
json: async () => cloudPayload('D3RO 음성 인식이 완료되었습니다.', {
|
||||
duration_seconds: 3.5,
|
||||
}),
|
||||
})
|
||||
)
|
||||
|
||||
const audio = makePcmBuffer(3.5, 16000)
|
||||
const result = await mgr.transcribe(audio, { language: 'ko', initialPrompt: 'D3RO Voice' })
|
||||
const result = await driver.transcribe(audio, { language: 'ko', initialPrompt: 'D3RO Voice' })
|
||||
|
||||
expect(result.text).toBe('D3RO 음성 인식이 완료되었습니다.')
|
||||
expect(result.language).toBe('ko')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue