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(); });