Files
2026-11-05 21:22:03 +00:00

97 lines
6.6 KiB
PHP

<?php
declare(strict_types=1);
/**
* Gelöste und geschlossene Tickets gelten gleich als erledigt: Die SLA-Uhr läuft nicht weiter, es gibt kein „überfällig“, keine SLA-Meldung,
* keine Eskalation, keine Erinnerung und keine Wiedervorlage-Benachrichtigung mehr.
*/
putenv('DESKHUB_TESTING=1');
putenv('DESKHUB_SQLITE_PATH=:memory:');
require_once dirname(__DIR__) . '/includes/bootstrap.php';
if (!defined('DESKHUB_CRON_RUNNER')) define('DESKHUB_CRON_RUNNER', true);
require_once dirname(__DIR__) . '/cron/check_sla.php';
$failures = [];
$total = 0;
$check = function (bool $ok, string $what) use (&$failures, &$total): void {
$total++;
if (!$ok) $failures[] = $what;
};
$pdo = get_db();
$now = date('Y-m-d H:i:s');
$ago = fn(int $h) => date('Y-m-d H:i:s', time() - $h * 3600);
$dept = (int) $pdo->query('SELECT id FROM departments ORDER BY id LIMIT 1')->fetchColumn();
$prio = (int) $pdo->query('SELECT id FROM priorities ORDER BY id LIMIT 1')->fetchColumn();
$pdo->exec('DELETE FROM staff');
$pdo->prepare('INSERT INTO staff (name, email, password_hash, role, department_id, is_active, created_at) VALUES (?,?,?,?,?,1,?)')->execute(['Agent', 'agent@test.de', 'x', 'agent', $dept, $now]);
$agent = (int) $pdo->lastInsertId();
$mk = function (string $n, string $status, array $o = []) use ($pdo, $dept, $prio, $now, $agent): int {
$pdo->prepare('INSERT INTO tickets (ticket_number, subject, requester_name, requester_email, department_id, priority_id, status, source, assigned_staff_id, resolution_due_at, first_response_due_at, is_overdue, snoozed_until, snoozed_by, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)')
->execute([$n, 'S ' . $n, 'K', 'k@example.org', $dept, $prio, $status, 'web', $agent, $o['due'] ?? null, $o['rdue'] ?? null, $o['overdue'] ?? 0, $o['snooze'] ?? null, $o['snooze'] ?? null ? $agent : null, $o['created'] ?? $now, $o['updated'] ?? $now]);
return (int) $pdo->lastInsertId();
};
$row = fn(int $id) => $pdo->query("SELECT * FROM tickets WHERE id = {$id}")->fetch(PDO::FETCH_ASSOC);
// 1. Erledigen löscht „überfällig“ und die Wiedervorlage, für gelöst und geschlossen gleich
foreach (['resolved', 'closed'] as $st) {
$t = $mk('D-' . $st, 'open', ['due' => $ago(5), 'overdue' => 1, 'snooze' => $ago(-5), 'created' => $ago(60)]);
$pdo->prepare('UPDATE tickets SET status = ? WHERE id = ?')->execute([$st, $t]);
ticket_sla_status_transition($pdo, $t, 'open', $st);
$r = $row($t);
$check((int) $r['is_overdue'] === 0 && $r['snoozed_until'] === null, "offen → {$st}: nicht mehr überfällig, Wiedervorlage entfällt");
}
// Ein offenes Ticket bleibt unberührt
$tOpen = $mk('D-open', 'open', ['due' => $ago(5), 'overdue' => 1, 'created' => $ago(60)]);
ticket_sla_status_transition($pdo, $tOpen, 'open', 'answered');
$check((int) $row($tOpen)['is_overdue'] === 1, 'offen → beantwortet: bleibt überfällig');
// gelöst → geschlossen: nichts Neues
$tc = $mk('D-rc', 'closed', ['due' => $ago(5), 'created' => $ago(60)]);
ticket_sla_status_transition($pdo, $tc, 'resolved', 'closed');
$check((int) $row($tc)['is_overdue'] === 0, 'gelöst → geschlossen: weiter nicht überfällig');
// 2. SLA-Prüfung fasst erledigte Tickets nicht an und meldet nichts
$a = $mk('S-closed', 'closed', ['due' => $ago(9), 'rdue' => $ago(20), 'created' => $ago(80)]);
$b = $mk('S-resolved', 'resolved', ['due' => $ago(9), 'rdue' => $ago(20), 'created' => $ago(80)]);
$c = $mk('S-flag', 'closed', ['due' => $ago(9), 'overdue' => 1, 'created' => $ago(80)]);
$res = run_check_sla($pdo);
$check($row($a)['is_overdue'] == 0 && $row($b)['is_overdue'] == 0, 'SLA-Prüfung: geschlossene und gelöste Tickets werden nicht überfällig');
$check((int) $row($c)['is_overdue'] === 0, 'SLA-Prüfung: ein übrig gebliebenes „überfällig“ bei einem erledigten Ticket wird entfernt');
$check($res['newlyOverdue'] === 0, 'SLA-Prüfung: keine neue Überschreitung gemeldet');
$hist = (int) $pdo->query("SELECT COUNT(*) FROM ticket_sla_history WHERE ticket_id IN ({$a},{$b}) AND event_type = 'overdue'")->fetchColumn();
$check($hist === 0, 'SLA-Verlauf: für erledigte Tickets entsteht kein „überschritten“');
// 3. Eskalationsregeln erfassen keine erledigten Tickets
require_once dirname(__DIR__) . '/cron/check_escalations.php';
$pdo->prepare("INSERT INTO escalation_rules (name,is_active,trigger_type,trigger_hours,action_notify_admin,action_notify_staff,sort_order,created_at) VALUES (?,?,?,?,?,?,?,?)")
->execute(['Alt', 1, 'overdue', 1, 1, 1, 1, $now]);
$eo = $mk('E-open', 'open', ['due' => $ago(5), 'overdue' => 1, 'created' => $ago(50)]);
$ec = $mk('E-closed', 'closed', ['due' => $ago(5), 'overdue' => 1, 'created' => $ago(50)]);
$r = run_check_escalations($pdo);
$check((int) $pdo->query("SELECT COUNT(*) FROM escalation_log WHERE ticket_id = {$eo}")->fetchColumn() === 1, 'Eskalation: die Regel greift bei einem offenen Ticket (Gegenprobe)');
$hit = (int) $pdo->query("SELECT COUNT(*) FROM escalation_log WHERE ticket_id IN ({$a},{$b},{$c},{$ec})")->fetchColumn();
$check($hit === 0, 'Eskalation: erledigte Tickets werden nicht eskaliert');
// 4. Wiedervorlage: bei erledigten Tickets still entfernt, bei offenen mit Benachrichtigung
$sc = $mk('W-closed', 'closed', ['snooze' => $ago(1)]);
$so = $mk('W-open', 'open', ['snooze' => $ago(1)]);
$woken = ticket_snooze_wake_due($pdo);
$check($woken === 1, "Wiedervorlage: nur das offene Ticket wird zurückgeholt (war {$woken})");
$check($row($sc)['snoozed_until'] === null && $row($so)['snoozed_until'] === null, 'Wiedervorlage: bei beiden aufgeräumt');
$act = fn(int $id) => (int) $pdo->query("SELECT COUNT(*) FROM activity_log WHERE ticket_id = {$id} AND action LIKE '%Wiedervorlage fällig%'")->fetchColumn();
$check($act($sc) === 0 && $act($so) === 1, 'Wiedervorlage: Hinweis „fällig“ nur beim offenen Ticket');
// 5. Erinnerung „Ticket wartet auf Antwort“ erfasst nur offene Tickets
set_setting('wait_reminder_enabled', '1');
set_setting('wait_reminder_hours', '1');
$wo = $mk('A-open', 'open', ['due' => $ago(-40), 'created' => $ago(100), 'updated' => $ago(100)]);
$wc = $mk('A-closed', 'closed', ['due' => $ago(-40), 'created' => $ago(100), 'updated' => $ago(100)]);
$wr = wait_reminders_run($pdo, $now);
$check((int) ($wr['sent'] ?? 0) === 1, 'Erinnerung: das offene Ticket wird erinnert (Gegenprobe)');
$check($row($wc)['wait_reminded_at'] === null && $row($wo)['wait_reminded_at'] !== null, 'Erinnerung: für das erledigte Ticket wird nichts gesendet');
echo $failures ? "FEHLGESCHLAGEN:\n - " . implode("\n - ", $failures) . "\n" : '';
echo "Ticket-erledigt-Test: " . ($total - count($failures)) . " von {$total} Prüfungen in Ordnung.\n";
exit($failures ? 1 : 0);