Files
HealthLog/tests/integration/briefing-cache-locale.test.ts
T
Marc-André Bombeck b6da360856 refactor(db): drop the runtime schema mirror
ensureDbCompatibility ran ALTER TABLE / CREATE TABLE IF NOT EXISTS on the
login, register, token and session paths, mirroring columns every
migration under prisma/migrations already creates. The entrypoint applies
those migrations before the server listens and stops the boot when one
fails, so the mirror never had work to do, and DDL on a request path
needs the table's owner: a connection that may only read and write rows
fails every login on it. Drop the module, its call sites and the mocks
that stubbed it out.
2026-11-03 20:32:35 +01:00

124 lines
4.3 KiB
TypeScript

/**
* The comprehensive-briefing cache is one slot per user. When it holds prose
* in a language other than the reader's, the dashboard snapshot serves no
* briefing text at all rather than the wrong language.
*
* This runs the real Prisma path end to end: the cache row is written with
* its locale tag through the client, and the snapshot is read through the
* same `readDashboardSnapshotCached` the dashboard route and the Today
* digest use, with the reader's locale resolved from the cookie the client
* sets when the user switches language.
*/
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { cookieJar, headerJar } from "./mock-next-headers";
import { getPrismaClient, truncateAllTables } from "./setup";
import { sealInsightsCachedText } from "@/lib/insights/cached-text-cipher";
vi.mock("next/headers", async () => {
const { cookieJar, headerJar } = await import("./mock-next-headers");
return {
headers: vi.fn(async () => ({
get: (name: string) => headerJar.get(name.toLowerCase()) ?? null,
})),
cookies: vi.fn(async () => ({
get: (name: string) => {
const value = cookieJar.get(name);
return value ? { name, value } : undefined;
},
set: (name: string, value: string) => {
cookieJar.set(name, value);
},
delete: (name: string) => {
cookieJar.delete(name);
},
})),
};
});
const BRIEFING = {
greeting: "Good morning",
paragraph: "Your readings this week stayed inside their usual range.",
keyFindings: [],
};
beforeEach(async () => {
await truncateAllTables(getPrismaClient());
cookieJar.clear();
headerJar.clear();
});
afterEach(() => {
vi.restoreAllMocks();
});
async function seedUserWithCachedBriefing(cachedLocale: string | null) {
const prisma = getPrismaClient();
return prisma.user.create({
data: {
username: "briefing-user",
email: "briefing@example.test",
role: "USER",
// A German-language account: the stored preference is what the
// snapshot read resolves the reader's locale from.
locale: "de",
insightsCachedAt: new Date(),
// Generated now: only text written today is served as today's read.
insightsCachedTextEncrypted: sealInsightsCachedText(
JSON.stringify({
dailyBriefing: BRIEFING,
briefingGeneratedAt: new Date().toISOString(),
}),
),
insightsCachedLocale: cachedLocale,
// The briefing is shown only while its AI capability is available, so
// the account has its own provider (presence only) and a receipt.
aiProvider: "ANTHROPIC",
aiModel: "claude-sonnet-4-6",
aiAnthropicKeyEncrypted: "v1:presence-only",
consentReceipts: {
create: { kind: "ai_full", artefact: "test", signedAt: new Date() },
},
},
});
}
describe("dashboard snapshot — briefing cache written in another language", () => {
it("serves NO briefing prose to a 'de' reader from a cache tagged 'en'", async () => {
const user = await seedUserWithCachedBriefing("en");
const { readDashboardSnapshotCached } =
await import("@/lib/dashboard/snapshot-read");
const { body, locale } = await readDashboardSnapshotCached(user);
expect(locale).toBe("de");
expect(body.briefing).toBeNull();
expect(body.briefingStale).toBe(false);
expect(body.briefingUpdatedAt).toBeNull();
// A provider is configured, so a warm pass will refill the slot.
expect(body.briefingState).toBe("preparing");
});
it("serves the briefing when the tag matches the reader", async () => {
const user = await seedUserWithCachedBriefing("de");
const { readDashboardSnapshotCached } =
await import("@/lib/dashboard/snapshot-read");
const { body } = await readDashboardSnapshotCached(user);
expect(body.briefingState).toBe("ready");
expect(body.briefing?.paragraph).toBe(BRIEFING.paragraph);
});
it("serves an untagged row written before the tag existed", async () => {
const user = await seedUserWithCachedBriefing(null);
const { readDashboardSnapshotCached } =
await import("@/lib/dashboard/snapshot-read");
const { body } = await readDashboardSnapshotCached(user);
expect(body.briefingState).toBe("ready");
expect(body.briefing?.paragraph).toBe(BRIEFING.paragraph);
});
});