Files
2026-10-24 20:14:43 +00:00

121 lines
7.5 KiB
PHP

<?php
declare(strict_types=1);
/**
* Prüft die Live-Anzeige für neue Tickets: Ausgangsstand ohne Hinweise, neue Tickets seit der letzten ID, Ausschluss von Spam, Live-Chats und
* zusammengeführten Tickets, Abteilungsrechte, Kennzahlen, Begrenzung auf 6 Hinweise und die Einbindung ins Layout.
*/
$db = sys_get_temp_dir() . '/deskhub-lt-' . bin2hex(random_bytes(4)) . '.sqlite';
putenv('DESKHUB_TESTING=1');
putenv('DESKHUB_SQLITE_PATH=' . $db);
require_once dirname(__DIR__) . '/includes/bootstrap.php';
$failures = [];
$check = function (bool $ok, string $what) use (&$failures): void {
if (!$ok) $failures[] = $what;
};
$pdo = get_db();
$d1 = (int) $pdo->query('SELECT id FROM departments ORDER BY id LIMIT 1')->fetchColumn();
$pdo->prepare("INSERT INTO departments (name, email, is_active, created_at) VALUES ('Technik', 'technik@team.example', 1, ?)")->execute([db_now()]);
$d2 = (int) $pdo->lastInsertId();
$prio = (int) $pdo->query('SELECT id FROM priorities ORDER BY sort_order LIMIT 1')->fetchColumn();
$mkStaff = function (string $name, string $role, ?int $dept) use ($pdo): int {
$pdo->prepare('INSERT INTO staff (name, email, password_hash, role, department_id, is_active, created_at) VALUES (?,?,?,?,?,1,?)')->execute([$name, strtolower($name) . '@team.example', 'x', $role, $dept, db_now()]);
return (int) $pdo->lastInsertId();
};
$anna = $mkStaff('Anna', 'agent', $d1);
$chef = $mkStaff('Chef', 'admin', null);
$n = 0;
$mk = function (string $subject, int $dept, array $o = []) use ($pdo, $prio, &$n): int {
$n++;
$now = db_now();
$pdo->prepare('INSERT INTO tickets (ticket_number, subject, requester_name, requester_email, department_id, priority_id, status, source, is_spam, merged_into, assigned_staff_id, resolution_due_at, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)')
->execute(['LT-' . $n, $subject, $o['name'] ?? 'Kunde', 'k@x.example', $dept, $prio, $o['status'] ?? 'open', $o['source'] ?? 'web', $o['spam'] ?? 0, $o['merged'] ?? null, $o['assigned'] ?? null, $o['due'] ?? null, $now, $now]);
return (int) $pdo->lastInsertId();
};
$call = function (int $staffId, int $since) {
$_SESSION['staff_id'] = $staffId;
$_GET = ['since' => (string) $since];
// current_staff() merkt sich den Mitarbeiter je Prozess; für den Test über Reflection zurücksetzen
$rf = new ReflectionFunction('current_staff');
foreach ($rf->getStaticVariables() as $k => $v) { /* nur lesen */ }
ob_start();
include dirname(__DIR__) . '/staff/live_tickets.php';
return json_decode((string) ob_get_clean(), true);
};
// current_staff() cached den ersten Mitarbeiter im Prozess: deshalb jeden Fall in einem eigenen Prozess prüfen
$php = escapeshellarg(PHP_BINARY);
$quiet = PHP_OS_FAMILY === 'Windows' ? '2>NUL' : '2>/dev/null';
$script = sys_get_temp_dir() . '/lt-call-' . bin2hex(random_bytes(3)) . '.php';
file_put_contents($script, "<?php\nputenv('DESKHUB_TESTING=1');\nputenv('DESKHUB_SQLITE_PATH=' . " . var_export($db, true) . ");\nrequire " . var_export(dirname(__DIR__) . '/includes/bootstrap.php', true) . ";\nif ((int) (\$argv[1] ?? 0) > 0) \$_SESSION['staff_id'] = (int) \$argv[1];\n\$_GET = ['since' => \$argv[2] ?? '0'];\ninclude " . var_export(dirname(__DIR__) . '/staff/live_tickets.php', true) . ";\n");
$run = function (int $staffId, int $since) use ($php, $script, $quiet): ?array {
$out = [];
exec("{$php} " . escapeshellarg($script) . " {$staffId} {$since} " . $quiet, $out);
return json_decode(implode("\n", $out), true);
};
// Ausgangsstand
$base = $mk('Altes Ticket', $d1);
$r0 = $run($chef, 0);
$check(is_array($r0) && $r0['latest'] === $base && $r0['tickets'] === [] && $r0['more'] === 0, 'Ausgangsstand liefert die letzte ID und keine Hinweise: ' . json_encode($r0));
$check(is_array($r0) && $r0['counts']['open'] === 1 && $r0['counts']['unassigned'] === 1 && $r0['counts']['mine'] === 0 && $r0['counts']['unanswered'] === 1 && $r0['counts']['overdue'] === 0, 'Kennzahlen stimmen');
// Neue Tickets
$t1 = $mk('Login geht nicht', $d1, ['name' => 'Erika Muster']);
$t2 = $mk('Technik-Frage', $d2);
$mk('Spam', $d1, ['spam' => 1]);
$mk('Live-Chat', $d1, ['source' => 'chat']);
$mk('Zusammengeführt', $d1, ['merged' => $t1]);
$r1 = $run($chef, $base);
$ids = array_column((array) ($r1['tickets'] ?? []), 'id');
$check($ids === [$t1, $t2], 'Administrator sieht die beiden echten neuen Tickets (kein Spam, kein Chat, kein zusammengeführtes): ' . json_encode($ids));
$check(($r1['tickets'][0]['subject'] ?? '') === 'Login geht nicht' && ($r1['tickets'][0]['name'] ?? '') === 'Erika Muster' && ($r1['tickets'][0]['number'] ?? '') === 'LT-2' && ($r1['tickets'][1]['department'] ?? '') === 'Technik', 'Hinweis enthält Nummer, Betreff, Name und Abteilung');
$check(($r1['latest'] ?? 0) === $t2, 'latest ist die ID des neuesten sichtbaren Tickets (Spam, Chat und zusammengeführte zählen nicht)');
// Abteilungsrechte
$r2 = $run($anna, $base);
$check(array_column((array) ($r2['tickets'] ?? []), 'id') === [$t1], 'Mitarbeiter sehen nur Tickets ihrer Abteilung');
$check(($r2['counts']['open'] ?? -1) === 5 && ($r1['counts']['open'] ?? -1) === 6, 'Zahlen werden je Abteilung gezählt wie auf der Übersicht (Mitarbeiter 5, Administrator 6): ' . ($r2['counts']['open'] ?? '?') . '/' . ($r1['counts']['open'] ?? '?'));
$ra = $run($anna, 0);
$check(($ra['counts']['open'] ?? -1) === $r2['counts']['open'] && ($ra['counts']['open'] ?? -1) < ($run($chef, 0)['counts']['open'] ?? 0), 'Mitarbeiter zählen weniger als Administratoren');
// Nach dem Abruf nichts Neues
$r3 = $run($chef, (int) $r1['latest']);
$check(($r3['tickets'] ?? null) === [] && ($r3['more'] ?? -1) === 0, 'kein Hinweis, wenn nichts Neues kam');
// Mir zugewiesen und überfällig
$mk('Zugewiesen', $d1, ['assigned' => $anna, 'due' => date('Y-m-d H:i:s', time() - 3600)]);
$r4 = $run($anna, 0);
$check(($r4['counts']['mine'] ?? 0) === 1 && ($r4['counts']['overdue'] ?? 0) === 1, 'zugewiesene und überfällige Tickets werden gezählt');
// Begrenzung auf 6 Hinweise
$before = (int) $run($chef, 0)['latest'];
for ($i = 0; $i < 9; $i++) $mk('Massenticket ' . $i, $d1);
$r5 = $run($chef, $before);
$check(count($r5['tickets'] ?? []) === 6 && ($r5['more'] ?? 0) === 3, 'höchstens 6 Hinweise, der Rest wird als „weitere“ gezählt: ' . count($r5['tickets'] ?? []) . ' / ' . ($r5['more'] ?? '?'));
$check(($r5['tickets'][0]['subject'] ?? '') === 'Massenticket 0', 'älteste zuerst');
// Ohne Anmeldung
$out = [];
exec("{$php} " . escapeshellarg($script) . " 0 0 " . $quiet, $out);
$check(json_decode(implode('', $out), true) === ['error' => 'login'], 'ohne Anmeldung: Fehlermeldung statt Daten');
// Einbindung
$footer = (string) file_get_contents(dirname(__DIR__) . '/staff/_footer.php');
$check(str_contains($footer, '/assets/js/live-tickets.js'), 'Skript ist im Layout eingebunden');
$reports = (string) file_get_contents(dirname(__DIR__) . '/staff/reports.php');
$check(substr_count($reports, 'data-live-count=') === 5, 'fünf Kennzahlen der Übersicht aktualisieren sich live');
$js = (string) file_get_contents(dirname(__DIR__) . '/assets/js/live-tickets.js');
$check(str_contains($js, '/staff/live_tickets.php') && str_contains($js, 'textContent') && !str_contains($js, 'innerHTML'), 'Skript fragt den Abruf ab und setzt Inhalte nur als Text (kein innerHTML)');
$check(preg_match('/setTimeout\(poll, document\.hidden \? 30000 : 10000\)/', $js) === 1, 'Abruf alle 10 Sekunden (im Hintergrund-Tab alle 30)');
@unlink($script);
@unlink($db);
if ($failures) {
fwrite(STDERR, "Fehlgeschlagen:\n - " . implode("\n - ", $failures) . "\n");
exit(1);
}
echo 'Live tickets checks passed.' . "\n";