141 lines
5.9 KiB
JavaScript
141 lines
5.9 KiB
JavaScript
/**
|
|
* Modul: Meal-Planning-Test
|
|
* Zweck: Validiert native Meal-Planning-Signal-Tabellen und Constraints.
|
|
* Ausführen: node --experimental-sqlite test-meal-planning.js
|
|
*/
|
|
|
|
import { DatabaseSync } from 'node:sqlite';
|
|
import fs from 'node:fs';
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
|
|
function test(name, fn) {
|
|
try { fn(); console.log(` ✓ ${name}`); passed++; }
|
|
catch (err) { console.error(` ✗ ${name}: ${err.message}`); failed++; }
|
|
}
|
|
function assert(cond, msg) { if (!cond) throw new Error(msg || 'Assertion fehlgeschlagen'); }
|
|
|
|
function migrationSql(version) {
|
|
const source = fs.readFileSync(new URL('./server/db.js', import.meta.url), 'utf8');
|
|
const re = new RegExp('version:\\s*' + version + ',[\\s\\S]*?up:\\s*`([\\s\\S]*?)`');
|
|
const match = source.match(re);
|
|
if (!match) throw new Error(`Migration v${version} nicht gefunden`);
|
|
return match[1];
|
|
}
|
|
|
|
const db = new DatabaseSync(':memory:');
|
|
db.exec('PRAGMA foreign_keys = ON;');
|
|
db.exec(`
|
|
CREATE TABLE users (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
display_name TEXT NOT NULL,
|
|
avatar_color TEXT
|
|
);
|
|
CREATE TABLE recipes (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
title TEXT NOT NULL
|
|
);
|
|
CREATE TABLE meals (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
date TEXT NOT NULL,
|
|
meal_type TEXT NOT NULL CHECK(meal_type IN ('breakfast', 'lunch', 'dinner', 'snack')),
|
|
title TEXT NOT NULL
|
|
);
|
|
`);
|
|
db.exec(migrationSql(39));
|
|
|
|
console.log('\n[Meal-Planning-Test] Native Signaltabellen\n');
|
|
|
|
const tables = [
|
|
'meal_cooking_rules',
|
|
'recipe_family_preferences',
|
|
'recipe_variation_meta',
|
|
'planned_meal_cooks',
|
|
'meal_plan_feedback',
|
|
'kids_cookbooks',
|
|
];
|
|
|
|
for (const table of tables) {
|
|
test(`Tabelle "${table}" existiert`, () => {
|
|
const row = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(table);
|
|
assert(row, `Tabelle ${table} fehlt`);
|
|
});
|
|
}
|
|
|
|
let userId, recipeId, mealId;
|
|
|
|
test('Fixture-Daten erstellen', () => {
|
|
userId = db.prepare("INSERT INTO users (display_name, avatar_color) VALUES ('Liv', '#FF6B9D')").run().lastInsertRowid;
|
|
recipeId = db.prepare("INSERT INTO recipes (title) VALUES ('Pasta med tomat')").run().lastInsertRowid;
|
|
mealId = db.prepare("INSERT INTO meals (date, meal_type, title) VALUES ('2026-05-12', 'dinner', 'Pasta med tomat')").run().lastInsertRowid;
|
|
assert(userId > 0 && recipeId > 0 && mealId > 0);
|
|
});
|
|
|
|
test('Recurring cook rule gemmes', () => {
|
|
db.prepare('INSERT INTO meal_cooking_rules (user_id, weekday, meal_type, priority) VALUES (?, 0, ?, 100)').run(userId, 'dinner');
|
|
const row = db.prepare('SELECT * FROM meal_cooking_rules WHERE user_id = ?').get(userId);
|
|
assert(row.weekday === 0 && row.meal_type === 'dinner');
|
|
});
|
|
|
|
test('Recipe preference/capability-signaler gemmes', () => {
|
|
db.prepare(`
|
|
INSERT INTO recipe_family_preferences (
|
|
recipe_id, user_id, preference, can_cook, can_help_cook, will_eat_modified, adult_only, swap_in_count, swap_away_count
|
|
) VALUES (?, ?, 'favorite', 1, 1, 1, 0, 2, 1)
|
|
`).run(recipeId, userId);
|
|
const row = db.prepare('SELECT * FROM recipe_family_preferences WHERE recipe_id = ? AND user_id = ?').get(recipeId, userId);
|
|
assert(row.preference === 'favorite');
|
|
assert(row.can_cook === 1 && row.can_help_cook === 1 && row.will_eat_modified === 1 && row.adult_only === 0);
|
|
assert(row.swap_in_count === 2 && row.swap_away_count === 1);
|
|
});
|
|
|
|
test('Variation metadata gemmes', () => {
|
|
db.prepare('INSERT INTO recipe_variation_meta (recipe_id, protein, style, kid_suitable_confidence) VALUES (?, ?, ?, 85)')
|
|
.run(recipeId, 'vegetarian', 'quick');
|
|
const row = db.prepare('SELECT * FROM recipe_variation_meta WHERE recipe_id = ?').get(recipeId);
|
|
assert(row.protein === 'vegetarian' && row.style === 'quick' && row.kid_suitable_confidence === 85);
|
|
});
|
|
|
|
test('Planned meal cook assignment gemmes', () => {
|
|
db.prepare('INSERT INTO planned_meal_cooks (meal_id, user_id, planned_for_date, meal_type, source_plan_id) VALUES (?, ?, ?, ?, ?)')
|
|
.run(mealId, userId, '2026-05-12', 'dinner', 'plan-test');
|
|
const row = db.prepare('SELECT * FROM planned_meal_cooks WHERE meal_id = ?').get(mealId);
|
|
assert(row.user_id === userId && row.source_plan_id === 'plan-test');
|
|
});
|
|
|
|
test('Feedback event gemmes', () => {
|
|
const id = db.prepare(`
|
|
INSERT INTO meal_plan_feedback (plan_id, meal_id, recipe_id, slot_date, meal_type, action, original_title, final_title, user_id)
|
|
VALUES ('plan-test', ?, ?, '2026-05-12', 'dinner', 'accept', 'Pasta', 'Pasta med tomat', ?)
|
|
`).run(mealId, recipeId, userId).lastInsertRowid;
|
|
const row = db.prepare('SELECT * FROM meal_plan_feedback WHERE id = ?').get(id);
|
|
assert(row.action === 'accept' && row.recipe_id === recipeId);
|
|
});
|
|
|
|
test('Kids cookbook gemmes som JSON', () => {
|
|
const content = JSON.stringify({ title: 'Pasta for børn', steps: ['Vask hænder', 'Rør sovs'] });
|
|
const id = db.prepare('INSERT INTO kids_cookbooks (recipe_id, title, content_json, created_by) VALUES (?, ?, ?, ?)')
|
|
.run(recipeId, 'Pasta for børn', content, userId).lastInsertRowid;
|
|
const row = db.prepare('SELECT * FROM kids_cookbooks WHERE id = ?').get(id);
|
|
assert(JSON.parse(row.content_json).steps.length === 2);
|
|
});
|
|
|
|
test('Ugyldig preference afvises', () => {
|
|
let failedConstraint = false;
|
|
try {
|
|
db.prepare('INSERT INTO recipe_family_preferences (recipe_id, user_id, preference) VALUES (?, ?, ?)').run(recipeId, userId + 1, 'maybe');
|
|
} catch { failedConstraint = true; }
|
|
assert(failedConstraint, 'CHECK constraint skulle afvise ugyldig preference');
|
|
});
|
|
|
|
test('FK cascade sletter recipe-signaler', () => {
|
|
db.prepare('DELETE FROM recipes WHERE id = ?').run(recipeId);
|
|
const prefs = db.prepare('SELECT count(*) AS n FROM recipe_family_preferences WHERE recipe_id = ?').get(recipeId).n;
|
|
const meta = db.prepare('SELECT count(*) AS n FROM recipe_variation_meta WHERE recipe_id = ?').get(recipeId).n;
|
|
assert(prefs === 0 && meta === 0);
|
|
});
|
|
|
|
console.log(`\n[Meal-Planning-Test] Ergebnis: ${passed} bestanden, ${failed} fehlgeschlagen\n`);
|
|
if (failed > 0) process.exit(1);
|