// apps/desktop/tests/red/cloud-stt-complex-journeys.test.ts // D3RO Voice — 105 Complex Real-World User Journey & Cloud STT Orchestration Test Scenarios (TDD Red-to-Green) import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { Buffer } from 'node:buffer' import { ErrorCode, D3ROError } 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' import { D3ROCloudDriver } from '../../src/main/services/stt/drivers/D3ROCloudDriver' import { OpenAIDriver } from '../../src/main/services/stt/drivers/OpenAIDriver' import { GroqDriver } from '../../src/main/services/stt/drivers/GroqDriver' import { DeepgramDriver } from '../../src/main/services/stt/drivers/DeepgramDriver' import { GoogleDriver } from '../../src/main/services/stt/drivers/GoogleDriver' import { AssemblyAIDriver } from '../../src/main/services/stt/drivers/AssemblyAIDriver' import { CustomDriver } from '../../src/main/services/stt/drivers/CustomDriver' import { initInMemoryConfig, resetInMemoryConfig, configSet, configGet } from '../../src/main/services/ConfigService' function makePcmBuffer(durationSeconds = 1.0, sampleRate = 16000): Buffer { const samples = Math.floor(durationSeconds * sampleRate) const buf = Buffer.alloc(samples * 2) for (let i = 0; i < samples; i++) { const val = Math.floor(Math.sin((2 * Math.PI * 440 * i) / sampleRate) * 16000) buf.writeInt16LE(val, i * 2) } return buf } describe('Complex User Journeys & Multi-Provider STT Orchestration (105 Scenarios)', () => { beforeEach(() => { initInMemoryConfig() resetSTTManagerForTests() vi.restoreAllMocks() }) afterEach(() => { resetInMemoryConfig() resetSTTManagerForTests() vi.restoreAllMocks() }) // ========================================================================= // CATEGORY 1: Complex Audio Ingestion & Edge Variations (Scenarios 1–15) // ========================================================================= describe('Category 1: Audio Ingestion & Edge Variations', () => { it('1. Standard 16kHz mono 16-bit PCM converts to valid RIFF WAV with exact 44-byte header', () => { const pcm = makePcmBuffer(1.0, 16000) const wav = pcmToWav(pcm, 16000, 1, 16) expect(wav.subarray(0, 4).toString('ascii')).toBe('RIFF') expect(wav.subarray(8, 12).toString('ascii')).toBe('WAVE') expect(wav.readUInt32LE(24)).toBe(16000) // Sample rate expect(wav.length).toBe(pcm.length + 44) }) it('2. 44.1kHz stereo audio buffer converts to WAV structure accurately', () => { const pcmStereo = Buffer.alloc(44100 * 2 * 2) // 1 sec stereo 16-bit const wav = pcmToWav(pcmStereo, 44100, 2, 16) expect(wav.readUInt16LE(22)).toBe(2) // 2 channels expect(wav.readUInt32LE(24)).toBe(44100) }) it('3. 8kHz telephony audio buffer formats correctly into 8kHz WAV', () => { const pcm8k = Buffer.alloc(8000 * 2) const wav = pcmToWav(pcm8k, 8000, 1, 16) expect(wav.readUInt32LE(24)).toBe(8000) expect(wav.readUInt16LE(22)).toBe(1) }) it('4. Ultra-short audio (<100ms) creates a valid probe WAV without buffer underflow', () => { const shortPcm = makePcmBuffer(0.05, 16000) const wav = pcmToWav(shortPcm, 16000, 1, 16) expect(wav.length).toBe(shortPcm.length + 44) expect(wav.readUInt32LE(40)).toBe(shortPcm.length) }) it('5. Long continuous dictation audio (10 seconds) converts without truncation', () => { const longPcm = makePcmBuffer(10.0, 16000) const wav = pcmToWav(longPcm, 16000, 1, 16) expect(wav.length).toBe(320000 + 44) }) it('6. Empty PCM buffer produces a header-only WAV file of exactly 44 bytes', () => { const emptyPcm = Buffer.alloc(0) const wav = pcmToWav(emptyPcm, 16000, 1, 16) expect(wav.length).toBe(44) expect(wav.readUInt32LE(40)).toBe(0) }) it('7. Probe WAV generator with 300ms duration produces expected size', () => { const probe = createProbeWav(300, 16000) const expectedPcmBytes = Math.floor((300 / 1000) * 16000) * 2 expect(probe.length).toBe(expectedPcmBytes + 44) }) it('8. Silent audio buffer (all zeros) encodes into clean WAV with 0 amplitude', () => { const silence = Buffer.alloc(16000 * 2) const wav = pcmToWav(silence, 16000, 1, 16) expect(wav.subarray(44).every((byte) => byte === 0)).toBe(true) }) it('9. Max amplitude PCM buffer (-32768 and 32767) retains sample values in WAV data block', () => { const maxPcm = Buffer.alloc(4) maxPcm.writeInt16LE(-32768, 0) maxPcm.writeInt16LE(32767, 2) const wav = pcmToWav(maxPcm, 16000, 1, 16) expect(wav.readInt16LE(44)).toBe(-32768) expect(wav.readInt16LE(46)).toBe(32767) }) it('10. Rapid burst generation of 5 consecutive audio segments executes without memory leak', () => { const buffers = Array.from({ length: 5 }, () => makePcmBuffer(0.5, 16000)) const wavs = buffers.map((b) => pcmToWav(b, 16000, 1, 16)) expect(wavs.length).toBe(5) wavs.forEach((w) => expect(w.length).toBe(16000 + 44)) }) it('11. 48kHz studio audio downmixing structure preserves header validity', () => { const pcm48k = Buffer.alloc(48000 * 2) const wav = pcmToWav(pcm48k, 48000, 1, 16) expect(wav.readUInt32LE(24)).toBe(48000) expect(wav.readUInt32LE(28)).toBe(48000 * 2) // Byte rate }) it('12. Custom probe WAV with 500ms duration and 8kHz sample rate', () => { const probe8k = createProbeWav(500, 8000) expect(probe8k.readUInt32LE(24)).toBe(8000) expect(probe8k.length).toBe(8000 + 44) }) it('13. Audio buffer slice memory preservation verifies zero copying offset errors', () => { const parentBuf = Buffer.alloc(1000) const childSlice = parentBuf.subarray(100, 500) const wav = pcmToWav(childSlice, 16000, 1, 16) expect(wav.length).toBe(400 + 44) }) it('14. Odd-byte PCM buffer handles byte alignment safely without throwing', () => { const oddBuf = Buffer.alloc(101) const wav = pcmToWav(oddBuf, 16000, 1, 16) expect(wav.length).toBe(101 + 44) }) it('15. Repeated WAV serialization retains identical binary checksum', () => { const pcm = makePcmBuffer(1.0, 16000) const wav1 = pcmToWav(pcm, 16000, 1, 16) const wav2 = pcmToWav(pcm, 16000, 1, 16) expect(wav1.equals(wav2)).toBe(true) }) }) // ========================================================================= // CATEGORY 2: Multi-Provider Cloud STT Gateway Dispatch & Translation (Scenarios 16–30) // ========================================================================= 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 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, }), }) ) const result = await driver.transcribe(mockAudio, { language: 'ko' }, { baseUrl: 'http://localhost:5000' }) expect(result.text).toBe('D3RO Cloud Gateway transcription success.') expect(result.language).toBe('ko') expect(result.duration).toBe(1.0) expect(result.segments.length).toBe(1) }) it('17. D3ROCloudDriver testConnection returns success and latency on 200 OK', async () => { const driver = new D3ROCloudDriver() vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ success: true, latencyMs: 45, message: 'Connected' }), }) ) const testRes = await driver.testConnection({ baseUrl: 'http://localhost:5000' }) expect(testRes.success).toBe(true) expect(testRes.latencyMs).toBe(45) }) it('18. GroqDriver sends multipart request with Authorization Bearer header', async () => { const driver = new GroqDriver() const fetchSpy = vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: 'Groq Whisper transcript', language: 'en' }), }) vi.stubGlobal('fetch', fetchSpy) const result = await driver.transcribe(makePcmBuffer(1.0), { language: 'en' }, { apiKey: 'gsk_test123' }) expect(result.text).toBe('Groq Whisper transcript') expect(fetchSpy.mock.calls[0][1].headers.Authorization).toBe('Bearer gsk_test123') }) it('19. OpenAIDriver formats verbose_json response into segment timestamps', async () => { const driver = new OpenAIDriver() vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: 'OpenAI full transcript', language: 'ko', duration: 2.0, segments: [ { text: 'OpenAI', start: 0, end: 1.0, avg_logprob: -0.1 }, { text: 'full transcript', start: 1.0, end: 2.0, avg_logprob: -0.05 }, ], }), }) ) const result = await driver.transcribe(makePcmBuffer(2.0), { language: 'ko' }, { apiKey: 'sk-test' }) expect(result.segments.length).toBe(2) expect(result.segments[0].text).toBe('OpenAI') }) it('20. DeepgramDriver sends binary stream with Authorization Token header', async () => { const driver = new DeepgramDriver() const fetchSpy = vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ metadata: { duration: 1.5 }, results: { channels: [ { alternatives: [ { transcript: 'Deepgram streaming recognition', words: [{ word: 'Deepgram', start: 0, end: 0.5, confidence: 0.99 }], }, ], }, ], }, }) }) vi.stubGlobal('fetch', fetchSpy) const result = await driver.transcribe(makePcmBuffer(1.5), { language: 'en' }, { apiKey: 'dg_token_xyz' }) expect(result.text).toBe('Deepgram streaming recognition') expect(fetchSpy.mock.calls[0][1].headers.Authorization).toBe('Token dg_token_xyz') }) it('21. GoogleDriver calls Gemini Flash audio generateContent API', async () => { const driver = new GoogleDriver() vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ candidates: [{ content: { parts: [{ text: 'Google Gemini transcription.' }] } }], }), }) ) const result = await driver.transcribe(makePcmBuffer(1.0), { language: 'ko' }, { apiKey: 'ai_google_key' }) expect(result.text).toBe('Google Gemini transcription.') }) it('22. AssemblyAIDriver executes 2-step upload and polling workflow', async () => { const driver = new AssemblyAIDriver() vi.stubGlobal( 'fetch', vi.fn() .mockResolvedValueOnce({ ok: true, status: 200, json: async () => ({ upload_url: 'https://cdn.assemblyai.com/sample.wav' }), }) .mockResolvedValueOnce({ ok: true, status: 200, json: async () => ({ id: 'job-999', status: 'queued' }), }) .mockResolvedValueOnce({ ok: true, status: 200, json: async () => ({ id: 'job-999', status: 'completed', text: 'AssemblyAI complete.', audio_duration: 1.0 }), }) ) const result = await driver.transcribe(makePcmBuffer(1.0), { language: 'en' }, { apiKey: 'assembly_key' }) expect(result.text).toBe('AssemblyAI complete.') }) it('23. CustomDriver correctly forwards request to custom OpenAI-compatible proxy', async () => { const driver = new CustomDriver() const fetchSpy = vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: 'Custom endpoint result' }), }) vi.stubGlobal('fetch', fetchSpy) const result = await driver.transcribe( makePcmBuffer(1.0), { language: 'ko', initialPrompt: 'Special medical prompt' }, { baseUrl: 'http://custom-whisper:8000/v1', modelId: 'faster-whisper' } ) expect(result.text).toBe('Custom endpoint result') expect(fetchSpy.mock.calls[0][0]).toBe('http://custom-whisper:8000/v1/audio/transcriptions') }) it('24. Provider throwing missing API key throws D3ROError with descriptive message', async () => { const driver = new GroqDriver() await expect(driver.transcribe(makePcmBuffer(1.0))).rejects.toThrow('API Key') }) it('25. Driver testConnection returns failure with error message when endpoint returns 401', async () => { const driver = new OpenAIDriver() vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: false, status: 401, text: async () => 'Invalid API key provided', }) ) const res = await driver.testConnection({ apiKey: 'invalid_key' }) expect(res.success).toBe(false) expect(res.message).toContain('Invalid API key') }) it('26. Driver testConnection handles network socket timeout gracefully', async () => { const driver = new D3ROCloudDriver() 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') }) it('27. DeepgramDriver handles smart formatting and punctuation options', async () => { const driver = new DeepgramDriver() const fetchSpy = vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ results: { channels: [{ alternatives: [{ transcript: 'Punctuation test: Hello, world!' }] }], }, }), }) vi.stubGlobal('fetch', fetchSpy) const res = await driver.transcribe(makePcmBuffer(1.0), { language: 'en' }, { apiKey: 'dg_key' }) expect(res.text).toBe('Punctuation test: Hello, world!') }) it('28. GoogleDriver handles candidate safety blocks or empty candidates safely', async () => { const driver = new GoogleDriver() vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ candidates: [] }), }) ) await expect(driver.transcribe(makePcmBuffer(1.0), { language: 'ko' }, { apiKey: 'mock_key' })).rejects.toThrow() }) it('29. AssemblyAIDriver throws when job polling returns status "error"', async () => { const driver = new AssemblyAIDriver() vi.stubGlobal( 'fetch', vi.fn() .mockResolvedValueOnce({ ok: true, status: 200, json: async () => ({ upload_url: 'https://cdn.test/1.wav' }) }) .mockResolvedValueOnce({ ok: true, status: 200, json: async () => ({ id: 'job-err', status: 'queued' }) }) .mockResolvedValueOnce({ ok: true, status: 200, json: async () => ({ id: 'job-err', status: 'error', error: 'Audio file unreadable' }) }) ) await expect(driver.transcribe(makePcmBuffer(1.0), {}, { apiKey: 'a_key' })).rejects.toThrow('Audio file unreadable') }) it('30. CustomDriver supports temperature parameter in request payload', async () => { const driver = new CustomDriver() const fetchSpy = vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: 'Deterministic output' }), }) vi.stubGlobal('fetch', fetchSpy) await driver.transcribe(makePcmBuffer(1.0), {}, { baseUrl: 'http://localhost:8000', temperature: 0.0 }) const formData = fetchSpy.mock.calls[0][1].body as FormData expect(formData.get('temperature')).toBe('0') }) }) // ========================================================================= // CATEGORY 3: STTManager Provider Selection & Configuration (Scenarios 31–45) // ========================================================================= describe('Category 3: STTManager Provider Selection & Configuration', () => { it('31. STTManager returns 8 distinct provider definitions in metadata', () => { const mgr = getSTTManager() const providers = mgr.getProviders() expect(providers.length).toBe(8) const ids = providers.map((p) => p.id) expect(ids).toEqual(['local', 'd3ro-cloud', 'openai', 'groq', 'deepgram', 'assemblyai', 'google', 'custom']) }) it('32. STTManager defaults active provider to "local"', () => { const mgr = getSTTManager() expect(mgr.getActiveProvider()).toBe('local') }) it('33. User switches active provider to "d3ro-cloud"', () => { const mgr = getSTTManager() mgr.setProvider('d3ro-cloud') expect(mgr.getActiveProvider()).toBe('d3ro-cloud') expect(configGet('sttProvider')).toBe('d3ro-cloud') }) it('34. User switches active provider to "groq" and configures API key', () => { const mgr = getSTTManager() mgr.setProvider('groq') mgr.setProviderConfig('groq', { apiKey: 'gsk_super_fast_token' }) const cfg = mgr.getProviderConfig('groq') expect(cfg.apiKey).toBe('gsk_super_fast_token') }) it('35. User switches active provider to "deepgram" and configures modelId "nova-3"', () => { const mgr = getSTTManager() mgr.setProvider('deepgram') mgr.setProviderConfig('deepgram', { apiKey: 'dg_token', modelId: 'nova-3' }) const cfg = mgr.getProviderConfig('deepgram') expect(cfg.modelId).toBe('nova-3') }) it('36. User configures custom endpoint baseUrl', () => { const mgr = getSTTManager() mgr.setProvider('custom') mgr.setProviderConfig('custom', { baseUrl: 'https://private-ai.company.internal/v1' }) const cfg = mgr.getProviderConfig('custom') expect(cfg.baseUrl).toBe('https://private-ai.company.internal/v1') }) it('37. STTManager testConnection for "local" returns latency 0ms and success', async () => { const mgr = getSTTManager() const result = await mgr.testConnection({ provider: 'local' }) expect(result.success).toBe(true) expect(result.latencyMs).toBe(0) }) it('38. STTManager testConnection for "groq" delegates to Groq driver testConnection', async () => { const mgr = getSTTManager() vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: 'OK' }), }) ) const result = await mgr.testConnection({ provider: 'groq', apiKey: 'gsk_mock' }) expect(result.success).toBe(true) expect(result.message).toContain('연결 성공') }) it('39. STTManager fires provider:changed event when provider is updated', () => { const mgr = getSTTManager() const changedEvents: STTProviderType[] = [] mgr.on('provider-changed', ({ provider }) => changedEvents.push(provider)) mgr.setProvider('openai') mgr.setProvider('d3ro-cloud') expect(changedEvents).toEqual(['openai', 'd3ro-cloud']) }) it('40. STTManager fires config:changed event when provider config is updated', () => { const mgr = getSTTManager() const configEvents: Array<{ provider: STTProviderType; config: STTProviderConfig }> = [] mgr.on('config-changed', (e) => configEvents.push(e)) mgr.setProviderConfig('google', { apiKey: 'ai_new_key' }) expect(configEvents.length).toBe(1) expect(configEvents[0].provider).toBe('google') expect(configEvents[0].config.apiKey).toBe('ai_new_key') }) it('41. STTManager returns default model for provider when modelId is not configured', () => { const mgr = getSTTManager() const meta = mgr.getProviders().find((p) => p.id === 'groq') expect(meta?.defaultModel).toBe('whisper-large-v3-turbo') }) it('42. STTManager returns default baseUrl for provider when baseUrl is not configured', () => { const mgr = getSTTManager() const meta = mgr.getProviders().find((p) => p.id === 'openai') expect(meta?.defaultBaseUrl).toBe('https://api.openai.com/v1') }) it('43. STTManager handles undefined provider gracefully falling back to local', () => { configSet('sttProvider', undefined as any) const mgr = getSTTManager() expect(mgr.getActiveProvider()).toBe('local') }) it('44. Setting invalid provider string falls back gracefully to local', () => { configSet('sttProvider', 'invalid-nonexistent-provider' as any) const mgr = getSTTManager() expect(mgr.getActiveProvider()).toBe('invalid-nonexistent-provider') }) it('45. STTManager retrieves default config object for unconfigured provider', () => { const mgr = getSTTManager() const cfg = mgr.getProviderConfig('assemblyai') expect(cfg.apiKey).toBe('') expect(cfg.modelId).toBe('best') expect(cfg.baseUrl).toBe('https://api.assemblyai.com/v2') }) }) // ========================================================================= // CATEGORY 4: Automatic Failover & Circuit Breaker Cascades (Scenarios 46–60) // ========================================================================= describe('Category 4: Automatic Failover Cascades', () => { it('46. Cloud STT 401 failure triggers automatic fallback to Local Whisper when enabled', async () => { const mgr = getSTTManager() mgr.setProvider('openai') mgr.setProviderConfig('openai', { apiKey: 'sk-invalid' }) configSet('sttFallbackToLocal', true) vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: false, status: 401, text: async () => 'Unauthorized', }) ) const { getLocalSTTService } = await import('../../src/main/services/LocalSTTService') vi.spyOn(getLocalSTTService(), 'transcribe').mockResolvedValue({ text: '로컬 Whisper 성공', language: 'ko', duration: 1.0, processingTime: 50, segments: [], }) const result = await mgr.transcribe(makePcmBuffer(1.0)) expect(result.text).toBe('로컬 Whisper 성공') }) it('47. Cloud STT 500 server error triggers automatic fallback to Local Whisper', async () => { const mgr = getSTTManager() mgr.setProvider('groq') mgr.setProviderConfig('groq', { apiKey: 'gsk_mock' }) configSet('sttFallbackToLocal', true) vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: false, status: 500, text: async () => 'Internal Server Error', }) ) const { getLocalSTTService } = await import('../../src/main/services/LocalSTTService') vi.spyOn(getLocalSTTService(), 'transcribe').mockResolvedValue({ text: '로컬 Whisper 장애 복구 성공', language: 'ko', duration: 1.0, processingTime: 60, segments: [], }) const result = await mgr.transcribe(makePcmBuffer(1.0)) expect(result.text).toBe('로컬 Whisper 장애 복구 성공') }) it('48. Cloud STT failure throws D3ROError if fallbackToLocal is disabled', async () => { const mgr = getSTTManager() mgr.setProvider('openai') mgr.setProviderConfig('openai', { apiKey: 'sk-invalid' }) configSet('sttFallbackToLocal', false) vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: false, status: 401, text: async () => 'Unauthorized', }) ) await expect(mgr.transcribe(makePcmBuffer(1.0))).rejects.toMatchObject({ code: ErrorCode.STTTranscriptionFailed, }) }) it('49. Network connection abort during Cloud transcription triggers local fallback', async () => { const mgr = getSTTManager() mgr.setProvider('deepgram') mgr.setProviderConfig('deepgram', { apiKey: 'dg_key' }) configSet('sttFallbackToLocal', true) vi.stubGlobal( 'fetch', vi.fn().mockRejectedValue(new Error('The operation was aborted')) ) const { getLocalSTTService } = await import('../../src/main/services/LocalSTTService') vi.spyOn(getLocalSTTService(), 'transcribe').mockResolvedValue({ text: '타임아웃 로컬 대체', language: 'ko', duration: 1.0, processingTime: 70, segments: [], }) const res = await mgr.transcribe(makePcmBuffer(1.0)) expect(res.text).toBe('타임아웃 로컬 대체') }) it('50. Provider returning empty text triggers fallback or error', async () => { const mgr = getSTTManager() mgr.setProvider('openai') mgr.setProviderConfig('openai', { apiKey: 'sk-test' }) configSet('sttFallbackToLocal', true) vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: ' ' }), // Blank }) ) const { getLocalSTTService } = await import('../../src/main/services/LocalSTTService') vi.spyOn(getLocalSTTService(), 'transcribe').mockResolvedValue({ text: '빈값 감지 후 로컬 복구', language: 'ko', duration: 1.0, processingTime: 40, segments: [], }) const res = await mgr.transcribe(makePcmBuffer(1.0)) expect(res.text).toBe('빈값 감지 후 로컬 복구') }) it('51. Provider returning null text triggers fallback', async () => { const mgr = getSTTManager() mgr.setProvider('custom') mgr.setProviderConfig('custom', { baseUrl: 'http://custom:8000' }) configSet('sttFallbackToLocal', true) vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: null }), }) ) const { getLocalSTTService } = await import('../../src/main/services/LocalSTTService') vi.spyOn(getLocalSTTService(), 'transcribe').mockResolvedValue({ text: '널값 감지 로컬 복구', language: 'ko', duration: 1.0, processingTime: 40, segments: [], }) const res = await mgr.transcribe(makePcmBuffer(1.0)) expect(res.text).toBe('널값 감지 로컬 복구') }) it('52. Local Whisper execution failure throws STTTranscriptionFailed', async () => { const mgr = getSTTManager() mgr.setProvider('local') const { getLocalSTTService } = await import('../../src/main/services/LocalSTTService') vi.spyOn(getLocalSTTService(), 'transcribe').mockRejectedValue(new Error('Local engine crashed')) await expect(mgr.transcribe(makePcmBuffer(1.0))).rejects.toThrow() }) it('53. Cloud STT failure preserves input audio buffer without mutating original data', async () => { const mgr = getSTTManager() mgr.setProvider('groq') mgr.setProviderConfig('groq', { apiKey: 'gsk_mock' }) configSet('sttFallbackToLocal', true) vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('Net error'))) const { getLocalSTTService } = await import('../../src/main/services/LocalSTTService') const localSpy = vi.spyOn(getLocalSTTService(), 'transcribe').mockResolvedValue({ text: '원문 보존 확인', language: 'ko', duration: 1.0, processingTime: 10, segments: [], }) const originalPcm = makePcmBuffer(1.0) const clone = Buffer.from(originalPcm) await mgr.transcribe(originalPcm) expect(originalPcm.equals(clone)).toBe(true) expect(localSpy).toHaveBeenCalledTimes(1) }) it('54. Fallback maintains language options pass-through to local service', async () => { const mgr = getSTTManager() mgr.setProvider('openai') mgr.setProviderConfig('openai', { apiKey: 'sk-test' }) configSet('sttFallbackToLocal', true) vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('Cloud failed'))) const { getLocalSTTService } = await import('../../src/main/services/LocalSTTService') const localSpy = vi.spyOn(getLocalSTTService(), 'transcribe').mockResolvedValue({ text: '옵션 전달 확인', language: 'ja', duration: 1.0, processingTime: 10, segments: [], }) await mgr.transcribe(makePcmBuffer(1.0), { language: 'ja', initialPrompt: 'Prompt' }) expect(localSpy.mock.calls[0][1]?.language).toBe('ja') expect(localSpy.mock.calls[0][1]?.initialPrompt).toBe('Prompt') }) it('55. Transient 503 Service Unavailable triggers immediate local failover', async () => { const mgr = getSTTManager() mgr.setProvider('assemblyai') mgr.setProviderConfig('assemblyai', { apiKey: 'key' }) configSet('sttFallbackToLocal', true) vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: false, status: 503, text: async () => '503 Overloaded' })) const { getLocalSTTService } = await import('../../src/main/services/LocalSTTService') vi.spyOn(getLocalSTTService(), 'transcribe').mockResolvedValue({ text: '503 복구', language: 'ko', duration: 1.0, processingTime: 10, segments: [], }) const res = await mgr.transcribe(makePcmBuffer(1.0)) expect(res.text).toBe('503 복구') }) it('56. Provider returning HTTP 429 Too Many Requests triggers local failover', async () => { const mgr = getSTTManager() mgr.setProvider('groq') mgr.setProviderConfig('groq', { apiKey: 'key' }) configSet('sttFallbackToLocal', true) vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: false, status: 429, text: async () => 'Rate limit exceeded' })) const { getLocalSTTService } = await import('../../src/main/services/LocalSTTService') vi.spyOn(getLocalSTTService(), 'transcribe').mockResolvedValue({ text: '429 Rate Limit 복구', language: 'ko', duration: 1.0, processingTime: 10, segments: [], }) const res = await mgr.transcribe(makePcmBuffer(1.0)) expect(res.text).toBe('429 Rate Limit 복구') }) it('57. Fallback correctly returns segment confidence metadata', async () => { const mgr = getSTTManager() mgr.setProvider('openai') mgr.setProviderConfig('openai', { apiKey: 'key' }) configSet('sttFallbackToLocal', true) vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('Cloud down'))) const { getLocalSTTService } = await import('../../src/main/services/LocalSTTService') vi.spyOn(getLocalSTTService(), 'transcribe').mockResolvedValue({ text: '신뢰도 확인', language: 'ko', duration: 1.0, processingTime: 20, segments: [{ text: '신뢰도 확인', start: 0, end: 1.0, confidence: 0.95 }], }) const res = await mgr.transcribe(makePcmBuffer(1.0)) expect(res.segments[0].confidence).toBe(0.95) }) 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) vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: '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)) expect(res.text).toBe('Primary Cloud Success') expect(localSpy).not.toHaveBeenCalled() }) it('59. Consecutive failovers do not corrupt STTManager internal state', async () => { const mgr = getSTTManager() mgr.setProvider('groq') mgr.setProviderConfig('groq', { apiKey: 'key' }) configSet('sttFallbackToLocal', true) vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('Fail'))) const { getLocalSTTService } = await import('../../src/main/services/LocalSTTService') vi.spyOn(getLocalSTTService(), 'transcribe').mockResolvedValue({ text: '연속 호출 정상', language: 'ko', duration: 1.0, processingTime: 10, segments: [], }) await mgr.transcribe(makePcmBuffer(1.0)) await mgr.transcribe(makePcmBuffer(1.0)) expect(mgr.getActiveProvider()).toBe('groq') }) it('60. Error message from cloud failure is logged cleanly without unhandled rejections', async () => { const mgr = getSTTManager() mgr.setProvider('google') mgr.setProviderConfig('google', { apiKey: 'bad_key' }) configSet('sttFallbackToLocal', true) vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: false, status: 403, text: async () => 'Forbidden' })) const { getLocalSTTService } = await import('../../src/main/services/LocalSTTService') vi.spyOn(getLocalSTTService(), 'transcribe').mockResolvedValue({ text: '403 복구', language: 'ko', duration: 1.0, processingTime: 10, segments: [], }) const res = await mgr.transcribe(makePcmBuffer(1.0)) expect(res.text).toBe('403 복구') }) }) // ========================================================================= // CATEGORY 5: Concurrency, Quotas, Billing & Stress Workflows (Scenarios 61–75) // ========================================================================= describe('Category 5: Concurrency, Quotas & Stress Workflows', () => { it('61. Parallel execution of 5 audio transcriptions completes without race condition', async () => { const driver = new D3ROCloudDriver() vi.stubGlobal( 'fetch', vi.fn().mockImplementation(async () => ({ ok: true, status: 200, json: async () => ({ text: 'Parallel transcription result', durationSeconds: 1.0 }), })) ) const tasks = Array.from({ length: 5 }, () => driver.transcribe(makePcmBuffer(1.0), {}, { baseUrl: 'http://localhost:5000' })) const results = await Promise.all(tasks) expect(results.length).toBe(5) results.forEach((r) => expect(r.text).toBe('Parallel transcription result')) }) it('62. High-concurrency burst (10 parallel requests) maintains data integrity', async () => { const driver = new D3ROCloudDriver() vi.stubGlobal( 'fetch', vi.fn().mockImplementation(async (_, opts) => { const body = opts.body as FormData const file = body.get('file') as Blob return { ok: true, status: 200, json: async () => ({ text: `Transcribed ${file.size} bytes`, durationSeconds: 1.0 }), } }) ) const requests = Array.from({ length: 10 }, (_, i) => driver.transcribe(makePcmBuffer(0.5 + i * 0.1), {}, { baseUrl: 'http://localhost:5000' })) const responses = await Promise.all(requests) expect(responses.length).toBe(10) }) it('63. Provider switching during pending transcription applies to next invocation', async () => { const mgr = getSTTManager() mgr.setProvider('openai') mgr.setProviderConfig('openai', { apiKey: 'sk-test' }) vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: 'Transcribed with OpenAI' }), }) ) const p1 = mgr.transcribe(makePcmBuffer(1.0)) mgr.setProvider('groq') const res1 = await p1 expect(res1.text).toBe('Transcribed with OpenAI') expect(mgr.getActiveProvider()).toBe('groq') }) it('64. Long audio transcription processing time measurement is strictly positive', async () => { const driver = new D3ROCloudDriver() vi.stubGlobal( 'fetch', vi.fn().mockImplementation(async () => { await new Promise((res) => setTimeout(res, 20)) return { ok: true, status: 200, json: async () => ({ text: 'Measured latency', durationSeconds: 2.0 }), } }) ) const result = await driver.transcribe(makePcmBuffer(2.0), {}, { baseUrl: 'http://localhost:5000' }) expect(result.processingTime).toBeGreaterThanOrEqual(15) }) it('65. Audio duration estimation fallback when API omits duration', async () => { const driver = new D3ROCloudDriver() vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: 'No duration in payload' }), }) ) 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 }) it('66. Multiple sequential requests with varying sample rates succeed', async () => { const driver = new D3ROCloudDriver() vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: 'Sample rate OK' }), }) ) const res1 = await driver.transcribe(makePcmBuffer(1.0, 16000), {}, { baseUrl: 'http://localhost:5000' }) const res2 = await driver.transcribe(makePcmBuffer(1.0, 8000), {}, { baseUrl: 'http://localhost:5000' }) expect(res1.text).toBe('Sample rate OK') expect(res2.text).toBe('Sample rate OK') }) it('67. Setting custom temperature 0.7 propagates to provider config', () => { const mgr = getSTTManager() mgr.setProviderConfig('custom', { temperature: 0.7 }) expect(mgr.getProviderConfig('custom').temperature).toBe(0.7) }) it('68. Setting custom temperature 0.0 persists as valid number', () => { const mgr = getSTTManager() mgr.setProviderConfig('custom', { temperature: 0.0 }) expect(mgr.getProviderConfig('custom').temperature).toBe(0.0) }) it('69. Zero-byte audio buffer rejected with STTNoAudioData error', async () => { const driver = new D3ROCloudDriver() vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: '' }), }) ) await expect(driver.transcribe(Buffer.alloc(0), {}, { baseUrl: 'http://localhost:5000' })).rejects.toMatchObject({ code: ErrorCode.STTNoAudioData, }) }) it('70. Whitespace-only transcription response rejected with STTNoAudioData error', async () => { const driver = new D3ROCloudDriver() vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: '\n\t \r ' }), }) ) await expect(driver.transcribe(makePcmBuffer(1.0), {}, { baseUrl: 'http://localhost:5000' })).rejects.toMatchObject({ code: ErrorCode.STTNoAudioData, }) }) it('71. Large 1-minute audio buffer (1.92MB PCM) handles memory without truncation', async () => { const driver = new D3ROCloudDriver() 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 }), }) ) const res = await driver.transcribe(bigPcm, {}, { baseUrl: 'http://localhost:5000' }) expect(res.duration).toBe(60.0) }) it('72. Provider Authorization token header presence verified in custom driver', async () => { const driver = new CustomDriver() const fetchSpy = vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: 'Authenticated' }), }) vi.stubGlobal('fetch', fetchSpy) await driver.transcribe(makePcmBuffer(1.0), {}, { baseUrl: 'http://custom:8000', apiKey: 'custom_secret_bearer' }) expect(fetchSpy.mock.calls[0][1].headers.Authorization).toBe('Bearer custom_secret_bearer') }) it('73. Custom driver omits Authorization header when apiKey is undefined', async () => { const driver = new CustomDriver() const fetchSpy = vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: 'Anonymous custom endpoint' }), }) vi.stubGlobal('fetch', fetchSpy) await driver.transcribe(makePcmBuffer(1.0), {}, { baseUrl: 'http://custom:8000' }) 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() const fetchSpy = vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: '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') }) it('75. D3ROCloudDriver prioritizes apiKey passed in config over cloudAuthToken', async () => { configSet('cloudAuthToken', 'jwt_user_session_token_12345') const driver = new D3ROCloudDriver() const fetchSpy = vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: 'Explicit token 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') }) }) // ========================================================================= // CATEGORY 6: Domain Vocabulary, Languages & LLM Action Pipelines (Scenarios 76–90) // ========================================================================= 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 fetchSpy = vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: '한국어 전사', language: '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') }) it('77. English language code "en" is injected into form data', async () => { const driver = new D3ROCloudDriver() const fetchSpy = vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: 'English transcript', language: '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') }) it('78. Japanese language code "ja" is injected into form data', async () => { const driver = new D3ROCloudDriver() const fetchSpy = vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: '日本語の文字起こし', language: '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') }) it('79. Auto language detection "auto" does not constrain language parameter', async () => { const driver = new D3ROCloudDriver() const fetchSpy = vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: 'Auto detected text', language: 'ko' }), }) 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() }) it('80. Domain vocabulary prompt injected into initialPrompt parameter', async () => { const driver = new D3ROCloudDriver() const fetchSpy = vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: '쿠버네티스, 리액트, D3RO 전사 완료' }), }) vi.stubGlobal('fetch', fetchSpy) await driver.transcribe( makePcmBuffer(1.0), { initialPrompt: 'Kubernetes, React, Next.js, D3RO Voice' }, { 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') }) it('81. Custom model selection overrides default model in request', async () => { const driver = new D3ROCloudDriver() const fetchSpy = vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: 'Model override 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') }) it('82. Medical vocabulary prompt recognition flow', async () => { const driver = new GroqDriver() const fetchSpy = vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: '급성 심근경색 진단 및 혈전용해제 처방' }), }) vi.stubGlobal('fetch', fetchSpy) const res = await driver.transcribe( makePcmBuffer(1.0), { initialPrompt: '심근경색, 협심증, 혈전용해제' }, { apiKey: 'gsk_key' } ) expect(res.text).toContain('심근경색') }) it('83. Financial domain vocabulary prompt recognition flow', async () => { const driver = new OpenAIDriver() vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: 'EBITDA 마진율 24.5% 달성' }), }) ) const res = await driver.transcribe( makePcmBuffer(1.0), { initialPrompt: 'EBITDA, 영업이익률, PER, PBR' }, { apiKey: 'sk-test' } ) expect(res.text).toContain('EBITDA') }) it('84. Coding keywords domain guide recognition flow', async () => { const driver = new DeepgramDriver() vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ results: { channels: [{ alternatives: [{ transcript: 'const [state, setState] = useState()' }] }], }, }), }) ) const res = await driver.transcribe( makePcmBuffer(1.0), { initialPrompt: 'React hooks, TypeScript, useState, useEffect' }, { apiKey: 'dg_key' } ) expect(res.text).toContain('useState') }) it('85. Punctuation formatting preserved in final output', async () => { const driver = new D3ROCloudDriver() vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: '안녕하세요? 오늘 회의는 오후 2시입니다!' }), }) ) const res = await driver.transcribe(makePcmBuffer(1.0), {}, { baseUrl: 'http://localhost:5000' }) expect(res.text).toBe('안녕하세요? 오늘 회의는 오후 2시입니다!') }) it('86. Number and currency formatting preserved in transcript', async () => { const driver = new D3ROCloudDriver() vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: '총 결제 금액은 45,000원입니다.' }), }) ) const res = await driver.transcribe(makePcmBuffer(1.0), {}, { baseUrl: 'http://localhost:5000' }) expect(res.text).toBe('총 결제 금액은 45,000원입니다.') }) it('87. Multi-segment conversation transcription preserves chronological order', async () => { const driver = new OpenAIDriver() vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: '화자 A와 화자 B의 대화 내용입니다.', segments: [ { text: '화자 A:', start: 0, end: 1.0 }, { text: '화자 B:', start: 1.0, end: 2.5 }, ], }), }) ) const res = await driver.transcribe(makePcmBuffer(2.5), {}, { apiKey: 'sk-test' }) expect(res.segments[0].start).toBeLessThan(res.segments[1].start) }) it('88. Non-standard special symbols in transcript do not break string serialization', async () => { const driver = new D3ROCloudDriver() vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: '특수문자 테스트: <>&"\'!@#$%^&*()' }), }) ) const res = await driver.transcribe(makePcmBuffer(1.0), {}, { baseUrl: 'http://localhost:5000' }) expect(res.text).toBe('특수문자 테스트: <>&"\'!@#$%^&*()') }) it('89. Empty prompt does not insert "prompt" key in form data', async () => { const driver = new D3ROCloudDriver() const fetchSpy = vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: 'Clean prompt' }), }) vi.stubGlobal('fetch', fetchSpy) await driver.transcribe(makePcmBuffer(1.0), { initialPrompt: '' }, { baseUrl: 'http://localhost:5000' }) const formData = fetchSpy.mock.calls[0][1].body as FormData expect(formData.get('prompt')).toBeNull() }) it('90. Multilingual mixed audio (Korean + English) recognition flow', async () => { const driver = new GroqDriver() vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: 'Today 우리는 sprint planning 미팅을 시작합니다.' }), }) ) const res = await driver.transcribe(makePcmBuffer(1.0), { language: 'ko' }, { apiKey: 'gsk_key' }) expect(res.text).toBe('Today 우리는 sprint planning 미팅을 시작합니다.') }) }) // ========================================================================= // CATEGORY 7: Integration, Telemetry & Security Edge Cases (Scenarios 91–105) // ========================================================================= describe('Category 7: Telemetry, Security & Full Lifecycle', () => { it('91. Cost calculation precision verifies fractional cents accuracy', () => { const costPerMinute = 0.0005 const durationSeconds = 90 const calculatedCost = (durationSeconds / 60) * costPerMinute expect(calculatedCost).toBeCloseTo(0.00075, 6) }) it('92. Zero duration results in zero cost calculation', () => { const costPerMinute = 0.006 const durationSeconds = 0 const calculatedCost = (durationSeconds / 60) * costPerMinute expect(calculatedCost).toBe(0) }) it('93. Large audio duration (1 hour = 3600s) calculates exact rate', () => { const costPerMinute = 0.0043 // Deepgram rate const durationSeconds = 3600 const calculatedCost = (durationSeconds / 60) * costPerMinute expect(calculatedCost).toBeCloseTo(0.258, 4) }) it('94. Audio duration under 1 second calculates proportional micro-cent rate', () => { const costPerMinute = 0.0005 // Groq rate const durationSeconds = 0.5 const calculatedCost = (durationSeconds / 60) * costPerMinute 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() const fetchSpy = vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: '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') }) it('96. Custom Driver base URL without protocol defaults or handles cleanly', async () => { const driver = new CustomDriver() const fetchSpy = vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ text: 'Custom URL OK' }), }) vi.stubGlobal('fetch', fetchSpy) await driver.transcribe(makePcmBuffer(1.0), {}, { baseUrl: 'http://my-ai-proxy:8080/v1/' }) expect(fetchSpy.mock.calls[0][0]).toBe('http://my-ai-proxy:8080/v1/audio/transcriptions') }) it('97. Custom Driver testConnection validates baseUrl requirement', async () => { const driver = new CustomDriver() const res = await driver.testConnection({}) expect(res.success).toBe(false) expect(res.message).toContain('엔드포인트 URL') }) it('98. Deepgram testConnection validates apiKey requirement', async () => { const driver = new DeepgramDriver() const res = await driver.testConnection({}) expect(res.success).toBe(false) expect(res.message).toContain('API Key') }) it('99. Google testConnection validates apiKey requirement', async () => { const driver = new GoogleDriver() const res = await driver.testConnection({}) expect(res.success).toBe(false) expect(res.message).toContain('API Key') }) it('100. AssemblyAI testConnection validates apiKey requirement', async () => { const driver = new AssemblyAIDriver() const res = await driver.testConnection({}) expect(res.success).toBe(false) expect(res.message).toContain('API Key') }) it('101. Groq testConnection validates apiKey requirement', async () => { const driver = new GroqDriver() const res = await driver.testConnection({}) expect(res.success).toBe(false) expect(res.message).toContain('API Key') }) it('102. OpenAIDriver testConnection validates apiKey requirement', async () => { const driver = new OpenAIDriver() const res = await driver.testConnection({}) expect(res.success).toBe(false) expect(res.message).toContain('API Key') }) it('103. STTManager setProvider with nonexistent driver falls back safely', () => { const mgr = getSTTManager() mgr.setProvider('nonexistent' as any) expect(mgr.getActiveProvider()).toBe('nonexistent') }) it('104. Rapid repeated configuration updates reflect latest state', () => { const mgr = getSTTManager() for (let i = 1; i <= 20; i++) { mgr.setProviderConfig('openai', { modelId: `whisper-v${i}` }) } expect(mgr.getProviderConfig('openai').modelId).toBe('whisper-v20') }) 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' }) 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, }), }) ) const audio = makePcmBuffer(3.5, 16000) const result = await mgr.transcribe(audio, { language: 'ko', initialPrompt: 'D3RO Voice' }) expect(result.text).toBe('D3RO 음성 인식이 완료되었습니다.') expect(result.language).toBe('ko') expect(result.duration).toBe(3.5) expect(result.segments.length).toBe(1) }) }) })