* Plugins: add inbound claim and Telegram interaction seams * Plugins: add Discord interaction surface * Chore: fix formatting after plugin rebase * fix(hooks): preserve observers after inbound claim * test(hooks): cover claimed inbound observer delivery * fix(plugins): harden typing lease refreshes * fix(discord): pass real auth to plugin interactions * fix(plugins): remove raw session binding runtime exposure * fix(plugins): tighten interactive callback handling * Plugins: gate conversation binding with approvals * Plugins: migrate legacy plugin binding records * Plugins/phone-control: update test command context * Plugins: migrate legacy binding ids * Plugins: migrate legacy codex session bindings * Discord: fix plugin interaction handling * Discord: support direct plugin conversation binds * Plugins: preserve Discord command bind targets * Tests: fix plugin binding and interactive fallout * Discord: stabilize directory lookup tests * Discord: route bound DMs to plugins * Discord: restore plugin bindings after restart * Telegram: persist detached plugin bindings * Plugins: limit binding APIs to Telegram and Discord * Plugins: harden bound conversation routing * Plugins: fix extension target imports * Plugins: fix Telegram runtime extension imports * Plugins: format rebased binding handlers * Discord: bind group DM interactions by channel --------- Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
106 lines
3.2 KiB
TypeScript
106 lines
3.2 KiB
TypeScript
import { MessageFlags } from "discord-api-types/v10";
|
|
import { describe, expect, it, beforeEach } from "vitest";
|
|
import {
|
|
clearDiscordComponentEntries,
|
|
registerDiscordComponentEntries,
|
|
resolveDiscordComponentEntry,
|
|
resolveDiscordModalEntry,
|
|
} from "./components-registry.js";
|
|
import {
|
|
buildDiscordComponentMessage,
|
|
buildDiscordComponentMessageFlags,
|
|
readDiscordComponentSpec,
|
|
} from "./components.js";
|
|
|
|
describe("discord components", () => {
|
|
it("builds v2 containers with modal trigger", () => {
|
|
const spec = readDiscordComponentSpec({
|
|
text: "Choose a path",
|
|
blocks: [
|
|
{
|
|
type: "actions",
|
|
buttons: [{ label: "Approve", style: "success", callbackData: "codex:approve" }],
|
|
},
|
|
],
|
|
modal: {
|
|
title: "Details",
|
|
callbackData: "codex:modal",
|
|
allowedUsers: ["discord:user-1"],
|
|
fields: [{ type: "text", label: "Requester" }],
|
|
},
|
|
});
|
|
if (!spec) {
|
|
throw new Error("Expected component spec to be parsed");
|
|
}
|
|
|
|
const result = buildDiscordComponentMessage({ spec });
|
|
expect(result.components).toHaveLength(1);
|
|
expect(result.components[0]?.isV2).toBe(true);
|
|
expect(buildDiscordComponentMessageFlags(result.components)).toBe(MessageFlags.IsComponentsV2);
|
|
expect(result.modals).toHaveLength(1);
|
|
|
|
const trigger = result.entries.find((entry) => entry.kind === "modal-trigger");
|
|
expect(trigger?.modalId).toBe(result.modals[0]?.id);
|
|
expect(result.entries.find((entry) => entry.kind === "button")?.callbackData).toBe(
|
|
"codex:approve",
|
|
);
|
|
expect(result.modals[0]?.callbackData).toBe("codex:modal");
|
|
expect(result.modals[0]?.allowedUsers).toEqual(["discord:user-1"]);
|
|
});
|
|
|
|
it("requires options for modal select fields", () => {
|
|
expect(() =>
|
|
readDiscordComponentSpec({
|
|
modal: {
|
|
title: "Details",
|
|
fields: [{ type: "select", label: "Priority" }],
|
|
},
|
|
}),
|
|
).toThrow("options");
|
|
});
|
|
|
|
it("requires attachment references for file blocks", () => {
|
|
expect(() =>
|
|
readDiscordComponentSpec({
|
|
blocks: [{ type: "file", file: "https://example.com/report.pdf" }],
|
|
}),
|
|
).toThrow("attachment://");
|
|
expect(() =>
|
|
readDiscordComponentSpec({
|
|
blocks: [{ type: "file", file: "attachment://" }],
|
|
}),
|
|
).toThrow("filename");
|
|
});
|
|
});
|
|
|
|
describe("discord component registry", () => {
|
|
beforeEach(() => {
|
|
clearDiscordComponentEntries();
|
|
});
|
|
|
|
it("registers and consumes component entries", () => {
|
|
registerDiscordComponentEntries({
|
|
entries: [{ id: "btn_1", kind: "button", label: "Confirm" }],
|
|
modals: [
|
|
{
|
|
id: "mdl_1",
|
|
title: "Details",
|
|
fields: [{ id: "fld_1", name: "name", label: "Name", type: "text" }],
|
|
},
|
|
],
|
|
messageId: "msg_1",
|
|
ttlMs: 1000,
|
|
});
|
|
|
|
const entry = resolveDiscordComponentEntry({ id: "btn_1", consume: false });
|
|
expect(entry?.messageId).toBe("msg_1");
|
|
|
|
const modal = resolveDiscordModalEntry({ id: "mdl_1", consume: false });
|
|
expect(modal?.messageId).toBe("msg_1");
|
|
|
|
const consumed = resolveDiscordComponentEntry({ id: "btn_1" });
|
|
expect(consumed?.id).toBe("btn_1");
|
|
expect(resolveDiscordComponentEntry({ id: "btn_1" })).toBeNull();
|
|
});
|
|
});
|