Files
GSB-Construction/tests/Feature/TenantScopeTest.php

270 lines
9.2 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Modules\ContractorManagement\Models\Contractor;
use Modules\ProjectManagement\Models\Project;
use Spatie\Permission\Models\Role;
use Tests\TestCase;
class TenantScopeTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
// Create required roles for tests
Role::firstOrCreate(['name' => 'admin']);
Role::firstOrCreate(['name' => 'contractor-admin']);
}
public function test_tenant_isolation_is_enforced_between_independent_contractors(): void
{
// 1. Create two independent contractors
$contractorA = Contractor::create([
'company_name' => 'Contractor A',
'contact_person' => 'Person A',
'email' => 'a@contractor.com',
'status' => 'active',
'payment_terms' => 'net_30',
]);
$contractorB = Contractor::create([
'company_name' => 'Contractor B',
'contact_person' => 'Person B',
'email' => 'b@contractor.com',
'status' => 'active',
'payment_terms' => 'net_30',
]);
// 2. Create projects for each
$projectA = Project::create([
'name' => 'Project A',
'contractor_id' => $contractorA->id,
'code' => 'PRJ-2026-001',
]);
$projectB = Project::create([
'name' => 'Project B',
'contractor_id' => $contractorB->id,
'code' => 'PRJ-2026-002',
]);
// 3. Create admin user for Contractor B
$userB = User::factory()->create([
'contractor_id' => $contractorB->id,
'user_type' => 'admin',
'status' => 'active',
]);
$userB->assignRole('contractor-admin');
// 4. Authenticate as Contractor B user
$this->actingAs($userB);
// 5. Query projects and verify Contractor B only sees Project B
$visibleProjects = Project::all();
$this->assertTrue($visibleProjects->contains($projectB));
$this->assertFalse($visibleProjects->contains($projectA));
}
public function test_platform_owners_bypass_tenant_filtering(): void
{
// 1. Create independent contractors and projects
$contractorA = Contractor::create([
'company_name' => 'Contractor A',
'contact_person' => 'Person A',
'email' => 'a@contractor.com',
'status' => 'active',
'payment_terms' => 'net_30',
]);
$contractorB = Contractor::create([
'company_name' => 'Contractor B',
'contact_person' => 'Person B',
'email' => 'b@contractor.com',
'status' => 'active',
'payment_terms' => 'net_30',
]);
$projectA = Project::create([
'name' => 'Project A',
'contractor_id' => $contractorA->id,
'code' => 'PRJ-2026-001',
]);
$projectB = Project::create([
'name' => 'Project B',
'contractor_id' => $contractorB->id,
'code' => 'PRJ-2026-002',
]);
// 2. Create Platform Owner (contractor_id === null)
$platformAdmin = User::factory()->create([
'contractor_id' => null,
'user_type' => 'admin',
'status' => 'active',
]);
$platformAdmin->assignRole('admin');
// 3. Authenticate as Platform Owner
$this->actingAs($platformAdmin);
// 4. Query projects and verify both projects are visible
$visibleProjects = Project::all();
$this->assertTrue($visibleProjects->contains($projectA));
$this->assertTrue($visibleProjects->contains($projectB));
}
public function test_parent_contractor_can_traverse_subcontractor_hierarchy(): void
{
// 1. Create Parent Contractor A
$parentContractor = Contractor::create([
'company_name' => 'Parent Contractor',
'contact_person' => 'Parent Person',
'email' => 'parent@contractor.com',
'status' => 'active',
'type' => 'main',
'payment_terms' => 'net_30',
]);
// 2. Create Subcontractor B (parent_id = Parent Contractor A)
$subContractor = Contractor::create([
'company_name' => 'Sub Contractor',
'contact_person' => 'Sub Person',
'email' => 'sub@contractor.com',
'status' => 'active',
'type' => 'sub',
'parent_id' => $parentContractor->id,
'payment_terms' => 'net_30',
]);
// 3. Create projects
$parentProject = Project::create([
'name' => 'Parent Project',
'contractor_id' => $parentContractor->id,
'code' => 'PRJ-2026-001',
]);
$subProject = Project::create([
'name' => 'Sub Project',
'contractor_id' => $subContractor->id,
'code' => 'PRJ-2026-002',
]);
// 4. Create Parent Admin User
$parentAdmin = User::factory()->create([
'contractor_id' => $parentContractor->id,
'user_type' => 'admin',
'status' => 'active',
]);
$parentAdmin->assignRole('contractor-admin');
// 5. Authenticate as Parent Admin
$this->actingAs($parentAdmin);
// 6. Query projects and verify parent admin sees BOTH parent and sub projects
$visibleProjects = Project::all();
$this->assertTrue($visibleProjects->contains($parentProject));
$this->assertTrue($visibleProjects->contains($subProject));
// 7. Create Sub Admin User
$subAdmin = User::factory()->create([
'contractor_id' => $subContractor->id,
'user_type' => 'admin',
'status' => 'active',
]);
$subAdmin->assignRole('contractor-admin');
// 8. Authenticate as Sub Admin
$this->actingAs($subAdmin);
// 9. Query projects and verify sub admin ONLY sees sub project
$visibleProjectsForSub = Project::all();
$this->assertTrue($visibleProjectsForSub->contains($subProject));
$this->assertFalse($visibleProjectsForSub->contains($parentProject));
}
public function test_contractor_sees_global_materials_if_shares_materials_catalog_is_true(): void
{
// 1. Create a contractor with shares_materials_catalog = true
$contractor = Contractor::create([
'company_name' => 'Sharing Contractor',
'contact_person' => 'Person S',
'email' => 's@contractor.com',
'status' => 'active',
'shares_materials_catalog' => true,
'payment_terms' => 'net_30',
]);
// 2. Create custom material and global material
$customMaterial = \Modules\MasterData\Models\Material::create([
'name' => 'Custom Cement',
'unit' => 'bag',
'contractor_id' => $contractor->id,
]);
$globalMaterial = \Modules\MasterData\Models\Material::create([
'name' => 'Global Sand',
'unit' => 'cubic_meter',
'contractor_id' => null,
]);
// 3. Authenticate as contractor user
$user = User::factory()->create([
'contractor_id' => $contractor->id,
'user_type' => 'admin',
'status' => 'active',
]);
$this->actingAs($user);
// 4. Query materials and verify both are visible
$visibleMaterials = \Modules\MasterData\Models\Material::all();
$this->assertTrue($visibleMaterials->contains($customMaterial));
$this->assertTrue($visibleMaterials->contains($globalMaterial));
}
public function test_contractor_does_not_see_global_materials_if_shares_materials_catalog_is_false(): void
{
// 1. Create a contractor with shares_materials_catalog = false
$contractor = Contractor::create([
'company_name' => 'Isolated Contractor',
'contact_person' => 'Person I',
'email' => 'i@contractor.com',
'status' => 'active',
'shares_materials_catalog' => false,
'payment_terms' => 'net_30',
]);
// 2. Create custom material and global material
$customMaterial = \Modules\MasterData\Models\Material::create([
'name' => 'Private Steel',
'unit' => 'kg',
'contractor_id' => $contractor->id,
]);
$globalMaterial = \Modules\MasterData\Models\Material::create([
'name' => 'Global Sand',
'unit' => 'cubic_meter',
'contractor_id' => null,
]);
// 3. Authenticate as contractor user
$user = User::factory()->create([
'contractor_id' => $contractor->id,
'user_type' => 'admin',
'status' => 'active',
]);
$this->actingAs($user);
// 4. Query materials and verify ONLY custom material is visible
$visibleMaterials = \Modules\MasterData\Models\Material::all();
$this->assertTrue($visibleMaterials->contains($customMaterial));
$this->assertFalse($visibleMaterials->contains($globalMaterial));
}
}