$roleName]); } $permissions = [ 'dashboard.access', 'projects.access', 'contractors.access', 'users.access', 'materials-catalog.access', 'inventory.access', 'finance.access', 'documents.access', 'approvals.access', 'roles.access', 'labors.access', 'equipments.access' ]; foreach ($permissions as $p) { Permission::firstOrCreate(['name' => $p]); } // 2. Create Users & Assign Roles $this->superAdmin = User::factory()->create(['user_type' => 'admin', 'name' => 'Super Admin User']); $this->superAdmin->assignRole('Super Admin'); $this->admin = User::factory()->create(['user_type' => 'admin', 'name' => 'Admin User']); $this->admin->assignRole('admin'); $this->projectManager = User::factory()->create(['user_type' => 'employee', 'name' => 'PM User']); $this->projectManager->assignRole('Project Manager'); $this->projectManager->givePermissionTo([ 'dashboard.access', 'projects.access', 'contractors.access', 'materials-catalog.access', 'inventory.access', 'finance.access', 'documents.access', 'approvals.access', 'labors.access', 'equipments.access' ]); $this->contractorAdmin = User::factory()->create(['user_type' => 'contractor', 'name' => 'Contractor Admin']); $this->contractorAdmin->assignRole('Main Contractor Admin'); $this->contractorAdmin->givePermissionTo([ 'dashboard.access', 'projects.access', 'finance.access', 'documents.access' ]); $this->supervisor = User::factory()->create(['user_type' => 'employee', 'name' => 'Supervisor User']); $this->supervisor->assignRole('Construction Supervisor'); $this->supervisor->givePermissionTo([ 'dashboard.access', 'projects.access', 'inventory.access', 'materials-catalog.access', 'documents.access' ]); $this->siteTech = User::factory()->create(['user_type' => 'employee', 'name' => 'Site Technical User']); $this->siteTech->assignRole('Site Technical'); $this->siteTech->givePermissionTo([ 'dashboard.access', 'projects.access', 'inventory.access', 'documents.access' ]); $this->project = Project::factory()->create([ 'current_wizard_step' => 7, ]); $this->project->personnel()->attach($this->projectManager->id, ['role' => 'pm']); $this->project->personnel()->attach($this->supervisor->id, ['role' => 'supervisor']); } /** @test */ public function test_e2e_all_roles_page_accessibility_across_modules(): void { // 1. Super Admin Page Traversal $this->actingAs($this->superAdmin); $routes = [ 'dashboard', 'projects.index', 'contractors.index', 'users.index', 'materials-catalog.items.index', 'inventory.index', 'labors.index', 'equipments.index', 'resources.index', 'finance.index', 'retention.index', 'cash-advances.index', 'documents.index', 'approvals.index' ]; foreach ($routes as $route) { $response = $this->get(route($route)); $response->assertStatus(200); } // 2. Project Manager Page Traversal $this->actingAs($this->projectManager); foreach (['dashboard', 'projects.index', 'inventory.index', 'finance.index', 'approvals.index'] as $route) { $response = $this->get(route($route)); $response->assertStatus(200); } // 3. Contractor Admin Page Traversal $this->actingAs($this->contractorAdmin); foreach (['dashboard', 'projects.index', 'finance.index'] as $route) { $response = $this->get(route($route)); $response->assertStatus(200); } // 4. Construction Supervisor Page Traversal $this->actingAs($this->supervisor); foreach (['dashboard', 'projects.index', 'inventory.index', 'cash-advances.index'] as $route) { $response = $this->get(route($route)); $response->assertStatus(200); } // 5. Site Technical Page Traversal $this->actingAs($this->siteTech); foreach (['dashboard', 'projects.index', 'inventory.index', 'cash-advances.index'] as $route) { $response = $this->get(route($route)); $response->assertStatus(200); } } /** @test */ public function test_e2e_cash_advance_full_lifecycle_and_security_rules(): void { // Step 1: Supervisor creates cash advance request $this->actingAs($this->supervisor); $createResponse = $this->post(route('cash-advances.store'), [ 'project_ulid' => $this->project->ulid, 'amount' => 750.50, 'reason' => 'Emergency site safety gear purchase', ]); $createResponse->assertSessionHasNoErrors(); $cashAdvance = CashAdvance::where('amount', 750.50)->firstOrFail(); $this->assertEquals('pending', $cashAdvance->status); // Step 2: Site Technical attempts to approve (Unauthorized/Blocked logic) $this->actingAs($this->siteTech); $this->patch(route('cash-advances.approve', $cashAdvance->ulid)); $this->assertEquals('pending', $cashAdvance->fresh()->status); // Step 3: PM creates request and attempts self-approval (Blocked rule) $this->actingAs($this->projectManager); $pmAdvance = CashAdvance::create([ 'project_id' => $this->project->id, 'amount' => 1200.00, 'reason' => 'PM site inspection travel', 'status' => 'pending', 'requested_by' => $this->projectManager->id, ]); $this->patch(route('cash-advances.approve', $pmAdvance->ulid)); $this->assertEquals('pending', $pmAdvance->fresh()->status); // Step 4: PM approves Supervisor's request $this->patch(route('cash-advances.approve', $cashAdvance->ulid)); $this->assertEquals('approved', $cashAdvance->fresh()->status); $this->assertEquals($this->projectManager->id, $cashAdvance->fresh()->approved_by); // Step 5: Admin approves PM's self-requested advance $this->actingAs($this->admin); $this->patch(route('cash-advances.approve', $pmAdvance->ulid)); $this->assertEquals('approved', $pmAdvance->fresh()->status); } /** @test */ public function test_e2e_financial_invoice_and_retention_flow(): void { // Step 1: Contractor submits invoice $invoice = FinancialInvoice::create([ 'project_id' => $this->project->id, 'invoice_number' => 'INV-E2E-101', 'status' => 'submitted', 'subtotal' => 200000.00, 'retention_rate' => 10, 'retention_amount' => 20000.00, 'total_amount' => 180000.00, 'billed_percentage' => 20, 'invoice_date' => now(), ]); // Step 2: Supervisor attempts invoice approval (Supervisor is restricted from final invoice approval) $this->actingAs($this->supervisor); $this->patch(route('finance.approve', $invoice->ulid)); $this->assertEquals(\Modules\FinancialManagement\Enums\InvoiceStatus::Submitted, $invoice->fresh()->status); // Step 3: Super Admin approves invoice $this->actingAs($this->superAdmin); $response = $this->patch(route('finance.approve', $invoice->ulid)); $response->assertSessionHasNoErrors(); $this->assertEquals(\Modules\FinancialManagement\Enums\InvoiceStatus::Approved, $invoice->fresh()->status); } /** @test */ public function test_e2e_project_wizard_flow_integrity(): void { $this->actingAs($this->admin); // Step 1: Create Initial Draft Project $postResponse = $this->post(route('projects.store'), [ 'name' => 'E2E Mega Tower Construction', 'project_type' => 'standard', 'contract_value' => 5000000, 'start_date' => now()->addDays(5)->format('Y-m-d'), 'target_end_date' => now()->addYear()->format('Y-m-d'), ]); $postResponse->assertSessionHasNoErrors(); $project = Project::where('name', 'E2E Mega Tower Construction')->firstOrFail(); $this->assertEquals(2, $project->current_wizard_step); // Step 2: Submit WBS Tasks $taskResponse = $this->post(route('projects.wizard.tasks', $project->ulid), [ 'milestones' => [ [ 'name' => 'Foundation & Earthworks', 'weight_percentage' => 25, 'tasks' => [ ['name' => 'Excavation & Site Prep', 'weight' => 10], ['name' => 'Concrete Pouring', 'weight' => 15], ], ] ], ]); $taskResponse->assertSessionHasNoErrors(); $this->assertGreaterThanOrEqual(2, $project->fresh()->current_wizard_step); } }