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