Tests: cover interactive handler validation and dedupe

This commit is contained in:
Vincent Koc 2026-03-15 15:53:14 -07:00
parent 8ffa9769f7
commit 35ae37ccc1

View File

@ -89,6 +89,30 @@ describe("plugin interactive handlers", () => {
});
});
it("rejects unsupported interactive channels and malformed handlers", () => {
expect(
registerPluginInteractiveHandler("plugin-a", {
channel: "slack",
namespace: "codex",
handler: async () => ({ handled: true }),
} as unknown as Parameters<typeof registerPluginInteractiveHandler>[1]),
).toEqual({
ok: false,
error: 'Interactive handler channel must be either "telegram" or "discord"',
});
expect(
registerPluginInteractiveHandler("plugin-a", {
channel: "telegram",
namespace: "codex",
handler: "not-a-function",
} as unknown as Parameters<typeof registerPluginInteractiveHandler>[1]),
).toEqual({
ok: false,
error: "Interactive handler must be a function",
});
});
it("routes Discord interactions by namespace and dedupes interaction ids", async () => {
const handler = vi.fn(async () => ({ handled: true }));
expect(
@ -198,4 +222,128 @@ describe("plugin interactive handlers", () => {
});
expect(handler).toHaveBeenCalledTimes(2);
});
it("does not share dedupe keys across channels", async () => {
const telegramHandler = vi.fn(async () => ({ handled: true }));
const discordHandler = vi.fn(async () => ({ handled: true }));
expect(
registerPluginInteractiveHandler("codex-plugin", {
channel: "telegram",
namespace: "codex",
handler: telegramHandler,
}),
).toEqual({ ok: true });
expect(
registerPluginInteractiveHandler("codex-plugin", {
channel: "discord",
namespace: "codex",
handler: discordHandler,
}),
).toEqual({ ok: true });
const telegramResult = await dispatchPluginInteractiveHandler({
channel: "telegram",
data: "codex:resume",
callbackId: "same-id",
ctx: {
accountId: "default",
callbackId: "same-id",
conversationId: "chat-1",
senderId: "user-1",
senderUsername: "ada",
isGroup: false,
isForum: false,
auth: { isAuthorizedSender: true },
callbackMessage: {
messageId: 1,
chatId: "chat-1",
},
},
respond: {
reply: vi.fn(async () => {}),
editMessage: vi.fn(async () => {}),
editButtons: vi.fn(async () => {}),
clearButtons: vi.fn(async () => {}),
deleteMessage: vi.fn(async () => {}),
},
});
const discordResult = await dispatchPluginInteractiveHandler({
channel: "discord",
data: "codex:resume",
interactionId: "same-id",
ctx: {
accountId: "default",
interactionId: "same-id",
conversationId: "channel-1",
senderId: "user-1",
senderUsername: "ada",
auth: { isAuthorizedSender: true },
interaction: {
kind: "button",
},
},
respond: {
acknowledge: vi.fn(async () => {}),
reply: vi.fn(async () => {}),
followUp: vi.fn(async () => {}),
editMessage: vi.fn(async () => {}),
clearComponents: vi.fn(async () => {}),
},
});
expect(telegramResult).toEqual({ matched: true, handled: true, duplicate: false });
expect(discordResult).toEqual({ matched: true, handled: true, duplicate: false });
expect(telegramHandler).toHaveBeenCalledTimes(1);
expect(discordHandler).toHaveBeenCalledTimes(1);
});
it("does not consume dedupe keys when a handler declines", async () => {
const handler = vi
.fn(async () => ({ handled: false }))
.mockResolvedValueOnce({ handled: false })
.mockResolvedValueOnce({ handled: true });
expect(
registerPluginInteractiveHandler("codex-plugin", {
channel: "discord",
namespace: "codex",
handler,
}),
).toEqual({ ok: true });
const baseParams = {
channel: "discord" as const,
data: "codex:approve:thread-1",
interactionId: "ix-decline",
ctx: {
accountId: "default",
interactionId: "ix-decline",
conversationId: "channel-1",
senderId: "user-1",
senderUsername: "ada",
auth: { isAuthorizedSender: true },
interaction: {
kind: "button" as const,
},
},
respond: {
acknowledge: vi.fn(async () => {}),
reply: vi.fn(async () => {}),
followUp: vi.fn(async () => {}),
editMessage: vi.fn(async () => {}),
clearComponents: vi.fn(async () => {}),
},
};
await expect(dispatchPluginInteractiveHandler(baseParams)).resolves.toEqual({
matched: true,
handled: false,
duplicate: false,
});
await expect(dispatchPluginInteractiveHandler(baseParams)).resolves.toEqual({
matched: true,
handled: true,
duplicate: false,
});
expect(handler).toHaveBeenCalledTimes(2);
});
});