121 lines
7.5 KiB
PHP
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";
|