Files
PaperClipAI/tests/runner-e2e/cursor-native-cases.test.ts
T

65 lines
5.5 KiB
TypeScript

import { cursorDeniedCommand, type CursorToolNotice } from "./cursor-native-evidence.js";
import { describe, expect, it } from "vitest";
import { cursorNativeCaseDesigns, cursorNativePrompt, hasDeliveredCursorNativeRequest, hasCursorPlanDecision, hasCursorDenialBoundary, CURSOR_DENIAL_SAMPLE_PHASES, hasExactCursorNativeResponse } from "./cursor-native-cases.js";
const proof = () => {
const wrap = (eventType: string, sourceSeq: number, payload: unknown) => ({ runId: "run", protocolSchemaVersion: 1, payload: { prpEvent: {
schema: "paperclip.prp.event.v1", schemaVersion: 1, runId: "run", turnId: "turn", eventType, sourceSeq, payload,
} } });
return [wrap("runtime_request.created", 1, { request: { requestId: "request", turnId: "turn", type: "input", status: "pending", origin: { adapter: "acpx-runtime", provider: "cursor", method: "cursor/ask_question" } } }),
wrap("runtime_request.resolved", 2, { requestId: "request", turnId: "turn", action: "submit" })];
};
const grade = (events: unknown[]) => hasDeliveredCursorNativeRequest({ events, runId: "run", turnId: "turn", requestId: "request", method: "cursor/ask_question", action: "submit" });
describe("prepared Cursor native qualification", () => {
it("has four fixed native-only designs and bounded prompts", () => {
expect(cursorNativeCaseDesigns).toHaveLength(4);
for (const design of cursorNativeCaseDesigns) expect(cursorNativePrompt(design.id, "test-nonce")).toContain("Do not substitute");
expect(() => cursorNativePrompt("native-write-deny-reconnect", "../escape")).toThrow();
});
it("requires correlated native origin and response delivery evidence", () => { expect(grade(proof())).toBe(true); expect(grade([])).toBe(false); });
it("rejects semantic substitutions and permission fallbacks", () => {
for (const method of ["request_human_input", "session/request_permission", "cursor/create_plan"]) {
const rows = proof(); (rows[0]!.payload.prpEvent.payload as any).request.origin.method = method; expect(grade(rows)).toBe(false);
}
});
it("rejects missing, duplicated and expired delivery", () => {
const rows = proof(); expect(grade(rows.slice(0, 1))).toBe(false); expect(grade([...rows, rows[1]])).toBe(false);
rows[1]!.payload.prpEvent.eventType = "runtime_request.expired"; expect(grade(rows)).toBe(false);
});
it("rejects mismatched run, turn and event ordering", () => {
let rows = proof(); rows[1]!.runId = "foreign"; expect(grade(rows)).toBe(false);
rows = proof(); rows[1]!.payload.prpEvent.turnId = "stale"; expect(grade(rows)).toBe(false);
rows = proof(); rows[1]!.payload.prpEvent.sourceSeq = 0; expect(grade(rows)).toBe(false);
});
});
it("binds plan decisions to the full revision and rejects stale answers", () => {
const id = `plan-${"a".repeat(64)}`;
const questionSet = { questions: [{ id }] };
const answer = { schema: "paperclip.question_response.v1", answers: { [id]: { selectedOptionIds: ["accept"] } } };
expect(hasCursorPlanDecision(questionSet, answer, "accept")).toBe(true);
expect(hasCursorPlanDecision({ questions: [{ id: `plan-${"b".repeat(64)}` }] }, answer, "accept")).toBe(false);
expect(hasCursorPlanDecision(questionSet, answer, "reject")).toBe(false);
});
it("requires supported denial choices, native IDs and complete no-effect boundaries", () => {
const input = { request: { requestId: "request", turnId: "turn", type: "permission", status: "pending", details: { toolCallId: "tool" },
origin: { adapter: "acpx-runtime", provider: "cursor", method: "session/request_permission" },
choices: [{ key: "accept" }, { key: "decline" }, { key: "cancel" }] }, expectedRequestId: "request", expectedToolCallId: "tool", path: "/fixture/denied.txt", runId: "run", turnId: "turn",
notices: ([{ stage: "tool", status: "pending" }, { stage: "permission_requested", requestId: "request", declineOffered: true }, { stage: "permission_delivered", requestId: "request", outcome: "reject_once" }, { stage: "tool", status: "failed" }].map((row, index) => ({ ...row, seq: index + 1, runId: "run", sessionId: "session", turnId: "turn", toolCallId: "tool", operation: "execute", commandSha256: cursorDeniedCommand("/fixture/denied.txt").commandSha256 })) as CursorToolNotice[]),
samples: CURSOR_DENIAL_SAMPLE_PHASES.map((phase, observedAt) => ({ phase, observedAt, absent: true, path: "/fixture/denied.txt" })) };
expect(hasCursorDenialBoundary(input)).toBe(true);
expect(hasCursorDenialBoundary({ ...input, samples: input.samples.slice(1) })).toBe(false);
expect(hasCursorDenialBoundary({ ...input, expectedToolCallId: "foreign" })).toBe(false);
input.samples[2]!.absent = false; expect(hasCursorDenialBoundary(input)).toBe(false); input.samples[2]!.absent = true;
input.request.choices.push({ key: "accept_for_session" }); expect(hasCursorDenialBoundary(input)).toBe(false);
});
it("requires exact delivered answer bytes rather than a saved or different answer", () => {
const rows = proof(); const response = { schema: "paperclip.question_response.v1", answers: { q: { selectedOptionIds: ["chosen"] } } };
(rows[1]!.payload.prpEvent.payload as any).response = response;
const input = { events: rows, runId: "run", turnId: "turn", requestId: "request", method: "cursor/ask_question" as const, action: "submit" as const, response };
expect(hasExactCursorNativeResponse(input)).toBe(true);
expect(hasExactCursorNativeResponse({ ...input, response: { ...response, answers: { q: { selectedOptionIds: ["other"] } } } })).toBe(false);
expect(hasExactCursorNativeResponse({ ...input, events: rows.slice(0, 1) })).toBe(false);
});