Files
kochwas/tests/integration/db.test.ts
Hendrik 18547a7301 feat(wishlist): add shared family wishlist with likes
Each recipe appears at most once on the wishlist. Any profile can add,
remove, like, and unlike. Ratings and cooking log stay independent.

Data model: wishlist(recipe_id PK, added_by_profile_id, added_at)
            wishlist_like(recipe_id, profile_id, created_at)

Why: 'das will ich essen' — family members pick candidates, everyone
can +1 to signal agreement, cook decides based on popularity.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 17:08:22 +02:00

89 lines
2.8 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { openInMemoryForTest } from '../../src/lib/server/db';
import { runMigrations } from '../../src/lib/server/db/migrate';
describe('db migrations', () => {
it('creates all expected tables', () => {
const db = openInMemoryForTest();
const tables = (
db.prepare("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name").all() as {
name: string;
}[]
).map((r) => r.name);
for (const t of [
'profile',
'recipe',
'ingredient',
'step',
'tag',
'recipe_tag',
'rating',
'comment',
'favorite',
'cooking_log',
'allowed_domain',
'schema_migration'
]) {
expect(tables).toContain(t);
}
});
it('is idempotent', () => {
const db = openInMemoryForTest();
const countBefore = (
db.prepare('SELECT COUNT(*) AS c FROM schema_migration').get() as { c: number }
).c;
runMigrations(db);
const countAfter = (
db.prepare('SELECT COUNT(*) AS c FROM schema_migration').get() as { c: number }
).c;
expect(countAfter).toBe(countBefore);
});
it('cascades recipe delete to ingredients and steps', () => {
const db = openInMemoryForTest();
const row = db
.prepare('INSERT INTO recipe(title) VALUES (?) RETURNING id')
.get('Test') as { id: number };
db.prepare('INSERT INTO ingredient(recipe_id, position, name) VALUES (?, ?, ?)').run(
row.id,
1,
'Salz'
);
db.prepare('INSERT INTO step(recipe_id, position, text) VALUES (?, ?, ?)').run(
row.id,
1,
'Kochen'
);
db.prepare('DELETE FROM recipe WHERE id = ?').run(row.id);
const ings = db.prepare('SELECT COUNT(*) AS c FROM ingredient').get() as { c: number };
const steps = db.prepare('SELECT COUNT(*) AS c FROM step').get() as { c: number };
expect(ings.c).toBe(0);
expect(steps.c).toBe(0);
});
it('FTS5 index finds recipes by title', () => {
const db = openInMemoryForTest();
db.prepare('INSERT INTO recipe(title, description) VALUES (?, ?)').run(
'Spaghetti Carbonara',
'Italienisches Pasta-Klassiker'
);
const hits = db
.prepare("SELECT rowid FROM recipe_fts WHERE recipe_fts MATCH 'carbonara'")
.all();
expect(hits.length).toBe(1);
});
it('enforces rating stars 1..5', () => {
const db = openInMemoryForTest();
db.prepare('INSERT INTO profile(name) VALUES (?)').run('Hendrik');
const r = db
.prepare('INSERT INTO recipe(title) VALUES (?) RETURNING id')
.get('Test') as { id: number };
db.prepare('INSERT INTO rating(recipe_id, profile_id, stars) VALUES (?, 1, 5)').run(r.id);
expect(() =>
db.prepare('INSERT INTO rating(recipe_id, profile_id, stars) VALUES (?, 1, 6)').run(r.id)
).toThrow();
});
});