import { describe, it, expect, beforeEach } from 'vitest'; import { groceries, categories, references } from '$lib/server/db/schema'; import { eq } from 'drizzle-orm'; import { createTestDb, type TestDb } from '$lib/server/db/test-helper'; import { GroceriesService } from './groceries'; describe('groceries service', () => { let db: TestDb['db']; let service: GroceriesService; beforeEach(async () => { db = (await createTestDb()).db; service = new GroceriesService(db); }); it('loadUnchecked returns only unchecked items', async () => { const now = Temporal.Now.instant().toString({ smallestUnit: 'millisecond', }); await db.insert(groceries).values([ { name: 'milk', checked: false, createdAt: now }, { name: 'bread', checked: true, createdAt: now }, ]); const result = await service.loadUnchecked(); expect(result.items).toHaveLength(1); expect(result.items[0].name).toBe('milk'); }); it('loadAll returns all items', async () => { const now = Temporal.Now.instant().toString({ smallestUnit: 'millisecond', }); await db.insert(groceries).values([ { name: 'milk', checked: false, createdAt: now }, { name: 'bread', checked: true, createdAt: now }, ]); const result = await service.loadAll(); expect(result.items).toHaveLength(2); }); it('orders items with identical createdAt deterministically', async () => { const now = Temporal.Now.instant().toString({ smallestUnit: 'millisecond', }); await db.insert(groceries).values([ { name: 'milk', checked: false, createdAt: now }, { name: 'bread', checked: false, createdAt: now }, ]); const unchecked = await service.loadUnchecked(); expect(unchecked.items.map((i) => i.name)).toEqual(['bread', 'milk']); const all = await service.loadAll(); expect(all.items.map((i) => i.name)).toEqual(['bread', 'milk']); }); it('picks bestRef deterministically when quality scores tie', async () => { const now = Temporal.Now.instant().toString({ smallestUnit: 'millisecond', }); const [g] = await db .insert(groceries) .values({ name: 'coffee', checked: false, createdAt: now }) .returning(); await db.insert(references).values([ { groceryId: g.id, reference: 'Brand A', qualityScore: 80 }, { groceryId: g.id, reference: 'Brand B', qualityScore: 80 }, ]); const result = await service.loadAll(); expect(result.items[0].bestRef?.reference).toBe('Brand A'); }); it('bestRef prefers the promoted ref over a higher score', async () => { const now = Temporal.Now.instant().toString({ smallestUnit: 'millisecond', }); const [g] = await db .insert(groceries) .values({ name: 'coffee', checked: false, createdAt: now }) .returning(); const [, b] = await db .insert(references) .values([ { groceryId: g.id, reference: 'Brand A', qualityScore: 80 }, { groceryId: g.id, reference: 'Brand B', qualityScore: 20 }, ]) .returning(); await db .update(references) .set({ isPrimary: true }) .where(eq(references.id, b.id)); const result = await service.loadAll(); expect(result.items[0].bestRef?.reference).toBe('Brand B'); }); it('toggle flips checked state', async () => { const now = Temporal.Now.instant().toString({ smallestUnit: 'millisecond', }); await db .insert(groceries) .values({ name: 'milk', checked: false, createdAt: now }); const [item] = await db.select().from(groceries); await service.toggle(item.id); const [updated] = await db .select() .from(groceries) .where(eq(groceries.id, item.id)); expect(updated.checked).toBe(true); }); it('addOrUncheck creates a new grocery', async () => { const { item, created } = await service.addOrUncheck('eggs', null); expect(created).toBe(true); expect(item.name).toBe('eggs'); const rows = await db.select().from(groceries); expect(rows).toHaveLength(1); }); it('addOrUncheck unchecks an existing checked item', async () => { const now = Temporal.Now.instant().toString({ smallestUnit: 'millisecond', }); await db .insert(groceries) .values({ name: 'milk', checked: true, createdAt: now }); const { item, created } = await service.addOrUncheck('milk', null); expect(created).toBe(false); expect(item.checked).toBe(false); const rows = await db.select().from(groceries); expect(rows).toHaveLength(1); expect(rows[0].checked).toBe(false); }); it('deleteGrocery removes the item', async () => { const now = Temporal.Now.instant().toString({ smallestUnit: 'millisecond', }); await db .insert(groceries) .values({ name: 'milk', checked: false, createdAt: now }); const [item] = await db.select().from(groceries); await service.deleteGrocery(item.id); const rows = await db.select().from(groceries); expect(rows).toHaveLength(0); }); });