test(documents): wire content-index mocks into the list route test

P2-1. The list GET now issues a documentContentIndex lookup and unions a
content-token OR branch when a search term is present, so the existing route
test gains the mock + the third OR branch in its where assertion. Fix the
backfill test's findFirst mock signature under strict tsc.
This commit is contained in:
Marc-André Bombeck committed 2026-07-07 18:41:09 +02:00
1 parent b210c743e9
commit 1f7807aead
16 files changed
+211 -55

No files matched your search

@@ -78,15 +78,21 @@ const SESSION_OK = {
};
const ctx = (id: string) => ({ params: Promise.resolve({ id }) });
const visionReq = (id: string) =>
new NextRequest(new URL(`http://localhost/api/documents/inbound/${id}/index`), {
method: "POST",
});
new NextRequest(
new URL(`http://localhost/api/documents/inbound/${id}/index`),
{
method: "POST",
},
);
const textReq = (id: string, text: string) =>
new NextRequest(new URL(`http://localhost/api/documents/inbound/${id}/index`), {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ mode: "text", text }),
});
new NextRequest(
new URL(`http://localhost/api/documents/inbound/${id}/index`),
{
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ mode: "text", text }),
},
);
beforeEach(() => {
vi.clearAllMocks();
@@ -220,7 +220,12 @@ async function handleVisionIndex(
images: vision.images,
documents: vision.documents,
});
await reconcileSpend(userId, reservation.reserved, reservation.reserved, dateKey);
await reconcileSpend(
userId,
reservation.reserved,
reservation.reserved,
dateKey,
);
const { tokenCount } = await upsertContentIndex({
userId,
documentId: document.id,
@@ -76,9 +76,12 @@ const SESSION_OK = {
};
const ctx = (id: string) => ({ params: Promise.resolve({ id }) });
const visionReq = (id: string) =>
new NextRequest(new URL(`http://localhost/api/documents/inbound/${id}/suggest`), {
method: "POST",
});
new NextRequest(
new URL(`http://localhost/api/documents/inbound/${id}/suggest`),
{
method: "POST",
},
);
beforeEach(() => {
vi.clearAllMocks();
@@ -141,7 +144,9 @@ describe("POST /api/documents/inbound/[id]/suggest", () => {
});
it("404s an unknown document", async () => {
vi.mocked(prisma.inboundDocument.findFirst).mockResolvedValue(null as never);
vi.mocked(prisma.inboundDocument.findFirst).mockResolvedValue(
null as never,
);
const res = await POST(visionReq("nope") as never, ctx("nope") as never);
expect(res.status).toBe(404);
});
@@ -179,7 +179,12 @@ async function handleTextSuggest(
providerType: pick.providerType,
ocrText: parsed.data.text,
});
await reconcileSpend(userId, reservation.reserved, reservation.reserved, dateKey);
await reconcileSpend(
userId,
reservation.reserved,
reservation.reserved,
dateKey,
);
return finishSuggest(request, userId, document.id, "text", suggestion);
} catch (err) {
await reconcileSpend(userId, reservation.reserved, 0, dateKey);
@@ -261,7 +266,12 @@ async function handleVisionSuggest(
images: vision.images,
documents: vision.documents,
});
await reconcileSpend(userId, reservation.reserved, reservation.reserved, dateKey);
await reconcileSpend(
userId,
reservation.reserved,
reservation.reserved,
dateKey,
);
return finishSuggest(request, userId, document.id, "vision", suggestion);
} catch (err) {
await reconcileSpend(userId, reservation.reserved, 0, dateKey);
@@ -69,7 +69,10 @@ import { prisma } from "@/lib/db";
import { getSession } from "@/lib/auth/session";
import { resolveVisionProvider } from "@/lib/labs/ocr-capability";
import { auditLog } from "@/lib/auth/audit";
import { runDocumentSummary, transcribeDocument } from "@/lib/documents/describe";
import {
runDocumentSummary,
transcribeDocument,
} from "@/lib/documents/describe";
const SESSION_OK = {
session: { id: "s1", expiresAt: new Date(Date.now() + 3_600_000) },
@@ -118,7 +121,10 @@ describe("POST /api/documents/inbound/[id]/summary", () => {
summary: "A blood panel from a lab.",
} as never);
const res = await POST(req("doc-1", "summary") as never, ctx("doc-1") as never);
const res = await POST(
req("doc-1", "summary") as never,
ctx("doc-1") as never,
);
expect(res.status).toBe(200);
const body = await res.json();
expect(body.data).toEqual({ summary: "A blood panel from a lab." });
@@ -134,7 +140,10 @@ describe("POST /api/documents/inbound/[id]/summary", () => {
text: "raw transcribed body",
} as never);
const res = await POST(req("doc-1", "text") as never, ctx("doc-1") as never);
const res = await POST(
req("doc-1", "text") as never,
ctx("doc-1") as never,
);
expect(res.status).toBe(200);
const body = await res.json();
expect(body.data).toEqual({ text: "raw transcribed body" });
@@ -146,7 +155,10 @@ describe("POST /api/documents/inbound/[id]/summary", () => {
chain: [],
pick: null,
} as never);
const res = await POST(req("doc-1", "summary") as never, ctx("doc-1") as never);
const res = await POST(
req("doc-1", "summary") as never,
ctx("doc-1") as never,
);
expect(res.status).toBe(422);
assertNoPersistence();
});
@@ -215,7 +215,12 @@ async function handleTextSummary(
providerType: pick.providerType,
ocrText: parsed.data.text,
});
await reconcileSpend(userId, reservation.reserved, reservation.reserved, dateKey);
await reconcileSpend(
userId,
reservation.reserved,
reservation.reserved,
dateKey,
);
return finishSummary(request, userId, document.id, "text", mode, result);
} catch (err) {
await reconcileSpend(userId, reservation.reserved, 0, dateKey);
@@ -286,7 +291,12 @@ async function handleVisionSummary(
images: vision.images,
documents: vision.documents,
});
await reconcileSpend(userId, reservation.reserved, reservation.reserved, dateKey);
await reconcileSpend(
userId,
reservation.reserved,
reservation.reserved,
dateKey,
);
return finishSummary(request, userId, document.id, "vision", mode, result);
} catch (err) {
await reconcileSpend(userId, reservation.reserved, 0, dateKey);
@@ -39,6 +39,9 @@ vi.mock("@/lib/db", () => {
documentConditionLink: {
findMany: vi.fn(),
},
documentContentIndex: {
findMany: vi.fn(),
},
illnessEpisode: {
findMany: vi.fn(),
},
@@ -155,6 +158,9 @@ beforeEach(() => {
vi.mocked(prisma.documentConditionLink.findMany).mockResolvedValue(
[] as never,
);
vi.mocked(prisma.documentContentIndex.findMany).mockResolvedValue(
[] as never,
);
vi.mocked(prisma.inboundDocument.findFirst).mockResolvedValue(null as never);
// No settings row / no user override → policy defaults (25 MiB / 1 GiB).
vi.mocked(prisma.appSettings.findUnique).mockResolvedValue(null as never);
@@ -346,9 +352,12 @@ describe("GET /api/documents/inbound (list)", () => {
expect(where.deletedAt).toBeNull();
expect(where.kind).toEqual({ in: ["LAB_RESULT", "IMAGING"] });
expect(where.conditionLinks).toEqual({ some: { episodeId: "ep-1" } });
// Title/filename substring branches, unioned with the blind content-token
// overlap (the third branch carries the HMAC'd query tokens for "panel").
expect(where.OR).toEqual([
{ title: { contains: "panel", mode: "insensitive" } },
{ filename: { contains: "panel", mode: "insensitive" } },
{ contentIndex: { is: { searchTokens: { hasSome: expect.any(Array) } } } },
]);
// limit+1 fetched for the has-more probe.
expect(arg.take).toBe(11);
+4 -4
View File
@@ -34,10 +34,10 @@ export const GET = apiHandler(async () => {
FROM inbound_documents
WHERE user_id = ${user.id}
`,
// Episodes that carry at least one LIVE document link — the filter
// bar's condition chips. Sourced here (not from the loaded corpus) so
// a chip exists even when its documents sit pages deep in the
// timeline; one indexed grouped query, no blobs.
// Episodes that carry at least one LIVE document link — the filter
// bar's condition chips. Sourced here (not from the loaded corpus) so
// a chip exists even when its documents sit pages deep in the
// timeline; one indexed grouped query, no blobs.
prisma.documentConditionLink.findMany({
where: { userId: user.id, document: { deletedAt: null } },
select: { episodeId: true, episode: { select: { label: true } } },