src/features/reviews/storage-errors.test.ts
Linked repository file from this build.
import * as inboxStore from "@/lib/db/inbox-store";
import { createImportTemplate } from "@/programs/snap-negative/review-template";
import { readFileSync } from "node:fs";
import { beforeEach, afterEach, expect, spyOn, test } from "bun:test";
import { protectReviewStore, ReviewStoreUnavailableError, type ReviewStore } from "./review-store";
import { reviewStore } from "@/lib/db/review-store";
import { GET as load, POST as save } from "@/app/api/reviews/[reviewKey]/route";
import { POST as promote } from "@/app/api/imports/snap-negative/route";
import * as auth from "@/lib/auth/server";
import { demoActor } from "@/lib/auth/roster";
const mocks: ReturnType<typeof spyOn>[] = [];
beforeEach(() => {
mocks.push(spyOn(auth, "getActor").mockResolvedValue(demoActor("admin")));
});
afterEach(() => {
for (const mock of mocks.splice(0)) mock.mockRestore();
});
const body = {
version: 0,
requestId: crypto.randomUUID(),
commands: [
{
command: { type: "note", body: "Synthetic note" },
id: crypto.randomUUID(),
},
],
};
function request(data: unknown) {
return new Request("http://localhost/api/reviews/60001", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
}
const context = { params: Promise.resolve({ reviewKey: createImportTemplate().id }) };
test("storage boundary classifies network and database failures without exposing details", async () => {
const fail = async () => {
throw new Error("private database details");
};
const store = protectReviewStore({
load: fail,
findBusiness: fail,
findRequest: fail,
append: fail,
list: fail,
} as ReviewStore);
for (const operation of [
() => store.load("60001"),
() => store.findRequest("id"),
() => store.list(),
]) {
await expect(operation()).rejects.toBeInstanceOf(ReviewStoreUnavailableError);
await expect(operation()).rejects.toThrow("Review storage is unavailable. Please retry.");
}
});
test("review POST returns 503 for storage outages and 400 for malformed commands", async () => {
mocks.push(
spyOn(reviewStore, "load").mockRejectedValue(
new ReviewStoreUnavailableError(new Error("private details")),
),
);
const outage = await save(request(body), context);
expect(outage.status).toBe(503);
expect(await outage.json()).toEqual({ error: "Review storage is unavailable. Please retry." });
expect((await save(request({ ...body, commands: [] }), context)).status).toBe(400);
});
test("intake POST returns 503 for storage outages", async () => {
mocks.push(
spyOn(reviewStore, "findRequest").mockRejectedValue(
new ReviewStoreUnavailableError(new Error("private details")),
),
);
const outage = await promote(
request({
files: {
closedSuspended: readFileSync("public/fixtures/snap-negative/closed-suspended.txt", "utf8"),
rejected: readFileSync("public/fixtures/snap-negative/rejected.txt", "utf8"),
},
identity: { program: "SNAP", fiscalYear: 2026, reviewNumber: "60002" },
expectedVersion: null,
requestId: crypto.randomUUID(),
}),
{},
);
expect(outage.status).toBe(503);
expect(await outage.json()).toEqual({ error: "Review storage is unavailable. Please retry." });
});
test("intake rejects an unqualified number present in two fiscal years with 400", async () => {
const files = {
closedSuspended: readFileSync("public/fixtures/snap-negative/closed-suspended.txt", "utf8"),
rejected: readFileSync("public/fixtures/snap-negative/rejected.txt", "utf8"),
};
const response = await promote(
request({
files: {
closedSuspended:
files.closedSuspended + "\n" + files.closedSuspended.replaceAll("|2026|", "|2025|"),
rejected: files.rejected,
},
reviewNumber: "60002",
expectedVersion: null,
requestId: crypto.randomUUID(),
}),
{},
);
expect(response.status).toBe(400);
});
test("opening a UUID touches the same opaque identity", async () => {
const review = { ...createImportTemplate(), sampleMonth: "2025-09" };
const stored = {
review,
version: 3,
requestId: "previous-year",
actor: demoActor("admin"),
savedAt: "2025-09-01T12:00:00Z",
};
const touched = spyOn(inboxStore, "touchReview").mockResolvedValue(undefined);
mocks.push(spyOn(reviewStore, "load").mockResolvedValue(stored), touched);
expect((await load(request(body), context)).status).toBe(200);
expect(touched).toHaveBeenCalledWith(demoActor("admin"), review.id, 3);
});
test("opening a missing earlier-year demo number does not initialize a review", async () => {
const append = spyOn(reviewStore, "append").mockResolvedValue(undefined);
mocks.push(spyOn(reviewStore, "load").mockResolvedValue(undefined), append);
const response = await load(request(body), {
params: Promise.resolve({ reviewKey: "2025:60001" }),
});
expect(response.status).toBe(404);
expect(append).not.toHaveBeenCalled();
});