reader = new InMemoryTeacherStatisticsReader(); } #[Test] public function itReturnsEmptyWhenNoPeriodFound(): void { $handler = $this->createHandler(periodInfo: null); $result = $handler($this->query()); self::assertNull($result->average); self::assertSame(0.0, $result->successRate); self::assertSame([], $result->students); } #[Test] public function itComputesClassStatisticsFromGrades(): void { $this->reader->feedClassGrades([8.0, 10.0, 12.0, 14.0, 16.0]); $this->reader->feedMonthlyAverages([ ['month' => '2026-01', 'average' => 11.0], ['month' => '2026-02', 'average' => 12.5], ]); $this->reader->feedStudentAverages([ ['studentId' => 's1', 'studentName' => 'Alice Dupont', 'average' => 14.0], ['studentId' => 's2', 'studentName' => 'Bob Martin', 'average' => 7.0], ]); $handler = $this->createHandler(periodInfo: $this->currentPeriod()); $result = $handler($this->query()); self::assertSame(12.0, $result->average); self::assertSame(80.0, $result->successRate); // 4/5 >= 10 self::assertSame([0, 0, 0, 1, 2, 1, 1, 0], $result->distribution); self::assertCount(2, $result->evolution); self::assertCount(2, $result->students); self::assertFalse($result->students[0]->inDifficulty); // Alice 14.0 >= 8.0 self::assertTrue($result->students[1]->inDifficulty); // Bob 7.0 < 8.0 } private function query(): GetClassStatisticsDetailQuery { return new GetClassStatisticsDetailQuery( teacherId: self::TEACHER_ID, classId: self::CLASS_ID, subjectId: self::SUBJECT_ID, tenantId: self::TENANT_ID, ); } private function createHandler(?PeriodInfo $periodInfo): GetClassStatisticsDetailHandler { $periodFinder = new class($periodInfo) implements PeriodFinder { public function __construct(private readonly ?PeriodInfo $info) { } public function findForDate(DateTimeImmutable $date, TenantId $tenantId): ?PeriodInfo { return $this->info; } }; return new GetClassStatisticsDetailHandler( $this->reader, $periodFinder, new TeacherStatisticsCalculator(), new AverageCalculator(), ); } private function currentPeriod(): PeriodInfo { return new PeriodInfo( periodId: 'period-1', startDate: new DateTimeImmutable('2026-01-05'), endDate: new DateTimeImmutable('2026-03-31'), ); } }