218 lines
9.5 KiB
PHP
218 lines
9.5 KiB
PHP
<?php
|
|
|
|
// tests/Feature/Cancellation/TeardownHasReadersTest.php
|
|
|
|
use App\Livewire\Admin\Instances as AdminInstances;
|
|
use App\Models\Customer;
|
|
use App\Models\Host;
|
|
use App\Models\Instance;
|
|
use Livewire\Livewire;
|
|
|
|
/**
|
|
* Wer sieht, was der Abbau hinterlassen hat.
|
|
*
|
|
* Zwei Zustände standen bis hierher nur in der Datenbank, und der Fehler wäre
|
|
* derselbe gewesen wie bei `export_wish` vor Teil A — nur teurer:
|
|
*
|
|
* - `teardown_error`: eine Instanz mit gefülltem Grund steht **unbegrenzt** und
|
|
* belegt weiter einen Platz auf ihrem Host. Sieht sie niemand, war der ganze
|
|
* Abbau umsonst.
|
|
* - `archive_volid` / `archived_at`: ohne diese Angabe ist eine
|
|
* Wiederherstellung eine Suche über fremde Ablagen.
|
|
*
|
|
* Und einen Fehler ausdrücklich nicht noch einmal: der Kasten „Datenexport
|
|
* bestellt" lässt sich bis heute nicht abräumen und sortiert die ältesten nach
|
|
* oben. Beide Kästen hier tun das Gegenteil, und beides ist unten festgenagelt.
|
|
*/
|
|
|
|
/**
|
|
* Eine Instanz, deren Abbau hängengeblieben ist: Grund vermerkt, Maschine
|
|
* steht noch. Eigener Name statt der Vorlagen aus den Nachbardateien — Pest
|
|
* führt diese Funktionen in einem gemeinsamen Namensraum.
|
|
*/
|
|
function haengenderAbbau(string $subdomain, string $grund, ?string $kunde = null): Instance
|
|
{
|
|
$host = Host::factory()->active()->create(['node' => 'pve']);
|
|
|
|
return Instance::factory()->create([
|
|
'customer_id' => Customer::factory()->create(['name' => $kunde ?? 'Kundin '.$subdomain]),
|
|
'host_id' => $host->id,
|
|
'vmid' => 900 + strlen($subdomain),
|
|
'status' => 'ended',
|
|
'subdomain' => $subdomain,
|
|
'service_ends_at' => now()->subDays(30),
|
|
'teardown_error' => $grund,
|
|
]);
|
|
}
|
|
|
|
// ---- Was gehakt hat: der wichtigere der beiden Kästen ----
|
|
|
|
it('zeigt dem Betreiber einen hängengebliebenen Abbau samt Grund', function () {
|
|
// Die Zusicherung, an der der ganze Plan hängt. Ohne diesen Kasten stünde
|
|
// die Maschine für immer, und der Grund läge in einer Spalte, die in der
|
|
// ganzen Konsole keinen Leser hat.
|
|
haengenderAbbau('nc-haengt', 'Die Ablage "local" nimmt keine Sicherungen auf.', kunde: 'Hängenkundin GmbH');
|
|
|
|
Livewire::actingAs(admin(), 'operator')->test(AdminInstances::class)
|
|
->assertSee(__('admin.teardown.stuck_title'))
|
|
->assertSee('Hängenkundin GmbH')
|
|
->assertSee('nc-haengt')
|
|
->assertSee('Die Ablage "local" nimmt keine Sicherungen auf.');
|
|
});
|
|
|
|
it('nennt beim hängenden Abbau das Archiv, wenn es schon liegt', function () {
|
|
// Der Zwischenstand, den es wirklich gibt: gesichert, aber nicht gelöscht.
|
|
// Wer ihn von Hand zu Ende bringt, braucht genau diese Kennung — sonst
|
|
// sichert er ein zweites Mal und zahlt die Stunde noch einmal.
|
|
$instanz = haengenderAbbau('nc-halbfertig', 'Das Löschen wurde abgewiesen.');
|
|
$instanz->update([
|
|
'archive_volid' => 'local:backup/vzdump-qemu-909.vma.zst',
|
|
'archived_at' => now()->subHour(),
|
|
]);
|
|
|
|
Livewire::actingAs(admin(), 'operator')->test(AdminInstances::class)
|
|
->assertSee('local:backup/vzdump-qemu-909.vma.zst');
|
|
});
|
|
|
|
it('stellt den zuletzt gescheiterten Abbau nach oben', function () {
|
|
// Der Fehler des Nachbarkastens, ausdrücklich nicht wiederholt: „Datenexport
|
|
// bestellt" sortiert aufsteigend, also besetzt der älteste Eintrag den Kopf
|
|
// der Liste für immer und die frischen wandern nach unten. Ein Kasten, in
|
|
// dem der Dauerfall oben klebt, wird nach der zweiten Woche nicht mehr
|
|
// gelesen.
|
|
$alt = haengenderAbbau('nc-alterfall', 'scheitert seit Wochen');
|
|
// Über den Abfrageerzeuger und nicht über das Modell: `updated_at` steht
|
|
// nicht in $fillable, ein update() darauf verschluckt den Wert stillschweigend.
|
|
Instance::query()->whereKey($alt->id)->update(['updated_at' => now()->subMonth()]);
|
|
haengenderAbbau('nc-neuerfall', 'heute Nacht gescheitert');
|
|
|
|
$seite = Livewire::actingAs(admin(), 'operator')->test(AdminInstances::class);
|
|
|
|
$reihenfolge = collect($seite->viewData('stuck'))->pluck('address')->all();
|
|
|
|
expect($reihenfolge)->toBe(['nc-neuerfall', 'nc-alterfall']);
|
|
});
|
|
|
|
it('räumt die Zeile ab, sobald der Abbau durchgelaufen ist', function () {
|
|
// Der Unterschied zum Kasten „Datenexport bestellt", der sich nie abräumen
|
|
// lässt: hier gibt es einen Mechanismus, der das ohne Buchführung tut. Der
|
|
// nächtliche Lauf versucht es erneut, und ein erfolgreicher Abbau setzt
|
|
// `teardown_error` auf null und `torn_down_at`.
|
|
$instanz = haengenderAbbau('nc-geheilt', 'beim letzten Mal gescheitert');
|
|
$instanz->update([
|
|
'teardown_error' => null,
|
|
'archive_volid' => 'local:backup/vzdump-qemu-911.vma.zst',
|
|
'archived_at' => now(),
|
|
'torn_down_at' => now(),
|
|
]);
|
|
|
|
Livewire::actingAs(admin(), 'operator')->test(AdminInstances::class)
|
|
->assertDontSee(__('admin.teardown.stuck_title'));
|
|
});
|
|
|
|
it('nimmt eine wiederbelebte Instanz aus dem roten Kasten', function () {
|
|
// Der Kasten verspricht im Untertitel, dass eine Zeile von selbst
|
|
// verschwindet, sobald der Abbau durchläuft. Für eine wiederbelebte Instanz
|
|
// läuft er nie wieder durch: `isDue()` verlangt `ended`, also fasst die
|
|
// Aktion sie nicht mehr an, der alte Grund bleibt stehen — und die Zeile
|
|
// stünde für immer da, für eine Maschine, die fröhlich läuft. Sie gehört
|
|
// damit auch nicht mehr in einen Kasten über hängende Abbauten.
|
|
$instanz = haengenderAbbau('nc-wiederbelebt', 'beim letzten Mal gescheitert');
|
|
$instanz->update(['status' => 'active']);
|
|
|
|
Livewire::actingAs(admin(), 'operator')->test(AdminInstances::class)
|
|
->assertDontSee(__('admin.teardown.stuck_title'));
|
|
});
|
|
|
|
it('zeigt gar keinen Kasten, wenn nichts hängt', function () {
|
|
// Ein dauerhaft leerer Kasten wird nach der zweiten Woche übersehen — und
|
|
// mit ihm der erste Eintrag, der wirklich einmal darin steht.
|
|
Instance::factory()->create(['status' => 'active', 'subdomain' => 'nc-laeuft']);
|
|
|
|
Livewire::actingAs(admin(), 'operator')->test(AdminInstances::class)
|
|
->assertDontSee(__('admin.teardown.stuck_title'));
|
|
});
|
|
|
|
// ---- Wo das Archiv liegt ----
|
|
|
|
it('sagt, was abgebaut wurde und wo das Archiv liegt', function () {
|
|
$customer = Customer::factory()->create(['name' => 'Abgebautkundin GmbH']);
|
|
$instanz = Instance::factory()->create([
|
|
'customer_id' => $customer->id,
|
|
'status' => 'ended',
|
|
'subdomain' => 'nc-abgebaut',
|
|
'service_ends_at' => now()->subDays(30),
|
|
'archive_volid' => 'local:backup/vzdump-qemu-777.vma.zst',
|
|
'archived_at' => now()->subDays(2),
|
|
'torn_down_at' => now()->subDays(2),
|
|
]);
|
|
|
|
Livewire::actingAs(admin(), 'operator')->test(AdminInstances::class)
|
|
->assertSee(__('admin.teardown.archive_title'))
|
|
->assertSee('Abgebautkundin GmbH')
|
|
->assertSee('local:backup/vzdump-qemu-777.vma.zst')
|
|
// R19: die Wanduhr des Betreibers, nicht die Speicherzone.
|
|
->assertSee($instanz->archived_at->local()->isoFormat('LLL'));
|
|
});
|
|
|
|
it('lässt ein Archiv aus der Liste fallen, sobald die Aufbewahrung um ist', function () {
|
|
// Der Kasten hat damit ein Ende, das ihm jemand anderes setzt: was hier
|
|
// steht, ist genau das, was noch auf einer Ablage liegt. Eine Liste jeder
|
|
// Löschung seit Anbeginn wäre wieder der Kasten, den nichts abräumen kann.
|
|
Instance::factory()->create([
|
|
'status' => 'ended',
|
|
'subdomain' => 'nc-uralt',
|
|
'service_ends_at' => now()->subYears(2),
|
|
'archive_volid' => 'local:backup/vzdump-qemu-666.vma.zst',
|
|
'archived_at' => now()->subMonths(13),
|
|
'torn_down_at' => now()->subMonths(13),
|
|
]);
|
|
|
|
Livewire::actingAs(admin(), 'operator')->test(AdminInstances::class)
|
|
->assertDontSee('local:backup/vzdump-qemu-666.vma.zst');
|
|
});
|
|
|
|
it('stellt den jüngsten Abbau nach oben', function () {
|
|
Instance::factory()->create([
|
|
'status' => 'ended', 'subdomain' => 'nc-frueher',
|
|
'archive_volid' => 'local:backup/vzdump-qemu-111.vma.zst',
|
|
'archived_at' => now()->subMonths(6), 'torn_down_at' => now()->subMonths(6),
|
|
]);
|
|
Instance::factory()->create([
|
|
'status' => 'ended', 'subdomain' => 'nc-gestern',
|
|
'archive_volid' => 'local:backup/vzdump-qemu-222.vma.zst',
|
|
'archived_at' => now()->subDay(), 'torn_down_at' => now()->subDay(),
|
|
]);
|
|
|
|
$seite = Livewire::actingAs(admin(), 'operator')->test(AdminInstances::class);
|
|
|
|
expect(collect($seite->viewData('teardowns'))->pluck('address')->all())
|
|
->toBe(['nc-gestern', 'nc-frueher']);
|
|
});
|
|
|
|
it('führt eine Instanz ohne nachgewiesenes Archiv nicht als abgebaut auf', function () {
|
|
// `torn_down_at` ohne `archive_volid` darf es nach ArchiveAndTearDown gar
|
|
// nicht geben — und wenn es das doch einmal gibt, ist die Zeile eine
|
|
// Behauptung über ein Archiv, das niemand finden wird. Lieber gar kein
|
|
// Eintrag als ein Fundort, den es nicht gibt.
|
|
Instance::factory()->create([
|
|
'status' => 'ended',
|
|
'subdomain' => 'nc-ohnearchiv',
|
|
'archived_at' => now()->subDay(),
|
|
'torn_down_at' => now()->subDay(),
|
|
]);
|
|
|
|
Livewire::actingAs(admin(), 'operator')->test(AdminInstances::class)
|
|
->assertDontSee(__('admin.teardown.archive_title'));
|
|
});
|
|
|
|
// ---- Beide Sprachdateien tragen dieselben Schlüssel ----
|
|
|
|
it('hat jeden Schlüssel in beiden Sprachen', function () {
|
|
$de = __('admin.teardown', locale: 'de');
|
|
$en = __('admin.teardown', locale: 'en');
|
|
|
|
expect(array_keys($de))->toBe(array_keys($en))
|
|
->and($de)->not->toBe($en);
|
|
});
|