GET log (engagements and listens, keyset over at/kind/id), listChanges title and intoTitle, listCopies workTitle, all via batched lookups. Co-Authored-By: Claude Opus 5.5 <[email protected]>
97 lines
4.8 KiB
TypeScript
97 lines
4.8 KiB
TypeScript
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 { upsertEntry } from "../src/entries.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,
|
|
);
|
|
});
|
|
|
|
test("listCopies carries the work title, with the user's title override", async () => {
|
|
const db = getDb();
|
|
const userId = await makeUser(db);
|
|
const dune = await makeWork(db, { title: "Dune" });
|
|
const hyperion = await makeWork(db, { title: "Hyperion" });
|
|
await upsertEntry(db, userId, dune, { overrides: { title: "Dune (1965)" } }, "user");
|
|
const a = await createCopy(db, userId, { workId: dune });
|
|
const b = await createCopy(db, userId, { workId: hyperion });
|
|
const c = await createCopy(db, userId, { titleHint: "HALF-LIFE 2" });
|
|
const byId = new Map((await listCopies(db, userId)).map((x) => [x.id, x]));
|
|
expect(byId.get(a.id)!.workTitle).toBe("Dune (1965)");
|
|
expect(byId.get(b.id)!.workTitle).toBe("Hyperion");
|
|
expect(byId.get(c.id)!.workTitle).toBeNull();
|
|
// Another user's override does not leak.
|
|
const bob = await makeUser(db, "bob");
|
|
await createCopy(db, bob, { workId: dune });
|
|
expect((await listCopies(db, bob))[0]!.workTitle).toBe("Dune");
|
|
});
|
|
});
|