evaluationRepo = new InMemoryEvaluationRepository(); $this->gradeRepo = new InMemoryGradeRepository(); $this->evalStatsRepo = new InMemoryEvaluationStatisticsRepository(); $this->studentAvgRepo = new InMemoryStudentAverageRepository(); $tenantContext = new TenantContext(); $tenantContext->setCurrentTenant(new TenantConfig( tenantId: InfraTenantId::fromString(self::TENANT_ID), subdomain: 'test', databaseUrl: 'postgresql://test', )); $periodFinder = new class implements PeriodFinder { public function findForDate(DateTimeImmutable $date, TenantId $tenantId): ?PeriodInfo { return new PeriodInfo( periodId: RecalculerMoyennesOnEvaluationModifieeHandlerTest::PERIOD_ID, startDate: new DateTimeImmutable('2026-01-01'), endDate: new DateTimeImmutable('2026-03-31'), ); } }; $service = new RecalculerMoyennesService( evaluationRepository: $this->evaluationRepo, gradeRepository: $this->gradeRepo, evaluationStatisticsRepository: $this->evalStatsRepo, studentAverageRepository: $this->studentAvgRepo, periodFinder: $periodFinder, calculator: new AverageCalculator(), ); $this->handler = new RecalculerMoyennesOnEvaluationModifieeHandler( tenantContext: $tenantContext, service: $service, ); } #[Test] public function itRecalculatesStatisticsWhenEvaluationModified(): void { $evaluationId = $this->seedPublishedEvaluationWithGrades( grades: [ [self::STUDENT_1, 14.0, GradeStatus::GRADED], [self::STUDENT_2, 8.0, GradeStatus::GRADED], ], ); ($this->handler)(new EvaluationModifiee( evaluationId: $evaluationId, title: 'Titre modifié', evaluationDate: new DateTimeImmutable('2026-02-15'), occurredOn: new DateTimeImmutable(), )); $stats = $this->evalStatsRepo->findByEvaluation($evaluationId); self::assertNotNull($stats); self::assertSame(11.0, $stats->average); self::assertSame(8.0, $stats->min); self::assertSame(14.0, $stats->max); self::assertSame(2, $stats->gradedCount); } #[Test] public function itRecalculatesStudentAveragesWhenEvaluationModified(): void { $evaluationId = $this->seedPublishedEvaluationWithGrades( grades: [ [self::STUDENT_1, 16.0, GradeStatus::GRADED], [self::STUDENT_2, 12.0, GradeStatus::GRADED], ], ); ($this->handler)(new EvaluationModifiee( evaluationId: $evaluationId, title: 'Titre modifié', evaluationDate: new DateTimeImmutable('2026-02-15'), occurredOn: new DateTimeImmutable(), )); $student1Avg = $this->studentAvgRepo->findSubjectAverage( UserId::fromString(self::STUDENT_1), SubjectId::fromString(self::SUBJECT_ID), self::PERIOD_ID, TenantId::fromString(self::TENANT_ID), ); self::assertNotNull($student1Avg); self::assertSame(16.0, $student1Avg['average']); $student2Avg = $this->studentAvgRepo->findSubjectAverage( UserId::fromString(self::STUDENT_2), SubjectId::fromString(self::SUBJECT_ID), self::PERIOD_ID, TenantId::fromString(self::TENANT_ID), ); self::assertNotNull($student2Avg); self::assertSame(12.0, $student2Avg['average']); } #[Test] public function itRecalculatesGeneralAverageForAllStudents(): void { $evaluationId = $this->seedPublishedEvaluationWithGrades( grades: [ [self::STUDENT_1, 14.0, GradeStatus::GRADED], ], ); ($this->handler)(new EvaluationModifiee( evaluationId: $evaluationId, title: 'Titre modifié', evaluationDate: new DateTimeImmutable('2026-02-15'), occurredOn: new DateTimeImmutable(), )); $generalAvg = $this->studentAvgRepo->findGeneralAverageForStudent( UserId::fromString(self::STUDENT_1), self::PERIOD_ID, TenantId::fromString(self::TENANT_ID), ); self::assertSame(14.0, $generalAvg); } #[Test] public function itRecalculatesStatsButNotStudentAveragesWhenNotPublished(): void { $evaluationId = $this->seedUnpublishedEvaluationWithGrades( grades: [ [self::STUDENT_1, 14.0, GradeStatus::GRADED], ], ); ($this->handler)(new EvaluationModifiee( evaluationId: $evaluationId, title: 'Titre modifié', evaluationDate: new DateTimeImmutable('2026-02-15'), occurredOn: new DateTimeImmutable(), )); // Les stats sont calculées (le handler ne filtre pas sur publication) $stats = $this->evalStatsRepo->findByEvaluation($evaluationId); self::assertNotNull($stats); self::assertSame(14.0, $stats->average); // Mais pas de recalcul des moyennes élèves (recalculerTousEleves filtre) self::assertNull($this->studentAvgRepo->findSubjectAverage( UserId::fromString(self::STUDENT_1), SubjectId::fromString(self::SUBJECT_ID), self::PERIOD_ID, TenantId::fromString(self::TENANT_ID), )); } #[Test] public function itExcludesAbsentStudentsFromStatistics(): void { $evaluationId = $this->seedPublishedEvaluationWithGrades( grades: [ [self::STUDENT_1, 18.0, GradeStatus::GRADED], [self::STUDENT_2, null, GradeStatus::ABSENT], ], ); ($this->handler)(new EvaluationModifiee( evaluationId: $evaluationId, title: 'Titre modifié', evaluationDate: new DateTimeImmutable('2026-02-15'), occurredOn: new DateTimeImmutable(), )); $stats = $this->evalStatsRepo->findByEvaluation($evaluationId); self::assertNotNull($stats); self::assertSame(18.0, $stats->average); self::assertSame(1, $stats->gradedCount); } /** * @param list $grades */ private function seedPublishedEvaluationWithGrades( array $grades, float $coefficient = 1.0, ): EvaluationId { return $this->seedEvaluationWithGrades($grades, $coefficient, published: true); } /** * @param list $grades */ private function seedUnpublishedEvaluationWithGrades( array $grades, float $coefficient = 1.0, ): EvaluationId { return $this->seedEvaluationWithGrades($grades, $coefficient, published: false); } /** * @param list $grades */ private function seedEvaluationWithGrades( array $grades, float $coefficient = 1.0, bool $published = true, ): EvaluationId { $tenantId = TenantId::fromString(self::TENANT_ID); $now = new DateTimeImmutable(); $evaluation = Evaluation::creer( tenantId: $tenantId, classId: ClassId::fromString(self::CLASS_ID), subjectId: SubjectId::fromString(self::SUBJECT_ID), teacherId: UserId::fromString(self::TEACHER_ID), title: 'Test Evaluation', description: null, evaluationDate: new DateTimeImmutable('2026-02-15'), gradeScale: new GradeScale(20), coefficient: new Coefficient($coefficient), now: $now, ); if ($published) { $evaluation->publierNotes($now); } $evaluation->pullDomainEvents(); $this->evaluationRepo->save($evaluation); foreach ($grades as [$studentId, $value, $status]) { $grade = Grade::saisir( tenantId: $tenantId, evaluationId: $evaluation->id, studentId: UserId::fromString($studentId), value: $value !== null ? new GradeValue($value) : null, status: $status, gradeScale: new GradeScale(20), createdBy: UserId::fromString(self::TEACHER_ID), now: $now, ); $grade->pullDomainEvents(); $this->gradeRepo->save($grade); } return $evaluation->id; } }