aboutsummaryrefslogtreecommitdiff
path: root/src/lib/server/services/groceries.test.ts
blob: d12111faf61f3e1bcbe6a14ede8bf79bbd5932fc (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140

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 {
	loadUnchecked,
	loadAll,
	toggle,
	addOrUncheck,
	deleteGrocery,
} from './groceries';

describe('groceries service', () => {
	let db: TestDb['db'];

	beforeEach(async () => {
		db = (await createTestDb()).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 loadUnchecked(db);
		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 loadAll(db);
		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 loadUnchecked(db);
		expect(unchecked.items.map((i) => i.name)).toEqual(['bread', 'milk']);

		const all = await loadAll(db);
		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 loadAll(db);
		expect(result.items[0].bestRef?.reference).toBe('Brand A');
	});

	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 toggle(item.id, db);

		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 addOrUncheck('eggs', null, db);

		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 addOrUncheck('milk', null, db);

		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 deleteGrocery(item.id, db);

		const rows = await db.select().from(groceries);
		expect(rows).toHaveLength(0);
	});
});