'array']); app(PermissionRegistrar::class)->initializeCache(); app(PermissionRegistrar::class)->forgetCachedPermissions(); // The test database has the admin_roles definitions but no permissions foreach (AdminRole::all() as $definition) { foreach ($definition->permissions ?? [] as $name) { Permission::findOrCreate($name, 'web'); } } app(PermissionRegistrar::class)->forgetCachedPermissions(); } private function actAsAdminProfile(User $user, Admin $admin): void { $this->actingAs($user, 'web'); session(['activeProfileType' => Admin::class, 'activeProfileId' => $admin->id, 'active_guard' => 'admin']); } private function checks(): object { return new class { use ProfilePermissionTrait; public function profiles(): bool { return (bool) $this->getCanManageProfiles(); } public function accounts(): bool { return (bool) $this->getCanManageAccounts(); } }; } public function test_an_administrator_without_the_legacy_role_may_manage_profiles_and_accounts(): void { $user = User::factory()->create(); $admin = Admin::factory()->create(); AdminRole::where('name', 'administrator')->firstOrFail()->assignScopedTo($user, $admin->id); $this->actAsAdminProfile($user, $admin); $this->assertTrue($this->checks()->profiles()); $this->assertTrue($this->checks()->accounts()); } public function test_a_role_without_the_permission_is_refused(): void { $user = User::factory()->create(); $admin = Admin::factory()->create(); $definition = AdminRole::where('name', 'content-editor')->firstOrFail(); $this->assertNotContains('manage profiles', $definition->permissions); $definition->assignScopedTo($user, $admin->id); $this->actAsAdminProfile($user, $admin); $this->assertFalse($this->checks()->profiles()); $this->assertFalse($this->checks()->accounts()); } public function test_a_role_on_another_admin_profile_does_not_count(): void { $user = User::factory()->create(); [$mine, $other] = Admin::factory()->count(2)->create(); AdminRole::where('name', 'administrator')->firstOrFail()->assignScopedTo($user, $other->id); $this->actAsAdminProfile($user, $mine); $this->assertFalse($this->checks()->profiles()); } public function test_the_global_super_admin_role_grants_nothing_without_a_scoped_role(): void { $user = User::factory()->create(); $user->assignRole(Role::findOrCreate('super-admin', 'web')); $admin = Admin::factory()->create(); $this->actAsAdminProfile($user, $admin); $this->assertFalse($this->checks()->profiles()); $this->assertFalse($user->can('manage admins')); } public function test_a_new_admin_profile_gives_the_linked_user_the_default_role_only(): void { Mail::fake(); config([config('app.timebank_config') . '.admin_settings.default_admin_role' => 'site-editor']); $user = User::factory()->create(); $data = Admin::factory()->raw(['password' => 'secret-password', 'linkUser' => $user->id]); $create = new \ReflectionMethod(Create::class, 'createAdminProfile'); $admin = $create->invoke(app(Create::class), $data); $user->refresh(); $this->assertTrue($user->hasRole("Admin\\{$admin->id}\\site-editor")); $this->assertFalse($user->hasRole("Admin\\{$admin->id}\\admin"), 'no legacy role'); $this->assertFalse($user->hasRole('admin'), 'no global admin role'); $this->assertEqualsCanonicalizing( AdminRole::where('name', 'site-editor')->first()->permissions, Role::findByName("Admin\\{$admin->id}\\site-editor", 'web')->permissions->pluck('name')->all() ); } private function legacyRole(Admin $admin, array $holders): Role { $role = Role::create(['name' => "Admin\\{$admin->id}\\admin", 'guard_name' => 'web']); $role->syncPermissions(Permission::all()); foreach ($holders as $holder) { $holder->assignRole($role); } app(PermissionRegistrar::class)->forgetCachedPermissions(); return $role; } public function test_the_migration_gives_holders_the_default_role_then_removes_the_legacy_role(): void { $admin = Admin::factory()->create(); [$onlyLegacy, $both] = User::factory()->count(2)->create(); $administrator = AdminRole::where('name', 'administrator')->firstOrFail(); $administrator->assignScopedTo($both, $admin->id); $this->legacyRole($admin, [$onlyLegacy, $both]); $this->artisan('admin-roles:migrate')->assertExitCode(0); $target = "Admin\\{$admin->id}\\administrator"; $this->assertTrue($onlyLegacy->fresh()->hasRole($target), 'the holder with only the legacy role keeps access'); $this->assertTrue($both->fresh()->hasRole($target)); $this->assertNull(Role::where('name', "Admin\\{$admin->id}\\admin")->first(), 'legacy role removed'); $this->assertEqualsCanonicalizing($administrator->permissions, Role::findByName($target, 'web')->permissions->pluck('name')->all()); // A second run changes nothing $this->artisan('admin-roles:migrate')->expectsOutputToContain('nothing to do')->assertExitCode(0); } public function test_a_dry_run_changes_nothing(): void { $admin = Admin::factory()->create(); $user = User::factory()->create(); $this->legacyRole($admin, [$user]); $this->artisan('admin-roles:migrate', ['--dry-run' => true]) ->expectsOutputToContain("user #{$user->id} gets Admin\\{$admin->id}\\administrator") ->assertExitCode(0); $this->assertNotNull(Role::where('name', "Admin\\{$admin->id}\\admin")->first()); $this->assertFalse($user->fresh()->hasRole("Admin\\{$admin->id}\\administrator")); } public function test_without_a_definition_for_the_default_role_nothing_changes(): void { config([config('app.timebank_config') . '.admin_settings.default_admin_role' => 'no-such-role']); $admin = Admin::factory()->create(); $user = User::factory()->create(); $this->legacyRole($admin, [$user]); $this->artisan('admin-roles:migrate')->expectsOutputToContain("'no-such-role'")->assertExitCode(1); $this->assertNotNull(Role::where('name', "Admin\\{$admin->id}\\admin")->first()); $this->assertTrue($user->fresh()->hasRole("Admin\\{$admin->id}\\admin")); } public function test_upgrade_verify_warns_about_legacy_roles(): void { $this->legacyRole(Admin::factory()->create(), []); $this->artisan('upgrade:verify', ['--only' => 'permissions']) ->expectsOutputToContain('legacy roles') ->assertExitCode(0); } public function test_the_settings_form_opens_for_a_role_with_admin_settings_but_without_manage_admins(): void { $user = User::factory()->create(); $admin = Admin::factory()->create(); // The migration's definitions predate the admin capabilities that database:update adds $definition = AdminRole::where('name', 'content-editor')->firstOrFail(); $this->assertNotContains('manage admins', $definition->permissions); Permission::findOrCreate('admin settings', 'web'); $definition->update(['permissions' => array_values(array_unique([...$definition->permissions, 'admin settings']))]); $definition->assignScopedTo($user, $admin->id); $this->actAsAdminProfile($user, $admin); \Livewire\Livewire::test(\App\Http\Livewire\Profile\UpdateSettingsForm::class)->assertStatus(200); } public function test_the_flat_admin_role_migration_only_grants_on_linked_admin_profiles(): void { [$mine, $other] = Admin::factory()->count(2)->create(); [$linked, $unlinked] = User::factory()->count(2)->create(); $mine->users()->attach($linked->id); $flat = Role::findOrCreate('admin', 'web'); $linked->assignRole($flat); $unlinked->assignRole($flat); $command = app(\App\Console\Commands\DatabaseUpdate::class); $command->setLaravel(app()); $output = new \Symfony\Component\Console\Output\BufferedOutput(); $command->setOutput(new \Illuminate\Console\OutputStyle(new \Symfony\Component\Console\Input\ArrayInput([]), $output)); (new \ReflectionMethod($command, 'migrateFlatAdminRoleToScoped'))->invoke($command); $scoped = fn (User $user) => $user->fresh()->roles->pluck('name')->filter(fn ($n) => str_starts_with($n, 'Admin\\'))->values()->all(); $this->assertSame(["Admin\\{$mine->id}\\administrator"], $scoped($linked), 'only the linked admin profile'); $this->assertSame([], $scoped($unlinked), 'no admin profile link: no role on any admin profile'); $this->assertStringContainsString("id:{$unlinked->id}) has the flat admin role but no admin profile link", $output->fetch()); } }