mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-13 00:30:42 +00:00
116 lines
3.3 KiB
TypeScript
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,
|
|
);
|
|
});
|
|
});
|