seed(RoleSeeder::class); $this->staff = User::factory()->create(); $this->staff->assignRole('staff'); $this->approver = User::factory()->create(); $this->approver->assignRole('travel_approver'); $this->workflow = ApprovalWorkflow::factory()->create(['is_active' => true]); $this->step1 = ApprovalStep::factory()->create([ 'workflow_id' => $this->workflow->id, 'order' => 1, 'role' => 'travel_approver', ]); $this->step2 = ApprovalStep::factory()->create([ 'workflow_id' => $this->workflow->id, 'order' => 2, 'role' => 'administrator', ]); } public function test_submitting_request_creates_approval_records_and_dispatches_email(): void { Queue::fake(); $request = TravelRequest::factory()->create([ 'user_id' => $this->staff->id, 'workflow_id' => $this->workflow->id, 'status' => TravelStatus::Draft, ]); app(ApprovalService::class)->submit($request); $request->refresh(); $this->assertEquals(TravelStatus::Pending, $request->status); $this->assertNotNull($request->submitted_at); $this->assertDatabaseHas('travel_request_approvals', [ 'travel_request_id' => $request->id, 'approval_step_id' => $this->step1->id, 'status' => ApprovalStatus::Pending->value, ]); Queue::assertPushed(SendApprovalRequestEmail::class); } public function test_approving_first_step_advances_to_second_step(): void { Queue::fake(); $request = TravelRequest::factory()->create([ 'user_id' => $this->staff->id, 'workflow_id' => $this->workflow->id, 'status' => TravelStatus::Pending, 'submitted_at' => now(), ]); $approval1 = TravelRequestApproval::create([ 'travel_request_id' => $request->id, 'approval_step_id' => $this->step1->id, 'status' => ApprovalStatus::Pending->value, ]); TravelRequestApproval::create([ 'travel_request_id' => $request->id, 'approval_step_id' => $this->step2->id, 'status' => null, ]); app(ApprovalService::class)->approve($approval1, $this->approver, 'Looks good'); $approval1->refresh(); $this->assertEquals(ApprovalStatus::Approved, $approval1->status); $this->assertEquals($this->approver->id, $approval1->approver_id); $this->assertDatabaseHas('travel_request_approvals', [ 'travel_request_id' => $request->id, 'approval_step_id' => $this->step2->id, 'status' => ApprovalStatus::Pending->value, ]); Queue::assertPushed(SendApprovalRequestEmail::class); } public function test_approving_final_step_marks_request_as_approved(): void { Queue::fake(); $request = TravelRequest::factory()->create([ 'user_id' => $this->staff->id, 'workflow_id' => $this->workflow->id, 'status' => TravelStatus::Pending, ]); TravelRequestApproval::create([ 'travel_request_id' => $request->id, 'approval_step_id' => $this->step1->id, 'status' => ApprovalStatus::Approved->value, 'approver_id' => $this->approver->id, 'acted_at' => now(), ]); $finalApproval = TravelRequestApproval::create([ 'travel_request_id' => $request->id, 'approval_step_id' => $this->step2->id, 'status' => ApprovalStatus::Pending->value, ]); $admin = User::factory()->create(); $admin->assignRole('administrator'); app(ApprovalService::class)->approve($finalApproval, $admin, null); $request->refresh(); $this->assertEquals(TravelStatus::Approved, $request->status); Queue::assertPushed(SendApprovalDecisionEmail::class); } public function test_rejection_marks_request_as_rejected_and_dispatches_email(): void { Queue::fake(); $request = TravelRequest::factory()->create([ 'user_id' => $this->staff->id, 'workflow_id' => $this->workflow->id, 'status' => TravelStatus::Pending, ]); $approval = TravelRequestApproval::create([ 'travel_request_id' => $request->id, 'approval_step_id' => $this->step1->id, 'status' => ApprovalStatus::Pending->value, ]); app(ApprovalService::class)->reject($approval, $this->approver, 'Budget not available'); $request->refresh(); $this->assertEquals(TravelStatus::Rejected, $request->status); $this->assertEquals('Budget not available', $approval->fresh()->comments); Queue::assertPushed(SendApprovalDecisionEmail::class); } public function test_approve_page_requires_approver_role(): void { $request = TravelRequest::factory()->create(['user_id' => $this->staff->id]); $this->actingAs($this->staff) ->get("/travel-requests/{$request->id}/approve") ->assertForbidden(); } }