Files
deskhub/DeskHub-Webseite/tests/vacation_no_notifications.php
2026-11-05 21:22:03 +00:00

136 lines
7.9 KiB
PHP

<?php
declare(strict_types=1);
/**
* Prüft, dass Mitarbeiter im Urlaub keine Benachrichtigungen bekommen: E-Mail (neues Ticket, überfällig, Erwähnung, Team, Eskalation),
* Browser-Push und Android-Push. Mitarbeiter, die zurück sind, bekommen sie wieder.
*/
$db = sys_get_temp_dir() . '/deskhub-vn-' . bin2hex(random_bytes(4)) . '.sqlite';
putenv('DESKHUB_TESTING=1');
putenv('DESKHUB_SQLITE_PATH=' . $db);
require_once dirname(__DIR__) . '/includes/bootstrap.php';
require_once dirname(__DIR__) . '/includes/push.php';
$failures = [];
$check = function (bool $ok, string $what) use (&$failures): void { if (!$ok) $failures[] = $what; };
$pdo = get_db();
set_setting('sla_notify_mode', 'assignee'); // diese Prüfungen betreffen den Modus „Bearbeiter bzw. Abteilung“ (Standard ist „nur Administratoren“)
$now = db_now();
$pdo->prepare('INSERT INTO departments (name, email) VALUES (?,?)')->execute(['Support', 'support@firma.example']);
$dept = (int) $pdo->lastInsertId();
$person = function (string $name, string $role, int $d) use ($pdo, $now): int {
$pdo->prepare('INSERT INTO staff (name, email, password_hash, role, department_id, is_active, created_at) VALUES (?,?,?,?,?,1,?)')
->execute([$name, strtolower($name) . '@firma.example', 'x', $role, $d, $now]);
return (int) $pdo->lastInsertId();
};
$anna = $person('Anna', 'agent', $dept);
$ben = $person('Ben', 'agent', $dept);
$chef = $person('Chef', 'admin', $dept);
$away = fn(int $id, bool $on, ?int $sub = null) => $pdo->prepare('UPDATE staff SET is_on_vacation = ?, vacation_substitute_id = ? WHERE id = ?')->execute([$on ? 1 : 0, $sub, $id]);
// Mails über die Warteschlange abfangen: SMTP absichtlich ohne Server, Mails landen in mail_queue
set_setting('mail_enabled', '1');
set_setting('smtp_host', '127.0.0.1');
set_setting('smtp_port', '1');
set_setting('smtp_encryption', 'none');
set_setting('smtp_from_email', 'noreply@firma.example');
$queued = function () use ($pdo): array { return $pdo->query('SELECT to_email FROM mail_queue ORDER BY id')->fetchAll(PDO::FETCH_COLUMN); };
$reset = fn() => $pdo->exec('DELETE FROM mail_queue');
$ticket = ['id' => 1, 'ticket_number' => '261030-0001', 'subject' => 'Test', 'requester_name' => 'Kunde', 'requester_email' => 'kunde@x.example', 'department_id' => $dept, 'department_name' => 'Support', 'assigned_staff_id' => null];
// 1) Neues Ticket
$reset();
notify_staff_new_ticket($ticket);
$to = $queued(); sort($to);
$check(in_array('anna@firma.example', $to, true) && in_array('ben@firma.example', $to, true) && in_array('chef@firma.example', $to, true), 'Ohne Urlaub gehen Mails an alle: ' . json_encode($to));
$reset();
$away($ben, true);
notify_staff_new_ticket($ticket);
$check(!in_array('ben@firma.example', $queued(), true), 'Neues Ticket: Mitarbeiter im Urlaub bekommt keine Mail');
$away($ben, false);
// 2) Überfällig an Admins
$reset();
$away($chef, true);
notify_admins_overdue_ticket($ticket);
$check(!in_array('chef@firma.example', $queued(), true), 'Überfällig: Administrator im Urlaub bekommt keine Mail');
$away($chef, false);
// 3) SLA-Empfänger: Bearbeiter im Urlaub ohne Vertretung bekommt nichts, mit Vertretung bekommt diese die Mail
$away($anna, true);
$r = sla_overdue_recipients($pdo, ['assigned_staff_id' => $anna, 'department_id' => $dept]);
$check(!in_array('anna@firma.example', $r, true), 'SLA: Bearbeiter im Urlaub ohne Vertretung bekommt keine Mail: ' . json_encode($r));
$away($anna, true, $ben);
$r = sla_overdue_recipients($pdo, ['assigned_staff_id' => $anna, 'department_id' => $dept]);
$check($r === ['ben@firma.example'], 'SLA: mit Vertretung bekommt diese die Mail: ' . json_encode($r));
$away($anna, false);
// 4) Erwähnung per Mail
$pdo->prepare("INSERT INTO tickets (ticket_number, subject, status, department_id, requester_name, requester_email, priority_id, created_at, updated_at) VALUES (?,?,?,?,?,?,(SELECT MIN(id) FROM priorities),?,?)")
->execute(['261030-0001', 'Test', 'open', $dept, 'Kunde', 'kunde@x.example', $now, $now]);
$ticket['id'] = (int) $pdo->lastInsertId();
$pdo->prepare("INSERT INTO ticket_threads (ticket_id, author_type, message, is_internal_note, created_at) VALUES (?,?,?,0,?)")->execute([$ticket['id'], 'staff', 'x', $now]);
$threadId = (int) $pdo->lastInsertId();
$reset();
$away($ben, true);
if (true) {
save_mentions($pdo, (int) $ticket['id'], $threadId, $anna, '@Ben bitte prüfen', $ticket + ['ticket_number' => '261030-0001']);
$check(!in_array('ben@firma.example', $queued(), true), 'Erwähnung: Mitarbeiter im Urlaub bekommt keine Mail');
}
$away($ben, false);
// 5) Push-Hilfsfunktion und Abfragen
$check(staff_is_on_vacation($ben) === false, 'Ben ist zurück');
$away($ben, true);
$check(staff_is_on_vacation($ben) === true, 'Ben ist im Urlaub');
$src = file_get_contents(dirname(__DIR__) . '/includes/push.php') . file_get_contents(dirname(__DIR__) . '/includes/fcm.php');
$check(str_contains($src, 'vacation_expand_ids') && substr_count($src, 'vacation_expand_ids') >= 3, 'Web-Push und Android-Push ersetzen Mitarbeiter im Urlaub durch die Vertretung');
$away($ben, false);
// 6) Android-Push: Geräte von Mitarbeitern im Urlaub werden übersprungen
$pdo->prepare('INSERT INTO push_devices (staff_id, token, token_hash, created_at) VALUES (?,?,?,?)')->execute([$anna, str_repeat('a', 40), hash('sha256', 'a'), $now]);
$pdo->prepare('INSERT INTO push_devices (staff_id, token, token_hash, created_at) VALUES (?,?,?,?)')->execute([$ben, str_repeat('b', 40), hash('sha256', 'b'), $now]);
$away($ben, true);
$sel = $pdo->prepare("SELECT d.staff_id FROM push_devices d JOIN staff s ON s.id = d.staff_id WHERE s.is_active = 1 AND s.is_on_vacation = 0 AND d.staff_id IN (?,?)");
$sel->execute([$anna, $ben]);
$check($sel->fetchAll(PDO::FETCH_COLUMN) == [$anna], 'Android: nur das Gerät des anwesenden Mitarbeiters bleibt');
// 7) Die Vertretung bekommt stattdessen die Benachrichtigung
$reset();
$away($ben, true, $anna);
notify_staff_new_ticket($ticket);
$to = $queued();
$check(in_array('anna@firma.example', $to, true) && !in_array('ben@firma.example', $to, true), 'Neues Ticket: die Vertretung bekommt die Mail des Abwesenden: ' . json_encode($to));
$check(count(array_keys($to, 'anna@firma.example', true)) === 1, 'Die Vertretung bekommt die Mail nur einmal, auch wenn sie selbst Empfänger ist');
$reset();
$away($chef, true, $ben);
$away($ben, true, $anna);
notify_admins_overdue_ticket($ticket);
$to = $queued();
$check(in_array('anna@firma.example', $to, true) && !in_array('chef@firma.example', $to, true) && !in_array('ben@firma.example', $to, true), 'Kette: Admin im Urlaub → Vertretung im Urlaub → deren Vertretung bekommt die Mail: ' . json_encode($to));
$away($chef, false);
$away($ben, false);
// Push: Abos der Abwesenden gehen an die Vertretung
$sub = fn(int $id, string $ep) => $pdo->prepare('INSERT INTO push_subscriptions (staff_id, endpoint, p256dh, auth, created_at) VALUES (?,?,?,?,?)')->execute([$id, $ep, 'k', 'a', $now]);
$sub($anna, 'https://push.example/anna');
$sub($ben, 'https://push.example/ben');
$endpoints = fn() => array_map(fn($r) => $r['endpoint'], push_subscriptions_for_department($pdo, $dept));
$check(in_array('https://push.example/ben', $endpoints(), true), 'Ohne Urlaub bekommt Ben das Browser-Push');
$away($ben, true, $anna);
$e = $endpoints();
$check(!in_array('https://push.example/ben', $e, true) && in_array('https://push.example/anna', $e, true), 'Im Urlaub geht das Browser-Push an die Vertretung: ' . json_encode($e));
$away($ben, true, null);
$e = $endpoints();
$check(!in_array('https://push.example/ben', $e, true), 'Ohne Vertretung bekommt der Abwesende nichts');
$check(vacation_expand_ids($pdo, [$ben]) === [], 'Ohne Vertretung entfällt der Abwesende');
$away($ben, true, $anna);
$check(vacation_expand_ids($pdo, [$ben, $anna]) === [$anna], 'Vertretung wird nicht doppelt gezählt');
$away($ben, false);
@unlink($db);
if ($failures) {
fwrite(STDERR, "Urlaub ohne Benachrichtigungen fehlgeschlagen:\n- " . implode("\n- ", $failures) . "\n");
exit(1);
}
echo "Urlaub ohne Benachrichtigungen bestanden.\n";