import assert from 'node:assert/strict'; import { readFile } from 'node:fs/promises'; import test from 'node:test'; import vm from 'node:vm'; import ts from 'typescript'; const source = (path) => readFile(new URL(path, import.meta.url), 'utf8'); const preferenceRuntime = async (storage = new Map()) => { const service = await source('../src/services/tts-voice-preference.ts'); const compiled = ts.transpileModule(service, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 } }); const runtimeModule = { exports: {} }; vm.runInNewContext(compiled.outputText, { exports: runtimeModule.exports, module: runtimeModule, uni: { getStorageSync: (key) => storage.get(key), setStorageSync: (key, value) => storage.set(key, value) } }); return runtimeModule.exports; }; test('播报音色按租户和账号隔离保存,非法值回退跟随场景', async () => { const storage = new Map(); const runtime = await preferenceRuntime(storage); assert.equal(runtime.getTtsVoicePreference('000000', '13800000000'), ''); assert.equal(runtime.setTtsVoicePreference('000000', '13800000000', 'longanhuan_v3.6'), 'longanhuan_v3.6'); assert.equal(runtime.getTtsVoicePreference('000000', '13800000000'), 'longanhuan_v3.6'); assert.equal(runtime.getTtsVoicePreference('000000', '13900000000'), ''); assert.equal(runtime.setTtsVoicePreference('000000', '13800000000', 'untrusted-voice'), ''); assert.equal(runtime.setTtsDialectPreference('000000', '13800000000', 'shanghai'), 'shanghai'); assert.equal(runtime.getTtsDialectPreference('000000', '13800000000'), 'shanghai'); }); test('全局选择只覆盖 voice,保留场景角色、情绪、语速和方言', async () => { const runtime = await preferenceRuntime(); const profile = { role: 'customer', emotion: 'intense', speed: 1.12, dialect: 'sichuanese' }; assert.deepEqual( JSON.parse(JSON.stringify(runtime.applyTtsVoicePreference(profile, 'longanhuan_v3.6', 'shanghai'))), { ...profile, voice: 'longanhuan_v3.6', dialect: 'shanghai' } ); assert.deepEqual(JSON.parse(JSON.stringify(runtime.applyTtsVoicePreference(profile, ''))), profile); }); test('语音播放链路在合成前统一注入全局音色,且缓存键包含处理后的 profile', async () => { const sourceText = await source('../src/services/speech.ts'); assert.match(sourceText, /applyTtsVoicePreference/); assert.match(sourceText, /getTtsVoicePreference/); assert.match(sourceText, /cacheKey\(value, effectiveProfile, practiceContext\)/); assert.match(sourceText, /synthesizeSpeech\(value, effectiveProfile, practiceContext\)/); });