CluPilotCloud/tests/Feature/Nextcloud/NextcloudUsersTest.php

148 lines
6.4 KiB
PHP

<?php // tests/Feature/Nextcloud/NextcloudUsersTest.php
use App\Models\Host;
use App\Models\Instance;
use App\Models\Seat;
use App\Services\Nextcloud\NextcloudUsers;
use App\Services\Proxmox\FakeProxmoxClient;
use App\Services\Proxmox\ProxmoxClient;
function gastBereit(): array
{
$pve = new FakeProxmoxClient;
app()->instance(ProxmoxClient::class, $pve);
// Ohne host_id bliebe die host-Beziehung null, und run() wiese jeden
// Befehl kommentarlos ab, statt einen zu bauen — die Instanz braucht
// einen echten Host, damit forHost() ueberhaupt greifen kann.
return [$pve, Instance::factory()->create(['status' => 'active', 'vmid' => 201, 'host_id' => Host::factory()])];
}
it('legt einen Benutzer mit erzeugtem Passwort an, das niemand sieht', function () {
[$pve, $instance] = gastBereit();
// FakeProxmoxClient antwortet auf einen ungeskripteten Befehl mit Exitcode
// 0 — ohne diese Zeile saehe user:info wie "Benutzer existiert bereits"
// aus. Dieselbe Falle, dasselbe Skript wie in CustomerStepsTest.
$pve->guestScript('user:info', 1);
$sitz = Seat::factory()->create(['email' => 'anna@firma.tld', 'name' => 'Anna', 'nc_username' => 'anna@firma.tld']);
app(NextcloudUsers::class)->invite($instance, $sitz);
$befehle = implode("\n", $pve->guestCommands);
// --generate-password: Nextcloud erzeugt es, NIEMAND bekommt es zu sehen.
// --email: dorthin geht der Link, an dem der Mitarbeiter sein eigenes setzt.
expect($befehle)->toContain('--generate-password')
->and($befehle)->toContain('--email')
->and($befehle)->toContain('anna@firma.tld');
});
it('verschickt bei einem bestehenden Benutzer nur die Willkommensmail neu', function () {
[$pve, $instance] = gastBereit();
$pve->guestScripts['user:info'] = ['exitcode' => 0, 'out-data' => 'user_id: anna@firma.tld'];
$sitz = Seat::factory()->create(['email' => 'anna@firma.tld', 'nc_username' => 'anna@firma.tld']);
app(NextcloudUsers::class)->invite($instance, $sitz);
$befehle = implode("\n", $pve->guestCommands);
// Wiederholbar: ein zweiter Lauf nach einem Absturz legt keinen zweiten
// Benutzer an. Genau wie CreateCustomerAdmin es tut.
expect($befehle)->toContain('user:welcome --reset-password')
->and($befehle)->not->toContain('user:add');
});
it('setzt bei readonly einen eigenen Speicherplatz von null', function () {
[$pve, $instance] = gastBereit();
$sitz = Seat::factory()->create(['role' => 'readonly', 'nc_username' => 'anna@firma.tld']);
app(NextcloudUsers::class)->applyRole($instance, $sitz);
$befehle = implode("\n", $pve->guestCommands);
expect($befehle)->toContain('group:adduser')
->and($befehle)->toContain('nur-lesen')
->and($befehle)->toContain('files quota')
->and($befehle)->toContain('0 B');
});
it('LOESCHT den eigenen Speicherplatz, wenn readonly verlassen wird', function () {
// Die Falle aus ApplyStorageQuota: "An account with an explicit quota stops
// following the default". Ein Konto, das mit einem festen Wert aus der
// Rolle herauskommt, waere bei der naechsten Paketaenderung stumm
// ausgenommen — und niemand merkte es, bis der Kunde fragt, warum sein
// Mitarbeiter weniger Platz hat als bezahlt.
[$pve, $instance] = gastBereit();
$sitz = Seat::factory()->create(['role' => 'member', 'nc_username' => 'anna@firma.tld']);
app(NextcloudUsers::class)->applyRole($instance, $sitz);
$befehle = implode("\n", $pve->guestCommands);
expect($befehle)->toContain('files quota --delete')
->and($befehle)->not->toContain('0 B');
});
it('nimmt admin nicht kurz aus der eigenen Gruppe, wenn owner und admin auf denselben Namen zeigen', function () {
// owner und admin teilen sich dieselbe Nextcloud-Gruppe (admin). Ohne
// array_unique liefe hier group:removeuser admin VOR group:adduser admin
// — der Benutzer fiele kurz aus genau der Gruppe heraus, in die er
// gerade soll.
[$pve, $instance] = gastBereit();
$sitz = Seat::factory()->create(['role' => 'admin', 'nc_username' => 'anna@firma.tld']);
app(NextcloudUsers::class)->applyRole($instance, $sitz);
$befehle = implode("\n", $pve->guestCommands);
expect($befehle)->not->toContain("group:removeuser 'admin'")
->and($befehle)->toContain("group:adduser 'admin'");
});
it('laesst eine Instanz ohne Host unangetastet', function () {
// run() steigt vor dem Verbindungsaufbau aus, wenn es keinen Host gibt —
// es darf dabei kein einziger Befehl abgesetzt werden, denn es gibt
// nichts, worueber forHost() eine Verbindung aufbauen koennte.
$pve = new FakeProxmoxClient;
app()->instance(ProxmoxClient::class, $pve);
$instance = Instance::factory()->create(['status' => 'active', 'vmid' => 201, 'host_id' => null]);
$sitz = Seat::factory()->create(['nc_username' => 'anna@firma.tld']);
expect(app(NextcloudUsers::class)->applyRole($instance, $sitz))->toBeFalse()
->and($pve->guestCommands)->toBe([]);
});
it('wirft Sitzungen beim Sperren SOFORT hinaus', function () {
// user:disable allein laesst laufende Sitzungen bis zu fuenf Minuten
// weiterleben. Bei einem Mitarbeiter, der gerade gegangen ist, sind fuenf
// Minuten fuenf zu viel.
[$pve, $instance] = gastBereit();
$sitz = Seat::factory()->create(['nc_username' => 'anna@firma.tld']);
app(NextcloudUsers::class)->disable($instance, $sitz);
$befehle = implode("\n", $pve->guestCommands);
expect($befehle)->toContain('user:disable')
->and($befehle)->toContain('user:auth-tokens:delete');
});
it('gibt false zurueck statt zu werfen, wenn der Gast nicht antwortet', function () {
[$pve, $instance] = gastBereit();
$pve->guestThrows[201] = new RuntimeException('guest agent unreachable');
$sitz = Seat::factory()->create(['nc_username' => 'anna@firma.tld']);
expect(app(NextcloudUsers::class)->disable($instance, $sitz))->toBeFalse();
});
it('fuehrt gar nichts aus, wenn der Benutzername keiner ist', function () {
// Der Name wandert in eine Wurzel-Shell im Gast. Dieselbe Regel wie bei
// HostFirewall: ein Dienst, der eine Shell fuettert, darf sich nicht
// darauf verlassen, dass sein Aufrufer sauber war.
[$pve, $instance] = gastBereit();
$sitz = Seat::factory()->create(['nc_username' => 'anna; rm -rf /']);
expect(app(NextcloudUsers::class)->disable($instance, $sitz))->toBeFalse()
->and($pve->guestCommands)->toBe([]);
});