115 lines
8.3 KiB
PHP
115 lines
8.3 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Prüft, dass ein Update eines aktiven Plugins seine neuen Datenbank-Änderungen automatisch einspielt (vor dem Laden des Plugins),
|
|
* dass fehlerhafte Änderungen sichtbar werden statt still zu scheitern, und die Meldungen nach Installation und Update.
|
|
*/
|
|
|
|
$pluginDir = sys_get_temp_dir() . '/deskhub-um-' . bin2hex(random_bytes(4));
|
|
mkdir($pluginDir, 0755, true);
|
|
putenv('DESKHUB_PLUGINS_DIR=' . $pluginDir);
|
|
putenv('DESKHUB_TESTING=1');
|
|
putenv('DESKHUB_CATALOG_DEFAULTS=' . __DIR__ . '/fixtures/no-catalog.php');
|
|
putenv('DESKHUB_SQLITE_PATH=:memory:');
|
|
require_once dirname(__DIR__) . '/includes/bootstrap.php';
|
|
|
|
$failures = [];
|
|
$check = function (bool $ok, string $what) use (&$failures): void {
|
|
if (!$ok) $failures[] = $what;
|
|
};
|
|
$pdo = get_db();
|
|
|
|
$mkZip = function (string $version, array $migrations, array $perms = ['db.migrations', 'hooks.cron']) use ($pluginDir): string {
|
|
$p = sys_get_temp_dir() . '/um-' . bin2hex(random_bytes(3)) . '.zip';
|
|
$z = new ZipArchive();
|
|
$z->open($p, ZipArchive::CREATE);
|
|
$z->addFromString('updprobe/plugin.json', json_encode(['slug' => 'updprobe', 'name' => 'Update-Probe', 'version' => $version, 'permissions' => $perms]));
|
|
// Das Plugin liest beim Laden die neue Spalte: ohne eingespielte Änderung käme es zu einem Fehler
|
|
$z->addFromString('updprobe/plugin.php', "<?php\nplugin_add_hook('cron.run', function () {});\n\$GLOBALS['updprobe_loaded'] = (\$GLOBALS['updprobe_loaded'] ?? 0) + 1;\n");
|
|
foreach ($migrations as $name => $sql) $z->addFromString('updprobe/migrations/' . $name, $sql);
|
|
$z->close();
|
|
return $p;
|
|
};
|
|
$reboot = function (): void {
|
|
$state = &plugin_state();
|
|
$state['booted'] = false;
|
|
plugins_boot();
|
|
};
|
|
$cols = fn() => array_column($pdo->query('PRAGMA table_info(plugin_updprobe_items)')->fetchAll(), 'name');
|
|
|
|
// Version 1 mit Tabelle
|
|
$v1 = $mkZip('1.0.0', ['001_items.sqlite.sql' => 'CREATE TABLE IF NOT EXISTS plugin_updprobe_items (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)', '001_items.mysql.sql' => 'CREATE TABLE IF NOT EXISTS plugin_updprobe_items (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL)']);
|
|
$r = plugin_install_zip($pdo, $v1);
|
|
$check($r['ok'] && !$r['updated'], 'Version 1 lässt sich installieren');
|
|
$check(plugin_install_message($r, plugins_installed($pdo)['updprobe']) === 'Update-Probe 1.0.0 installiert (nicht signiert). Es ist noch nicht aktiv.', 'Meldung bei Neuinstallation: ' . plugin_install_message($r, plugins_installed($pdo)['updprobe']));
|
|
$check(plugin_set_active($pdo, 'updprobe', true, ['db.migrations', 'hooks.cron'])['ok'], 'Version 1 lässt sich aktivieren');
|
|
$check($cols() === ['id', 'name'], 'Tabelle von Version 1 ist da');
|
|
$reboot();
|
|
$check(($GLOBALS['updprobe_loaded'] ?? 0) === 1, 'Plugin wird geladen');
|
|
$hash1 = $pdo->query("SELECT package_hash FROM plugins WHERE slug = 'updprobe'")->fetchColumn();
|
|
$check(get_setting('plugin_migrated.updprobe', '') === $hash1, 'Paketstand von Version 1 ist als eingespielt vermerkt');
|
|
$migrationsBefore = (int) $pdo->query("SELECT COUNT(*) FROM schema_migrations WHERE migration LIKE 'plugin_updprobe_%'")->fetchColumn();
|
|
|
|
// Version 2 bringt eine neue Spalte mit; das Plugin bleibt aktiv
|
|
$v2 = $mkZip('1.1.0', [
|
|
'001_items.sqlite.sql' => 'CREATE TABLE IF NOT EXISTS plugin_updprobe_items (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)',
|
|
'001_items.mysql.sql' => 'CREATE TABLE IF NOT EXISTS plugin_updprobe_items (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL)',
|
|
'002_extra.sqlite.sql' => 'ALTER TABLE plugin_updprobe_items ADD COLUMN extra TEXT NULL',
|
|
'002_extra.mysql.sql' => 'ALTER TABLE plugin_updprobe_items ADD COLUMN extra VARCHAR(50) NULL',
|
|
]);
|
|
$r2 = plugin_install_zip($pdo, $v2);
|
|
$row = plugins_installed($pdo)['updprobe'];
|
|
$check($r2['ok'] && $r2['updated'] && (int) $row['is_active'] === 1, 'Update eines aktiven Plugins: bleibt aktiv');
|
|
$msg = plugin_install_message($r2, $row);
|
|
$check(str_contains($msg, 'auf Version 1.1.0 aktualisiert') && str_contains($msg, 'läuft weiter') && !str_contains($msg, 'noch nicht aktiv'), 'Meldung beim Update eines aktiven Plugins: ' . $msg);
|
|
$check(!in_array('extra', $cols(), true), 'unmittelbar nach dem Update ist die neue Spalte noch nicht da (läuft beim nächsten Aufruf)');
|
|
$reboot();
|
|
$check(in_array('extra', $cols(), true), 'beim nächsten Aufruf wird die neue Spalte automatisch eingespielt');
|
|
$hash2 = $pdo->query("SELECT package_hash FROM plugins WHERE slug = 'updprobe'")->fetchColumn();
|
|
$check($hash2 !== $hash1 && get_setting('plugin_migrated.updprobe', '') === $hash2, 'Paketstand von Version 2 ist vermerkt');
|
|
$check((int) $pdo->query("SELECT COUNT(*) FROM schema_migrations WHERE migration LIKE 'plugin_updprobe_%'")->fetchColumn() === $migrationsBefore + 1, 'nur die neue Änderung wurde eingespielt');
|
|
$reboot();
|
|
$check((int) $pdo->query("SELECT COUNT(*) FROM schema_migrations WHERE migration LIKE 'plugin_updprobe_%'")->fetchColumn() === $migrationsBefore + 1, 'weitere Aufrufe spielen nichts mehr ein');
|
|
$check((int) $pdo->query("SELECT fail_count FROM plugins WHERE slug = 'updprobe'")->fetchColumn() === 0, 'keine Fehler gezählt');
|
|
|
|
// Version 3 mit kaputter Änderung: Fehler wird sichtbar, das Plugin wird nicht stillschweigend weiterbenutzt
|
|
$v3 = $mkZip('1.2.0', [
|
|
'001_items.sqlite.sql' => 'CREATE TABLE IF NOT EXISTS plugin_updprobe_items (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)',
|
|
'002_extra.sqlite.sql' => 'ALTER TABLE plugin_updprobe_items ADD COLUMN extra TEXT NULL',
|
|
'003_kaputt.sqlite.sql' => 'ALTER TABLE plugin_gibt_es_nicht ADD COLUMN x TEXT NULL',
|
|
]);
|
|
plugin_install_zip($pdo, $v3);
|
|
$reboot();
|
|
$p = plugins_installed($pdo)['updprobe'];
|
|
$check((int) $p['fail_count'] >= 1 && str_contains((string) $p['last_error'], 'Datenbank-Änderung fehlgeschlagen'), 'fehlerhafte Änderung wird gemeldet: ' . $p['last_error']);
|
|
$check(get_setting('plugin_migrated.updprobe', '') === $hash2, 'der kaputte Stand gilt nicht als eingespielt (nächster Versuch beim nächsten Aufruf)');
|
|
|
|
// Update mit neuer Berechtigung (nicht signiert): Plugin wird angehalten, Meldung sagt es
|
|
$v4 = $mkZip('1.3.0', ['001_items.sqlite.sql' => 'CREATE TABLE IF NOT EXISTS plugin_updprobe_items (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)'], ['db.migrations', 'hooks.cron', 'ui.menu']);
|
|
plugin_set_active($pdo, 'updprobe', true, ['db.migrations', 'hooks.cron']);
|
|
$r4 = plugin_install_zip($pdo, $v4);
|
|
$check($r4['needs_reapproval'] === ['ui.menu'] && (int) plugins_installed($pdo)['updprobe']['is_active'] === 0, 'nicht signiertes Update mit neuer Berechtigung wird angehalten');
|
|
$check(str_contains(plugin_install_message($r4, plugins_installed($pdo)['updprobe']), 'zusätzliche Berechtigungen') && str_contains(plugin_install_message($r4, plugins_installed($pdo)['updprobe']), 'nicht aktiv'), 'Meldung nennt die zusätzlichen Berechtigungen und dass das Plugin nicht aktiv ist');
|
|
// Update eines inaktiven Plugins ohne neue Berechtigungen
|
|
plugin_set_active($pdo, 'updprobe', true, ['db.migrations', 'hooks.cron', 'ui.menu']);
|
|
plugin_set_active($pdo, 'updprobe', false);
|
|
$v5 = $mkZip('1.4.0', ['001_items.sqlite.sql' => 'CREATE TABLE IF NOT EXISTS plugin_updprobe_items (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)'], ['db.migrations', 'hooks.cron', 'ui.menu']);
|
|
$r5 = plugin_install_zip($pdo, $v5);
|
|
$check(str_contains(plugin_install_message($r5, plugins_installed($pdo)['updprobe']), 'weiterhin nicht aktiv'), 'Meldung beim Update eines inaktiven Plugins');
|
|
|
|
// Ohne Berechtigung db.migrations kein automatisches Einspielen
|
|
$check(!str_contains((string) file_get_contents(dirname(__DIR__) . '/includes/plugins.php'), "plugin_ensure_migrated(\$slug, (string) (\$row['package_hash'] ?? ''));\n") || str_contains((string) file_get_contents(dirname(__DIR__) . '/includes/plugins.php'), "in_array('db.migrations', \$manifest['permissions'], true)) plugin_ensure_migrated"), 'nur Plugins mit der Berechtigung db.migrations bekommen automatische Änderungen');
|
|
|
|
foreach (scandir($pluginDir) ?: [] as $entry) {
|
|
if ($entry === '.' || $entry === '..') continue;
|
|
is_dir($pluginDir . '/' . $entry) ? plugin_rmdir($pluginDir . '/' . $entry) : @unlink($pluginDir . '/' . $entry);
|
|
}
|
|
@rmdir($pluginDir);
|
|
foreach ([$v1, $v2, $v3, $v4, $v5] as $z) @unlink($z);
|
|
if ($failures) {
|
|
fwrite(STDERR, "Fehlgeschlagen:\n - " . implode("\n - ", $failures) . "\n");
|
|
exit(1);
|
|
}
|
|
echo 'Plugin update migration checks passed.' . "\n";
|