CluPilotCloud/tests/Feature/Admin/HostNamingTest.php

159 lines
5.6 KiB
PHP

<?php
use App\Actions\StartHostOnboarding;
use App\Livewire\Admin\HostCreate;
use App\Livewire\Admin\HostDetail;
use App\Models\Datacenter;
use App\Models\Host;
use App\Support\HostName;
use App\Support\HostTakeoverCommand;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\Queue;
use Livewire\Livewire;
beforeEach(function () {
Queue::fake();
fakeServices();
// Die Zone wird hier festgenagelt statt aus der .env dieser Maschine
// gelesen: `provisioning.dns.platform_zone` leitet sich sonst aus APP_URL
// ab, und dieselbe Prüfung fiele auf einer richtig konfigurierten
// Installation um. Geprüft wird die FORM des Namens, nicht die Domain
// dieser einen Kiste.
config()->set('provisioning.dns.platform_zone', 'clupilot.com');
// Die Migration von `create_datacenters_table` legt fsn und hel schon an;
// firstOrCreate steht hier, damit der Test nicht daran hängt.
Datacenter::query()->firstOrCreate(['code' => 'fsn'], ['name' => 'Falkenstein']);
});
/** Ein Host, der so angelegt wird, wie die Konsole es tut. */
function onboard(string $dc = 'fsn'): Host
{
static $n = 0;
$n++;
return app(StartHostOnboarding::class)->run([
'datacenter' => $dc,
'public_ip' => '203.0.113.'.$n,
'root_password' => 'supersecret',
]);
}
// --- Abnahme 1 + 2 ---
it('vergibt den Namen selbst, fortlaufend je Rechenzentrum', function () {
expect(onboard()->name)->toBe('fsn-01')
->and(onboard()->name)->toBe('fsn-02');
});
it('führt je Rechenzentrum einen eigenen Zähler', function () {
Datacenter::query()->firstOrCreate(['code' => 'hel'], ['name' => 'Helsinki']);
expect(onboard('fsn')->name)->toBe('fsn-01')
->and(onboard('hel')->name)->toBe('hel-01');
});
// --- Abnahme 3: der Punkt, der die Sorgfalt trägt ---
it('gibt die Nummer eines entfernten Hosts nie wieder aus', function () {
onboard(); // fsn-01
$zweiter = onboard(); // fsn-02
onboard(); // fsn-03
// Die HÖCHSTE zu entfernen ist der gefährliche Fall: MAX(nummer) + 1
// reichte fsn-03 sofort wieder heraus, und ein zwischengespeicherter Name
// zeigte danach auf eine andere Maschine.
$zweiter->delete();
expect(onboard()->name)->toBe('fsn-04');
});
it('überlebt auch das Entfernen des höchsten Hosts', function () {
onboard(); // fsn-01
$hoechster = onboard(); // fsn-02
$hoechster->delete();
expect(onboard()->name)->toBe('fsn-03');
});
// --- Abnahme 5 ---
it('zeigt den Namen vorher an, ohne ihn zu verbrauchen', function () {
onboard(); // fsn-01 ist weg
$seite = Livewire::actingAs(admin(), 'operator')->test(HostCreate::class)
->set('datacenter', 'fsn');
$seite->assertSee('fsn-02')->assertSee('fsn-02.node.clupilot.com');
// Zweimal ansehen verbraucht nichts.
Livewire::actingAs(admin(), 'operator')->test(HostCreate::class)
->set('datacenter', 'fsn')
->assertSee('fsn-02');
expect(HostName::preview('fsn'))->toBe('fsn-02');
});
it('gibt zwei gleichzeitig geöffneten Formularen zwei verschiedene Namen', function () {
// Beide sehen fsn-01, gespeichert wird fsn-01 und fsn-02.
expect(HostName::preview('fsn'))->toBe('fsn-01')
->and(HostName::preview('fsn'))->toBe('fsn-01');
expect(onboard()->name)->toBe('fsn-01')
->and(onboard()->name)->toBe('fsn-02');
});
// --- Der Riegel ---
it('lässt zwei Hosts nicht denselben Namen tragen', function () {
onboard();
expect(fn () => Host::factory()->create(['name' => 'fsn-01']))
->toThrow(QueryException::class);
});
it('gibt zwei ähnlich geschriebenen Rechenzentrums-Codes nicht denselben Namen', function () {
// Codes von vor der Verschärfung der Prüfregel: eu_west und eu-west werden
// zur selben DNS-Bezeichnung, führen aber getrennte Zähler. Ohne das
// Weiterzählen bekäme der zweite eu-west-01 und liefe in den Index.
Datacenter::factory()->create(['code' => 'eu_west', 'name' => 'EU West (alt)']);
Datacenter::factory()->create(['code' => 'eu-west', 'name' => 'EU West']);
expect(onboard('eu_west')->name)->toBe('eu-west-01')
->and(onboard('eu-west')->name)->toBe('eu-west-02');
});
it('fällt auf eine gültige Bezeichnung zurück, wenn der Code keine hergibt', function () {
expect(HostName::label('---'))->toBe('node');
});
it('zählt ab hundert ohne Sonderfall weiter', function () {
Datacenter::query()->where('code', 'fsn')->update(['next_host_number' => 100]);
expect(onboard()->name)->toBe('fsn-100');
});
// --- Ein Name, überall derselbe ---
it('nennt den Host im DNS so, wie die Konsole ihn nennt', function () {
$host = onboard();
expect(HostName::fqdn($host->name))->toBe('fsn-01.node.clupilot.com')
->and(HostTakeoverCommand::fqdnFor($host))
->toBe(HostName::fqdn($host->name));
});
it('zeigt auf der Detailseite die IP und die Domain', function () {
$host = onboard();
$host->update(['status' => 'active', 'wg_ip' => '10.66.0.100', 'public_ip' => '203.0.113.200']);
Livewire::actingAs(admin(), 'operator')
->test(HostDetail::class, ['host' => $host])
->assertSee('203.0.113.200')
->assertSee('10.66.0.100')
// Der Name allein hilft nicht, wenn niemand die Adresse dazu kennt.
// 8006 ist die Proxmox-Oberfläche; SecureHostFirewall lässt sie nur aus
// dem Tunnel zu, und der hostsdir-Eintrag löst nur dort auf.
->assertSee('fsn-01.node.clupilot.com')
->assertSee('https://fsn-01.node.clupilot.com:8006', false);
});