Files
alrahma_sunday_school_api/tests/Unit/Services/Promotions/PromotionEligibilityServiceTest.php
T
2026-06-09 01:25:14 -04:00

179 lines
6.5 KiB
PHP

<?php
namespace Tests\Unit\Services\Promotions;
use App\Models\LevelProgression;
use App\Models\StudentPromotionRecord;
use App\Services\Promotions\LevelProgressionService;
use App\Services\Promotions\PromotionAuditService;
use App\Services\Promotions\PromotionEligibilityService;
use App\Services\Promotions\PromotionStatusService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Tests\TestCase;
class PromotionEligibilityServiceTest extends TestCase
{
use RefreshDatabase;
public function test_passing_student_becomes_awaiting_parent_enrollment(): void
{
$this->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(),
]);
}
}