176 lines
6.5 KiB
PHP
176 lines
6.5 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\Services\Promotions;
|
|
|
|
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
|
|
\App\Models\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(),
|
|
]);
|
|
}
|
|
} |