import { beforeEach, describe, expect, it } from "bun:test"; import { eq } from "drizzle-orm"; import { items } from "../../src/db/schema.ts"; import { createCategory, deleteCategory, getAllCategories, updateCategory, } from "../../src/server/services/category.service.ts"; import { createItem } from "../../src/server/services/item.service.ts"; import { createSecondTestUser, createTestDb } from "../helpers/db.ts"; describe("Category Service", () => { let db: any; let userId: number; beforeEach(async () => { ({ db, userId } = await createTestDb()); }); describe("createCategory", () => { it("creates with name and icon", async () => { const cat = await createCategory(db, userId, { name: "Shelter", icon: "tent", }); expect(cat).toBeDefined(); expect(cat?.id).toBeGreaterThan(0); expect(cat?.name).toBe("Shelter"); expect(cat?.icon).toBe("tent"); }); it("uses default icon if not provided", async () => { const cat = await createCategory(db, userId, { name: "Cooking" }); expect(cat).toBeDefined(); expect(cat?.icon).toBe("package"); }); }); describe("getAllCategories", () => { it("returns all categories", async () => { await createCategory(db, userId, { name: "Shelter", icon: "tent" }); await createCategory(db, userId, { name: "Cooking", icon: "cooking-pot", }); const all = await getAllCategories(db, userId); // Includes seeded Uncategorized + 2 new expect(all.length).toBeGreaterThanOrEqual(3); }); }); describe("updateCategory", () => { it("renames category", async () => { const cat = await createCategory(db, userId, { name: "Shelter", icon: "tent", }); const updated = await updateCategory(db, userId, cat?.id, { name: "Sleep System", }); expect(updated).toBeDefined(); expect(updated?.name).toBe("Sleep System"); expect(updated?.icon).toBe("tent"); }); it("changes icon", async () => { const cat = await createCategory(db, userId, { name: "Shelter", icon: "tent", }); const updated = await updateCategory(db, userId, cat?.id, { icon: "home", }); expect(updated).toBeDefined(); expect(updated?.icon).toBe("home"); }); it("returns null for non-existent id", async () => { const result = await updateCategory(db, userId, 9999, { name: "Ghost", }); expect(result).toBeNull(); }); }); describe("deleteCategory", () => { it("reassigns items to Uncategorized then deletes", async () => { const shelter = await createCategory(db, userId, { name: "Shelter", icon: "tent", }); await createItem(db, userId, { name: "Tent", categoryId: shelter?.id, }); await createItem(db, userId, { name: "Tarp", categoryId: shelter?.id, }); const result = await deleteCategory(db, userId, shelter?.id); expect(result.success).toBe(true); // Items should now be in the Uncategorized category (id=1 for first user) const reassigned = await db .select() .from(items) .where(eq(items.categoryId, 1)); expect(reassigned).toHaveLength(2); expect(reassigned.map((i: any) => i.name).sort()).toEqual([ "Tarp", "Tent", ]); }); it("cannot delete Uncategorized", async () => { const result = await deleteCategory(db, userId, 1); expect(result.success).toBe(false); expect(result.error).toBeDefined(); }); }); describe("composite unique constraint", () => { it("two users can have categories with the same name", async () => { const userId2 = await createSecondTestUser(db); const cat1 = await createCategory(db, userId, { name: "Cycling", icon: "bike", }); const cat2 = await createCategory(db, userId2, { name: "Cycling", icon: "bike", }); expect(cat1).toBeDefined(); expect(cat2).toBeDefined(); expect(cat1?.name).toBe("Cycling"); expect(cat2?.name).toBe("Cycling"); expect(cat1?.id).not.toBe(cat2?.id); }); }); });