oclaw/openclaw/extensions/voice-call/src/telephony-tts.test.ts
oliver dbbe3add6a 重构主控编排与运行时预热链路,统一工作区提示词/专家调度协议并补齐 wiki 记忆注入与写回闭环。
同时收敛启动与运维脚本默认行为(含 wiki worker)、更新 Admin 可观测性与相关测试,降低首轮时延并提高运行稳定性。

Made-with: Cursor
2026-04-26 08:34:33 +08:00

119 lines
3.7 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
import type { VoiceCallTtsConfig } from "./config.js";
import type { CoreConfig } from "./core-bridge.js";
import { createTelephonyTtsProvider } from "./telephony-tts.js";
function createCoreConfig(): CoreConfig {
const tts: VoiceCallTtsConfig = {
provider: "openai",
providers: {
openai: {
model: "gpt-4o-mini-tts",
voice: "alloy",
},
},
};
return { messages: { tts } };
}
function requireMergedTtsConfig(mergedConfig: CoreConfig | undefined) {
const tts = mergedConfig?.messages?.tts;
if (!tts) {
throw new Error("telephony TTS runtime did not receive merged TTS config");
}
return tts as Record<string, unknown>;
}
function requireOpenAIProviderConfig(tts: Record<string, unknown>): Record<string, unknown> {
const providers =
tts.providers && typeof tts.providers === "object"
? (tts.providers as Record<string, unknown>)
: null;
const openai = providers?.openai;
if (!openai || typeof openai !== "object") {
throw new Error("merged TTS config did not preserve providers.openai");
}
return openai as Record<string, unknown>;
}
async function mergeOverride(override: unknown): Promise<Record<string, unknown>> {
let mergedConfig: CoreConfig | undefined;
const provider = createTelephonyTtsProvider({
coreConfig: createCoreConfig(),
ttsOverride: override as VoiceCallTtsConfig,
runtime: {
textToSpeechTelephony: async ({ cfg }) => {
mergedConfig = cfg;
return {
success: true,
audioBuffer: Buffer.alloc(2),
sampleRate: 8000,
};
},
},
});
await provider.synthesizeForTelephony("hello");
return requireMergedTtsConfig(mergedConfig);
}
afterEach(() => {
delete (Object.prototype as Record<string, unknown>).polluted;
});
describe("createTelephonyTtsProvider deepMerge hardening", () => {
it("merges safe nested overrides", async () => {
const tts = await mergeOverride({
providers: { openai: { voice: "coral" } },
});
const openai = requireOpenAIProviderConfig(tts);
expect(openai.voice).toBe("coral");
expect(openai.model).toBe("gpt-4o-mini-tts");
});
it("blocks top-level __proto__ keys", async () => {
const tts = await mergeOverride(
JSON.parse('{"__proto__":{"polluted":"top"},"providers":{"openai":{"voice":"coral"}}}'),
);
const openai = requireOpenAIProviderConfig(tts);
expect((Object.prototype as Record<string, unknown>).polluted).toBeUndefined();
expect(tts.polluted).toBeUndefined();
expect(openai.voice).toBe("coral");
});
it("blocks nested __proto__ keys", async () => {
const tts = await mergeOverride(
JSON.parse('{"providers":{"openai":{"model":"safe","__proto__":{"polluted":"nested"}}}}'),
);
const openai = requireOpenAIProviderConfig(tts);
expect((Object.prototype as Record<string, unknown>).polluted).toBeUndefined();
expect(openai.polluted).toBeUndefined();
expect(openai.model).toBe("safe");
});
it("logs fallback metadata when telephony TTS uses a fallback provider", async () => {
const warn = vi.fn();
const provider = createTelephonyTtsProvider({
coreConfig: createCoreConfig(),
runtime: {
textToSpeechTelephony: async () => ({
success: true,
audioBuffer: Buffer.alloc(2),
sampleRate: 8000,
provider: "microsoft",
fallbackFrom: "elevenlabs",
attemptedProviders: ["elevenlabs", "microsoft"],
}),
},
logger: { warn },
});
await provider.synthesizeForTelephony("hello");
expect(warn).toHaveBeenCalledWith(
"[voice-call] Telephony TTS fallback used from=elevenlabs to=microsoft attempts=elevenlabs -> microsoft",
);
});
});