openclaw / src /cli /program /message /register.discord-admin.test.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
eb3f11e verified
Raw
History Blame Contribute Delete
5.55 kB
import { Command } from "commander";
import { describe, expect, it, vi } from "vitest";
import { createMessageCliHelpers } from "./helpers.js";
import { registerMessageDiscordAdminCommands } from "./register.discord-admin.js";
type AdminCase = {
command: string;
action: string;
required: Record<string, string>;
optional?: Record<string, string>;
};
const cases: AdminCase[] = [
{ command: "role info", action: "role-info", required: { guildId: "guild-1" } },
{
command: "role add",
action: "role-add",
required: { guildId: "guild-1", userId: "user-1", roleId: "role-1" },
},
{
command: "role remove",
action: "role-remove",
required: { guildId: "guild-1", userId: "user-1", roleId: "role-1" },
},
{ command: "channel info", action: "channel-info", required: { target: "channel:123" } },
{ command: "channel list", action: "channel-list", required: { guildId: "guild-1" } },
{
command: "member info",
action: "member-info",
required: { userId: "user-1" },
optional: { guildId: "guild-1", channelId: "channel-1" },
},
{
command: "voice status",
action: "voice-status",
required: { guildId: "guild-1", userId: "user-1" },
},
{ command: "event list", action: "event-list", required: { guildId: "guild-1" } },
{
command: "event create",
action: "event-create",
required: { guildId: "guild-1", eventName: "QA event", startTime: "2026-09-11T12:00:00Z" },
optional: {
endTime: "2026-09-11T13:00:00Z",
desc: "Event description",
channelId: "channel-1",
location: "QA room",
eventType: "external",
image: "https://example.com/event.png",
},
},
{
command: "timeout",
action: "timeout",
required: { guildId: "guild-1", userId: "user-1" },
optional: { durationMin: "0", until: "2026-09-11T13:00:00Z", reason: "QA reason" },
},
{
command: "kick",
action: "kick",
required: { guildId: "guild-1", userId: "user-1" },
optional: { reason: "QA reason" },
},
{
command: "ban",
action: "ban",
required: { guildId: "guild-1", userId: "user-1" },
optional: { reason: "QA reason", deleteDays: "0" },
},
];
function flag(key: string) {
return "--" + key.replace(/[A-Z]/g, (letter) => "-" + letter.toLowerCase());
}
function argumentsFor(options: Record<string, string>) {
return Object.entries(options).flatMap(([key, value]) => [flag(key), value]);
}
function setup() {
const command = new Command()
.name("message")
.exitOverride()
.configureOutput({ writeErr() {}, writeOut() {} });
const runMessageAction = vi.fn(async () => {});
registerMessageDiscordAdminCommands(command, {
...createMessageCliHelpers("discord|matrix|msteams|slack"),
runMessageAction,
});
return { command, runMessageAction };
}
function leaf(command: Command, path: string) {
let current = command;
for (const name of path.split(" ")) {
const next = current.commands.find((candidate) => candidate.name() === name);
if (!next) {
throw new Error("Missing command " + path);
}
current = next;
}
return current;
}
describe("Discord-admin message registration", () => {
it.each(cases)(
"$command forwards the exact action, defaults and options",
async ({ command: path, action, required, optional = {} }) => {
const { command, runMessageAction } = setup();
await command.parseAsync(
[
...path.split(" "),
...argumentsFor(required),
...argumentsFor(optional),
"--channel",
"discord",
],
{ from: "user" },
);
expect(runMessageAction).toHaveBeenCalledExactlyOnceWith(action, {
json: false,
dryRun: false,
verbose: false,
...required,
...optional,
channel: "discord",
});
},
);
it.each(cases)(
"$command rejects every missing mandatory identifier before dispatch",
async ({ command: path, required }) => {
for (const missing of Object.keys(required)) {
const { command, runMessageAction } = setup();
const remaining = Object.fromEntries(
Object.entries(required).filter(([key]) => key !== missing),
);
await expect(
command.parseAsync([...path.split(" "), ...argumentsFor(remaining)], { from: "user" }),
).rejects.toMatchObject({ code: "commander.missingMandatoryOptionValue" });
expect(runMessageAction).not.toHaveBeenCalled();
}
},
);
it.each(cases)(
"$command keeps required, shared and leaf-only flags in help order",
({ command: path, required, optional = {} }) => {
const { command } = setup();
expect(leaf(command, path).options.map((option) => option.long)).toEqual([
...Object.keys(required).map(flag),
"--channel",
"--account",
"--json",
"--dry-run",
"--verbose",
...Object.keys(optional).map(flag),
]);
},
);
it.each([undefined, "discord", "slack"])(
"keeps guild and conversation optional for member info on %s",
async (channel) => {
const { command, runMessageAction } = setup();
const options = { userId: "user-1", ...(channel ? { channel } : {}) };
await command.parseAsync(["member", "info", ...argumentsFor(options)], { from: "user" });
expect(runMessageAction).toHaveBeenCalledExactlyOnceWith("member-info", {
json: false,
dryRun: false,
verbose: false,
...options,
});
},
);
});