CluPilotCloud/tests/Feature/Admin/RoleManagementTest.php

191 lines
7.4 KiB
PHP

<?php
use App\Livewire\Admin\Roles;
use App\Models\Operator;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
/**
* Rollen und ihre Rechte waren fest im Code. Zuweisbar war in der Konsole nur
* die Rolle selbst — was sie DARF, stand in Migrationen.
*
* Beim Nachsehen fiel zweierlei auf, und beides ist der Grund, warum die Liste
* jetzt aus der Datenbank kommt statt aus einer Konstanten:
*
* - `Developer` existiert als Rolle, wurde vom Einladen-Formular aber nie
* angeboten: die zulässigen Namen standen an DREI Stellen (zwei
* Validierungsregeln plus Operator::OPERATOR_ROLES), und eine davon war
* nicht mitgezogen worden.
* - `dpa.manage` lag zusätzlich auf dem `web`-Guard, wo kein Betreiber je
* hinsieht — eine Migration hatte den Guard verwechselt.
*/
it('offers every role that actually exists, not a list written down elsewhere', function () {
Role::findOrCreate('Buchhaltung', 'operator');
Livewire::actingAs(operator('Owner'), 'operator')
->test(Roles::class)
->assertSee('Buchhaltung')
->assertSee('Developer');
});
it('lets the owner give a role a capability', function () {
$role = Role::findOrCreate('Buchhaltung', 'operator');
Livewire::actingAs(operator('Owner'), 'operator')
->test(Roles::class)
->set("granted.{$role->id}", ['console.view', 'billing.manage'])
->call('save', $role->id)
->assertHasNoErrors();
expect($role->fresh()->permissions->pluck('name')->sort()->values()->all())
->toBe(['billing.manage', 'console.view']);
});
it('takes a capability away again', function () {
$role = Role::findOrCreate('Buchhaltung', 'operator');
$role->givePermissionTo('billing.manage', 'console.view');
Livewire::actingAs(operator('Owner'), 'operator')
->test(Roles::class)
->set("granted.{$role->id}", ['console.view'])
->call('save', $role->id);
expect($role->fresh()->permissions->pluck('name')->all())->toBe(['console.view']);
});
it('refuses to let Owner be stripped', function () {
// Sonst sperrt sich der Betreiber mit einem Klick selbst aus, und es gibt
// niemanden mehr, der es zurücknehmen könnte. Die einzige Rolle, die
// dieser Bildschirm nicht anfassen darf.
$owner = Role::where('name', 'Owner')->where('guard_name', 'operator')->firstOrFail();
$vorher = $owner->permissions->pluck('name')->sort()->values()->all();
Livewire::actingAs(operator('Owner'), 'operator')
->test(Roles::class)
->set("granted.{$owner->id}", ['console.view'])
->call('save', $owner->id)
->assertHasErrors();
expect($owner->fresh()->permissions->pluck('name')->sort()->values()->all())->toBe($vorher);
});
it('creates a role', function () {
Livewire::actingAs(operator('Owner'), 'operator')
->test(Roles::class)
->set('newRole', 'Buchhaltung')
->call('create')
->assertHasNoErrors();
expect(Role::where('name', 'Buchhaltung')->where('guard_name', 'operator')->exists())->toBeTrue();
});
it('never creates a role on the wrong guard', function () {
// Genau der Fehler, der `dpa.manage` auf dem web-Guard hinterlassen hat:
// ein Eintrag, den kein Betreiber je sieht, weil Operator gegen
// `operator` auflöst.
Livewire::actingAs(operator('Owner'), 'operator')
->test(Roles::class)
->set('newRole', 'Buchhaltung')
->call('create');
expect(Role::where('name', 'Buchhaltung')->where('guard_name', 'web')->exists())->toBeFalse();
});
it('will not delete a role somebody still holds', function () {
$role = Role::findOrCreate('Buchhaltung', 'operator');
Operator::factory()->create()->assignRole($role);
Livewire::actingAs(operator('Owner'), 'operator')
->test(Roles::class)
->call('delete', $role->id)
->assertHasErrors();
expect(Role::where('name', 'Buchhaltung')->exists())->toBeTrue();
});
it('deletes a role nobody holds', function () {
$role = Role::findOrCreate('Buchhaltung', 'operator');
Livewire::actingAs(operator('Owner'), 'operator')
->test(Roles::class)
->call('delete', $role->id)
->assertHasNoErrors();
expect(Role::where('name', 'Buchhaltung')->exists())->toBeFalse();
});
it('is not reachable without staff.manage', function () {
// Wer bestimmen darf, was eine Rolle kann, bestimmt alles andere gleich
// mit. Dasselbe Recht wie das Einladen von Mitarbeitern.
Livewire::actingAs(operator('Admin'), 'operator')
->test(Roles::class)
->assertForbidden();
});
it('keeps counting capabilities correctly past the first role', function () {
// Ein Fehler, den ich beim Umbau selbst gebaut habe: die Ansicht nannte
// die Zählvariable `$granted` — genauso wie die Eigenschaft des Bauteils.
// Ab der zweiten Rolle war sie eine Zahl statt der Liste, und die Seite
// starb an `count(int)`. Mit einer Rolle wäre es nie aufgefallen.
Role::findOrCreate('Buchhaltung', 'operator')->givePermissionTo('console.view');
Role::findOrCreate('Werkstatt', 'operator');
Livewire::actingAs(operator('Owner'), 'operator')
->test(Roles::class)
->assertOk()
->assertSee('Buchhaltung')
->assertSee('Werkstatt');
});
it('groups the capabilities instead of listing twenty-one in a row', function () {
$groups = Livewire::actingAs(operator('Owner'), 'operator')
->test(Roles::class)
->viewData('groups');
expect($groups)->toHaveKey('billing')
->and($groups)->toHaveKey('provisioning')
->and(count($groups))->toBeLessThan(21);
});
/**
* Die Rollenseite zeigte statt der Beschreibungen ihre eigenen Schlüssel:
* „roles.can.billing.manage" stand da, wo „Rechnungen, Gutschriften,
* Zahlungsprobleme" hingehört.
*
* Die Übersetzungen waren die ganze Zeit vorhanden. Der Aufruf lautete
* `__('roles.can.'.$permission)`, und ein Berechtigungsname trägt selbst einen
* Punkt — `__()` liest Punkte als Verschachtelung und suchte deshalb
* `roles → can → billing → manage`, während in der Sprachdatei ein FLACHER
* Schlüssel `'billing.manage'` steht. Gefunden wurde nie etwas.
*/
it('zeigt zu jeder Berechtigung ihre Beschreibung, nicht ihren Schluessel', function () {
foreach (['de', 'en'] as $locale) {
app()->setLocale($locale);
$labels = (array) trans('roles.can');
// Aus der Datenbank, nicht aus einer Liste im Test: so kann eine neu
// angelegte Berechtigung nicht ohne Beschreibung durchrutschen.
$permissions = Permission::query()
->where('guard_name', 'operator')->pluck('name');
foreach ($permissions as $permission) {
expect($labels[$permission] ?? null)
->not->toBeNull("{$permission}“ hat in lang/{$locale}/roles.php keine Beschreibung.")
// Und die Beschreibung ist keine Wiederholung des Schlüssels.
->not->toBe($permission);
}
}
});
it('rendert die Beschreibungen wirklich auf der Seite', function () {
// Gegen das ausgelieferte HTML, nicht gegen die Sprachdatei: der Fehler lag
// nicht in den Texten, sondern darin, wie die Seite sie holte.
$html = $this->actingAs(admin(), 'operator')->get(route('admin.roles'))->assertOk()->getContent();
expect($html)->toContain(e(trans('roles.can')['billing.manage']))
// Und kein roher Schlüssel mehr.
->and($html)->not->toContain('roles.can.billing.manage');
});