CluPilotCloud/tests/Feature/Cancellation/TeardownStateTest.php

114 lines
5.2 KiB
PHP

<?php // tests/Feature/Cancellation/TeardownStateTest.php
use App\Models\Instance;
use Illuminate\Support\Carbon;
it('eine frische Instanz weiss noch gar nichts über Archivierung oder Abbau', function () {
// Vier Felder, vier Zustände. Eine Instanz, die zum ersten Mal sichtbar wird, hat noch
// kein Archiv (archive_volid null), wurde nicht archiviert (archived_at null), wurde
// nicht abgebaut (torn_down_at null) und kein Fehler hat gehakt (teardown_error null).
// Das ist der gültige Normalzustand: noch am Leben.
$instance = Instance::factory()->create();
expect($instance->archive_volid)->toBeNull()
->and($instance->archived_at)->toBeNull()
->and($instance->torn_down_at)->toBeNull()
->and($instance->teardown_error)->toBeNull();
});
it('merkt sich, wo das Archiv liegt', function () {
$volid = '/mnt/archiv-2026-08-04/instance-abc123';
$instance = Instance::factory()->create(['archive_volid' => $volid]);
expect($instance->fresh()->archive_volid)->toEqual($volid);
});
it('speichert und gibt archived_at als Carbon zurück', function () {
// archived_at kommt als Datenbankzeichenkette an und muss als Carbon-Instanz wieder
// herauskommen — ein Objekt, nicht eine Zeichenkette, sonst kann ein Aufrufer
// Vergleiche, Arithmetik und Formatierungen nicht durchführen.
$now = now();
$instance = Instance::factory()->create(['archived_at' => $now]);
expect($instance->fresh()->archived_at)
->toBeInstanceOf(Carbon::class)
->and($instance->fresh()->archived_at->timestamp)->toBe($now->timestamp);
});
it('speichert und gibt torn_down_at als Carbon zurück', function () {
// torn_down_at kommt als Datenbankzeichenkette an und muss als Carbon-Instanz wieder
// herauskommen — ein Objekt, nicht eine Zeichenkette, sonst kann ein Aufrufer
// Vergleiche, Arithmetik und Formatierungen nicht durchführen.
$now = now();
$instance = Instance::factory()->create(['torn_down_at' => $now]);
expect($instance->fresh()->torn_down_at)
->toBeInstanceOf(Carbon::class)
->and($instance->fresh()->torn_down_at->timestamp)->toBe($now->timestamp);
});
it('speichert die Fehlermeldung beim Abbau', function () {
// teardown_error hält den Fehler fest, der beim Löschen gehakt hat — in Klartext,
// damit ein Betreiber ihn findet, ohne erst auf die Logs zu schauen.
$error = 'SSH-Verbindung zum Host timeout nach 30 Sekunden';
$instance = Instance::factory()->create(['teardown_error' => $error]);
expect($instance->fresh()->teardown_error)->toEqual($error);
});
it('ein gültiger Zwischenstand: archiviert aber nicht abgebaut', function () {
// Das ist der entscheidende Zwischenstand, der erklärt, warum es zwei Felder sind statt
// einem einzigen. Eine Instanz mit `archived_at` gesetzt und `torn_down_at` leer
// bedeutet: „Sicherung war erfolgreich, Löschung noch nicht versucht" oder „Sicherung
// erfolgreich, Löschung gescheitert — sieh teardown_error". Ein einziges Feld würde
// den Unterschied zwischen „noch nicht archiviert" und „archiviert aber noch nicht
// gelöscht" unsichtbar machen.
$archived = now()->subDays(5);
$instance = Instance::factory()->create([
'archive_volid' => '/mnt/archiv/instance-123',
'archived_at' => $archived,
'torn_down_at' => null,
'teardown_error' => null,
]);
expect($instance->fresh()->archived_at->timestamp)->toBe($archived->timestamp)
->and($instance->fresh()->torn_down_at)->toBeNull()
->and($instance->fresh()->teardown_error)->toBeNull();
});
it('ein fehlgeschlagener Abbau ist sichtbar', function () {
// Eine Instanz mit `archived_at` und `torn_down_at` beide gesetzt bedeutet: Archivierung
// UND Löschung waren erfolgreich. Eine mit `archived_at` gesetzt und `torn_down_at`
// leer aber `teardown_error` gefüllt bedeutet: Löschung hat gehakt. Der Fehler ist
// dokumentiert und der Betreiber kann reagieren.
$archived = now()->subDays(5);
$error = 'Löschen des Snapshots gescheitert: Volume ist noch in Verwendung';
$instance = Instance::factory()->create([
'archive_volid' => '/mnt/archiv/instance-123',
'archived_at' => $archived,
'torn_down_at' => null,
'teardown_error' => $error,
]);
expect($instance->fresh()->archived_at->timestamp)->toBe($archived->timestamp)
->and($instance->fresh()->torn_down_at)->toBeNull()
->and($instance->fresh()->teardown_error)->toEqual($error);
});
it('ein vollständiger Durchlauf: archiviert und abgebaut', function () {
// Die „glückliche" Sequenz: Archivierung erfolgreich, Löschung erfolgreich. Beide
// Zeitstempel sind gesetzt, kein Fehler.
$archived = now()->subDays(10);
$tornDown = now()->subDays(5);
$instance = Instance::factory()->create([
'archive_volid' => '/mnt/archiv/instance-123',
'archived_at' => $archived,
'torn_down_at' => $tornDown,
'teardown_error' => null,
]);
expect($instance->fresh()->archived_at->timestamp)->toBe($archived->timestamp)
->and($instance->fresh()->torn_down_at->timestamp)->toBe($tornDown->timestamp)
->and($instance->fresh()->teardown_error)->toBeNull();
});