evaluationRepo = new InMemoryEvaluationRepository(); $this->gradeRepo = new InMemoryGradeRepository(); $this->evalStatsRepo = new InMemoryEvaluationStatisticsRepository(); $this->studentAvgRepo = new InMemoryStudentAverageRepository(); $this->tenantConfig = new TenantConfig( tenantId: InfraTenantId::fromString(self::TENANT_ID), subdomain: 'ecole-test', databaseUrl: 'postgresql://test', ); } #[Test] public function itBackfillsStatisticsAndAveragesForPublishedEvaluations(): void { $this->seedPublishedEvaluationWithGrades(); $tester = $this->executeCommand(); self::assertSame(0, $tester->getStatusCode(), $tester->getDisplay()); self::assertStringContainsString('1 évaluation(s) publiée(s)', $tester->getDisplay()); self::assertStringContainsString('1 évaluation(s) traitée(s) avec succès', $tester->getDisplay()); // Vérifier que les stats évaluation sont créées $evaluations = $this->evaluationRepo->findAllWithPublishedGrades(TenantId::fromString(self::TENANT_ID)); $stats = $this->evalStatsRepo->findByEvaluation($evaluations[0]->id); self::assertNotNull($stats); self::assertSame(2, $stats->gradedCount); // Vérifier que la moyenne matière est créée $subjectAvg = $this->studentAvgRepo->findSubjectAverage( UserId::fromString(self::STUDENT_ID), SubjectId::fromString(self::SUBJECT_ID), self::PERIOD_ID, TenantId::fromString(self::TENANT_ID), ); self::assertNotNull($subjectAvg); self::assertSame(14.0, $subjectAvg['average']); } #[Test] public function itReportsSuccessWhenNoPublishedEvaluations(): void { $tester = $this->executeCommand(); self::assertSame(0, $tester->getStatusCode()); self::assertStringContainsString('aucune évaluation publiée', $tester->getDisplay()); self::assertStringContainsString('0 évaluation(s) traitée(s) avec succès', $tester->getDisplay()); } #[Test] public function itIgnoresUnpublishedEvaluations(): void { $tenantId = TenantId::fromString(self::TENANT_ID); $now = new DateTimeImmutable(); // Évaluation NON publiée $evaluation = Evaluation::creer( tenantId: $tenantId, classId: ClassId::fromString(self::CLASS_ID), subjectId: SubjectId::fromString(self::SUBJECT_ID), teacherId: UserId::fromString(self::TEACHER_ID), title: 'Non publiée', description: null, evaluationDate: new DateTimeImmutable('2026-02-15'), gradeScale: new GradeScale(20), coefficient: new Coefficient(1.0), now: $now, ); $evaluation->pullDomainEvents(); $this->evaluationRepo->save($evaluation); $tester = $this->executeCommand(); self::assertSame(0, $tester->getStatusCode()); self::assertStringContainsString('aucune évaluation publiée', $tester->getDisplay()); } #[Test] public function itProcessesMultipleEvaluations(): void { $this->seedPublishedEvaluationWithGrades(); $this->seedPublishedEvaluationWithGrades(coefficient: 2.0); $tester = $this->executeCommand(); self::assertSame(0, $tester->getStatusCode()); self::assertStringContainsString('2 évaluation(s) publiée(s)', $tester->getDisplay()); self::assertStringContainsString('2 évaluation(s) traitée(s) avec succès', $tester->getDisplay()); } private function seedPublishedEvaluationWithGrades(float $coefficient = 1.0): void { $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: 'Contrôle', description: null, evaluationDate: new DateTimeImmutable('2026-02-15'), gradeScale: new GradeScale(20), coefficient: new Coefficient($coefficient), now: $now, ); $evaluation->publierNotes($now); $evaluation->pullDomainEvents(); $this->evaluationRepo->save($evaluation); $grade1 = Grade::saisir( tenantId: $tenantId, evaluationId: $evaluation->id, studentId: UserId::fromString(self::STUDENT_ID), value: new GradeValue(14.0), status: GradeStatus::GRADED, gradeScale: new GradeScale(20), createdBy: UserId::fromString(self::TEACHER_ID), now: $now, ); $grade1->pullDomainEvents(); $this->gradeRepo->save($grade1); $grade2 = Grade::saisir( tenantId: $tenantId, evaluationId: $evaluation->id, studentId: UserId::fromString('33333333-3333-3333-3333-333333333333'), value: new GradeValue(10.0), status: GradeStatus::GRADED, gradeScale: new GradeScale(20), createdBy: UserId::fromString(self::TEACHER_ID), now: $now, ); $grade2->pullDomainEvents(); $this->gradeRepo->save($grade2); } private function executeCommand(): CommandTester { $tenantContext = new TenantContext(); $periodFinder = new class implements PeriodFinder { public function findForDate(DateTimeImmutable $date, TenantId $tenantId): ?PeriodInfo { return new PeriodInfo( periodId: RecalculerToutesMoyennesCommandTest::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(), ); $tenantRegistry = new class($this->tenantConfig) implements TenantRegistry { public function __construct(private readonly TenantConfig $config) { } #[Override] public function getConfig(InfraTenantId $tenantId): TenantConfig { return $this->config; } #[Override] public function getBySubdomain(string $subdomain): TenantConfig { return $this->config; } #[Override] public function exists(string $subdomain): bool { return true; } #[Override] public function getAllConfigs(): array { return [$this->config]; } }; $databaseSwitcher = new class implements TenantDatabaseSwitcher { #[Override] public function useTenantDatabase(string $databaseUrl): void { } #[Override] public function useDefaultDatabase(): void { } #[Override] public function currentDatabaseUrl(): ?string { return null; } }; $command = new RecalculerToutesMoyennesCommand( evaluationRepository: $this->evaluationRepo, tenantRegistry: $tenantRegistry, tenantContext: $tenantContext, databaseSwitcher: $databaseSwitcher, service: $service, ); $tester = new CommandTester($command); $tester->execute([]); return $tester; } }