seed([RoleSeeder::class, SamplePsgcSeeder::class, SampleDropOffPointsSeeder::class]); } public function test_nearby_returns_sorted_by_distance(): void { $response = $this->getJson('/api/v1/drop-off-points/nearby?lat=14.6539&lng=121.0685&radius_km=5'); $response->assertOk(); $items = $response->json('data'); $this->assertGreaterThanOrEqual(2, count($items)); $first = $items[0]; $this->assertSame('Diliman Triangle DOP', $first['name']); $this->assertEqualsWithDelta(0, $first['distance_meters'], 50.0); $distances = array_column($items, 'distance_meters'); $sorted = $distances; sort($sorted); $this->assertSame($sorted, $distances); } public function test_nearby_excludes_inactive_dops(): void { DropOffPoint::factory()->at(14.6539, 121.0685)->create([ 'name' => 'Closed DOP', 'status' => DropOffPoint::STATUS_CLOSED, ]); $response = $this->getJson('/api/v1/drop-off-points/nearby?lat=14.6539&lng=121.0685&radius_km=1'); $response->assertOk(); $names = array_column($response->json('data'), 'name'); $this->assertNotContains('Closed DOP', $names); } public function test_nearby_respects_radius(): void { $response = $this->getJson('/api/v1/drop-off-points/nearby?lat=14.6539&lng=121.0685&radius_km=0.1'); $response->assertOk(); // Only Diliman itself is within 100m $this->assertCount(1, $response->json('data')); } public function test_nearby_validates_coordinates(): void { $this->getJson('/api/v1/drop-off-points/nearby?lat=999&lng=121') ->assertStatus(422) ->assertJsonValidationErrors(['lat']); } public function test_show_returns_dop_details(): void { $dop = DropOffPoint::first(); $response = $this->getJson("/api/v1/drop-off-points/{$dop->uuid}"); $response->assertOk() ->assertJsonPath('data.id', $dop->uuid) ->assertJsonPath('data.code', $dop->code); } public function test_admin_can_create_dop(): void { $admin = User::factory()->create(['role' => User::ROLE_ADMIN, 'status' => User::STATUS_ACTIVE]); Sanctum::actingAs($admin); $response = $this->postJson('/api/v1/admin/drop-off-points', [ 'name' => 'New DOP', 'code' => 'DOP-NEW-01', 'lat' => 14.5, 'lng' => 121.0, 'address_line' => '1 New St', 'capacity_kg' => 500, 'accepted_waste_types' => ['general'], ]); $response->assertCreated() ->assertJsonPath('data.code', 'DOP-NEW-01'); $this->assertDatabaseHas('drop_off_points', ['code' => 'DOP-NEW-01']); } public function test_admin_can_update_dop(): void { $admin = User::factory()->create(['role' => User::ROLE_ADMIN, 'status' => User::STATUS_ACTIVE]); Sanctum::actingAs($admin); $dop = DropOffPoint::first(); $response = $this->patchJson("/api/v1/admin/drop-off-points/{$dop->uuid}", [ 'status' => DropOffPoint::STATUS_MAINTENANCE, ]); $response->assertOk() ->assertJsonPath('data.status', 'maintenance'); } public function test_admin_can_log_capacity(): void { $admin = User::factory()->create(['role' => User::ROLE_ADMIN, 'status' => User::STATUS_ACTIVE]); Sanctum::actingAs($admin); $dop = DropOffPoint::first(); $response = $this->postJson("/api/v1/admin/drop-off-points/{$dop->uuid}/capacity", [ 'fill_percent' => 75, 'notes' => 'Friday afternoon', ]); $response->assertCreated() ->assertJsonPath('data.fill_percent', 75); $this->assertDatabaseHas('drop_off_capacity_logs', [ 'drop_off_point_id' => $dop->id, 'fill_percent' => 75, ]); } public function test_resident_blocked_from_admin_dop_endpoints(): void { $resident = User::factory()->create(['role' => User::ROLE_RESIDENT, 'status' => User::STATUS_ACTIVE]); Sanctum::actingAs($resident); $this->postJson('/api/v1/admin/drop-off-points', [ 'name' => 'X', 'code' => 'X', 'lat' => 0, 'lng' => 0, 'address_line' => 'X', ])->assertStatus(403); } public function test_admin_can_delete_dop(): void { $admin = User::factory()->create(['role' => User::ROLE_ADMIN, 'status' => User::STATUS_ACTIVE]); Sanctum::actingAs($admin); $dop = DropOffPoint::first(); $this->deleteJson("/api/v1/admin/drop-off-points/{$dop->uuid}")->assertOk(); $this->assertSoftDeleted('drop_off_points', ['id' => $dop->id]); } public function test_dop_status_transition_reassigns_households(): void { $admin = User::factory()->create(['role' => User::ROLE_ADMIN, 'status' => User::STATUS_ACTIVE]); Sanctum::actingAs($admin); $dop1 = DropOffPoint::where('status', DropOffPoint::STATUS_ACTIVE)->first(); $household = \App\Models\Household::create([ 'tenant_id' => $dop1->tenant_id, 'head_user_id' => User::factory()->create()->id, 'barangay_id' => $dop1->barangay_id, 'address_line' => '123 Test St', 'coordinates' => new \MatanYadaev\EloquentSpatial\Objects\Point(14.6539, 121.0685, 4326), 'household_size' => 3, 'assigned_drop_off_point_id' => $dop1->id, 'verification_status' => 'approved', ]); $response = $this->patchJson("/api/v1/admin/drop-off-points/{$dop1->uuid}", [ 'status' => DropOffPoint::STATUS_MAINTENANCE, ]); $response->assertOk(); $household->refresh(); $this->assertNotEquals($dop1->id, $household->assigned_drop_off_point_id); } }