Files
deskhub/DeskHub-Webseite/tests/database_compatibility.php
2026-10-16 19:12:24 +00:00

113 lines
4.8 KiB
PHP

<?php
declare(strict_types=1);
require_once dirname(__DIR__) . '/includes/db.php';
require_once dirname(__DIR__) . '/includes/functions.php';
require_once dirname(__DIR__) . '/database/migrate.php';
$root = dirname(__DIR__);
$schema = file_get_contents($root . '/database/schema.sql');
if ($schema === false) {
fail('Datenbankschema konnte nicht gelesen werden.');
}
$pdo = new PDO('sqlite::memory:');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$pdo->exec('PRAGMA foreign_keys = ON');
$pdo->exec(convert_schema_to_sqlite($schema));
$result = run_migrations($pdo, true);
if ($result['errors']) {
fail('SQLite-Migrationen fehlgeschlagen: ' . implode('; ', $result['errors']));
}
$secondRun = run_migrations($pdo, true);
if ($secondRun['errors'] || $secondRun['applied'] !== 0) {
fail('SQLite-Migrationen sind bei wiederholter Ausführung nicht idempotent.');
}
foreach ([
['push_subscriptions', 'staff_id', 'staff'],
['ticket_thread_comments', 'thread_id', 'ticket_threads'],
['ticket_thread_comments', 'staff_id', 'staff'],
] as [$table, $column, $parent]) {
if (!sqlite_has_foreign_key($pdo, $table, $column, $parent)) {
fail("SQLite-Migration erzeugt {$table}.{$column} ohne FK auf {$parent}.");
}
}
$sqliteReportExpression = report_avg_response_hours_expression('sqlite');
$mysqlReportExpression = report_avg_response_hours_expression('mysql');
if (stripos($sqliteReportExpression, 'julianday') === false) {
fail('Der SQLite-Reportausdruck verwendet keine SQLite-kompatible Stundenberechnung.');
}
if (stripos($mysqlReportExpression, 'TIMESTAMPDIFF(SECOND') === false
|| stripos($mysqlReportExpression, 'julianday') !== false) {
fail('Der MySQL/MariaDB-Reportausdruck ist nicht treibersicher.');
}
$reportPdo = new PDO('sqlite::memory:');
$reportHours = $reportPdo->query(
"SELECT {$sqliteReportExpression}
FROM (SELECT '2026-08-03 08:00:00' AS created_at, '2026-08-03 09:30:00' AS first_responded_at) t"
)->fetchColumn();
if (abs((float) $reportHours - 1.5) > 0.0001) {
fail('Der SQLite-Reportausdruck berechnet Antwortstunden falsch.');
}
$legacy = new PDO('sqlite::memory:');
$legacy->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$legacy->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$legacy->exec('PRAGMA foreign_keys = ON');
$legacy->exec('CREATE TABLE staff (id INTEGER PRIMARY KEY)');
$legacy->exec('CREATE TABLE ticket_threads (id INTEGER PRIMARY KEY)');
$legacy->exec("CREATE TABLE push_subscriptions (
id INTEGER PRIMARY KEY AUTOINCREMENT, staff_id INT NOT NULL, endpoint TEXT NOT NULL,
p256dh TEXT NOT NULL, auth TEXT NOT NULL, created_at DATETIME NOT NULL
)");
$legacy->exec("CREATE TABLE ticket_thread_comments (
id INTEGER PRIMARY KEY AUTOINCREMENT, thread_id INT NOT NULL, staff_id INT NOT NULL,
message TEXT NOT NULL, created_at DATETIME NOT NULL
)");
$legacy->exec('INSERT INTO staff (id) VALUES (1)');
$legacy->exec('INSERT INTO ticket_threads (id) VALUES (1)');
$legacy->exec("INSERT INTO push_subscriptions VALUES (1, 1, 'endpoint', 'key', 'auth', CURRENT_TIMESTAMP)");
$legacy->exec("INSERT INTO ticket_thread_comments VALUES (1, 1, 1, 'Kommentar', CURRENT_TIMESTAMP)");
repair_sqlite_foreign_keys($legacy);
if ((int) $legacy->query('SELECT COUNT(*) FROM push_subscriptions')->fetchColumn() !== 1
|| (int) $legacy->query('SELECT COUNT(*) FROM ticket_thread_comments')->fetchColumn() !== 1) {
fail('Die SQLite-FK-Reparatur bewahrt bestehende Daten nicht.');
}
$legacy->exec('DELETE FROM staff WHERE id = 1');
if ((int) $legacy->query('SELECT COUNT(*) FROM push_subscriptions')->fetchColumn() !== 0
|| (int) $legacy->query('SELECT COUNT(*) FROM ticket_thread_comments')->fetchColumn() !== 0) {
fail('Die reparierten SQLite-FKs führen ON DELETE CASCADE nicht aus.');
}
$forbiddenTokens = [
'cron/process_backups.php' => ['INSERT OR REPLACE'],
'staff/backup.php' => ['INSERT OR REPLACE'],
'staff/ticket_presence.php' => ['INSERT OR REPLACE', "datetime('now'"],
'staff/availability.php' => ['INSERT OR REPLACE', 'REPLACE INTO'],
'cron/process_recurring.php' => ['DATE_ADD('],
'cron/check_escalations.php' => ["julianday('now')"],
'staff/recurring_tickets.php' => ['DATE_ADD(', 'CURDATE('],
];
foreach ($forbiddenTokens as $relativePath => $tokens) {
$source = file_get_contents($root . '/' . $relativePath);
if ($source === false) {
fail("Datei {$relativePath} konnte nicht gelesen werden.");
}
foreach ($tokens as $token) {
if (stripos($source, $token) !== false) {
fail("{$relativePath} enthält weiterhin den fehlerhaften Dialekt-Token {$token}.");
}
}
}
echo "Datenbankdialekte und SQLite-Migrationen sind kompatibel.\n";
function fail(string $message): never
{
fwrite(STDERR, $message . "\n");
exit(1);
}