mirror of
https://github.com/hanzoai/bot.git
synced 2026-11-01 17:14:58 +00:00
The automated rebrand sed script replaced "OpenClaw" with "Hanzo Bot" (with space) instead of "HanzoBot" in 8,315 TypeScript identifier occurrences across 1,632 files. This fixes all broken class names, type names, function names, and variable names. Co-authored-by: Hanzo Dev <[email protected]>
223 lines
6.0 KiB
TypeScript
223 lines
6.0 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import type { HanzoBotPluginCommandDefinition } from "../../test/helpers/extensions/plugin-command.js";
|
|
import { createPluginRuntimeMock } from "../../test/helpers/extensions/plugin-runtime-mock.js";
|
|
import register from "./index.js";
|
|
|
|
function createHarness(config: Record<string, unknown>) {
|
|
let command: HanzoBotPluginCommandDefinition | 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: HanzoBotPluginCommandDefinition) => {
|
|
command = definition;
|
|
}),
|
|
};
|
|
register.register(api as never);
|
|
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",
|
|
});
|
|
});
|
|
|
|
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",
|
|
});
|
|
});
|
|
});
|