3fde161f29
API CI/CD / Validate (composer + pint) (push) Successful in 3m6s
API CI/CD / Test (PHPUnit) (push) Failing after 6m55s
API CI/CD / Build frontend assets (push) Successful in 1m2s
API CI/CD / Security audit (push) Failing after 50s
API CI/CD / Deploy to shared hosting (PHP) (push) Has been skipped
174 lines
6.6 KiB
PHP
174 lines
6.6 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\Services;
|
|
|
|
use App\Models\ClassSection;
|
|
use App\Models\Configuration;
|
|
use App\Models\SemesterScore;
|
|
use App\Models\Student;
|
|
use App\Models\StudentClass;
|
|
use App\Services\Administrator\Trophy\TrophyReportService;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Tests\TestCase;
|
|
|
|
class TrophyReportServiceTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected TrophyReportService $service;
|
|
|
|
protected string $schoolYear = '2025-2026';
|
|
|
|
protected ClassSection $section;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
$this->service = app(TrophyReportService::class);
|
|
|
|
Configuration::query()->create([
|
|
'config_key' => 'school_year',
|
|
'config_value' => $this->schoolYear,
|
|
]);
|
|
|
|
$this->section = ClassSection::factory()->create([
|
|
'class_section_id' => 401,
|
|
'class_section_name' => 'Grade 4A',
|
|
]);
|
|
}
|
|
|
|
public function test_calculate_threshold_enforces_minimum_of_three_winners(): void
|
|
{
|
|
$result = $this->service->calculateThreshold([50, 60, 70, 80, 90], 95.0);
|
|
|
|
$this->assertSame('min3_reduced', $result['method']);
|
|
$this->assertSame(3, $result['winners']);
|
|
$this->assertSame(70.0, $result['threshold']);
|
|
}
|
|
|
|
public function test_projection_marks_students_without_fall_scores_as_not_projected(): void
|
|
{
|
|
$first = $this->enrollStudent('Amina', 'Top', 'Female', 'S-1', 98.0);
|
|
$second = $this->enrollStudent('Bilal', 'Strong', 'Male', 'S-2', 95.0);
|
|
$third = $this->enrollStudent('Celine', 'Good', 'Female', 'S-3', 90.0);
|
|
$fourth = $this->enrollStudent('David', 'Missing', 'Male', 'S-4', null);
|
|
|
|
$payload = $this->service->projection($this->schoolYear, 75.0);
|
|
|
|
$this->assertSame($this->schoolYear, $payload['selected_year']);
|
|
$this->assertSame(75.0, $payload['selected_percentile']);
|
|
$this->assertSame([$this->schoolYear], $payload['years']);
|
|
$this->assertCount(1, $payload['class_results']);
|
|
|
|
$classResult = $payload['class_results'][0];
|
|
|
|
$this->assertSame(4, $classResult['student_count']);
|
|
$this->assertSame(3, $classResult['scored_count']);
|
|
$this->assertSame(3, $classResult['trophy_count']);
|
|
$this->assertSame(90.0, $classResult['threshold']);
|
|
$this->assertSame(4, $payload['summary']['students']);
|
|
$this->assertSame(3, $payload['summary']['trophies']);
|
|
|
|
$students = collect($classResult['students'])->keyBy('student_id');
|
|
|
|
$this->assertTrue($students[$first->id]['projected_trophy']);
|
|
$this->assertTrue($students[$second->id]['projected_trophy']);
|
|
$this->assertTrue($students[$third->id]['projected_trophy']);
|
|
$this->assertFalse($students[$fourth->id]['projected_trophy']);
|
|
$this->assertNull($students[$fourth->id]['fall_score']);
|
|
}
|
|
|
|
public function test_final_report_classifies_confirmed_surprise_and_missed_winners(): void
|
|
{
|
|
$this->enrollStudent('Adam', 'Confirmed', 'Male', 'S-10', 99.0, 97.0);
|
|
$this->enrollStudent('Basma', 'Confirmed', 'Female', 'S-11', 95.0, 93.0);
|
|
$missed = $this->enrollStudent('Cyrus', 'Missed', 'Male', 'S-12', 92.0, 68.0);
|
|
$surprise = $this->enrollStudent('Dina', 'Surprise', 'Female', 'S-13', 88.0, 100.0);
|
|
$this->enrollStudent('Evan', 'None', 'Male', 'S-14', 70.0, 50.0);
|
|
|
|
$payload = $this->service->final($this->schoolYear, 75.0);
|
|
|
|
$this->assertCount(1, $payload['class_results']);
|
|
$this->assertSame(3, $payload['summary']['predicted']);
|
|
$this->assertSame(3, $payload['summary']['actual']);
|
|
$this->assertSame(2, $payload['summary']['confirmed']);
|
|
$this->assertSame(1, $payload['summary']['surprises']);
|
|
$this->assertSame(1, $payload['summary']['missed']);
|
|
$this->assertSame(67.0, $payload['summary']['accuracy']);
|
|
$this->assertSame(3, $payload['winner_gender_summary']['total']);
|
|
$this->assertSame(1, $payload['winner_gender_summary']['boys']);
|
|
$this->assertSame(2, $payload['winner_gender_summary']['girls']);
|
|
$this->assertCount(3, $payload['winner_stickers']);
|
|
|
|
$classResult = $payload['class_results'][0];
|
|
|
|
$this->assertSame(92.0, $classResult['fall_threshold']);
|
|
$this->assertSame(94.0, $classResult['year_threshold']);
|
|
$this->assertSame(2, $classResult['confirmed']);
|
|
$this->assertSame(1, $classResult['surprises']);
|
|
$this->assertSame(1, $classResult['missed']);
|
|
$this->assertSame(67.0, $classResult['accuracy']);
|
|
|
|
$students = collect($classResult['students'])->keyBy('student_id');
|
|
|
|
$this->assertSame('missed', $students[$missed->id]['status']);
|
|
$this->assertTrue($students[$missed->id]['predicted']);
|
|
$this->assertFalse($students[$missed->id]['actual']);
|
|
|
|
$this->assertSame('surprise', $students[$surprise->id]['status']);
|
|
$this->assertFalse($students[$surprise->id]['predicted']);
|
|
$this->assertTrue($students[$surprise->id]['actual']);
|
|
}
|
|
|
|
private function enrollStudent(
|
|
string $firstname,
|
|
string $lastname,
|
|
string $gender,
|
|
string $schoolId,
|
|
?float $fallScore,
|
|
?float $springScore = null
|
|
): Student {
|
|
$student = Student::factory()->create([
|
|
'firstname' => $firstname,
|
|
'lastname' => $lastname,
|
|
'gender' => $gender,
|
|
'school_id' => $schoolId,
|
|
'school_year' => $this->schoolYear,
|
|
'is_active' => 1,
|
|
]);
|
|
|
|
StudentClass::query()->create([
|
|
'student_id' => $student->id,
|
|
'class_section_id' => $this->section->class_section_id,
|
|
'school_year' => $this->schoolYear,
|
|
'semester' => 'Fall',
|
|
'is_event_only' => 0,
|
|
]);
|
|
|
|
if ($fallScore !== null) {
|
|
SemesterScore::query()->create([
|
|
'student_id' => $student->id,
|
|
'school_id' => $schoolId,
|
|
'class_section_id' => $this->section->class_section_id,
|
|
'semester_score' => $fallScore,
|
|
'semester' => 'Fall',
|
|
'school_year' => $this->schoolYear,
|
|
]);
|
|
}
|
|
|
|
if ($springScore !== null) {
|
|
SemesterScore::query()->create([
|
|
'student_id' => $student->id,
|
|
'school_id' => $schoolId,
|
|
'class_section_id' => $this->section->class_section_id,
|
|
'semester_score' => $springScore,
|
|
'semester' => 'Spring',
|
|
'school_year' => $this->schoolYear,
|
|
]);
|
|
}
|
|
|
|
return $student;
|
|
}
|
|
}
|