Files
oikos/test-tasks.js
T
Ulas b139eea623 refactor(esm): migrate server and tests from CommonJS to ESM
Convert all server/, test, and setup files from require()/module.exports
to import/export syntax. Activate ESM globally via "type": "module" in
package.json and load dotenv via --import dotenv/config in npm scripts.
2026-04-03 23:11:20 +02:00

208 lines
8.5 KiB
JavaScript

/**
* Modul: Aufgaben-Test
* Zweck: Validiert alle Tasks-API-Abfragen und Constraints
* Ausführen: node --experimental-sqlite test-tasks.js
*/
import { DatabaseSync } from 'node:sqlite';
import { MIGRATIONS_SQL } from './server/db-schema-test.js';
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'); }
const db = new DatabaseSync(':memory:');
db.exec('PRAGMA foreign_keys = ON;');
db.exec(`CREATE TABLE IF NOT EXISTS schema_migrations (
version INTEGER PRIMARY KEY, description TEXT NOT NULL,
applied_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
);`);
db.exec(MIGRATIONS_SQL[1]);
// Testdaten
const u1 = db.prepare(`INSERT INTO users (username, display_name, password_hash, avatar_color, role)
VALUES ('admin', 'Anna', 'x', '#007AFF', 'admin')`).run();
const u2 = db.prepare(`INSERT INTO users (username, display_name, password_hash, avatar_color)
VALUES ('max', 'Max', 'x', '#34C759')`).run();
const uid1 = u1.lastInsertRowid;
const uid2 = u2.lastInsertRowid;
const today = new Date().toISOString().slice(0, 10);
const yesterday = new Date(Date.now() - 86400000).toISOString().slice(0, 10);
const in3days = new Date(Date.now() + 3 * 86400000).toISOString().slice(0, 10);
console.log('\n[Tasks-Test] CRUD + Filter + Subtasks\n');
// --------------------------------------------------------
// Erstellen
// --------------------------------------------------------
let task1Id, task2Id, task3Id, subtaskId;
test('Aufgabe erstellen', () => {
const r = db.prepare(`INSERT INTO tasks
(title, category, priority, status, due_date, created_by, assigned_to)
VALUES ('Wohnung putzen', 'Haushalt', 'high', 'open', ?, ?, ?)`)
.run(today, uid1, uid2);
task1Id = r.lastInsertRowid;
assert(task1Id > 0, 'ID muss > 0 sein');
});
test('Zweite Aufgabe (überfällig, erledigt)', () => {
const r = db.prepare(`INSERT INTO tasks
(title, category, priority, status, due_date, created_by)
VALUES ('Bereits erledigt', 'Sonstiges', 'low', 'done', ?, ?)`)
.run(yesterday, uid1);
task2Id = r.lastInsertRowid;
assert(task2Id > 0);
});
test('Dritte Aufgabe (kein Datum)', () => {
const r = db.prepare(`INSERT INTO tasks (title, priority, status, created_by)
VALUES ('Später erledigen', 'medium', 'open', ?)`)
.run(uid1);
task3Id = r.lastInsertRowid;
assert(task3Id > 0);
});
test('Subtask erstellen (1 Ebene)', () => {
const r = db.prepare(`INSERT INTO tasks (title, priority, status, created_by, parent_task_id)
VALUES ('Küche putzen', 'medium', 'open', ?, ?)`)
.run(uid1, task1Id);
subtaskId = r.lastInsertRowid;
assert(subtaskId > 0);
});
test('Verschachtelungstiefe: Subtask-of-Subtask wird abgelehnt', () => {
// Simuliert Backend-Prüfung: parent muss parent_task_id = NULL haben
const parent = db.prepare('SELECT parent_task_id FROM tasks WHERE id = ?').get(subtaskId);
assert(parent.parent_task_id !== null, 'Subtask hat parent_task_id gesetzt');
// Backend darf keine weiteren Kinder erlauben
let threw = false;
// CHECK: parent_task_id des subtask ist nicht null → Backend würde 400 zurückgeben
if (parent.parent_task_id !== null) threw = true;
assert(threw, 'Tiefenprüfung sollte anschlagen');
});
// --------------------------------------------------------
// Lesen + Filter
// --------------------------------------------------------
test('Alle Top-Level-Aufgaben mit Subtask-Zähler', () => {
const tasks = db.prepare(`
SELECT t.*,
(SELECT COUNT(*) FROM tasks s WHERE s.parent_task_id = t.id) AS subtask_total,
(SELECT COUNT(*) FROM tasks s WHERE s.parent_task_id = t.id AND s.status = 'done') AS subtask_done
FROM tasks t
WHERE t.parent_task_id IS NULL
ORDER BY CASE t.priority WHEN 'urgent' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END
`).all();
assert(tasks.length === 3, `Erwartet 3, erhalten ${tasks.length}`);
const withSub = tasks.find((t) => t.id === task1Id);
assert(withSub.subtask_total === 1, 'subtask_total = 1');
assert(withSub.subtask_done === 0, 'subtask_done = 0');
});
test('Filter nach Status=open', () => {
const tasks = db.prepare(`SELECT * FROM tasks WHERE parent_task_id IS NULL AND status = 'open'`).all();
assert(tasks.length === 2, `Erwartet 2 offene, erhalten ${tasks.length}`);
assert(tasks.every((t) => t.status === 'open'), 'Alle sollten open sein');
});
test('Filter nach Priority=high', () => {
const tasks = db.prepare(`SELECT * FROM tasks WHERE parent_task_id IS NULL AND priority = 'high'`).all();
assert(tasks.length === 1, `Erwartet 1, erhalten ${tasks.length}`);
assert(tasks[0].title === 'Wohnung putzen');
});
test('Filter nach assigned_to', () => {
const tasks = db.prepare(`SELECT * FROM tasks WHERE parent_task_id IS NULL AND assigned_to = ?`).all(uid2);
assert(tasks.length === 1, `Erwartet 1, erhalten ${tasks.length}`);
assert(tasks[0].assigned_to === uid2);
});
test('Einzelne Aufgabe mit Subtasks und User-Join', () => {
const task = db.prepare(`
SELECT t.*, u.display_name AS assigned_name, u.avatar_color AS assigned_color
FROM tasks t LEFT JOIN users u ON t.assigned_to = u.id
WHERE t.id = ? AND t.parent_task_id IS NULL
`).get(task1Id);
assert(task, 'Aufgabe gefunden');
assert(task.assigned_name === 'Max', 'assigned_name korrekt');
assert(task.assigned_color === '#34C759', 'assigned_color korrekt');
const subtasks = db.prepare(`SELECT * FROM tasks WHERE parent_task_id = ?`).all(task1Id);
assert(subtasks.length === 1, 'Ein Subtask');
assert(subtasks[0].title === 'Küche putzen');
});
// --------------------------------------------------------
// Status-Änderungen
// --------------------------------------------------------
test('Status ändern: open → done', () => {
db.prepare(`UPDATE tasks SET status = 'done' WHERE id = ?`).run(task1Id);
const t = db.prepare('SELECT status FROM tasks WHERE id = ?').get(task1Id);
assert(t.status === 'done', 'Status sollte done sein');
});
test('Status ändern: done → open', () => {
db.prepare(`UPDATE tasks SET status = 'open' WHERE id = ?`).run(task1Id);
const t = db.prepare('SELECT status FROM tasks WHERE id = ?').get(task1Id);
assert(t.status === 'open', 'Status zurück auf open');
});
test('Subtask-Fortschritt nach Erledigung', () => {
db.prepare(`UPDATE tasks SET status = 'done' WHERE id = ?`).run(subtaskId);
const progress = db.prepare(`
SELECT
COUNT(*) AS total,
SUM(CASE WHEN status = 'done' THEN 1 ELSE 0 END) AS done
FROM tasks WHERE parent_task_id = ?
`).get(task1Id);
assert(progress.total === 1, 'total = 1');
assert(progress.done === 1, 'done = 1');
});
// --------------------------------------------------------
// Aktualisieren
// --------------------------------------------------------
test('Aufgabe aktualisieren', () => {
db.prepare(`UPDATE tasks SET title = 'Wohnung gründlich putzen', priority = 'urgent' WHERE id = ?`)
.run(task1Id);
const t = db.prepare('SELECT * FROM tasks WHERE id = ?').get(task1Id);
assert(t.title === 'Wohnung gründlich putzen', 'Titel aktualisiert');
assert(t.priority === 'urgent', 'Priorität aktualisiert');
});
// --------------------------------------------------------
// Löschen
// --------------------------------------------------------
test('Aufgabe löschen löscht Subtasks (CASCADE)', () => {
db.prepare('DELETE FROM tasks WHERE id = ?').run(task1Id);
const orphan = db.prepare('SELECT * FROM tasks WHERE parent_task_id = ?').get(task1Id);
assert(!orphan, 'Subtask sollte gelöscht sein');
});
test('Nicht existierende Aufgabe liefert keine Zeile', () => {
const t = db.prepare('SELECT * FROM tasks WHERE id = 99999').get();
assert(!t, 'Sollte undefined sein');
});
// --------------------------------------------------------
// Meta-Endpoint
// --------------------------------------------------------
test('Users für Meta-Endpoint abrufbar', () => {
const users = db.prepare('SELECT id, display_name, avatar_color FROM users ORDER BY display_name').all();
assert(users.length === 2, `Erwartet 2 User, erhalten ${users.length}`);
assert(users[0].avatar_color, 'avatar_color vorhanden');
});
// --------------------------------------------------------
// Ergebnis
// --------------------------------------------------------
console.log(`\n[Tasks-Test] Ergebnis: ${passed} bestanden, ${failed} fehlgeschlagen\n`);
if (failed > 0) process.exit(1);