{$state}()->create(); Livewire::actingAs($user)->test(Users::class) ->call('create') ->assertForbidden(); } } public function test_non_admin_cannot_confirm_remove(): void { $operator = User::factory()->operator()->create(); $victim = User::factory()->create(); Livewire::actingAs($operator)->test(Users::class) ->call('confirmRemove', $victim->id) ->assertForbidden(); } public function test_non_admin_cannot_confirm_role_change(): void { $viewer = User::factory()->viewer()->create(); $target = User::factory()->operator()->create(); Livewire::actingAs($viewer)->test(Users::class) ->call('confirmRoleChange', $target->id, 'viewer') ->assertForbidden(); } public function test_admin_may_open_create_and_confirm_role_change(): void { $admin = User::factory()->create(); $target = User::factory()->operator()->create(); Livewire::actingAs($admin)->test(Users::class) ->call('create') ->assertOk() ->assertDispatched('openModal'); Livewire::actingAs($admin)->test(Users::class) ->call('confirmRoleChange', $target->id, 'viewer') ->assertOk() ->assertDispatched('openModal'); } // ── applyRoleChange: happy paths ───────────────────────────────────────────────── public function test_admin_demotes_operator_to_viewer_and_audits(): void { $admin = User::factory()->create(); $target = User::factory()->operator()->create(['email' => 'op@example.com']); $this->actingAs($admin); $token = $this->confirmedToken('userRoleChanged', ['userId' => $target->id, 'role' => 'viewer']); Livewire::actingAs($admin)->test(Users::class) ->call('applyRoleChange', $token) ->assertDispatched('usersChanged'); $this->assertSame(Role::Viewer, $target->fresh()->role); $this->assertDatabaseHas('audit_events', [ 'action' => 'user.role_change', 'target' => 'op@example.com', 'user_id' => $admin->id, ]); } public function test_admin_promotes_operator_to_admin(): void { $admin = User::factory()->create(); $target = User::factory()->operator()->create(); $this->actingAs($admin); $token = $this->confirmedToken('userRoleChanged', ['userId' => $target->id, 'role' => 'admin']); Livewire::actingAs($admin)->test(Users::class) ->call('applyRoleChange', $token); $this->assertSame(Role::Admin, $target->fresh()->role); } // ── Last-admin invariant ───────────────────────────────────────────────────────── public function test_sole_admin_cannot_be_demoted(): void { // Exactly one admin (self); an operator keeps the account count > 1. $admin = User::factory()->create(); User::factory()->operator()->create(); $this->actingAs($admin); $token = $this->confirmedToken('userRoleChanged', ['userId' => $admin->id, 'role' => 'viewer']); Livewire::actingAs($admin)->test(Users::class) ->call('applyRoleChange', $token) ->assertNotDispatched('usersChanged'); // Role unchanged — the sole admin stays admin, no exception thrown. $this->assertSame(Role::Admin, $admin->fresh()->role); $this->assertSame(0, AuditEvent::where('action', 'user.role_change')->count()); } public function test_one_of_two_admins_may_be_demoted(): void { $admin = User::factory()->create(); $other = User::factory()->create(); // second admin (factory default role) $this->actingAs($admin); $token = $this->confirmedToken('userRoleChanged', ['userId' => $other->id, 'role' => 'operator']); Livewire::actingAs($admin)->test(Users::class) ->call('applyRoleChange', $token) ->assertDispatched('usersChanged'); $this->assertSame(Role::Operator, $other->fresh()->role); } public function test_removing_the_sole_admin_is_refused_and_admin_survives(): void { // Two accounts so the last-ACCOUNT guard passes, but only one admin (self). The // acting admin removing itself is caught by the self-guard first; the last-admin // removal guard is the defense-in-depth behind it. Either way: the admin survives. $admin = User::factory()->create(); User::factory()->operator()->create(); $this->actingAs($admin); $token = $this->confirmedToken('userRemoved', ['userId' => $admin->id]); Livewire::actingAs($admin)->test(Users::class) ->call('remove', $token); $this->assertDatabaseHas('users', ['id' => $admin->id]); $this->assertSame(Role::Admin, $admin->fresh()->role); } // ── CreateUser modal: admin-only, default role, validation ─────────────────────── public function test_non_admin_cannot_mount_create_user(): void { $operator = User::factory()->operator()->create(); Livewire::actingAs($operator)->test(CreateUser::class) ->assertForbidden(); } public function test_admin_creates_user_defaulting_to_operator(): void { $admin = User::factory()->create(); Livewire::actingAs($admin)->test(CreateUser::class) ->set('name', 'New Op') ->set('email', 'newop@example.com') ->call('save') ->assertHasNoErrors(); $new = User::where('email', 'newop@example.com')->first(); $this->assertNotNull($new); $this->assertSame(Role::Operator, $new->role); } public function test_create_user_role_validation_rejects_invalid_role(): void { $admin = User::factory()->create(); Livewire::actingAs($admin)->test(CreateUser::class) ->set('name', 'Bad Role') ->set('email', 'badrole@example.com') ->set('role', 'superuser') ->call('save') ->assertHasErrors('role'); $this->assertNull(User::where('email', 'badrole@example.com')->first()); } }