import { expect, test } from "bun:test"; import { forEachDialect } from "@sttracker/db/testing"; import { createCopy, listCopies, softDeleteCopy, updateCopy } from "../src/copies.ts"; import { defineCustomField } from "../src/custom-fields.ts"; import { NotFoundError, ValidationError } from "../src/errors.ts"; import { getHistory, restoreVersion } from "../src/history.ts"; import { makeUser, makeWork } from "./user-layer-fixtures.ts"; forEachDialect("copies", (getDb) => { test("createCopy stores price in cents and partial acquired date", async () => { const db = getDb(); const userId = await makeUser(db); const workId = await makeWork(db); const c = await createCopy(db, userId, { workId, format: "hardback", acquiredFrom: "Waterstones", acquiredAt: { value: "2019-03", precision: "month" }, priceCents: 1299, currency: "GBP", }); expect(c.priceCents).toBe(1299); expect(c.acquiredAt).toEqual({ value: "2019-03", precision: "month" }); const row = await db.k.selectFrom("copies").selectAll().where("id", "=", c.id).executeTakeFirstOrThrow(); expect(row.price_cents).toBe(1299); expect(row.acquired_precision).toBe("month"); await expect(createCopy(db, userId, { workId, priceCents: 12.5 })).rejects.toThrow(ValidationError); await expect(createCopy(db, userId, { workId, currency: "pounds" })).rejects.toThrow(ValidationError); await expect(createCopy(db, userId, {})).rejects.toThrow(ValidationError); }); test("unmatched copy keeps its title hint; listCopies filters by work", async () => { const db = getDb(); const userId = await makeUser(db); const workId = await makeWork(db); const steam = await createCopy(db, userId, { titleHint: "HALF-LIFE 2", format: "Steam", sourceRef: "steam:220" }, "plugin:steam:c1"); const book = await createCopy(db, userId, { workId }); expect(steam.workId).toBeNull(); expect(steam.titleHint).toBe("HALF-LIFE 2"); expect((await listCopies(db, userId, {})).map((c) => c.id).sort()).toEqual([steam.id, book.id].sort()); expect((await listCopies(db, userId, { workId })).map((c) => c.id)).toEqual([book.id]); }); test("updateCopy writes history; soft delete and restore", async () => { const db = getDb(); const userId = await makeUser(db); const def = await defineCustomField(db, userId, { name: "Signed", type: "boolean", level: "copy" }); const c = await createCopy(db, userId, { workId: await makeWork(db), notes: "shelf 3" }); const up = await updateCopy(db, userId, c.id, { notes: "shelf 4", customFields: { [def.id]: true } }); expect(up.notes).toBe("shelf 4"); expect(up.customFields).toEqual({ [def.id]: true }); await expect(updateCopy(db, userId, c.id, { customFields: { [def.id]: "yes" } })).rejects.toThrow(ValidationError); const [h] = await getHistory(db, userId, "copy", c.id); expect(h!.before).toEqual({ notes: "shelf 3", customFields: {} }); await softDeleteCopy(db, userId, c.id); expect(await listCopies(db, userId, {})).toEqual([]); const [del] = await getHistory(db, userId, "copy", c.id); await restoreVersion(db, userId, del!.id); expect((await listCopies(db, userId, {})).map((x) => x.id)).toEqual([c.id]); }); test("another user's copies are not found", async () => { const db = getDb(); const ada = await makeUser(db); const bob = await makeUser(db, "bob"); const c = await createCopy(db, ada, { titleHint: "x" }); await expect(updateCopy(db, bob, c.id, { notes: "y" })).rejects.toThrow(NotFoundError); await expect(softDeleteCopy(db, bob, c.id)).rejects.toThrow(NotFoundError); expect(await listCopies(db, bob, {})).toEqual([]); await expect(createCopy(db, bob, { workId: await makeWork(db, { ownerUserId: ada }) })).rejects.toThrow( NotFoundError, ); }); });