test: cover native meal planning schema
This commit is contained in:
+2
-1
@@ -30,7 +30,8 @@
|
||||
"test:backup-scheduler": "node --experimental-sqlite test-backup-scheduler.js",
|
||||
"test:caldav": "node --experimental-sqlite test-caldav-sync.js",
|
||||
"test:carddav": "node --experimental-sqlite test-carddav.js",
|
||||
"test": "node --experimental-sqlite test-db.js && node --experimental-sqlite test-dashboard.js && node --experimental-sqlite test-tasks.js && node --experimental-sqlite test-multi-assignment.js && node --experimental-sqlite test-shopping.js && node --experimental-sqlite test-meals.js && node --experimental-sqlite test-calendar.js && node --experimental-sqlite test-notes-contacts-budget.js && npm run test:ux-utils && npm run test:modal-utils && npm run test:reminders && npm run test:api && npm run test:ics-parser && npm run test:ics-sub && npm run test:setup && npm run test:kitchen-tabs && npm run test:backup-scheduler && npm run test:caldav && npm run test:carddav"
|
||||
"test": "node --experimental-sqlite test-db.js && node --experimental-sqlite test-dashboard.js && node --experimental-sqlite test-tasks.js && node --experimental-sqlite test-multi-assignment.js && node --experimental-sqlite test-shopping.js && node --experimental-sqlite test-meals.js && node --experimental-sqlite test-calendar.js && node --experimental-sqlite test-notes-contacts-budget.js && npm run test:ux-utils && npm run test:modal-utils && npm run test:reminders && npm run test:api && npm run test:ics-parser && npm run test:ics-sub && npm run test:setup && npm run test:kitchen-tabs && npm run test:backup-scheduler && npm run test:caldav && npm run test:carddav",
|
||||
"test:meal-planning": "node --experimental-sqlite test-meal-planning.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"bcrypt": "^6.0.0",
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* 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);
|
||||
Reference in New Issue
Block a user