fix: recognize wrapped document write conflicts

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Dotta 2026-09-08 15:47:26 -05:00
parent 45eabf6563
commit 40619e2e23
2 changed files with 37 additions and 6 deletions

View File

@ -1,5 +1,6 @@
import { describe, expect, it } from "vitest";
import { extractLegacyPlanBody } from "../services/documents.js";
import type { Db } from "@paperclipai/db";
import { describe, expect, it, vi } from "vitest";
import { documentService, extractLegacyPlanBody } from "../services/documents.js";
describe("extractLegacyPlanBody", () => {
it("returns null when no plan block exists", () => {
@ -27,3 +28,36 @@ intro
expect(extractLegacyPlanBody("<plan> </plan>")).toBeNull();
});
});
describe("document write conflicts", () => {
function failingService(error: Error) {
const db = {
select: () => ({
from: () => ({
where: () => Promise.resolve([{ id: "issue", companyId: "company" }]),
}),
}),
transaction: vi.fn().mockRejectedValue(error),
};
return documentService(db as unknown as Db);
}
const input = { issueId: "issue", key: "review", format: "markdown", body: "Review" };
it("reports a wrapped unique violation as a retryable document conflict", async () => {
const driverError = Object.assign(new Error("duplicate key"), { code: "23505" });
const queryError = new Error("Failed query: insert into issue_documents", { cause: driverError });
await expect(failingService(queryError).upsertIssueDocument(input)).rejects.toMatchObject({
status: 409,
message: "Document key already exists on this issue",
});
});
it("does not disguise other database errors as document conflicts", async () => {
const driverError = Object.assign(new Error("connection unavailable"), { code: "08006" });
const queryError = new Error("Failed query", { cause: driverError });
await expect(failingService(queryError).upsertIssueDocument(input)).rejects.toBe(queryError);
});
});

View File

@ -3,6 +3,7 @@ import { and, asc, desc, eq } from "drizzle-orm";
import type { Db } from "@paperclipai/db";
import { documentRevisions, documents, issueDocuments, issues } from "@paperclipai/db";
import { isSystemIssueDocumentKey, issueDocumentKeySchema } from "@paperclipai/shared";
import { isUniqueViolation } from "../db-errors.js";
import { conflict, notFound, unprocessable } from "../errors.js";
import { insertRowsInChunks } from "./batch-insert.js";
import type { ImportIssueDocumentRow } from "./import-write-types.js";
@ -16,10 +17,6 @@ function normalizeDocumentKey(key: string) {
return parsed.data;
}
function isUniqueViolation(error: unknown): boolean {
return !!error && typeof error === "object" && "code" in error && (error as { code?: string }).code === "23505";
}
function nextAvailableDocumentKey(sourceKey: string, existingKeys: string[]) {
const usedKeys = new Set(existingKeys);
for (let index = 2; index < 1000; index += 1) {