seedConfig(); $studentId = $this->seedStudent(); $this->seedClassChain(); $this->seedAssignment($studentId, 701, '2025-2026'); $this->seedDecision($studentId, '2025-2026', 77.5, 'promoted'); $service = $this->makeService(); $record = $service->evaluateStudent($studentId, '2025-2026', 1); $this->assertNotNull($record); $this->assertSame(StudentPromotionRecord::STATUS_AWAITING_PARENT, $record->promotion_status); $this->assertTrue((bool) $record->passed_current_level); $this->assertSame(77.5, (float) $record->final_average); $this->assertSame('Grade 3', $record->current_level_name); $this->assertSame('Grade 4', $record->promoted_level_name); $this->assertSame('2026-2027', $record->next_school_year); } public function test_failing_student_becomes_repeated(): void { $this->seedConfig(); $studentId = $this->seedStudent(); $this->seedClassChain(); $this->seedAssignment($studentId, 701, '2025-2026'); $this->seedDecision($studentId, '2025-2026', 52.5, 'retained'); $service = $this->makeService(); $record = $service->evaluateStudent($studentId, '2025-2026', 1); $this->assertNotNull($record); $this->assertSame(StudentPromotionRecord::STATUS_REPEATED, $record->promotion_status); $this->assertFalse((bool) $record->passed_current_level); } public function test_missing_scores_marks_on_hold(): void { $this->seedConfig(); $studentId = $this->seedStudent(); $this->seedClassChain(); $this->seedAssignment($studentId, 701, '2025-2026'); $service = $this->makeService(); $record = $service->evaluateStudent($studentId, '2025-2026', 1); $this->assertNotNull($record); $this->assertSame(StudentPromotionRecord::STATUS_ON_HOLD, $record->promotion_status); $this->assertNull($record->passed_current_level); } public function test_terminal_level_is_graduated(): void { $this->seedConfig(); $studentId = $this->seedStudent(); $this->seedClassChain(); $this->seedAssignment($studentId, 901, '2025-2026'); $this->seedDecision($studentId, '2025-2026', 90.0, 'promoted'); // Mark Youth as terminal LevelProgression::query()->where('current_level_name', 'Youth')->update(['is_terminal' => 1]); $service = $this->makeService(); $record = $service->evaluateStudent($studentId, '2025-2026', 1); $this->assertNotNull($record); $this->assertSame(StudentPromotionRecord::STATUS_GRADUATED, $record->promotion_status); } public function test_audit_log_records_status_changes(): void { $this->seedConfig(); $studentId = $this->seedStudent(); $this->seedClassChain(); $this->seedAssignment($studentId, 701, '2025-2026'); $this->seedDecision($studentId, '2025-2026', 80.0, 'promoted'); $service = $this->makeService(); $service->evaluateStudent($studentId, '2025-2026', 99); $this->assertDatabaseHas('promotion_audit_log', [ 'student_id' => $studentId, 'action_type' => 'eligibility_evaluated', 'user_id' => 99, ]); $this->assertDatabaseHas('promotion_audit_log', [ 'student_id' => $studentId, 'action_type' => 'record_created', ]); } private function makeService(): PromotionEligibilityService { $progression = new LevelProgressionService; $audit = new PromotionAuditService; $status = new PromotionStatusService($audit); return new PromotionEligibilityService($progression, $status, $audit); } private function seedConfig(): void { DB::table('configuration')->insert([ ['config_key' => 'school_year', 'config_value' => '2025-2026'], ['config_key' => 'semester', 'config_value' => 'Fall'], ]); } private function seedStudent(int $parentId = 5): int { return DB::table('students')->insertGetId([ 'school_id' => 'S-100', 'firstname' => 'Sara', 'lastname' => 'Test', 'parent_id' => $parentId, 'year_of_registration' => '2025', 'school_year' => '2025-2026', 'semester' => 'Fall', ]); } private function seedClassChain(): void { DB::table('classes')->insert([ ['id' => 7, 'class_name' => 'Grade 3', 'school_year' => '2025-2026', 'semester' => 'Fall', 'capacity' => 30], ['id' => 8, 'class_name' => 'Grade 4', 'school_year' => '2025-2026', 'semester' => 'Fall', 'capacity' => 30], ['id' => 13, 'class_name' => 'Youth', 'school_year' => '2025-2026', 'semester' => 'Fall', 'capacity' => 30], ]); DB::table('classSection')->insert([ ['class_id' => 7, 'class_section_id' => 701, 'class_section_name' => '3A'], ['class_id' => 13, 'class_section_id' => 901, 'class_section_name' => 'Youth A'], ]); } private function seedAssignment(int $studentId, int $classSectionId, string $schoolYear): void { DB::table('student_class')->insert([ 'student_id' => $studentId, 'class_section_id' => $classSectionId, 'school_year' => $schoolYear, 'semester' => 'Fall', 'is_event_only' => 0, ]); } private function seedDecision(int $studentId, string $schoolYear, float $score, string $decision): void { DB::table('student_decisions')->insert([ 'student_id' => $studentId, 'school_year' => $schoolYear, 'class_section_name' => '3A', 'year_score' => $score, 'decision' => $decision, 'source' => 'manual', 'notes' => 'Seeded test decision', 'created_at' => now(), 'updated_at' => now(), ]); } }