mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-15 02:20:38 +09:00
124 lines
3.8 KiB
TypeScript
124 lines
3.8 KiB
TypeScript
|
|
import { describe, expect, it } from "vitest";
|
||
|
|
import { createIssueThreadInteractionSchema } from "./validators/issue.js";
|
||
|
|
|
||
|
|
describe("issue thread interaction schemas", () => {
|
||
|
|
it("parses request_confirmation payloads with default no-wake continuation", () => {
|
||
|
|
const parsed = createIssueThreadInteractionSchema.parse({
|
||
|
|
kind: "request_confirmation",
|
||
|
|
payload: {
|
||
|
|
version: 1,
|
||
|
|
prompt: "Apply this plan?",
|
||
|
|
acceptLabel: "Apply",
|
||
|
|
rejectLabel: "Revise",
|
||
|
|
rejectRequiresReason: true,
|
||
|
|
rejectReasonLabel: "What needs to change?",
|
||
|
|
declineReasonPlaceholder: "Optional: tell the agent what you'd change.",
|
||
|
|
detailsMarkdown: "The current plan document will be accepted as-is.",
|
||
|
|
supersedeOnUserComment: true,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
expect(parsed).toMatchObject({
|
||
|
|
kind: "request_confirmation",
|
||
|
|
continuationPolicy: "none",
|
||
|
|
payload: {
|
||
|
|
prompt: "Apply this plan?",
|
||
|
|
acceptLabel: "Apply",
|
||
|
|
rejectLabel: "Revise",
|
||
|
|
rejectRequiresReason: true,
|
||
|
|
rejectReasonLabel: "What needs to change?",
|
||
|
|
allowDeclineReason: true,
|
||
|
|
declineReasonPlaceholder: "Optional: tell the agent what you'd change.",
|
||
|
|
supersedeOnUserComment: true,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it("accepts issue document targets for request_confirmation interactions", () => {
|
||
|
|
const parsed = createIssueThreadInteractionSchema.parse({
|
||
|
|
kind: "request_confirmation",
|
||
|
|
continuationPolicy: "wake_assignee_on_accept",
|
||
|
|
payload: {
|
||
|
|
version: 1,
|
||
|
|
prompt: "Accept the latest plan revision?",
|
||
|
|
allowDeclineReason: false,
|
||
|
|
target: {
|
||
|
|
type: "issue_document",
|
||
|
|
issueId: "11111111-1111-4111-8111-111111111111",
|
||
|
|
documentId: "22222222-2222-4222-8222-222222222222",
|
||
|
|
key: "plan",
|
||
|
|
revisionId: "33333333-3333-4333-8333-333333333333",
|
||
|
|
revisionNumber: 2,
|
||
|
|
label: "Plan v2",
|
||
|
|
href: "/issues/PAP-123#document-plan",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
expect(parsed.kind).toBe("request_confirmation");
|
||
|
|
if (parsed.kind !== "request_confirmation") return;
|
||
|
|
expect(parsed.payload.target).toMatchObject({
|
||
|
|
type: "issue_document",
|
||
|
|
key: "plan",
|
||
|
|
revisionNumber: 2,
|
||
|
|
label: "Plan v2",
|
||
|
|
href: "/issues/PAP-123#document-plan",
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it("accepts custom targets for request_confirmation interactions", () => {
|
||
|
|
const parsed = createIssueThreadInteractionSchema.parse({
|
||
|
|
kind: "request_confirmation",
|
||
|
|
payload: {
|
||
|
|
version: 1,
|
||
|
|
prompt: "Proceed with the external checklist?",
|
||
|
|
target: {
|
||
|
|
type: "custom",
|
||
|
|
key: "external-checklist",
|
||
|
|
revisionId: "checklist-v1",
|
||
|
|
revisionNumber: 1,
|
||
|
|
label: "Checklist v1",
|
||
|
|
href: "https://example.com/checklist",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
expect(parsed.kind).toBe("request_confirmation");
|
||
|
|
if (parsed.kind !== "request_confirmation") return;
|
||
|
|
expect(parsed.payload.target).toMatchObject({
|
||
|
|
type: "custom",
|
||
|
|
key: "external-checklist",
|
||
|
|
label: "Checklist v1",
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it("rejects unsafe request_confirmation target hrefs", () => {
|
||
|
|
const base = {
|
||
|
|
kind: "request_confirmation",
|
||
|
|
payload: {
|
||
|
|
version: 1,
|
||
|
|
prompt: "Proceed?",
|
||
|
|
target: {
|
||
|
|
type: "custom",
|
||
|
|
key: "external-checklist",
|
||
|
|
revisionId: "checklist-v1",
|
||
|
|
label: "Checklist v1",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
} as const;
|
||
|
|
|
||
|
|
for (const href of ["javascript:alert(1)", "data:text/html,hi", "//evil.example/path"]) {
|
||
|
|
expect(() => createIssueThreadInteractionSchema.parse({
|
||
|
|
...base,
|
||
|
|
payload: {
|
||
|
|
...base.payload,
|
||
|
|
target: {
|
||
|
|
...base.payload.target,
|
||
|
|
href,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
})).toThrow("href must not use javascript:, data:, or protocol-relative URLs");
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|