113 lines
4.8 KiB
PHP
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);
|
|
}
|