evOo-local/apps/api/src/serializable-retry.test.ts
cynarAI 6c7eed7580 Initial clean evOo-local from f00577b
Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline
(Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/*
packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
2026-09-20 17:15:16 +02:00

64 lines
2.3 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { withSerializableRetry } from "./serializable-retry.js";
function serializationConflict() {
return Object.assign(new Error("serialization conflict"), { code: "P2034" });
}
function adapterConflict(originalCode: "40001" | "40P01") {
return Object.assign(new Error("database conflict"), {
code: "P2039",
meta: { driverAdapterError: { cause: { originalCode } } },
});
}
describe("withSerializableRetry", () => {
it("retries serialization conflicts and returns the successful result", async () => {
const operation = vi
.fn<() => Promise<string>>()
.mockRejectedValueOnce(serializationConflict())
.mockRejectedValueOnce(serializationConflict())
.mockResolvedValue("ok");
await expect(withSerializableRetry(operation)).resolves.toBe("ok");
expect(operation).toHaveBeenCalledTimes(3);
});
it.each(["40001", "40P01"] as const)(
"retries driver-adapter transaction conflict %s",
async (databaseCode) => {
const operation = vi
.fn<() => Promise<string>>()
.mockRejectedValueOnce(adapterConflict(databaseCode))
.mockResolvedValue("ok");
await expect(withSerializableRetry(operation)).resolves.toBe("ok");
expect(operation).toHaveBeenCalledTimes(2);
},
);
it("rethrows non-serialization errors without retrying", async () => {
const error = new Error("unrelated failure");
const operation = vi.fn<() => Promise<never>>().mockRejectedValue(error);
await expect(withSerializableRetry(operation)).rejects.toBe(error);
expect(operation).toHaveBeenCalledTimes(1);
});
it("does not retry unrelated driver-adapter errors", async () => {
const error = Object.assign(new Error("external connector error"), { code: "P2039" });
const operation = vi.fn<() => Promise<never>>().mockRejectedValue(error);
await expect(withSerializableRetry(operation)).rejects.toBe(error);
expect(operation).toHaveBeenCalledTimes(1);
});
it("rethrows the final serialization conflict after the bounded retry limit", async () => {
const error = serializationConflict();
const operation = vi.fn<() => Promise<never>>().mockRejectedValue(error);
await expect(withSerializableRetry(operation)).rejects.toBe(error);
expect(operation).toHaveBeenCalledTimes(3);
});
});