Engagements store partial dates as value + precision and track field_sources; lists (list/grouping/thread, grouping-only rating/review) keep dense item order and snapshot items in history; copies store price in cents. All three restore through restoreVersion. Co-Authored-By: Claude Opus 5.5 <[email protected]>
105 lines
5.2 KiB
TypeScript
105 lines
5.2 KiB
TypeScript
import { expect, test } from "bun:test";
|
|
import { forEachDialect } from "@sttracker/db/testing";
|
|
import { NotFoundError, ValidationError } from "../src/errors.ts";
|
|
import { getHistory, restoreVersion } from "../src/history.ts";
|
|
import {
|
|
addListItem,
|
|
createList,
|
|
getList,
|
|
listLists,
|
|
moveListItem,
|
|
removeListItem,
|
|
softDeleteList,
|
|
updateList,
|
|
} from "../src/lists.ts";
|
|
import { makeUser, makeWork } from "./user-layer-fixtures.ts";
|
|
|
|
forEachDialect("lists", (getDb) => {
|
|
test("grouping list can be rated", async () => {
|
|
const db = getDb();
|
|
const userId = await makeUser(db);
|
|
const g = await createList(db, userId, { name: "Silent Hill, everything", kind: "grouping", rating: 90, review: "Dread." });
|
|
expect(g.kind).toBe("grouping");
|
|
expect(g.rating).toBe(90);
|
|
expect(g.review).toBe("Dread.");
|
|
expect(g.visibility).toBe("private");
|
|
const up = await updateList(db, userId, g.id, { rating: 95 });
|
|
expect(up.rating).toBe(95);
|
|
|
|
await expect(createList(db, userId, { name: "Plain", kind: "list", rating: 50 })).rejects.toThrow(ValidationError);
|
|
await expect(createList(db, userId, { name: "Bad", kind: "grouping", rating: 101 })).rejects.toThrow(
|
|
ValidationError,
|
|
);
|
|
await expect(updateList(db, userId, g.id, { kind: "thread" })).rejects.toThrow(ValidationError);
|
|
await expect(createList(db, userId, { name: " " })).rejects.toThrow(ValidationError);
|
|
await expect(createList(db, userId, { name: "x", kind: "box" as never })).rejects.toThrow(ValidationError);
|
|
});
|
|
|
|
test("items keep order through add, move and remove", async () => {
|
|
const db = getDb();
|
|
const userId = await makeUser(db);
|
|
const l = await createList(db, userId, { name: "Cosmic dread", kind: "thread" });
|
|
const [a, b, c] = [await makeWork(db, { title: "A" }), await makeWork(db, { title: "B" }), await makeWork(db, { title: "C" })];
|
|
const ia = await addListItem(db, userId, l.id, { workId: a, note: "start here" });
|
|
const ib = await addListItem(db, userId, l.id, { workId: b });
|
|
const ic = await addListItem(db, userId, l.id, { workId: c });
|
|
const titles = async () => (await getList(db, userId, l.id)).items.map((i) => i.work.title);
|
|
expect(await titles()).toEqual(["A", "B", "C"]);
|
|
expect((await getList(db, userId, l.id)).items[0]!.note).toBe("start here");
|
|
|
|
await moveListItem(db, userId, ic.id, 0);
|
|
expect(await titles()).toEqual(["C", "A", "B"]);
|
|
await moveListItem(db, userId, ic.id, 99);
|
|
expect(await titles()).toEqual(["A", "B", "C"]);
|
|
await removeListItem(db, userId, ib.id);
|
|
expect(await titles()).toEqual(["A", "C"]);
|
|
expect((await getList(db, userId, l.id)).items.map((i) => i.position)).toEqual([0, 1]);
|
|
|
|
const inserted = await addListItem(db, userId, l.id, { workId: b, position: 1 });
|
|
expect(await titles()).toEqual(["A", "B", "C"]);
|
|
expect(inserted.position).toBe(1);
|
|
expect(ia.position).toBe(0);
|
|
});
|
|
|
|
test("removing an item is recorded and restorable", async () => {
|
|
const db = getDb();
|
|
const userId = await makeUser(db);
|
|
const l = await createList(db, userId, { name: "Reading order" });
|
|
await addListItem(db, userId, l.id, { workId: await makeWork(db, { title: "A" }) });
|
|
const ib = await addListItem(db, userId, l.id, { workId: await makeWork(db, { title: "B" }) });
|
|
await removeListItem(db, userId, ib.id);
|
|
const [h] = await getHistory(db, userId, "list", l.id);
|
|
await restoreVersion(db, userId, h!.id);
|
|
expect((await getList(db, userId, l.id)).items.map((i) => i.work.title)).toEqual(["A", "B"]);
|
|
});
|
|
|
|
test("listLists and soft delete", async () => {
|
|
const db = getDb();
|
|
const userId = await makeUser(db);
|
|
const l1 = await createList(db, userId, { name: "One" });
|
|
const l2 = await createList(db, userId, { name: "Two", kind: "grouping" });
|
|
expect((await listLists(db, userId)).map((l) => l.id).sort()).toEqual([l1.id, l2.id].sort());
|
|
expect((await listLists(db, userId, { kind: "grouping" })).map((l) => l.id)).toEqual([l2.id]);
|
|
await softDeleteList(db, userId, l1.id);
|
|
expect((await listLists(db, userId)).map((l) => l.id)).toEqual([l2.id]);
|
|
await expect(getList(db, userId, l1.id)).rejects.toThrow(NotFoundError);
|
|
});
|
|
|
|
test("another user's lists and private works are not found", async () => {
|
|
const db = getDb();
|
|
const ada = await makeUser(db);
|
|
const bob = await makeUser(db, "bob");
|
|
const l = await createList(db, ada, { name: "Mine" });
|
|
const item = await addListItem(db, ada, l.id, { workId: await makeWork(db) });
|
|
await expect(getList(db, bob, l.id)).rejects.toThrow(NotFoundError);
|
|
await expect(updateList(db, bob, l.id, { name: "x" })).rejects.toThrow(NotFoundError);
|
|
await expect(addListItem(db, bob, l.id, { workId: await makeWork(db) })).rejects.toThrow(NotFoundError);
|
|
await expect(moveListItem(db, bob, item.id, 0)).rejects.toThrow(NotFoundError);
|
|
await expect(removeListItem(db, bob, item.id)).rejects.toThrow(NotFoundError);
|
|
expect(await listLists(db, bob)).toEqual([]);
|
|
const adaPrivate = await makeWork(db, { ownerUserId: ada });
|
|
const bobList = await createList(db, bob, { name: "Bob's" });
|
|
await expect(addListItem(db, bob, bobList.id, { workId: adaPrivate })).rejects.toThrow(NotFoundError);
|
|
});
|
|
});
|