Files
openclaw/src/gateway/server-methods/talk.test.ts
2026-04-05 18:34:14 +01:00

116 lines
3.3 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../config/config.js";
import { talkHandlers } from "./talk.js";
const mocks = vi.hoisted(() => ({
loadConfig: vi.fn<() => OpenClawConfig>(),
readConfigFileSnapshot: vi.fn(),
canonicalizeSpeechProviderId: vi.fn((providerId: string | undefined) => providerId),
getSpeechProvider: vi.fn(),
synthesizeSpeech: vi.fn(),
}));
vi.mock("../../config/config.js", () => ({
loadConfig: mocks.loadConfig,
readConfigFileSnapshot: mocks.readConfigFileSnapshot,
}));
vi.mock("../../tts/provider-registry.js", () => ({
canonicalizeSpeechProviderId: mocks.canonicalizeSpeechProviderId,
getSpeechProvider: mocks.getSpeechProvider,
}));
vi.mock("../../tts/tts.js", () => ({
synthesizeSpeech: mocks.synthesizeSpeech,
}));
function createTalkConfig(apiKey: unknown): OpenClawConfig {
return {
talk: {
provider: "acme",
providers: {
acme: {
apiKey,
voiceId: "stub-default-voice",
},
},
},
} as OpenClawConfig;
}
describe("talk.speak handler", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("uses the active runtime config snapshot instead of the raw config snapshot", async () => {
const runtimeConfig = createTalkConfig("env-acme-key");
const diskConfig = createTalkConfig({
source: "env",
provider: "default",
id: "ACME_SPEECH_API_KEY",
});
mocks.loadConfig.mockReturnValue(runtimeConfig);
mocks.readConfigFileSnapshot.mockResolvedValue({
path: "/tmp/openclaw.json",
hash: "test-hash",
valid: true,
config: diskConfig,
});
mocks.getSpeechProvider.mockReturnValue({
id: "acme",
label: "Acme Speech",
resolveTalkConfig: ({
talkProviderConfig,
}: {
talkProviderConfig: Record<string, unknown>;
}) => talkProviderConfig,
});
mocks.synthesizeSpeech.mockImplementation(
async ({ cfg }: { cfg: OpenClawConfig; text: string; disableFallback: boolean }) => {
expect(cfg.messages?.tts?.provider).toBe("acme");
expect(cfg.messages?.tts?.providers?.acme?.apiKey).toBe("env-acme-key");
return {
success: true,
provider: "acme",
audioBuffer: Buffer.from([1, 2, 3]),
outputFormat: "mp3",
voiceCompatible: false,
fileExtension: ".mp3",
};
},
);
const respond = vi.fn();
await talkHandlers["talk.speak"]({
req: { type: "req", id: "1", method: "talk.speak" },
params: { text: "Hello from talk mode." },
client: null,
isWebchatConnect: () => false,
respond: respond as never,
context: {} as never,
});
expect(mocks.loadConfig).toHaveBeenCalledTimes(1);
expect(mocks.readConfigFileSnapshot).not.toHaveBeenCalled();
expect(mocks.synthesizeSpeech).toHaveBeenCalledWith(
expect.objectContaining({
text: "Hello from talk mode.",
disableFallback: true,
}),
);
expect(respond).toHaveBeenCalledWith(
true,
expect.objectContaining({
provider: "acme",
audioBase64: Buffer.from([1, 2, 3]).toString("base64"),
outputFormat: "mp3",
mimeType: "audio/mpeg",
fileExtension: ".mp3",
}),
undefined,
);
});
});