249 lines
11 KiB
PHP
249 lines
11 KiB
PHP
<?php
|
|
|
|
// tests/Feature/Seats/SyncSeatToNextcloudTest.php
|
|
|
|
use App\Livewire\Users;
|
|
use App\Models\Customer;
|
|
use App\Models\Host;
|
|
use App\Models\Instance;
|
|
use App\Models\Seat;
|
|
use App\Provisioning\Jobs\SyncSeatToNextcloud;
|
|
use App\Services\Nextcloud\NextcloudUsers;
|
|
use App\Services\Proxmox\FakeProxmoxClient;
|
|
use App\Services\Proxmox\ProxmoxClient;
|
|
use Illuminate\Support\Facades\Queue;
|
|
use Livewire\Livewire;
|
|
use Symfony\Component\Finder\Finder;
|
|
|
|
it('schreibt den Erfolg an den Sitz', function () {
|
|
app()->instance(ProxmoxClient::class, new FakeProxmoxClient);
|
|
$customer = Customer::factory()->create();
|
|
// host_id, weil die host-Beziehung sonst null bliebe: der Auftrag bricht
|
|
// dann mit 'no_instance' ab und käme nie bis zum Gast — dieselbe Falle wie
|
|
// in NextcloudUsersTest::gastBereit().
|
|
Instance::factory()->for($customer)->create(['status' => 'active', 'vmid' => 201, 'host_id' => Host::factory()]);
|
|
$sitz = Seat::factory()->for($customer)->create([
|
|
'email' => 'anna@firma.tld', 'nc_username' => 'anna@firma.tld',
|
|
'nc_state' => Seat::STATE_PENDING,
|
|
]);
|
|
|
|
(new SyncSeatToNextcloud($sitz->uuid, 'invite'))->handle(app(NextcloudUsers::class));
|
|
|
|
expect($sitz->fresh()->nc_state)->toBe(Seat::STATE_SYNCED)
|
|
->and($sitz->fresh()->nc_error)->toBeNull()
|
|
->and($sitz->fresh()->nc_synced_at)->not->toBeNull();
|
|
});
|
|
|
|
it('schreibt den Fehlschlag mit Grund an den Sitz', function () {
|
|
$pve = new FakeProxmoxClient;
|
|
$pve->guestThrows[201] = new RuntimeException('guest agent unreachable');
|
|
app()->instance(ProxmoxClient::class, $pve);
|
|
$customer = Customer::factory()->create();
|
|
Instance::factory()->for($customer)->create(['status' => 'active', 'vmid' => 201, 'host_id' => Host::factory()]);
|
|
$sitz = Seat::factory()->for($customer)->create(['nc_username' => 'anna@firma.tld']);
|
|
|
|
(new SyncSeatToNextcloud($sitz->uuid, 'invite'))->handle(app(NextcloudUsers::class));
|
|
|
|
// Nicht nur „irgendein Fehler": der Grund muss der des unerreichbaren
|
|
// Gastes sein, nicht der der fehlenden Instanz — sonst liefe der Test auch
|
|
// dann grün, wenn die Instanz gar nicht gefunden wurde.
|
|
expect($sitz->fresh()->nc_state)->toBe(Seat::STATE_FAILED)
|
|
->and($sitz->fresh()->nc_error)->toBe('guest_failed');
|
|
});
|
|
|
|
it('sagt es am Sitz, wenn es gar keine erreichbare Instanz gibt', function () {
|
|
// Der Kunde hat keine laufende Instanz — es gibt nichts zu spiegeln, und
|
|
// der Inhaber muss den Unterschied zu „der Gast hat abgelehnt" lesen
|
|
// können, weil nur das eine von beiden er selbst beheben kann.
|
|
$customer = Customer::factory()->create();
|
|
$sitz = Seat::factory()->for($customer)->create(['nc_username' => 'anna@firma.tld']);
|
|
|
|
(new SyncSeatToNextcloud($sitz->uuid, 'invite'))->handle(app(NextcloudUsers::class));
|
|
|
|
expect($sitz->fresh()->nc_state)->toBe(Seat::STATE_FAILED)
|
|
->and($sitz->fresh()->nc_error)->toBe('no_instance');
|
|
});
|
|
|
|
it('laeuft auf der Bereitstellungs-Warteschlange, nirgends sonst', function () {
|
|
// Nur dieser Arbeiter hat Tunnel und Proxmox-Zugangsdaten. Auf der
|
|
// Standard-Warteschlange erreicht der Auftrag keinen einzigen Gast.
|
|
$auftrag = new SyncSeatToNextcloud('egal', 'invite');
|
|
|
|
expect($auftrag->connection)->toBe('provisioning')
|
|
->and($auftrag->queue)->toBe('provisioning');
|
|
});
|
|
|
|
it('legt beim Anlegen KEINEN Nextcloud-Benutzer an', function () {
|
|
// Anlegen ist nicht Einladen. Der Inhaber soll sein Team vorbereiten
|
|
// koennen, ohne dass jemand eine Mail bekommt.
|
|
Queue::fake();
|
|
$customer = Customer::factory()->create();
|
|
$user = $customer->ensureUser();
|
|
Instance::factory()->for($customer)->create(['status' => 'active']);
|
|
|
|
Livewire::actingAs($user)->test(Users::class)
|
|
->set('inviteEmail', 'anna@firma.tld')
|
|
->set('inviteName', 'Anna')
|
|
->call('addSeat');
|
|
|
|
Queue::assertNothingPushed();
|
|
expect(Seat::where('email', 'anna@firma.tld')->first()->nc_state)->toBe(Seat::STATE_NONE);
|
|
});
|
|
|
|
it('schickt erst beim Einladen einen Auftrag los', function () {
|
|
Queue::fake();
|
|
$customer = Customer::factory()->create();
|
|
$user = $customer->ensureUser();
|
|
Instance::factory()->for($customer)->create(['status' => 'active']);
|
|
$sitz = Seat::factory()->for($customer)->create(['role' => 'member']);
|
|
|
|
Livewire::actingAs($user)->test(Users::class)->call('sendInvite', $sitz->uuid);
|
|
|
|
Queue::assertPushed(SyncSeatToNextcloud::class);
|
|
expect($sitz->fresh()->nc_state)->toBe(Seat::STATE_PENDING)
|
|
->and($sitz->fresh()->nc_username)->toBe($sitz->email);
|
|
});
|
|
|
|
it('haelt den Benutzernamen fest, auch wenn die Mailadresse sich aendert', function () {
|
|
// Nextcloud kann Benutzer nicht umbenennen. Ein Sitz, dessen Adresse sich
|
|
// spaeter aendert, behaelt seinen Anmeldenamen.
|
|
Queue::fake();
|
|
$customer = Customer::factory()->create();
|
|
$user = $customer->ensureUser();
|
|
Instance::factory()->for($customer)->create(['status' => 'active']);
|
|
$sitz = Seat::factory()->for($customer)->create(['email' => 'alt@firma.tld', 'nc_username' => 'alt@firma.tld']);
|
|
|
|
$sitz->update(['email' => 'neu@firma.tld']);
|
|
Livewire::actingAs($user)->test(Users::class)->call('sendInvite', $sitz->uuid);
|
|
|
|
expect($sitz->fresh()->nc_username)->toBe('alt@firma.tld');
|
|
});
|
|
|
|
it('weist die elfte Einladung derselben Stunde ab und sagt die Restzeit', function () {
|
|
Queue::fake();
|
|
$customer = Customer::factory()->create();
|
|
$user = $customer->ensureUser();
|
|
Instance::factory()->for($customer)->create(['status' => 'active']);
|
|
$sitze = Seat::factory()->for($customer)->count(11)->create(['role' => 'member']);
|
|
|
|
$seite = Livewire::actingAs($user)->test(Users::class);
|
|
|
|
foreach ($sitze->take(10) as $sitz) {
|
|
$seite->call('sendInvite', $sitz->uuid);
|
|
}
|
|
|
|
$seite->call('sendInvite', $sitze->last()->uuid);
|
|
|
|
// Keine stumme Verweigerung: die Meldung nennt die echte Restzeit. Auf die
|
|
// Zahl geprüft statt auf den Satz — ein Test, der denselben __()-Aufruf
|
|
// nachrechnet wie die Implementierung, würde auch eine fehlende Restzeit
|
|
// durchwinken (siehe R19).
|
|
$seite->assertDispatched(
|
|
'notify',
|
|
fn ($ereignis, $daten) => str_contains($daten['message'], '60'),
|
|
);
|
|
expect($sitze->last()->fresh()->nc_state)->toBe(Seat::STATE_NONE);
|
|
Queue::assertPushed(SyncSeatToNextcloud::class, 10);
|
|
});
|
|
|
|
it('weist das vierte Draufdruecken auf denselben Sitz ab', function () {
|
|
// Die zweite Grenze: gegen das wiederholte Klicken an EINER Zeile, das
|
|
// demselben Menschen sonst vier Willkommensmails schickt.
|
|
Queue::fake();
|
|
$customer = Customer::factory()->create();
|
|
$user = $customer->ensureUser();
|
|
Instance::factory()->for($customer)->create(['status' => 'active']);
|
|
$sitz = Seat::factory()->for($customer)->create(['role' => 'member']);
|
|
|
|
$seite = Livewire::actingAs($user)->test(Users::class);
|
|
|
|
foreach (range(1, 4) as $ignoriert) {
|
|
$seite->call('sendInvite', $sitz->uuid);
|
|
}
|
|
|
|
Queue::assertPushed(SyncSeatToNextcloud::class, 3);
|
|
});
|
|
|
|
it('entzieht, ohne die Zeile zu loeschen', function () {
|
|
Queue::fake();
|
|
$customer = Customer::factory()->create();
|
|
$user = $customer->ensureUser();
|
|
Instance::factory()->for($customer)->create(['status' => 'active']);
|
|
$sitz = Seat::factory()->for($customer)->create(['role' => 'member', 'nc_username' => 'anna@firma.tld']);
|
|
|
|
Livewire::actingAs($user)->test(Users::class)->call('revoke', $sitz->uuid);
|
|
|
|
// Der Datensatz bleibt. Ein Fehlgriff im Userpanel darf die Arbeit eines
|
|
// Menschen nicht vernichten — und in einem Produkt, das mit
|
|
// Nachvollziehbarkeit verkauft wird, gaebe es danach nichts mehr zu zeigen.
|
|
expect(Seat::find($sitz->id))->not->toBeNull()
|
|
->and($sitz->fresh()->status)->toBe('revoked');
|
|
Queue::assertPushed(SyncSeatToNextcloud::class);
|
|
});
|
|
|
|
it('schickt fuer einen Sitz, der nie in der Nextcloud war, keinen Auftrag', function () {
|
|
// Angelegt, nie eingeladen: es gäbe dort nichts zu sperren. Ein Auftrag
|
|
// liefe ins Leere und stellte die Zeile danach auf „fehlgeschlagen" —
|
|
// eine Fehlermeldung für etwas, das nie ein Fehler war.
|
|
Queue::fake();
|
|
$customer = Customer::factory()->create();
|
|
$user = $customer->ensureUser();
|
|
Instance::factory()->for($customer)->create(['status' => 'active']);
|
|
$sitz = Seat::factory()->for($customer)->create(['role' => 'member']);
|
|
|
|
Livewire::actingAs($user)->test(Users::class)->call('revoke', $sitz->uuid);
|
|
|
|
Queue::assertNothingPushed();
|
|
expect($sitz->fresh()->status)->toBe('revoked')
|
|
->and($sitz->fresh()->nc_state)->toBe(Seat::STATE_NONE);
|
|
});
|
|
|
|
it('spiegelt eine Rollenaenderung in die Nextcloud', function () {
|
|
Queue::fake();
|
|
$customer = Customer::factory()->create();
|
|
$user = $customer->ensureUser();
|
|
Instance::factory()->for($customer)->create(['status' => 'active']);
|
|
$sitz = Seat::factory()->for($customer)->create([
|
|
'role' => 'member', 'nc_username' => 'anna@firma.tld', 'nc_state' => Seat::STATE_SYNCED,
|
|
]);
|
|
|
|
Livewire::actingAs($user)->test(Users::class)->call('setRole', $sitz->uuid, 'readonly');
|
|
|
|
Queue::assertPushed(SyncSeatToNextcloud::class, fn ($auftrag) => $auftrag->action === 'role');
|
|
expect($sitz->fresh()->nc_state)->toBe(Seat::STATE_PENDING);
|
|
});
|
|
|
|
it('gibt dem Inhaber nach einem Fehlschlag einen zweiten Versuch', function () {
|
|
Queue::fake();
|
|
$customer = Customer::factory()->create();
|
|
$user = $customer->ensureUser();
|
|
Instance::factory()->for($customer)->create(['status' => 'active']);
|
|
$sitz = Seat::factory()->for($customer)->create([
|
|
'role' => 'member', 'nc_username' => 'anna@firma.tld',
|
|
'nc_state' => Seat::STATE_FAILED, 'nc_error' => 'guest_failed',
|
|
]);
|
|
|
|
Livewire::actingAs($user)->test(Users::class)->call('retry', $sitz->uuid);
|
|
|
|
// Der Grund verschwindet mit dem neuen Versuch — ein stehengebliebener
|
|
// alter Fehlertext neben „wird eingerichtet …" wäre die Attrappe zurück.
|
|
Queue::assertPushed(SyncSeatToNextcloud::class, fn ($auftrag) => $auftrag->action === 'invite');
|
|
expect($sitz->fresh()->nc_state)->toBe(Seat::STATE_PENDING)
|
|
->and($sitz->fresh()->nc_error)->toBeNull();
|
|
});
|
|
|
|
it('nennt in keiner Datei unter app/ das Loeschen eines Benutzers', function () {
|
|
// Testerzwungene Regel: kein Nextcloud-Benutzer wird je geloescht, und
|
|
// keine Datei. Wer das aendern will, muss diese Pruefung anfassen und
|
|
// dabei ueber die Folgen stolpern.
|
|
$treffer = [];
|
|
|
|
foreach (Finder::create()->files()->in(app_path())->name('*.php') as $datei) {
|
|
if (str_contains($datei->getContents(), 'user:delete')) {
|
|
$treffer[] = $datei->getRelativePathname();
|
|
}
|
|
}
|
|
|
|
expect($treffer)->toBe([]);
|
|
});
|