Files
PaperClipAI/server/src/__tests__/issue-execution-policy-persistence.test.ts
T
Devin FoleyandPaperclip 317ed367d4 Handle incomplete task policies and preserve review limits (#15593)
Apply the reviewed change for Handle incomplete task policies and preserve review limits.

Validation: local typecheck/build and focused regression tests, passing exact-head CI, Greptile 5/5, and independent source review. The PR records full-suite evidence and any local environment limitations.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-08 10:20:32 -07:00

46 lines
2.7 KiB
TypeScript

import { randomUUID } from "node:crypto";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { companies, createDb, issues } from "@paperclipai/db";
import { buildExecutionPolicy } from "../../../ui/src/lib/issue-execution-policy.ts";
import { normalizeIssueExecutionPolicy } from "../services/issue-execution-policy.js";
import { issueService } from "../services/issues.js";
import { getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase } from "./helpers/embedded-postgres.js";
const support = await getEmbeddedPostgresTestSupport();
(support.supported ? describe : describe.skip)("edited execution policy persistence", () => {
let temporary: Awaited<ReturnType<typeof startEmbeddedPostgresTestDatabase>>;
let db: ReturnType<typeof createDb>;
beforeAll(async () => {
temporary = await startEmbeddedPostgresTestDatabase("paperclip-policy-persistence-");
db = createDb(temporary.connectionString);
}, 30_000);
afterAll(async () => { await temporary?.cleanup(); });
it.each(["reviewer", "monitor"] as const)("retains the independent review limit after removing the last %s", async (removed) => {
const companyId = randomUUID();
const issueId = randomUUID();
const original = normalizeIssueExecutionPolicy({
maxReviewRounds: 4,
...(removed === "monitor"
? { monitor: { nextCheckAt: "2099-01-01T00:00:00.000Z" } }
: { stages: [{ type: "review", participants: [{ type: "user", userId: "board-reviewer" }] }] }),
});
await db.insert(companies).values({ id: companyId, name: "Policy fixture", issuePrefix: `P${companyId.slice(0, 6)}` });
await db.insert(issues).values({ id: issueId, companyId, title: "Review limit fixture", executionPolicy: original });
const edited = buildExecutionPolicy({ existingPolicy: original, reviewerValues: [], approverValues: [] })!;
// The monitor editor removes this key after building the unchanged stages.
const { monitor: _removedMonitor, ...withoutMonitor } = edited;
const normalized = normalizeIssueExecutionPolicy(removed === "monitor" ? withoutMonitor : edited);
const service = issueService(db);
await service.update(issueId, { executionPolicy: normalized, companyGuard: companyId });
const saved = await service.getById(issueId);
expect(saved?.executionPolicy).toEqual({ mode: "normal", commentRequired: true, stages: [], maxReviewRounds: 4 });
const restored = normalizeIssueExecutionPolicy(buildExecutionPolicy({
existingPolicy: saved?.executionPolicy,
reviewerValues: ["user:board-reviewer"], approverValues: [],
}));
expect(restored).toMatchObject({ maxReviewRounds: 4, stages: [{ type: "review" }] });
});
});