import { describe, it, expect, beforeEach } from "bun:test"; import { createTestDb } from "../helpers/db.ts"; import { getAllSetups, getSetupWithItems, createSetup, updateSetup, deleteSetup, syncSetupItems, removeSetupItem, } from "../../src/server/services/setup.service.ts"; import { createItem } from "../../src/server/services/item.service.ts"; describe("Setup Service", () => { let db: ReturnType; beforeEach(() => { db = createTestDb(); }); describe("createSetup", () => { it("creates setup with name, returns setup with id/timestamps", () => { const setup = createSetup(db, { name: "Day Hike" }); expect(setup).toBeDefined(); expect(setup.id).toBeGreaterThan(0); expect(setup.name).toBe("Day Hike"); expect(setup.createdAt).toBeDefined(); expect(setup.updatedAt).toBeDefined(); }); }); describe("getAllSetups", () => { it("returns setups with itemCount, totalWeight, totalCost", () => { const setup = createSetup(db, { name: "Backpacking" }); const item1 = createItem(db, { name: "Tent", categoryId: 1, weightGrams: 1200, priceCents: 30000, }); const item2 = createItem(db, { name: "Sleeping Bag", categoryId: 1, weightGrams: 800, priceCents: 20000, }); syncSetupItems(db, setup.id, [item1.id, item2.id]); const setups = getAllSetups(db); expect(setups).toHaveLength(1); expect(setups[0].name).toBe("Backpacking"); expect(setups[0].itemCount).toBe(2); expect(setups[0].totalWeight).toBe(2000); expect(setups[0].totalCost).toBe(50000); }); it("returns 0 for weight/cost when setup has no items", () => { createSetup(db, { name: "Empty Setup" }); const setups = getAllSetups(db); expect(setups).toHaveLength(1); expect(setups[0].itemCount).toBe(0); expect(setups[0].totalWeight).toBe(0); expect(setups[0].totalCost).toBe(0); }); }); describe("getSetupWithItems", () => { it("returns setup with full item details and category info", () => { const setup = createSetup(db, { name: "Day Hike" }); const item = createItem(db, { name: "Water Bottle", categoryId: 1, weightGrams: 200, priceCents: 2500, }); syncSetupItems(db, setup.id, [item.id]); const result = getSetupWithItems(db, setup.id); expect(result).toBeDefined(); expect(result!.name).toBe("Day Hike"); expect(result!.items).toHaveLength(1); expect(result!.items[0].name).toBe("Water Bottle"); expect(result!.items[0].categoryName).toBe("Uncategorized"); expect(result!.items[0].categoryEmoji).toBeDefined(); }); it("returns null for non-existent setup", () => { const result = getSetupWithItems(db, 9999); expect(result).toBeNull(); }); }); describe("updateSetup", () => { it("updates setup name, returns updated setup", () => { const setup = createSetup(db, { name: "Original" }); const updated = updateSetup(db, setup.id, { name: "Renamed" }); expect(updated).toBeDefined(); expect(updated!.name).toBe("Renamed"); }); it("returns null for non-existent setup", () => { const result = updateSetup(db, 9999, { name: "Ghost" }); expect(result).toBeNull(); }); }); describe("deleteSetup", () => { it("removes setup and cascades to setup_items", () => { const setup = createSetup(db, { name: "To Delete" }); const item = createItem(db, { name: "Item", categoryId: 1 }); syncSetupItems(db, setup.id, [item.id]); const deleted = deleteSetup(db, setup.id); expect(deleted).toBe(true); // Setup gone const result = getSetupWithItems(db, setup.id); expect(result).toBeNull(); }); it("returns false for non-existent setup", () => { const result = deleteSetup(db, 9999); expect(result).toBe(false); }); }); describe("syncSetupItems", () => { it("sets items for a setup (delete-all + re-insert)", () => { const setup = createSetup(db, { name: "Kit" }); const item1 = createItem(db, { name: "Item 1", categoryId: 1 }); const item2 = createItem(db, { name: "Item 2", categoryId: 1 }); const item3 = createItem(db, { name: "Item 3", categoryId: 1 }); // Initial sync syncSetupItems(db, setup.id, [item1.id, item2.id]); let result = getSetupWithItems(db, setup.id); expect(result!.items).toHaveLength(2); // Re-sync with different items syncSetupItems(db, setup.id, [item2.id, item3.id]); result = getSetupWithItems(db, setup.id); expect(result!.items).toHaveLength(2); const names = result!.items.map((i: any) => i.name).sort(); expect(names).toEqual(["Item 2", "Item 3"]); }); it("syncing with empty array clears all items", () => { const setup = createSetup(db, { name: "Kit" }); const item = createItem(db, { name: "Item", categoryId: 1 }); syncSetupItems(db, setup.id, [item.id]); syncSetupItems(db, setup.id, []); const result = getSetupWithItems(db, setup.id); expect(result!.items).toHaveLength(0); }); }); describe("removeSetupItem", () => { it("removes single item from setup", () => { const setup = createSetup(db, { name: "Kit" }); const item1 = createItem(db, { name: "Item 1", categoryId: 1 }); const item2 = createItem(db, { name: "Item 2", categoryId: 1 }); syncSetupItems(db, setup.id, [item1.id, item2.id]); removeSetupItem(db, setup.id, item1.id); const result = getSetupWithItems(db, setup.id); expect(result!.items).toHaveLength(1); expect(result!.items[0].name).toBe("Item 2"); }); }); describe("cascade behavior", () => { it("deleting a collection item removes it from all setups", () => { const setup = createSetup(db, { name: "Kit" }); const item1 = createItem(db, { name: "Item 1", categoryId: 1 }); const item2 = createItem(db, { name: "Item 2", categoryId: 1 }); syncSetupItems(db, setup.id, [item1.id, item2.id]); // Delete item1 from collection (need direct DB access) const { items: itemsTable } = require("../../src/db/schema.ts"); const { eq } = require("drizzle-orm"); db.delete(itemsTable).where(eq(itemsTable.id, item1.id)).run(); const result = getSetupWithItems(db, setup.id); expect(result!.items).toHaveLength(1); expect(result!.items[0].name).toBe("Item 2"); }); }); });