From 963ece7848ffef81380097f815c666f008102dd3 Mon Sep 17 00:00:00 2001 From: Michael Nguyen Date: Fri, 24 Jul 2026 01:10:47 -0700 Subject: [PATCH] test(plugins): mirror checkbox selection rules Co-Authored-By: Paperclip --- packages/plugins/sdk/src/testing.ts | 38 +++++++++++++------ .../plugins/sdk/tests/testing-actions.test.ts | 37 +++++++++++++++++- 2 files changed, 63 insertions(+), 12 deletions(-) diff --git a/packages/plugins/sdk/src/testing.ts b/packages/plugins/sdk/src/testing.ts index e02c335b903..e823bd3faa7 100644 --- a/packages/plugins/sdk/src/testing.ts +++ b/packages/plugins/sdk/src/testing.ts @@ -1,5 +1,5 @@ import { randomUUID } from "node:crypto"; -import { pluginOperationIssueOriginKind } from "@paperclipai/shared"; +import { acceptIssueThreadInteractionSchema, pluginOperationIssueOriginKind } from "@paperclipai/shared"; import type { PaperclipPluginManifestV1, PluginCapability, @@ -1807,19 +1807,35 @@ export function createTestHarness(options: TestHarnessOptions): TestHarness { if (current.status !== "pending") { return { interaction: current, applied: false }; } + let checkboxResult: { version: 1; outcome: "accepted"; selectedOptionIds: string[] } | undefined; + if (input.action === "accept" && current.kind === "request_checkbox_confirmation") { + const parsed = acceptIssueThreadInteractionSchema.parse(input); + const optionIds = new Set(current.payload.options.map((option) => option.id)); + const selectedOptionIds = parsed.selectedOptionIds + ?? current.payload.defaultSelectedOptionIds + ?? []; + for (const optionId of selectedOptionIds) { + if (!optionIds.has(optionId)) { + throw new Error(`Unknown checkbox confirmation optionId: ${optionId}`); + } + } + const selected = current.payload.options + .filter((option) => selectedOptionIds.includes(option.id)) + .map((option) => option.id); + const minSelected = current.payload.minSelected ?? 0; + const maxSelected = current.payload.maxSelected ?? null; + if (selected.length < minSelected) { + throw new Error(`Select at least ${minSelected} checkbox confirmation option(s)`); + } + if (maxSelected != null && selected.length > maxSelected) { + throw new Error(`Select no more than ${maxSelected} checkbox confirmation option(s)`); + } + checkboxResult = { version: 1, outcome: "accepted", selectedOptionIds: selected }; + } const resolved: IssueThreadInteraction = { ...current, status: input.action === "accept" ? "accepted" : "rejected", - ...(input.action === "accept" - && current.kind === "request_checkbox_confirmation" - && input.selectedOptionIds !== undefined - ? { - result: { - outcome: "accepted", - selectedOptionIds: input.selectedOptionIds, - }, - } - : {}), + ...(checkboxResult ? { result: checkboxResult } : {}), updatedAt: new Date(), } as IssueThreadInteraction; const index = list.indexOf(current); diff --git a/packages/plugins/sdk/tests/testing-actions.test.ts b/packages/plugins/sdk/tests/testing-actions.test.ts index e661c001b0e..67072d3df0b 100644 --- a/packages/plugins/sdk/tests/testing-actions.test.ts +++ b/packages/plugins/sdk/tests/testing-actions.test.ts @@ -77,7 +77,21 @@ describe("createTestHarness issue interactions", () => { it("creates request_checkbox_confirmation interactions through the typed host helper", async () => { const harness = createTestHarness({ manifest, - capabilities: ["issues.create", "issue.interactions.create"], + capabilities: ["issues.create", "issue.interactions.create", "issue.interactions.respond"], + }); + const now = new Date().toISOString(); + harness.seed({ + accessMembers: [{ + id: "member-1", + companyId: "company-1", + principalType: "user", + principalId: "user-1", + status: "active", + membershipRole: "operator", + grants: [], + createdAt: now, + updatedAt: now, + }], }); const issue = await harness.ctx.issues.create({ companyId: "company-1", @@ -126,5 +140,26 @@ describe("createTestHarness issue interactions", () => { maxSelected: 2, }, }); + + const accepted = await harness.ctx.issues.respondInteraction( + issue.id, + interaction.id, + { + action: "accept", + actorUserId: "user-1", + selectedOptionIds: ["file-b"], + }, + "company-1", + ); + expect(accepted).toMatchObject({ + applied: true, + interaction: { + status: "accepted", + result: { + outcome: "accepted", + selectedOptionIds: ["file-b"], + }, + }, + }); }); });