223 lines
6.0 KiB
TypeScript
Raw Normal View History

2026-03-16 20:23:58 -07:00
import { describe, expect, it, vi } from "vitest";
import type { OpenClawPluginCommandDefinition } from "../../test/helpers/extensions/plugin-command.js";
import { createPluginRuntimeMock } from "../../test/helpers/extensions/plugin-runtime-mock.js";
2026-03-16 20:23:58 -07:00
import register from "./index.js";
function createHarness(config: Record<string, unknown>) {
let command: OpenClawPluginCommandDefinition | undefined;
const runtime = createPluginRuntimeMock({
config: {
loadConfig: vi.fn(() => config),
writeConfigFile: vi.fn().mockResolvedValue(undefined),
},
tts: {
listVoices: vi.fn(),
},
});
const api = {
runtime,
registerCommand: vi.fn((definition: OpenClawPluginCommandDefinition) => {
command = definition;
}),
};
register.register(api as never);
2026-03-16 20:23:58 -07:00
if (!command) {
throw new Error("talk-voice command not registered");
}
return { command, runtime };
}
function createCommandContext(args: string, channel: string = "discord") {
return {
args,
channel,
channelId: channel,
isAuthorizedSender: true,
commandBody: args ? `/voice ${args}` : "/voice",
config: {},
requestConversationBinding: vi.fn(),
detachConversationBinding: vi.fn(),
getCurrentConversationBinding: vi.fn(),
};
}
describe("talk-voice plugin", () => {
it("reports active provider status", async () => {
const { command } = createHarness({
talk: {
provider: "microsoft",
providers: {
microsoft: {
voiceId: "en-US-AvaNeural",
apiKey: "secret-token",
},
},
},
});
const result = await command.handler(createCommandContext(""));
expect(result).toEqual({
text:
"Talk voice status:\n" +
"- provider: microsoft\n" +
"- talk.voiceId: en-US-AvaNeural\n" +
"- microsoft.apiKey: secret…",
});
});
it("lists voices from the active provider", async () => {
const { command, runtime } = createHarness({
talk: {
provider: "elevenlabs",
providers: {
elevenlabs: {
apiKey: "sk-eleven",
baseUrl: "https://voices.example.test",
},
},
},
});
vi.mocked(runtime.tts.listVoices).mockResolvedValue([
{ id: "voice-a", name: "Claudia", category: "general" },
{ id: "voice-b", name: "Bert" },
]);
const result = await command.handler(createCommandContext("list 1"));
expect(runtime.tts.listVoices).toHaveBeenCalledWith({
provider: "elevenlabs",
cfg: {
talk: {
provider: "elevenlabs",
providers: {
elevenlabs: {
apiKey: "sk-eleven",
baseUrl: "https://voices.example.test",
},
},
},
},
apiKey: "sk-eleven",
baseUrl: "https://voices.example.test",
});
expect(result).toEqual({
text:
"ElevenLabs voices: 2\n\n" +
"- Claudia · general\n" +
" id: voice-a\n\n" +
"(showing first 1)",
});
});
it("surfaces richer provider voice metadata when available", async () => {
const { command, runtime } = createHarness({
talk: {
provider: "microsoft",
providers: {
microsoft: {},
},
},
});
vi.mocked(runtime.tts.listVoices).mockResolvedValue([
{
id: "en-US-AvaNeural",
name: "Ava",
category: "General",
locale: "en-US",
gender: "Female",
personalities: ["Friendly", "Positive"],
description: "Friendly, Positive",
},
]);
const result = await command.handler(createCommandContext("list"));
expect(result).toEqual({
text:
"Microsoft voices: 1\n\n" +
"- Ava · General\n" +
" id: en-US-AvaNeural\n" +
" meta: en-US · Female · Friendly, Positive\n" +
" note: Friendly, Positive",
});
});
2026-03-16 20:23:58 -07:00
it("writes canonical talk provider config and legacy elevenlabs voice id", async () => {
const { command, runtime } = createHarness({
talk: {
provider: "elevenlabs",
providers: {
elevenlabs: {
apiKey: "sk-eleven",
},
},
},
});
vi.mocked(runtime.tts.listVoices).mockResolvedValue([{ id: "voice-a", name: "Claudia" }]);
const result = await command.handler(createCommandContext("set Claudia"));
expect(runtime.config.writeConfigFile).toHaveBeenCalledWith({
talk: {
provider: "elevenlabs",
providers: {
elevenlabs: {
apiKey: "sk-eleven",
voiceId: "voice-a",
},
},
voiceId: "voice-a",
},
});
expect(result).toEqual({
text: "✅ ElevenLabs Talk voice set to Claudia\nvoice-a",
});
});
it("writes provider voice id without legacy top-level field for microsoft", async () => {
const { command, runtime } = createHarness({
talk: {
provider: "microsoft",
providers: {
microsoft: {},
},
},
});
vi.mocked(runtime.tts.listVoices).mockResolvedValue([{ id: "en-US-AvaNeural", name: "Ava" }]);
await command.handler(createCommandContext("set Ava"));
expect(runtime.config.writeConfigFile).toHaveBeenCalledWith({
talk: {
provider: "microsoft",
providers: {
microsoft: {
voiceId: "en-US-AvaNeural",
},
},
},
});
});
it("returns provider lookup errors cleanly", async () => {
const { command, runtime } = createHarness({
talk: {
provider: "microsoft",
providers: {
microsoft: {},
},
},
});
vi.mocked(runtime.tts.listVoices).mockRejectedValue(
new Error("speech provider microsoft does not support voice listing"),
);
const result = await command.handler(createCommandContext("list"));
expect(result).toEqual({
text: "Microsoft voice list failed: speech provider microsoft does not support voice listing",
});
});
});