seed(RolesAndPermissionsSeeder::class); $this->admin = User::factory()->approved()->create(); $this->admin->assignRole('admin'); } public function test_admin_can_list_approved_users(): void { $other = User::factory()->approved()->create(); User::factory()->create(['approved_at' => null]); $response = $this->actingAs($this->admin)->getJson('/web-api/admin/users'); $response->assertOk(); $emails = collect($response->json())->pluck('email'); $this->assertTrue($emails->contains($this->admin->email)); $this->assertTrue($emails->contains($other->email)); } public function test_non_admin_cannot_list_users(): void { $user = User::factory()->approved()->create(); $this->actingAs($user)->getJson('/web-api/admin/users')->assertForbidden(); } public function test_admin_can_create_a_user(): void { $response = $this->actingAs($this->admin)->postJson('/web-api/admin/users', [ 'name' => 'New Person', 'email' => 'newperson@example.com', 'password' => 'password123', 'role' => 'user', ]); $response->assertCreated(); $response->assertJsonPath('email', 'newperson@example.com'); $response->assertJsonPath('roles.0', 'user'); $created = User::where('email', 'newperson@example.com')->firstOrFail(); $this->assertNotNull($created->approved_at, 'un usuario creado por un admin queda aprobado automáticamente'); $this->assertDatabaseHas('audit_logs', ['action' => 'user.created', 'subject_id' => (string) $created->id]); } public function test_create_validates_unique_email_and_known_role(): void { $this->actingAs($this->admin)->postJson('/web-api/admin/users', [ 'name' => 'Dup', 'email' => $this->admin->email, 'password' => 'password123', 'role' => 'user', ])->assertUnprocessable()->assertJsonValidationErrors('email'); $this->actingAs($this->admin)->postJson('/web-api/admin/users', [ 'name' => 'Bad Role', 'email' => 'badrole@example.com', 'password' => 'password123', 'role' => 'superadmin', ])->assertUnprocessable()->assertJsonValidationErrors('role'); } public function test_admin_can_update_a_users_name_email_role_and_active_state(): void { $target = User::factory()->approved()->create(['is_active' => true]); $target->assignRole('user'); $response = $this->actingAs($this->admin)->putJson("/web-api/admin/users/{$target->id}", [ 'name' => 'Renamed', 'email' => 'renamed@example.com', 'is_active' => false, 'role' => 'admin', ]); $response->assertOk(); $response->assertJsonPath('name', 'Renamed'); $response->assertJsonPath('is_active', false); $response->assertJsonPath('roles.0', 'admin'); $this->assertDatabaseHas('audit_logs', ['action' => 'user.updated', 'subject_id' => (string) $target->id]); } public function test_deactivating_a_user_dispatches_the_user_deactivated_event(): void { \Illuminate\Support\Facades\Event::fake([\App\Events\UserDeactivated::class]); $target = User::factory()->approved()->create(['is_active' => true]); $target->assignRole('user'); $this->actingAs($this->admin)->putJson("/web-api/admin/users/{$target->id}", [ 'name' => $target->name, 'email' => $target->email, 'is_active' => false, 'role' => 'user', ])->assertOk(); \Illuminate\Support\Facades\Event::assertDispatched(\App\Events\UserDeactivated::class); } public function test_admin_cannot_remove_their_own_admin_role(): void { $response = $this->actingAs($this->admin)->putJson("/web-api/admin/users/{$this->admin->id}", [ 'name' => $this->admin->name, 'email' => $this->admin->email, 'role' => 'user', ]); $response->assertStatus(422); $this->assertTrue($this->admin->fresh()->hasRole('admin')); } public function test_admin_cannot_delete_themselves(): void { $this->actingAs($this->admin) ->deleteJson("/web-api/admin/users/{$this->admin->id}") ->assertStatus(422); $this->assertNull($this->admin->fresh()->deleted_at); } public function test_cannot_delete_the_last_admin(): void { $secondAdmin = User::factory()->approved()->create(); $secondAdmin->assignRole('admin'); // Con dos admins, borrar uno debe andar. $this->actingAs($this->admin) ->deleteJson("/web-api/admin/users/{$secondAdmin->id}") ->assertNoContent(); // Ahora $this->admin es el único admin — ni siquiera otro usuario con // permiso users.delete (pero sin rol admin) puede borrarlo. $viewer = User::factory()->approved()->create(); $viewer->givePermissionTo('users.delete'); $this->actingAs($viewer) ->deleteJson("/web-api/admin/users/{$this->admin->id}") ->assertStatus(422); $this->assertNull($this->admin->fresh()->deleted_at); } public function test_admin_can_soft_delete_restore_and_force_delete_a_user(): void { $target = User::factory()->approved()->create(); $this->actingAs($this->admin) ->deleteJson("/web-api/admin/users/{$target->id}") ->assertNoContent(); $this->assertSoftDeleted('users', ['id' => $target->id]); $this->assertDatabaseHas('audit_logs', ['action' => 'user.deleted', 'subject_id' => (string) $target->id]); $this->actingAs($this->admin) ->getJson('/web-api/admin/users/trashed') ->assertOk() ->assertJsonFragment(['email' => $target->email]); $this->actingAs($this->admin) ->postJson("/web-api/admin/users/{$target->id}/restore") ->assertOk(); $this->assertDatabaseHas('users', ['id' => $target->id, 'deleted_at' => null]); $this->assertDatabaseHas('audit_logs', ['action' => 'user.restored', 'subject_id' => (string) $target->id]); $this->actingAs($this->admin)->deleteJson("/web-api/admin/users/{$target->id}")->assertNoContent(); $this->actingAs($this->admin) ->deleteJson("/web-api/admin/users/{$target->id}/force") ->assertNoContent(); $this->assertDatabaseMissing('users', ['id' => $target->id]); $this->assertDatabaseHas('audit_logs', ['action' => 'user.force_deleted', 'subject_id' => (string) $target->id]); } public function test_admin_can_list_and_approve_pending_registrations(): void { $pending = User::factory()->create(['approved_at' => null]); $this->actingAs($this->admin) ->getJson('/web-api/admin/users/pending') ->assertOk() ->assertJsonFragment(['email' => $pending->email]); $this->actingAs($this->admin) ->postJson("/web-api/admin/users/{$pending->id}/approve") ->assertOk() ->assertJsonPath('email', $pending->email); $this->assertNotNull($pending->fresh()->approved_at); $this->assertDatabaseHas('audit_logs', ['action' => 'user.approved', 'subject_id' => (string) $pending->id]); } }