seed([\Database\Seeders\SamplePsgcSeeder::class]); $this->lguA = $this->defaultTenant; $this->lguB = Tenant::factory()->create(['code' => 'LGU-B', 'name' => 'LGU B']); $this->adminA = User::factory()->create([ 'tenant_id' => $this->lguA->id, 'role' => User::ROLE_ADMIN, ]); $this->adminB = User::factory()->create([ 'tenant_id' => $this->lguB->id, 'role' => User::ROLE_ADMIN, ]); $this->superAdmin = User::factory()->create([ 'tenant_id' => null, 'role' => User::ROLE_SUPER_ADMIN, ]); } public function test_lgu_a_can_create_and_view_own_entities() { $this->actingAs($this->adminA); $this->withHeaders(['X-Tenant-Code' => $this->lguA->code]); $city = \App\Models\CityMunicipality::first(); $barangay = \App\Models\Barangay::first(); $dumpsiteResponse = $this->postJson('/api/v1/admin/dumpsites', [ 'name' => 'LGU A Dumpsite', 'code' => 'DS-01', 'address_line' => '123 Test St', 'city_municipality_id' => $city->id, 'lat' => 14.0, 'lng' => 121.0, 'status' => 'active' ]); $dumpsiteResponse->assertCreated(); // Create Drop-off Point $dopResponse = $this->postJson('/api/v1/admin/drop-off-points', [ 'name' => 'LGU A DOP', 'code' => 'DOP-01', 'address_line' => '456 Test Ave', 'barangay_id' => $barangay->id, 'lat' => 14.1, 'lng' => 121.1, 'status' => 'active' ]); $dopResponse->assertCreated(); // Create Team $teamResponse = $this->postJson('/api/v1/admin/teams', [ 'name' => 'LGU A Team', 'status' => 'active' ]); $teamResponse->assertCreated(); } public function test_lgu_b_cannot_access_lgu_a_entities() { // Seed LGU A data $dumpsiteA = Dumpsite::factory()->create(['tenant_id' => $this->lguA->id]); $dopA = DropOffPoint::factory()->create(['tenant_id' => $this->lguA->id]); $teamA = new CollectionTeam(['name' => 'Team A', 'status' => 'active']); $teamA->tenant_id = $this->lguA->id; $teamA->save(); $routeA = new Route(['name' => 'Route A', 'code' => 'RTA-01', 'geojson' => []]); $routeA->tenant_id = $this->lguA->id; $routeA->save(); // Act as LGU B Admin $this->actingAs($this->adminB); $this->withHeaders(['X-Tenant-Code' => $this->lguB->code]); // 1. List Endpoints Should Not Contain LGU A Data $this->getJson('/api/v1/admin/dumpsites')->assertJsonMissing(['id' => $dumpsiteA->id]); $this->getJson('/api/v1/admin/drop-off-points')->assertJsonMissing(['id' => $dopA->id]); $this->getJson('/api/v1/admin/teams')->assertJsonMissing(['id' => $teamA->id]); $this->getJson('/api/v1/admin/routes')->assertJsonMissing(['id' => $routeA->id]); // 2. Direct Access Should Fail (404/403) $this->getJson("/api/v1/admin/routes/{$routeA->id}")->assertStatus(404); // 3. API Tampering: Attempt to create a trip for LGU B using LGU A's Route $truckB = new Truck(['plate_number' => 'ABC-1234', 'capacity_tons' => 5]); $truckB->tenant_id = $this->lguB->id; $truckB->save(); $teamB = new CollectionTeam(['name' => 'Team B', 'status' => 'active']); $teamB->tenant_id = $this->lguB->id; $teamB->save(); $response = $this->postJson('/api/v1/admin/trips', [ 'route_id' => $routeA->id, 'truck_id' => $truckB->id, 'collection_team_id' => $teamB->id, 'scheduled_date' => now()->format('Y-m-d'), ]); // Validation should fail because route_id doesn't belong to LGU B $response->assertStatus(422); } }