pediatric-ai-scribe-v3/test/tts-provider.test.js
2026-05-09 04:50:55 +02:00

117 lines
5.2 KiB
JavaScript

const { test } = require('node:test');
const assert = require('node:assert/strict');
const ENV_KEYS = ['TTS_PROVIDER', 'LITELLM_API_BASE', 'LITELLM_API_KEY', 'ELEVENLABS_API_KEY', 'GOOGLE_VERTEX_PROJECT'];
function withEnv(overrides, fn) {
const previous = {};
ENV_KEYS.forEach((key) => { previous[key] = process.env[key]; delete process.env[key]; });
Object.entries(overrides).forEach(([key, value]) => { process.env[key] = value; });
try {
return fn();
} finally {
ENV_KEYS.forEach((key) => {
if (previous[key] === undefined) delete process.env[key];
else process.env[key] = previous[key];
});
}
}
test('TTS provider defaults to LiteLLM when gateway is configured', () => {
const ttsProvider = require('../src/utils/ttsProvider');
withEnv({ LITELLM_API_BASE: 'https://llm.example.com' }, () => {
assert.equal(ttsProvider.getTTSEnvProvider(), 'auto');
assert.equal(ttsProvider.getTTSProvider(), 'litellm');
});
});
test('TTS provider respects explicit provider override', () => {
const ttsProvider = require('../src/utils/ttsProvider');
withEnv({ TTS_PROVIDER: 'elevenlabs', LITELLM_API_BASE: 'https://llm.example.com' }, () => {
assert.equal(ttsProvider.getTTSEnvProvider(), 'elevenlabs');
assert.equal(ttsProvider.getTTSProvider(), 'elevenlabs');
});
});
test('LiteLLM TTS voice list stays OpenAI-compatible', () => {
const ttsProvider = require('../src/utils/ttsProvider');
assert.deepEqual(ttsProvider.getTTSVoiceLists().litellm, [
'alloy',
'echo',
'fable',
'onyx',
'nova',
'shimmer',
'ash',
'sage',
'coral'
]);
});
test('TTS model discovery uses audio_speech metadata only', () => {
const ttsProvider = require('../src/utils/ttsProvider');
assert.equal(ttsProvider.isOpenAITTSModel, undefined);
assert.equal(ttsProvider.isLiteLLMTTSModel({ id: 'local-chatterbox-turbo' }), false);
assert.equal(ttsProvider.isLiteLLMTTSModel({ id: 'local-kokoro-tts' }), false);
assert.equal(ttsProvider.isLiteLLMTTSModel({ id: 'openai-tts-1' }), false);
assert.equal(ttsProvider.isLiteLLMTTSModel({ id: 'openai-gpt-4o-mini-tts' }), false);
assert.equal(ttsProvider.isLiteLLMTTSModel({ id: 'mistral-voxtral-mini-tts' }), false);
assert.equal(ttsProvider.isLiteLLMTTSModel({ id: 'vertex-gemini-2.5-flash-tts' }), false);
assert.equal(ttsProvider.isLiteLLMTTSModel({ model_name: 'local-chatterbox-turbo', model_info: { mode: 'audio_speech' } }), true);
assert.equal(ttsProvider.isLiteLLMTTSModel({ id: 'custom-provider-model', model_info: { mode: 'audio_speech' } }), true);
assert.equal(ttsProvider.isLiteLLMTTSModel({ id: 'local-parakeet-v3', model_info: { mode: 'audio_transcription' } }), false);
});
test('LiteLLM TTS model extraction filters gateway model objects', () => {
const ttsProvider = require('../src/utils/ttsProvider');
assert.deepEqual(ttsProvider.getLiteLLMTTSModels([
{ id: 'openai-tts-1' },
{ id: 'mistral-voxtral-mini-tts' },
{ id: 'openai-gpt-4o-mini-tts' },
{ id: 'local-chatterbox-turbo' },
{ model_name: 'local-chatterbox-turbo', model_info: { mode: 'audio_speech' } },
{ id: 'custom-provider-model', model_info: { mode: 'audio_speech' } },
{ id: 'local-parakeet-v3', model_info: { mode: 'audio_transcription' } },
{ id: 'vertex-gemini-2.5-flash-tts' },
{ id: 'gpt-4.1' }
]), ['local-chatterbox-turbo', 'custom-provider-model']);
});
test('LiteLLM TTS discovery includes metadata models and configured fallbacks', () => {
const ttsProvider = require('../src/utils/ttsProvider');
const items = ttsProvider.getLiteLLMTTSDiscoveryItems([
{ model_name: 'local-kokoro-tts', model_info: { mode: 'audio_speech' } },
{ model_name: 'not-tts-by-name-only' },
{ model_name: 'local-parakeet-v3', model_info: { mode: 'audio_transcription' } }
], {
currentModel: 'local-kokoro-tts',
currentVoice: 'sherpa/kokoro:am_adam'
});
assert.deepEqual(items.slice(0, 3), [
{ id: 'local-kokoro-tts', name: 'local-kokoro-tts', source: 'gateway-api', kind: 'model' },
{ id: 'sherpa/kokoro:am_adam', name: 'sherpa/kokoro:am_adam', source: 'configured-voice', kind: 'voice' },
{ id: 'alloy', name: 'alloy', source: 'openai-compatible', kind: 'voice' }
]);
assert.equal(items.some(function(item) { return item.id === 'not-tts-by-name-only'; }), false);
});
test('LiteLLM TTS discovery still shows configured model if metadata lookup fails', () => {
const ttsProvider = require('../src/utils/ttsProvider');
assert.deepEqual(ttsProvider.getLiteLLMTTSDiscoveryItems([], {
currentModel: 'local-kokoro-tts',
currentVoice: 'sherpa/kokoro:am_adam'
}).slice(0, 2), [
{ id: 'local-kokoro-tts', name: 'local-kokoro-tts', source: 'configured-model', kind: 'model' },
{ id: 'sherpa/kokoro:am_adam', name: 'sherpa/kokoro:am_adam', source: 'configured-voice', kind: 'voice' }
]);
});
test('LiteLLM headers include optional content type without exposing key value', () => {
const ttsProvider = require('../src/utils/ttsProvider');
withEnv({ LITELLM_API_KEY: 'secret-token' }, () => {
assert.deepEqual(ttsProvider.getLiteLLMHeaders('application/json'), {
'Content-Type': 'application/json',
Authorization: 'Bearer secret-token'
});
});
});