fix: recognize wrapped document write conflicts
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
45eabf6563
commit
40619e2e23
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue