/** * 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"; 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); }, })), }; }); vi.mock("@/lib/db-compat", () => ({ ensureDbCompatibility: vi.fn().mockResolvedValue(undefined), })); 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(), insightsCachedText: JSON.stringify({ dailyBriefing: BRIEFING }), insightsCachedLocale: cachedLocale, }, }); } 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(); // No provider is configured on this account, so the honest empty state // is "nothing will fill this"; with one it would be "preparing". expect(["preparing", "no-provider"]).toContain(body.briefingState); }); 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); }); });