d3ro-voice/apps/desktop/tests/red/cloud-stt-complex-journeys.test.ts
Yun Chan 708e20f747
Some checks failed
CI Pipeline / Code Quality & Typecheck (push) Waiting to run
CI Pipeline / Test Suite (macos-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (ubuntu-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (windows-latest) (push) Blocked by required conditions
CI Pipeline / Build Validation (admin) (push) Blocked by required conditions
CI Pipeline / Build Validation (desktop) (push) Blocked by required conditions
Deploy Landing Page / deploy (push) Blocked by required conditions
Deploy Landing Page / build (push) Waiting to run
Release & Packaging Pipeline / Build & Publish Admin Docker Image (push) Failing after 8s
Release & Code Signing CA Pipeline / build-and-sign-windows (push) Failing after 1m51s
Build macOS / Build & Package (macOS) (push) Failing after 4s
Build macOS / Build & Package (macOS)-1 (push) Failing after 5s
Release & Code Signing CA Pipeline / build-and-sign-macos (push) Failing after 3s
Release & Packaging Pipeline / Package macOS Desktop App (push) Failing after 4s
Release & Packaging Pipeline / Package Windows Desktop App (push) Failing after 2m28s
Release & Packaging Pipeline / Publish Official GitHub Release (push) Has been skipped
feat: complete release preparation, 10+ ad mediation, CI/CD, and docker deployment
2026-08-20 11:12:05 +09:00

1498 lines
57 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 115)
// =========================================================================
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 1630)
// =========================================================================
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 3145)
// =========================================================================
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 4660)
// =========================================================================
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 6175)
// =========================================================================
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 7690)
// =========================================================================
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 91105)
// =========================================================================
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)
})
})
})