import { afterAll, beforeEach, describe, expect, it } from "vitest"; import postgres from "postgres"; import * as repo from "../server/utils/repo"; const sql = postgres(process.env.DATABASE_URL!, { max: 1, onnotice: () => {} }); beforeEach(async () => { await sql`truncate lists cascade`; }); afterAll(async () => { await sql.end(); }); describe("lists", () => { it("full-replace upserts supplied and deletes missing", async () => { await repo.replaceLists(sql, [ { id: "a", name: "A" }, { id: "b", name: "B" }, ]); await repo.replaceLists(sql, [{ id: "a", name: "A2" }]); // b removed, a renamed const got = await repo.getLists(sql); expect(got).toEqual([{ id: "a", name: "A2" }]); }); it("empty payload clears all lists", async () => { await repo.replaceLists(sql, [{ id: "a", name: "A" }]); await repo.replaceLists(sql, []); expect(await repo.getLists(sql)).toEqual([]); }); it("deleting a list cascades its tasks", async () => { await repo.replaceLists(sql, [{ id: "a", name: "A" }]); await repo.upsertDesktopTask(sql, "t1", { listId: "a", title: "x" }); await repo.replaceLists(sql, []); // removes a + cascades t1 expect(await repo.getUnconsumed(sql)).toEqual([]); }); it("listExists validates listId", async () => { await repo.replaceLists(sql, [{ id: "L", name: "List" }]); expect(await repo.listExists(sql, "L")).toBe(true); expect(await repo.listExists(sql, "ghost")).toBe(false); }); }); describe("tasks", () => { beforeEach(async () => { await repo.replaceLists(sql, [{ id: "L", name: "List" }]); }); it("web create returns generated GUID, source=web, consumed=false", async () => { const t = await repo.createWebTask(sql, { listId: "L", title: "buy milk", description: null }); expect(t.id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/); expect(t.source).toBe("web"); expect(t.consumed).toBe(false); expect(t.title).toBe("buy milk"); }); it("desktop upsert is idempotent by id; source=desktop on insert", async () => { const a = await repo.upsertDesktopTask(sql, "fixed-id", { listId: "L", title: "one" }); expect(a.created).toBe(true); const b = await repo.upsertDesktopTask(sql, "fixed-id", { listId: "L", title: "two", description: "d" }); expect(b.created).toBe(false); const tasks = await repo.getTasksForList(sql, "L"); expect(tasks).toHaveLength(1); expect(tasks[0].title).toBe("two"); expect(tasks[0].description).toBe("d"); expect(tasks[0].source).toBe("desktop"); }); it("getUnconsumed returns only web tasks with consumed=false", async () => { await repo.createWebTask(sql, { listId: "L", title: "web1", description: null }); await repo.upsertDesktopTask(sql, "d1", { listId: "L", title: "desk1" }); const u = await repo.getUnconsumed(sql); expect(u).toHaveLength(1); expect(u[0].title).toBe("web1"); }); it("consume sets consumed=true and is reflected in getUnconsumed", async () => { const t = await repo.createWebTask(sql, { listId: "L", title: "c", description: null }); expect(await repo.consume(sql, t.id)).toBe(true); expect(await repo.getUnconsumed(sql)).toEqual([]); }); it("consume on unknown id returns false", async () => { expect(await repo.consume(sql, "nope")).toBe(false); }); it("deleteTask is idempotent", async () => { const t = await repo.createWebTask(sql, { listId: "L", title: "c", description: null }); await repo.deleteTask(sql, t.id); await repo.deleteTask(sql, t.id); // no throw on absent expect(await repo.getTasksForList(sql, "L")).toEqual([]); }); });