seed([ProjectStatusSeeder::class, ProjectTypeSeeder::class]); $service = app(ProjectService::class); $status = ProjectStatus::firstOrFail(); $project = Project::factory()->create([ 'status_id' => $status->id, ]); $forecast = ['2026-01' => 20, '2026-02' => 30]; $updated = $service->setForecastedEffort($project, $forecast); $this->assertSame($forecast, $updated->forecasted_effort); } public function test_forecasted_sum_must_equal_approved_estimate() { $this->seed([ProjectStatusSeeder::class, ProjectTypeSeeder::class]); $service = app(ProjectService::class); $status = ProjectStatus::firstOrFail(); $project = Project::factory()->create([ 'status_id' => $status->id, ]); $service->setApprovedEstimate($project, 100); $forecast = ['2026-01' => 40, '2026-02' => 60]; $updated = $service->setForecastedEffort($project, $forecast); $this->assertEquals(100, array_sum($updated->forecasted_effort)); } public function test_forecasted_effort_tolerance() { $this->seed([ProjectStatusSeeder::class, ProjectTypeSeeder::class]); $service = app(ProjectService::class); $status = ProjectStatus::firstOrFail(); $project = Project::factory()->create([ 'status_id' => $status->id, ]); $service->setApprovedEstimate($project, 100); $forecastWithinTolerance = ['2026-01' => 50, '2026-02' => 55]; $service->setForecastedEffort($project, $forecastWithinTolerance); $this->assertEquals(105, array_sum($project->refresh()->forecasted_effort)); $forecastTooHigh = ['2026-01' => 60, '2026-02' => 50]; $this->expectException(\RuntimeException::class); $service->setForecastedEffort($project, $forecastTooHigh); } }