import { describe, expect, it, vi } from "vitest"; import { isDuplicateEntryError, persistArticleReads, readArticleIdsFromDb } from "./db"; function createReadDb(existingIds: number[] = []) { const insertValues = vi.fn().mockResolvedValue(undefined); const insert = vi.fn(() => ({ values: insertValues })); const where = vi.fn().mockResolvedValue(existingIds.map((articleId) => ({ articleId }))); const from = vi.fn(() => ({ where })); const select = vi.fn(() => ({ from })); return { db: { select, insert }, insertValues }; } describe("isDuplicateEntryError", () => { it("identifie les erreurs de contrainte unique MySQL", () => { expect(isDuplicateEntryError({ code: "ER_DUP_ENTRY" })).toBe(true); expect(isDuplicateEntryError({ cause: { code: "ER_DUP_ENTRY" } })).toBe(true); }); it("ne masque jamais une erreur SQL non liée à un doublon", () => { expect(isDuplicateEntryError(new Error("Field 'readAt' doesn't have a default value"))).toBe(false); expect(isDuplicateEntryError({ code: "ER_NO_DEFAULT_FOR_FIELD" })).toBe(false); expect(isDuplicateEntryError(null)).toBe(false); }); }); describe("persistance des articles lus", () => { it("insère uniquement les articles encore non lus et déduplique la demande", async () => { const { db, insertValues } = createReadDb([10]); const inserted = await persistArticleReads(db, 2, "veille", [10, 12, 12, 13]); expect(inserted).toBe(2); expect(insertValues).toHaveBeenCalledWith([ { userId: 2, articleType: "veille", articleId: 12 }, { userId: 2, articleType: "veille", articleId: 13 }, ]); }); it("restaure les identifiants lus stockés pour le bon utilisateur et le bon flux", async () => { const { db } = createReadDb([4, 9]); await expect(readArticleIdsFromDb(db, 2, "aap")).resolves.toEqual([4, 9]); }); });