111 lines
3.1 KiB
TypeScript
111 lines
3.1 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
getSpeakerVoicePreviewUrl,
|
|
hasAnyVoiceBinding,
|
|
parseSpeakerVoiceMap,
|
|
resolveVoiceBindingForProvider,
|
|
} from '@/lib/voice/provider-voice-binding'
|
|
|
|
describe('provider voice binding', () => {
|
|
it('parses legacy fal speaker voice entry to explicit fal provider', () => {
|
|
const map = parseSpeakerVoiceMap(JSON.stringify({
|
|
Narrator: {
|
|
voiceType: 'uploaded',
|
|
audioUrl: 'voice/reference.wav',
|
|
},
|
|
}))
|
|
|
|
expect(map.Narrator).toEqual({
|
|
provider: 'fal',
|
|
voiceType: 'uploaded',
|
|
audioUrl: 'voice/reference.wav',
|
|
})
|
|
})
|
|
|
|
it('parses legacy bailian entry with voiceId and preview audio', () => {
|
|
const map = parseSpeakerVoiceMap(JSON.stringify({
|
|
Narrator: {
|
|
voiceType: 'qwen-designed',
|
|
voiceId: 'qwen-tts-vd-001',
|
|
audioUrl: 'voice/qwen-preview.wav',
|
|
},
|
|
}))
|
|
|
|
expect(map.Narrator).toEqual({
|
|
provider: 'bailian',
|
|
voiceType: 'qwen-designed',
|
|
voiceId: 'qwen-tts-vd-001',
|
|
previewAudioUrl: 'voice/qwen-preview.wav',
|
|
})
|
|
})
|
|
|
|
it('resolves bailian binding from speaker voiceId when character has no voice', () => {
|
|
const map = parseSpeakerVoiceMap(JSON.stringify({
|
|
Narrator: {
|
|
provider: 'bailian',
|
|
voiceType: 'qwen-designed',
|
|
voiceId: 'qwen-tts-vd-001',
|
|
},
|
|
}))
|
|
|
|
const binding = resolveVoiceBindingForProvider({
|
|
providerKey: 'bailian',
|
|
character: { customVoiceUrl: null, voiceId: null },
|
|
speakerVoice: map.Narrator,
|
|
})
|
|
|
|
expect(binding).toEqual({
|
|
provider: 'bailian',
|
|
source: 'speaker',
|
|
voiceId: 'qwen-tts-vd-001',
|
|
})
|
|
})
|
|
|
|
it('does not treat bailian voice entry as fal reference audio', () => {
|
|
const map = parseSpeakerVoiceMap(JSON.stringify({
|
|
Narrator: {
|
|
provider: 'bailian',
|
|
voiceType: 'qwen-designed',
|
|
voiceId: 'qwen-tts-vd-001',
|
|
previewAudioUrl: 'voice/qwen-preview.wav',
|
|
},
|
|
}))
|
|
|
|
const binding = resolveVoiceBindingForProvider({
|
|
providerKey: 'fal',
|
|
character: { customVoiceUrl: null, voiceId: null },
|
|
speakerVoice: map.Narrator,
|
|
})
|
|
|
|
expect(binding).toBeNull()
|
|
})
|
|
|
|
it('returns preview url from fal and bailian entry correctly', () => {
|
|
const map = parseSpeakerVoiceMap(JSON.stringify({
|
|
FalSpeaker: {
|
|
provider: 'fal',
|
|
voiceType: 'uploaded',
|
|
audioUrl: 'voice/fal.wav',
|
|
},
|
|
BailianSpeaker: {
|
|
provider: 'bailian',
|
|
voiceType: 'qwen-designed',
|
|
voiceId: 'qwen-tts-vd-001',
|
|
previewAudioUrl: 'voice/qwen-preview.wav',
|
|
},
|
|
}))
|
|
|
|
expect(getSpeakerVoicePreviewUrl(map.FalSpeaker)).toBe('voice/fal.wav')
|
|
expect(getSpeakerVoicePreviewUrl(map.BailianSpeaker)).toBe('voice/qwen-preview.wav')
|
|
expect(hasAnyVoiceBinding({ speakerVoice: map.BailianSpeaker })).toBe(true)
|
|
})
|
|
|
|
it('throws explicitly when a speaker entry has no usable binding', () => {
|
|
expect(() => parseSpeakerVoiceMap(JSON.stringify({
|
|
Narrator: {
|
|
voiceType: 'uploaded',
|
|
},
|
|
}))).toThrow('SPEAKER_VOICE_ENTRY_MISSING_BINDING')
|
|
})
|
|
})
|