Quelltext des aktuellen Standes
<?php
declare(strict_types=1);
use OpenNIT\Content\UpdateManager;
/**
* Deckt die Auswahl des Update-Kanals ab: welche Kanäle eine Instanz anbietet,
* wie ein gespeicherter Name aufgelöst wird und dass ein optionaler Kanal ohne
* Freischaltung in der Umgebung nirgends erreichbar ist.
*/
return static function (): void {
$wurzel = dirname(__DIR__);
/** Setzt die Freischaltung für die Dauer eines Falls und räumt danach auf. */
$mitSchalter = static function (?string $wert, callable $fall): void {
$name = 'OPENNIT_UPDATE_CHANNELS_EXTRA';
$vorher = $_ENV[$name] ?? null;
if ($wert === null) {
unset($_ENV[$name], $_SERVER[$name]);
} else {
$_ENV[$name] = $wert;
}
try {
$fall();
} finally {
unset($_ENV[$name], $_SERVER[$name]);
if ($vorher !== null) {
$_ENV[$name] = $vorher;
}
}
};
// ----------------------------------------------------------------
// Standardzustand: nur die beiden bekannten Kanäle
// ----------------------------------------------------------------
test('Ohne Freischaltung stehen genau Stable und Development zur Wahl', function () use ($mitSchalter) {
$mitSchalter(null, function () {
assertSame(['stable', 'development'], array_keys(UpdateManager::availableChannels()));
});
});
test('Ein nicht freigeschalteter Kanalname fällt auf Stable zurück', function () use ($mitSchalter) {
// Auch ein von Hand in die runtime.php geschriebener Name darf die
// Instanz nicht auf einen gesperrten Proxy zeigen lassen.
$mitSchalter(null, function () {
assertSame('stable', UpdateManager::resolveChannel('experimental'));
assertSame('stable', UpdateManager::resolveChannel('gibt-es-nicht'));
assertSame('stable', UpdateManager::resolveChannel(null));
assertSame('development', UpdateManager::resolveChannel('development'));
});
});
// ----------------------------------------------------------------
// Freigeschalteter Zustand
// ----------------------------------------------------------------
test('Mit Freischaltung kommt der optionale Kanal hinzu', function () use ($mitSchalter) {
$mitSchalter('1', function () {
$kanaele = UpdateManager::availableChannels();
assertTrue(array_key_exists('experimental', $kanaele));
assertSame('experimental', UpdateManager::resolveChannel('experimental'));
// Die bekannten Kanäle bleiben unverändert erreichbar.
assertSame(UpdateManager::CHANNELS['stable'], $kanaele['stable']);
assertSame(UpdateManager::CHANNELS['development'], $kanaele['development']);
});
});
test('Der Schalter versteht die üblichen Schreibweisen', function () use ($mitSchalter) {
foreach (['1', 'true', 'on', 'yes'] as $an) {
$mitSchalter($an, function () use ($an) {
assertSame('experimental', UpdateManager::resolveChannel('experimental'), "Wert: {$an}");
});
}
foreach (['0', 'false', 'off', '', 'vielleicht'] as $aus) {
$mitSchalter($aus, function () use ($aus) {
assertSame('stable', UpdateManager::resolveChannel('experimental'), "Wert: {$aus}");
});
}
});
// ----------------------------------------------------------------
// Verdrahtung: alle Stellen nutzen dieselbe Auflösung
// ----------------------------------------------------------------
test('Der Updater prüft die Auswahl gegen die verfügbaren Kanäle', function () use ($wurzel) {
// Die gespeicherte Auswahl darf nicht gegen die feste Liste geprüft
// werden – sonst wäre der optionale Kanal auch ohne Freischaltung
// speicherbar oder mit Freischaltung nicht speicherbar.
$src = (string)file_get_contents($wurzel . '/src/Controllers/Admin/UpdateController.php');
assertFalse(str_contains($src, 'UpdateManager::CHANNELS'), 'Feste Kanalliste im Controller');
assertTrue(str_contains($src, 'UpdateManager::resolveChannel'), 'Auflösung im Controller');
assertTrue(str_contains($src, 'UpdateManager::availableChannels()'), 'Kanalliste im Controller');
});
test('Die Kanal-Kachel erscheint nur bei freigeschaltetem Kanal', function () use ($wurzel) {
$tpl = (string)file_get_contents($wurzel . '/templates/admin/update/index.php');
// Jeder Auftritt des optionalen Kanals im Formular liegt hinter der Prüfung.
$bloecke = preg_split('/<\?php if \(in_array\(\'experimental\', \$updateChannels, true\)\): \?>/', $tpl);
assertSame(3, count($bloecke), 'Erwartet: Kachel und Warnung je hinter einer Prüfung');
assertFalse(str_contains($bloecke[0], 'experimental'), 'Optionaler Kanal ausserhalb der Pruefung');
});
test('Die Kanal-Kachel benennt jeden Kanal beim Namen', function () use ($wurzel) {
// Eine Zweiweg-Unterscheidung wuerde jeden Kanal ausser Development als
// "Stable" und "empfohlen" ausweisen – gerade beim risikoreichsten also
// das Gegenteil dessen, was gilt.
$tpl = (string)file_get_contents($wurzel . '/templates/admin/update/index.php');
$von = strpos($tpl, '$kanalKachel = [');
assertTrue($von !== false, 'Keine Zuordnung je Kanal');
$block = substr($tpl, (int)$von, 600);
foreach (['stable', 'development', 'experimental'] as $kanal) {
assertTrue(str_contains($block, "'{$kanal}'"), "Kanal fehlt in der Zuordnung: {$kanal}");
}
// Unbekannte Namen folgen der Auflösung im Controller.
assertTrue(str_contains($block, "][\$updateChannel] ?? ['Stable'"), 'Kein Ausweichwert');
// Die Kachel liest nur noch aus der Zuordnung.
$kachel = substr($tpl, (int)strpos($tpl, 'status-tile-label">Update-Kanal'), 400);
assertFalse(str_contains($kachel, "=== 'development'"), 'Zweiweg-Unterscheidung in der Kachel');
});
test('Der optionale Kanal ist in der Auslieferung nicht beschrieben', function () use ($wurzel) {
// Er soll nur finden, wer ihn kennt – also kein Eintrag in den
// Vorlagen, dem Handbuch oder dem Changelog.
foreach (['/.env.example', '/CHANGELOG.md', '/README.md'] as $datei) {
$pfad = $wurzel . $datei;
if (!is_file($pfad)) {
continue;
}
$inhalt = (string)file_get_contents($pfad);
assertFalse(stripos($inhalt, 'OPENNIT_UPDATE_CHANNELS_EXTRA') !== false, "Schalter in {$datei}");
assertFalse(stripos($inhalt, 'opennit-experimental') !== false, "Kanal in {$datei}");
}
});
};