Files
paperclip/cli/src/__tests__/worktree-merge-history.test.ts
2026-03-20 17:01:52 -05:00

393 lines
13 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { buildWorktreeMergePlan, parseWorktreeMergeScopes } from "../commands/worktree-merge-history-lib.js";
function makeIssue(overrides: Record<string, unknown> = {}) {
return {
id: "issue-1",
companyId: "company-1",
projectId: null,
projectWorkspaceId: null,
goalId: "goal-1",
parentId: null,
title: "Issue",
description: null,
status: "todo",
priority: "medium",
assigneeAgentId: null,
assigneeUserId: null,
checkoutRunId: null,
executionRunId: null,
executionAgentNameKey: null,
executionLockedAt: null,
createdByAgentId: null,
createdByUserId: "local-board",
issueNumber: 1,
identifier: "PAP-1",
requestDepth: 0,
billingCode: null,
assigneeAdapterOverrides: null,
executionWorkspaceId: null,
executionWorkspacePreference: null,
executionWorkspaceSettings: null,
startedAt: null,
completedAt: null,
cancelledAt: null,
hiddenAt: null,
createdAt: new Date("2026-03-20T00:00:00.000Z"),
updatedAt: new Date("2026-03-20T00:00:00.000Z"),
...overrides,
} as any;
}
function makeComment(overrides: Record<string, unknown> = {}) {
return {
id: "comment-1",
companyId: "company-1",
issueId: "issue-1",
authorAgentId: null,
authorUserId: "local-board",
body: "hello",
createdAt: new Date("2026-03-20T00:00:00.000Z"),
updatedAt: new Date("2026-03-20T00:00:00.000Z"),
...overrides,
} as any;
}
function makeIssueDocument(overrides: Record<string, unknown> = {}) {
return {
id: "issue-document-1",
companyId: "company-1",
issueId: "issue-1",
documentId: "document-1",
key: "plan",
linkCreatedAt: new Date("2026-03-20T00:00:00.000Z"),
linkUpdatedAt: new Date("2026-03-20T00:00:00.000Z"),
title: "Plan",
format: "markdown",
latestBody: "# Plan",
latestRevisionId: "revision-1",
latestRevisionNumber: 1,
createdByAgentId: null,
createdByUserId: "local-board",
updatedByAgentId: null,
updatedByUserId: "local-board",
documentCreatedAt: new Date("2026-03-20T00:00:00.000Z"),
documentUpdatedAt: new Date("2026-03-20T00:00:00.000Z"),
...overrides,
} as any;
}
function makeDocumentRevision(overrides: Record<string, unknown> = {}) {
return {
id: "revision-1",
companyId: "company-1",
documentId: "document-1",
revisionNumber: 1,
body: "# Plan",
changeSummary: null,
createdByAgentId: null,
createdByUserId: "local-board",
createdAt: new Date("2026-03-20T00:00:00.000Z"),
...overrides,
} as any;
}
function makeAttachment(overrides: Record<string, unknown> = {}) {
return {
id: "attachment-1",
companyId: "company-1",
issueId: "issue-1",
issueCommentId: null,
assetId: "asset-1",
provider: "local_disk",
objectKey: "company-1/issues/issue-1/2026/03/20/asset.png",
contentType: "image/png",
byteSize: 12,
sha256: "deadbeef",
originalFilename: "asset.png",
createdByAgentId: null,
createdByUserId: "local-board",
assetCreatedAt: new Date("2026-03-20T00:00:00.000Z"),
assetUpdatedAt: new Date("2026-03-20T00:00:00.000Z"),
attachmentCreatedAt: new Date("2026-03-20T00:00:00.000Z"),
attachmentUpdatedAt: new Date("2026-03-20T00:00:00.000Z"),
...overrides,
} as any;
}
describe("worktree merge history planner", () => {
it("parses default scopes", () => {
expect(parseWorktreeMergeScopes(undefined)).toEqual(["issues", "comments"]);
expect(parseWorktreeMergeScopes("issues")).toEqual(["issues"]);
});
it("dedupes nested worktree issues by preserved source uuid", () => {
const sharedIssue = makeIssue({ id: "issue-a", identifier: "PAP-10", title: "Shared" });
const branchOneIssue = makeIssue({
id: "issue-b",
identifier: "PAP-22",
title: "Branch one issue",
createdAt: new Date("2026-03-20T01:00:00.000Z"),
});
const branchTwoIssue = makeIssue({
id: "issue-c",
identifier: "PAP-23",
title: "Branch two issue",
createdAt: new Date("2026-03-20T02:00:00.000Z"),
});
const plan = buildWorktreeMergePlan({
companyId: "company-1",
companyName: "Paperclip",
issuePrefix: "PAP",
previewIssueCounterStart: 500,
scopes: ["issues", "comments"],
sourceIssues: [sharedIssue, branchOneIssue, branchTwoIssue],
targetIssues: [sharedIssue, branchOneIssue],
sourceComments: [],
targetComments: [],
targetAgents: [],
targetProjects: [],
targetProjectWorkspaces: [],
targetGoals: [{ id: "goal-1" }] as any,
});
expect(plan.counts.issuesToInsert).toBe(1);
expect(plan.issuePlans.filter((item) => item.action === "insert").map((item) => item.source.id)).toEqual(["issue-c"]);
expect(plan.issuePlans.find((item) => item.source.id === "issue-c" && item.action === "insert")).toMatchObject({
previewIdentifier: "PAP-501",
});
});
it("clears missing references and coerces in_progress without an assignee", () => {
const plan = buildWorktreeMergePlan({
companyId: "company-1",
companyName: "Paperclip",
issuePrefix: "PAP",
previewIssueCounterStart: 10,
scopes: ["issues"],
sourceIssues: [
makeIssue({
id: "issue-x",
identifier: "PAP-99",
status: "in_progress",
assigneeAgentId: "agent-missing",
projectId: "project-missing",
projectWorkspaceId: "workspace-missing",
goalId: "goal-missing",
}),
],
targetIssues: [],
sourceComments: [],
targetComments: [],
targetAgents: [],
targetProjects: [],
targetProjectWorkspaces: [],
targetGoals: [],
});
const insert = plan.issuePlans[0] as any;
expect(insert.targetStatus).toBe("todo");
expect(insert.targetAssigneeAgentId).toBeNull();
expect(insert.targetProjectId).toBeNull();
expect(insert.targetProjectWorkspaceId).toBeNull();
expect(insert.targetGoalId).toBeNull();
expect(insert.adjustments).toEqual([
"clear_assignee_agent",
"clear_project",
"clear_project_workspace",
"clear_goal",
"coerce_in_progress_to_todo",
]);
});
it("applies an explicit project mapping override instead of clearing the project", () => {
const plan = buildWorktreeMergePlan({
companyId: "company-1",
companyName: "Paperclip",
issuePrefix: "PAP",
previewIssueCounterStart: 10,
scopes: ["issues"],
sourceIssues: [
makeIssue({
id: "issue-project-map",
identifier: "PAP-77",
projectId: "source-project-1",
projectWorkspaceId: "source-workspace-1",
}),
],
targetIssues: [],
sourceComments: [],
targetComments: [],
targetAgents: [],
targetProjects: [{ id: "target-project-1", name: "Mapped project", status: "in_progress" }] as any,
targetProjectWorkspaces: [],
targetGoals: [{ id: "goal-1" }] as any,
projectIdOverrides: {
"source-project-1": "target-project-1",
},
});
const insert = plan.issuePlans[0] as any;
expect(insert.targetProjectId).toBe("target-project-1");
expect(insert.projectResolution).toBe("mapped");
expect(insert.mappedProjectName).toBe("Mapped project");
expect(insert.targetProjectWorkspaceId).toBeNull();
expect(insert.adjustments).toEqual(["clear_project_workspace"]);
});
it("imports comments onto shared or newly imported issues while skipping existing comments", () => {
const sharedIssue = makeIssue({ id: "issue-a", identifier: "PAP-10" });
const newIssue = makeIssue({
id: "issue-b",
identifier: "PAP-11",
createdAt: new Date("2026-03-20T01:00:00.000Z"),
});
const existingComment = makeComment({ id: "comment-existing", issueId: "issue-a" });
const sharedIssueComment = makeComment({ id: "comment-shared", issueId: "issue-a" });
const newIssueComment = makeComment({
id: "comment-new-issue",
issueId: "issue-b",
authorAgentId: "missing-agent",
createdAt: new Date("2026-03-20T01:05:00.000Z"),
});
const plan = buildWorktreeMergePlan({
companyId: "company-1",
companyName: "Paperclip",
issuePrefix: "PAP",
previewIssueCounterStart: 10,
scopes: ["issues", "comments"],
sourceIssues: [sharedIssue, newIssue],
targetIssues: [sharedIssue],
sourceComments: [existingComment, sharedIssueComment, newIssueComment],
targetComments: [existingComment],
targetAgents: [],
targetProjects: [],
targetProjectWorkspaces: [],
targetGoals: [{ id: "goal-1" }] as any,
});
expect(plan.counts.commentsToInsert).toBe(2);
expect(plan.counts.commentsExisting).toBe(1);
expect(plan.commentPlans.filter((item) => item.action === "insert").map((item) => item.source.id)).toEqual([
"comment-shared",
"comment-new-issue",
]);
expect(plan.adjustments.clear_author_agent).toBe(1);
});
it("merges document revisions onto an existing shared document and renumbers conflicts", () => {
const sharedIssue = makeIssue({ id: "issue-a", identifier: "PAP-10" });
const sourceDocument = makeIssueDocument({
issueId: "issue-a",
documentId: "document-a",
latestBody: "# Branch plan",
latestRevisionId: "revision-branch-2",
latestRevisionNumber: 2,
documentUpdatedAt: new Date("2026-03-20T02:00:00.000Z"),
linkUpdatedAt: new Date("2026-03-20T02:00:00.000Z"),
});
const targetDocument = makeIssueDocument({
issueId: "issue-a",
documentId: "document-a",
latestBody: "# Main plan",
latestRevisionId: "revision-main-2",
latestRevisionNumber: 2,
documentUpdatedAt: new Date("2026-03-20T01:00:00.000Z"),
linkUpdatedAt: new Date("2026-03-20T01:00:00.000Z"),
});
const sourceRevisionOne = makeDocumentRevision({ documentId: "document-a", id: "revision-1" });
const sourceRevisionTwo = makeDocumentRevision({
documentId: "document-a",
id: "revision-branch-2",
revisionNumber: 2,
body: "# Branch plan",
createdAt: new Date("2026-03-20T02:00:00.000Z"),
});
const targetRevisionOne = makeDocumentRevision({ documentId: "document-a", id: "revision-1" });
const targetRevisionTwo = makeDocumentRevision({
documentId: "document-a",
id: "revision-main-2",
revisionNumber: 2,
body: "# Main plan",
createdAt: new Date("2026-03-20T01:00:00.000Z"),
});
const plan = buildWorktreeMergePlan({
companyId: "company-1",
companyName: "Paperclip",
issuePrefix: "PAP",
previewIssueCounterStart: 10,
scopes: ["issues", "comments"],
sourceIssues: [sharedIssue],
targetIssues: [sharedIssue],
sourceComments: [],
targetComments: [],
sourceDocuments: [sourceDocument],
targetDocuments: [targetDocument],
sourceDocumentRevisions: [sourceRevisionOne, sourceRevisionTwo],
targetDocumentRevisions: [targetRevisionOne, targetRevisionTwo],
sourceAttachments: [],
targetAttachments: [],
targetAgents: [],
targetProjects: [],
targetProjectWorkspaces: [],
targetGoals: [{ id: "goal-1" }] as any,
});
expect(plan.counts.documentsToMerge).toBe(1);
expect(plan.counts.documentRevisionsToInsert).toBe(1);
expect(plan.documentPlans[0]).toMatchObject({
action: "merge_existing",
latestRevisionId: "revision-branch-2",
latestRevisionNumber: 3,
});
const mergePlan = plan.documentPlans[0] as any;
expect(mergePlan.revisionsToInsert).toHaveLength(1);
expect(mergePlan.revisionsToInsert[0]).toMatchObject({
source: { id: "revision-branch-2" },
targetRevisionNumber: 3,
});
});
it("imports attachments while clearing missing comment and author references", () => {
const sharedIssue = makeIssue({ id: "issue-a", identifier: "PAP-10" });
const attachment = makeAttachment({
issueId: "issue-a",
issueCommentId: "comment-missing",
createdByAgentId: "agent-missing",
});
const plan = buildWorktreeMergePlan({
companyId: "company-1",
companyName: "Paperclip",
issuePrefix: "PAP",
previewIssueCounterStart: 10,
scopes: ["issues"],
sourceIssues: [sharedIssue],
targetIssues: [sharedIssue],
sourceComments: [],
targetComments: [],
sourceDocuments: [],
targetDocuments: [],
sourceDocumentRevisions: [],
targetDocumentRevisions: [],
sourceAttachments: [attachment],
targetAttachments: [],
targetAgents: [],
targetProjects: [],
targetProjectWorkspaces: [],
targetGoals: [{ id: "goal-1" }] as any,
});
expect(plan.counts.attachmentsToInsert).toBe(1);
expect(plan.adjustments.clear_attachment_agent).toBe(1);
expect(plan.attachmentPlans[0]).toMatchObject({
action: "insert",
targetIssueCommentId: null,
targetCreatedByAgentId: null,
});
});
});