Add synthetic dry-run sync regression test

This commit is contained in:
Paperclip Bot 2026-06-02 10:56:57 +00:00
parent 6e7c092484
commit 6d323393a3

View file

@ -1,3 +1,5 @@
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { describe, expect, it, vi } from "vitest"; import { describe, expect, it, vi } from "vitest";
import type { Issue } from "@paperclipai/shared"; import type { Issue } from "@paperclipai/shared";
import { ATTACHMENT_NOTE } from "../src/constants.js"; import { ATTACHMENT_NOTE } from "../src/constants.js";
@ -76,6 +78,11 @@ function buildIssue(overrides: Partial<Issue> = {}): Issue {
} as Issue; } as Issue;
} }
function readExampleIssue(name: string): Issue {
const filePath = resolve(import.meta.dirname, "..", "examples", name);
return JSON.parse(readFileSync(filePath, "utf8")) as Issue;
}
describe("paperclip issue sync", () => { describe("paperclip issue sync", () => {
it("selects issues by the configured sync label", () => { it("selects issues by the configured sync label", () => {
const issue = buildIssue(); const issue = buildIssue();
@ -325,4 +332,81 @@ describe("paperclip issue sync", () => {
expect(retryComplete).toHaveBeenCalledOnce(); expect(retryComplete).toHaveBeenCalledOnce();
expect(retryQueueManualReview).not.toHaveBeenCalled(); expect(retryQueueManualReview).not.toHaveBeenCalled();
}); });
it("reproduces the synthetic dry-run payload and reuses the stored mapping on retry", async () => {
const issue = readExampleIssue("first-dry-run-paperclip-issue.json");
const payload = buildForgejoIssuePayload(issue);
const createdRemoteIssue = {
id: 401,
number: 77,
url: "https://forgejo.example/acme/repo/issues/77",
apiUrl: "https://forgejo.example/api/v1/repos/acme/repo/issues/77"
};
const createRemoteIssue = vi.fn(async () => createdRemoteIssue);
const complete = vi.fn(async () => undefined);
expect(payload.title).toBe("[PRIA-DRY-1] Synthetic dry-run: reproduce outbound issue creation");
expect(payload.body).toContain("A controlled dry-run payload for the first Forgejo sync rehearsal.");
expect(payload.body).toContain(ATTACHMENT_NOTE);
expect(payload.body).toContain("trace.log | text/plain | 2.0 KiB");
expect(payload.body).toContain("<!-- paperclip-sync:company-dry-run:issue-dry-run-1 -->");
const firstResult = await syncIssueToForgejo(
{
config: { get: async () => ({ forgejoOwner: "acme", forgejoRepo: "repo" }) },
activity: { log: vi.fn(async () => undefined) }
} as never,
issue,
{
reserve: async () => ({ kind: "reserved" as const }),
createRemoteIssue,
recordRemote: vi.fn(async () => undefined),
complete,
fail: vi.fn(async () => undefined),
failAfterRemote: vi.fn(async () => undefined),
queueManualReview: vi.fn(async () => undefined)
}
);
const retryResult = await syncIssueToForgejo(
{
config: { get: async () => ({ forgejoOwner: "acme", forgejoRepo: "repo" }) },
activity: { log: vi.fn(async () => undefined) }
} as never,
issue,
{
reserve: async () => ({
kind: "existing" as const,
mapping: {
companyId: "company-dry-run",
paperclipIssueId: "issue-dry-run-1",
repoOwner: "acme",
repoName: "repo",
dedupeKey: "paperclip-issue:company-dry-run:issue-dry-run-1",
sourceTitle: payload.title,
sourceBody: payload.body,
attachmentMetadata: [],
manualReviewRequired: false,
reviewReasonCode: null,
forgejoIssueId: createdRemoteIssue.id,
forgejoIssueNumber: createdRemoteIssue.number,
forgejoIssueUrl: createdRemoteIssue.url,
forgejoApiUrl: createdRemoteIssue.apiUrl,
syncStatus: "synced",
lastError: null
}
}),
createRemoteIssue,
complete,
fail: vi.fn(async () => undefined),
failAfterRemote: vi.fn(async () => undefined),
queueManualReview: vi.fn(async () => undefined)
}
);
expect(firstResult).toBe("created");
expect(retryResult).toBe("existing");
expect(createRemoteIssue).toHaveBeenCalledTimes(1);
expect(complete).toHaveBeenCalledTimes(2);
});
}); });