openclaw/extensions/discord/src/voice/command.test.ts
scoootscooob 5682ec37fa
refactor: move Discord channel implementation to extensions/ (#45660)
* refactor: move Discord channel implementation to extensions/discord/src/

Move all Discord source files from src/discord/ to extensions/discord/src/,
following the extension migration pattern. Source files in src/discord/ are
replaced with re-export shims. Channel-plugin files from
src/channels/plugins/*/discord* are similarly moved and shimmed.

- Copy all .ts source files preserving subdirectory structure (monitor/, voice/)
- Move channel-plugin files (actions, normalize, onboarding, outbound, status-issues)
- Fix all relative imports to use correct paths from new location
- Create re-export shims at original locations for backward compatibility
- Delete test files from shim locations (tests live in extension now)
- Update tsconfig.plugin-sdk.dts.json rootDir from "src" to "." to accommodate
  extension files outside src/
- Update write-plugin-sdk-entry-dts.ts to match new declaration output paths

* fix: add importOriginal to thread-bindings session-meta mock for extensions test

* style: fix formatting in thread-bindings lifecycle test
2026-03-14 02:53:57 -07:00

100 lines
3.1 KiB
TypeScript

import type { CommandInteraction, CommandWithSubcommands } from "@buape/carbon";
import { describe, expect, it, vi } from "vitest";
import { createDiscordVoiceCommand } from "./command.js";
import type { DiscordVoiceManager } from "./manager.js";
function findVoiceSubcommand(command: CommandWithSubcommands, name: string) {
const subcommands = (
command as unknown as { subcommands?: Array<{ name: string; run: unknown }> }
).subcommands;
const subcommand = subcommands?.find((entry) => entry.name === name) as
| { run: (interaction: CommandInteraction) => Promise<void> }
| undefined;
if (!subcommand) {
throw new Error(`Missing vc ${name} subcommand`);
}
return subcommand;
}
function createVoiceCommandHarness(manager: DiscordVoiceManager | null = null) {
const command = createDiscordVoiceCommand({
cfg: {},
discordConfig: {},
accountId: "default",
groupPolicy: "open",
useAccessGroups: false,
getManager: () => manager,
ephemeralDefault: true,
});
return {
command,
leave: findVoiceSubcommand(command, "leave"),
status: findVoiceSubcommand(command, "status"),
};
}
function createInteraction(overrides?: Partial<CommandInteraction>): {
interaction: CommandInteraction;
reply: ReturnType<typeof vi.fn>;
} {
const reply = vi.fn(async () => undefined);
const interaction = {
guild: undefined,
user: { id: "u1", username: "tester" },
rawData: { member: { roles: [] } },
reply,
...overrides,
} as unknown as CommandInteraction;
return { interaction, reply };
}
describe("createDiscordVoiceCommand", () => {
it("vc leave reports missing guild before manager lookup", async () => {
const { leave } = createVoiceCommandHarness(null);
const { interaction, reply } = createInteraction();
await leave.run(interaction);
expect(reply).toHaveBeenCalledTimes(1);
expect(reply).toHaveBeenCalledWith({
content: "Unable to resolve guild for this command.",
ephemeral: true,
});
});
it("vc status reports unavailable voice manager", async () => {
const { status } = createVoiceCommandHarness(null);
const { interaction, reply } = createInteraction({
guild: { id: "g1" } as CommandInteraction["guild"],
});
await status.run(interaction);
expect(reply).toHaveBeenCalledTimes(1);
expect(reply).toHaveBeenCalledWith({
content: "Voice manager is not available yet.",
ephemeral: true,
});
});
it("vc status reports no active sessions when manager has none", async () => {
const statusSpy = vi.fn(() => []);
const manager = {
status: statusSpy,
} as unknown as DiscordVoiceManager;
const { status } = createVoiceCommandHarness(manager);
const { interaction, reply } = createInteraction({
guild: { id: "g1", name: "Guild" } as CommandInteraction["guild"],
});
await status.run(interaction);
expect(statusSpy).toHaveBeenCalledTimes(1);
expect(reply).toHaveBeenCalledTimes(1);
expect(reply).toHaveBeenCalledWith({
content: "No active voice sessions.",
ephemeral: true,
});
});
});