seed(RoleSeeder::class); $this->admin = User::factory()->create([ 'role' => User::ROLE_ADMIN, 'status' => User::STATUS_ACTIVE, ]); } public function test_admin_can_list_users_with_filters(): void { Sanctum::actingAs($this->admin); User::factory()->count(3)->create(['role' => User::ROLE_RESIDENT]); User::factory()->count(2)->create(['role' => User::ROLE_DRIVER]); $response = $this->getJson('/api/v1/admin/users?role=resident'); $response->assertOk(); $items = $response->json('data'); $this->assertGreaterThanOrEqual(3, count($items)); foreach ($items as $u) { $this->assertSame('resident', $u['role']); } } public function test_admin_can_search_users_by_name_or_email(): void { Sanctum::actingAs($this->admin); User::factory()->create(['email' => 'unique-find@example.com']); $response = $this->getJson('/api/v1/admin/users?q=unique-find'); $response->assertOk() ->assertJsonFragment(['email' => 'unique-find@example.com']); } public function test_admin_can_show_user_with_profile(): void { Sanctum::actingAs($this->admin); $user = User::factory()->create(['role' => User::ROLE_RESIDENT]); ResidentProfile::create(['user_id' => $user->id, 'occupation' => 'Teacher']); $response = $this->getJson("/api/v1/admin/users/{$user->uuid}"); $response->assertOk() ->assertJsonPath('data.id', $user->uuid) ->assertJsonPath('data.profile.occupation', 'Teacher'); } public function test_admin_can_update_user(): void { Sanctum::actingAs($this->admin); $user = User::factory()->create(); $response = $this->patchJson("/api/v1/admin/users/{$user->uuid}", [ 'first_name' => 'Updated', 'preferred_language' => 'tl', ]); $response->assertOk() ->assertJsonPath('data.first_name', 'Updated') ->assertJsonPath('data.preferred_language', 'tl'); } public function test_admin_can_suspend_and_activate_user(): void { Sanctum::actingAs($this->admin); $user = User::factory()->create([ 'role' => User::ROLE_DRIVER, 'status' => User::STATUS_ACTIVE, ]); $this->postJson("/api/v1/admin/users/{$user->uuid}/suspend") ->assertOk() ->assertJsonPath('data.status', User::STATUS_SUSPENDED); $this->postJson("/api/v1/admin/users/{$user->uuid}/activate") ->assertOk() ->assertJsonPath('data.status', User::STATUS_ACTIVE); } public function test_admin_cannot_suspend_other_admin(): void { Sanctum::actingAs($this->admin); $otherAdmin = User::factory()->create(['role' => User::ROLE_ADMIN]); $response = $this->postJson("/api/v1/admin/users/{$otherAdmin->uuid}/suspend"); $response->assertStatus(422); } public function test_admin_can_soft_delete_user(): void { Sanctum::actingAs($this->admin); $user = User::factory()->create(['role' => User::ROLE_RESIDENT]); $response = $this->deleteJson("/api/v1/admin/users/{$user->uuid}"); $response->assertOk(); $this->assertSoftDeleted('users', ['id' => $user->id]); } public function test_admin_can_approve_driver_profile(): void { Sanctum::actingAs($this->admin); $driver = User::factory()->create([ 'role' => User::ROLE_DRIVER, 'status' => User::STATUS_PENDING, ]); DriverProfile::create(['user_id' => $driver->id, 'license_number' => 'D-1234']); $response = $this->postJson("/api/v1/admin/users/{$driver->uuid}/approve-profile"); $response->assertOk() ->assertJsonPath('data.status', User::STATUS_ACTIVE) ->assertJsonPath('data.profile.verification_status', 'approved'); $this->assertDatabaseHas('driver_profiles', [ 'user_id' => $driver->id, 'verification_status' => 'approved', ]); } public function test_admin_can_reject_driver_profile_with_reason(): void { Sanctum::actingAs($this->admin); $driver = User::factory()->create(['role' => User::ROLE_DRIVER]); DriverProfile::create(['user_id' => $driver->id, 'license_number' => 'D-9999']); $response = $this->postJson("/api/v1/admin/users/{$driver->uuid}/reject-profile", [ 'reason' => 'License photo unreadable', ]); $response->assertOk() ->assertJsonPath('data.profile.verification_status', 'rejected') ->assertJsonPath('data.profile.rejection_reason', 'License photo unreadable'); } public function test_resident_blocked_from_admin_users(): void { $resident = User::factory()->create([ 'role' => User::ROLE_RESIDENT, 'status' => User::STATUS_ACTIVE, ]); Sanctum::actingAs($resident); $this->getJson('/api/v1/admin/users')->assertStatus(403); } public function test_unauthed_blocked_from_admin_users(): void { $this->getJson('/api/v1/admin/users')->assertStatus(401); } public function test_resident_activation_requires_super_admin_and_coordinates(): void { $resident = User::factory()->create([ 'role' => User::ROLE_RESIDENT, 'status' => User::STATUS_PENDING, ]); Sanctum::actingAs($this->admin); $this->postJson("/api/v1/admin/users/{$resident->uuid}/activate") ->assertStatus(403); $superAdmin = User::factory()->create([ 'role' => User::ROLE_SUPER_ADMIN, 'status' => User::STATUS_ACTIVE, ]); Sanctum::actingAs($superAdmin); $this->postJson("/api/v1/admin/users/{$resident->uuid}/activate") ->assertStatus(422) ->assertJsonFragment(['message' => 'Resident household must have coordinates pinned before activation']); $household = Household::factory()->create([ 'head_user_id' => $resident->id, 'coordinates' => new Point(14.5995, 120.9842, 4326), ]); HouseholdMember::create([ 'household_id' => $household->id, 'user_id' => $resident->id, 'relationship' => HouseholdMember::RELATIONSHIP_HEAD, 'full_name' => $resident->full_name, ]); $this->postJson("/api/v1/admin/users/{$resident->uuid}/activate") ->assertOk() ->assertJsonPath('data.status', User::STATUS_ACTIVE); } public function test_admin_can_group_resident_into_existing_household(): void { Sanctum::actingAs($this->admin); $resident = User::factory()->create([ 'role' => User::ROLE_RESIDENT, 'status' => User::STATUS_PENDING, ]); $household = Household::factory()->create(); $response = $this->postJson("/api/v1/admin/users/{$resident->uuid}/group-into-household", [ 'household_id' => $household->id, ]); $response->assertOk() ->assertJsonPath('data.household.id', $household->uuid); $this->assertDatabaseHas('household_members', [ 'household_id' => $household->id, 'user_id' => $resident->id, 'relationship' => HouseholdMember::RELATIONSHIP_OTHER, ]); } public function test_admin_can_group_resident_into_new_household(): void { Sanctum::actingAs($this->admin); $this->seed(SamplePsgcSeeder::class); $barangay = Barangay::firstOrFail(); $resident = User::factory()->create([ 'role' => User::ROLE_RESIDENT, 'status' => User::STATUS_PENDING, ]); $response = $this->postJson("/api/v1/admin/users/{$resident->uuid}/group-into-household", [ 'address_line' => '123 New Pin Street', 'barangay_id' => $barangay->id, 'household_size' => 3, 'lat' => 14.5995, 'lng' => 120.9842, ]); $response->assertOk() ->assertJsonPath('data.household.address_line', '123 New Pin Street') ->assertJsonPath('data.household.household_size', 3); $this->assertDatabaseHas('households', [ 'head_user_id' => $resident->id, 'address_line' => '123 New Pin Street', 'household_size' => 3, ]); $this->assertDatabaseHas('household_members', [ 'user_id' => $resident->id, 'relationship' => HouseholdMember::RELATIONSHIP_HEAD, ]); } public function test_admin_can_group_resident_with_existing_household_and_clean_it_up(): void { Sanctum::actingAs($this->admin); $resident = User::factory()->create([ 'role' => User::ROLE_RESIDENT, 'status' => User::STATUS_ACTIVE, ]); $oldHousehold = Household::factory()->create([ 'head_user_id' => $resident->id, ]); $oldMember = HouseholdMember::create([ 'household_id' => $oldHousehold->id, 'user_id' => $resident->id, 'relationship' => HouseholdMember::RELATIONSHIP_HEAD, 'full_name' => $resident->full_name, ]); $newHousehold = Household::factory()->create(); $response = $this->postJson("/api/v1/admin/users/{$resident->uuid}/group-into-household", [ 'household_id' => $newHousehold->id, ]); $response->assertOk(); // Check new membership $this->assertDatabaseHas('household_members', [ 'household_id' => $newHousehold->id, 'user_id' => $resident->id, 'relationship' => HouseholdMember::RELATIONSHIP_OTHER, ]); // Check old membership deleted $this->assertSoftDeleted('household_members', [ 'id' => $oldMember->id, ]); // Check old household deleted $this->assertSoftDeleted('households', [ 'id' => $oldHousehold->id, ]); } }