Files
Verde-Web/tests/Feature/LguTenantIsolationTest.php

138 lines
4.5 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\Tenant;
use App\Models\User;
use App\Models\DropOffPoint;
use App\Models\Dumpsite;
use App\Models\Route;
use App\Models\CollectionTeam;
use App\Models\Trip;
use App\Models\Truck;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class LguTenantIsolationTest extends TestCase
{
use RefreshDatabase;
protected $seed = true;
protected Tenant $lguA;
protected Tenant $lguB;
protected User $adminA;
protected User $adminB;
protected User $superAdmin;
protected function setUp(): void
{
parent::setUp();
$this->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);
}
}