199 lines
8.1 KiB
PHP
199 lines
8.1 KiB
PHP
<?php
|
|
|
|
use App\Livewire\Admin\ConfirmReleaseReservation;
|
|
use App\Livewire\Admin\HostDetail;
|
|
use App\Models\Customer;
|
|
use App\Models\Host;
|
|
use App\Services\Provisioning\HostCapacity;
|
|
use Livewire\Livewire;
|
|
|
|
/**
|
|
* Ein Host, der jemandem gehört.
|
|
*
|
|
* Enterprise verkauft eine eigene Maschine. Ohne Markierung landet der nächste
|
|
* Startkunde darauf, und die Kapazitätszahlen versprechen den Platz obendrein
|
|
* ein zweites Mal.
|
|
*/
|
|
it('platziert keinen fremden Kunden auf einem reservierten Host', function () {
|
|
$mieter = Customer::factory()->create();
|
|
$fremder = Customer::factory()->create();
|
|
|
|
Host::factory()->active()->create([
|
|
'datacenter' => 'fsn', 'total_gb' => 4000,
|
|
'reserved_for_customer_id' => $mieter->id,
|
|
]);
|
|
|
|
expect(Host::placeableIn('fsn', 100, $fremder->id))->toBeNull()
|
|
->and(Host::placeableIn('fsn', 100, $mieter->id))->not->toBeNull();
|
|
});
|
|
|
|
it('zählt einen reservierten Host nicht zum allgemeinen Bestand', function () {
|
|
$mieter = Customer::factory()->create();
|
|
|
|
Host::factory()->active()->create([
|
|
'datacenter' => 'fsn', 'total_gb' => 4000,
|
|
'reserved_for_customer_id' => $mieter->id,
|
|
]);
|
|
|
|
expect(app(HostCapacity::class)->largestPlaceableGb('fsn'))->toBe(0)
|
|
->and(app(HostCapacity::class)->canPlace('fsn', 40))->toBeFalse();
|
|
});
|
|
|
|
it('lässt einen freien Host frei', function () {
|
|
Host::factory()->active()->create(['datacenter' => 'fsn', 'total_gb' => 500]);
|
|
|
|
expect(app(HostCapacity::class)->canPlace('fsn', 40))->toBeTrue();
|
|
});
|
|
|
|
/**
|
|
* preferredDatacenter() ruft placeableIn() nicht auf — sie fragt selbst nach
|
|
* dem Rechenzentrum mit dem meisten freien Platz, für einen Checkout, der
|
|
* noch keinen Kunden kennt. Ohne ->unreserved() sieht ein Rechenzentrum mit
|
|
* einem großen, aber komplett reservierten Host geräumiger aus als eines mit
|
|
* echtem allgemeinem Bestand — der Checkout legt die neue Bestellung dorthin,
|
|
* placeableIn() findet dort dann niemanden, und die Bestellung parkt, obwohl
|
|
* anderswo echter Platz war.
|
|
*/
|
|
it('bevorzugt kein Rechenzentrum, dessen größter Host nur reservierter Platz ist', function () {
|
|
App\Models\Datacenter::factory()->create(['code' => 'fsn4']);
|
|
App\Models\Datacenter::factory()->create(['code' => 'hel4']);
|
|
|
|
$mieter = Customer::factory()->create();
|
|
// fsn4: der größere Host, aber vollständig einem Kunden versprochen.
|
|
Host::factory()->active()->create(['datacenter' => 'fsn4', 'total_gb' => 4000, 'reserved_for_customer_id' => $mieter->id]);
|
|
// hel4: kleiner, aber echter allgemeiner Bestand.
|
|
Host::factory()->active()->create(['datacenter' => 'hel4', 'total_gb' => 500]);
|
|
|
|
expect(app(HostCapacity::class)->preferredDatacenter())->toBe('hel4');
|
|
});
|
|
|
|
/**
|
|
* Die Reservierung überlebt den Kunden — sie löst sich nicht von selbst.
|
|
*
|
|
* Vorgabe: "Die Beziehung löscht nicht mit dem Kunden … die Reservierung
|
|
* wird beim Auszug von Hand gelöst — sonst stünde die Maschine still im
|
|
* Bestand, ohne dass jemand sie angeboten hätte." Ein `nullOnDelete()` täte
|
|
* genau das Verbotene: die Datenbank hebt die Reservierung von sich aus auf,
|
|
* sobald der Kunde verschwindet (z. B. eine DSGVO-Löschung). `restrictOnDelete()`
|
|
* macht daraus stattdessen eine bewusste Handlung — das Löschen selbst
|
|
* scheitert, solange die Maschine noch jemandem gehört.
|
|
*/
|
|
it('verhindert das Löschen eines Kunden, dem noch ein Host gehört', function () {
|
|
$mieter = Customer::factory()->create();
|
|
Host::factory()->active()->create(['reserved_for_customer_id' => $mieter->id]);
|
|
|
|
expect(fn () => $mieter->delete())->toThrow(\Illuminate\Database\QueryException::class);
|
|
|
|
// Nichts wurde stillschweigend aufgelöst — der Kunde steht noch da.
|
|
expect(Customer::query()->whereKey($mieter->id)->exists())->toBeTrue();
|
|
});
|
|
|
|
it('lässt einen Kunden ohne Host normal löschen', function () {
|
|
$customer = Customer::factory()->create();
|
|
|
|
$customer->delete();
|
|
|
|
expect(Customer::query()->whereKey($customer->id)->exists())->toBeFalse();
|
|
});
|
|
|
|
/**
|
|
* Die Konsole: setzen und lösen.
|
|
*
|
|
* Setzen braucht keine Bestätigung — ein Host, der noch niemandem gehört,
|
|
* bekommt einen Besitzer, nichts geht verloren. Lösen schon (R23): danach
|
|
* ist die Maschine wieder allgemeiner Bestand, und der nächste Startkunde
|
|
* kann darauf landen.
|
|
*/
|
|
it('reserviert einen Host für einen Kunden von der Konsole aus', function () {
|
|
$host = Host::factory()->active()->create();
|
|
$customer = Customer::factory()->create();
|
|
|
|
Livewire::actingAs(admin(), 'operator')
|
|
->test(HostDetail::class, ['host' => $host])
|
|
->call('reserveFor', $customer->uuid)
|
|
->assertDispatched('notify');
|
|
|
|
expect($host->fresh()->reserved_for_customer_id)->toBe($customer->id);
|
|
});
|
|
|
|
it('verweigert das Reservieren ohne hosts.manage', function () {
|
|
$host = Host::factory()->active()->create();
|
|
$customer = Customer::factory()->create();
|
|
|
|
Livewire::actingAs(operator('Read-only'), 'operator')
|
|
->test(HostDetail::class, ['host' => $host])
|
|
->call('reserveFor', $customer->uuid)
|
|
->assertForbidden();
|
|
|
|
expect($host->fresh()->reserved_for_customer_id)->toBeNull();
|
|
});
|
|
|
|
it('bestätigt das Lösen im eigenen Modal, ohne dabei selbst etwas zu ändern', function () {
|
|
$mieter = Customer::factory()->create(['name' => 'Acme GmbH']);
|
|
$host = Host::factory()->active()->create(['reserved_for_customer_id' => $mieter->id]);
|
|
|
|
Livewire::actingAs(admin(), 'operator')
|
|
->test(ConfirmReleaseReservation::class, ['uuid' => $host->uuid])
|
|
->assertSee('Acme GmbH')
|
|
->call('confirmRelease')
|
|
->assertDispatched('host-reservation-release-confirmed');
|
|
|
|
// Das Modal selbst rührt nichts an — die Reservierung steht noch.
|
|
expect($host->fresh()->reserved_for_customer_id)->toBe($mieter->id);
|
|
});
|
|
|
|
it('löst die Reservierung, sobald die Seite das bestätigte Ereignis empfängt', function () {
|
|
$mieter = Customer::factory()->create();
|
|
$host = Host::factory()->active()->create(['reserved_for_customer_id' => $mieter->id]);
|
|
|
|
Livewire::actingAs(admin(), 'operator')
|
|
->test(HostDetail::class, ['host' => $host])
|
|
->call('onReleaseConfirmed')
|
|
->assertDispatched('notify');
|
|
|
|
expect($host->fresh()->reserved_for_customer_id)->toBeNull();
|
|
});
|
|
|
|
/**
|
|
* Erklärt statt versteckt (Aufgabenauflösung #4): eine reservierte Maschine
|
|
* verschwindet nirgends kommentarlos aus dem Bestand — Host-Detailseite,
|
|
* Host-Liste und Kapazitätsseite weisen sie jeweils als solche aus.
|
|
*/
|
|
it('zeigt die Reservierung auf der Host-Detailseite', function () {
|
|
$mieter = Customer::factory()->create(['name' => 'Contoso KG']);
|
|
$host = Host::factory()->active()->create(['reserved_for_customer_id' => $mieter->id]);
|
|
|
|
$this->actingAs(admin(), 'operator')->get(route('admin.hosts.show', $host))
|
|
->assertOk()
|
|
->assertSee(__('hosts.detail.reserved_for', ['name' => 'Contoso KG']));
|
|
});
|
|
|
|
it('bietet auf der Host-Detailseite eine Kundenauswahl, solange nichts reserviert ist', function () {
|
|
$host = Host::factory()->active()->create();
|
|
$customer = Customer::factory()->create(['name' => 'Globex AG']);
|
|
|
|
$this->actingAs(admin(), 'operator')->get(route('admin.hosts.show', $host))
|
|
->assertOk()
|
|
->assertSee(__('hosts.detail.reserved_general'))
|
|
->assertSee('Globex AG');
|
|
});
|
|
|
|
it('weist die Reservierung auf der Host-Liste aus', function () {
|
|
$mieter = Customer::factory()->create(['name' => 'Umbrella Corp']);
|
|
Host::factory()->active()->create(['name' => 'pve-reserved-1', 'reserved_for_customer_id' => $mieter->id]);
|
|
|
|
$this->actingAs(admin(), 'operator')->get(route('admin.hosts'))
|
|
->assertOk()
|
|
->assertSee(__('hosts.reserved_badge', ['name' => 'Umbrella Corp']));
|
|
});
|
|
|
|
it('weist die Reservierung auf der Kapazitätsseite aus', function () {
|
|
App\Models\Datacenter::factory()->create(['code' => 'fsn2']);
|
|
$mieter = Customer::factory()->create(['name' => 'Initech Ltd']);
|
|
Host::factory()->active()->create(['datacenter' => 'fsn2', 'reserved_for_customer_id' => $mieter->id]);
|
|
|
|
$this->actingAs(admin(), 'operator')->get(route('admin.capacity'))
|
|
->assertOk()
|
|
->assertSee(__('hosts.reserved_badge', ['name' => 'Initech Ltd']));
|
|
});
|