evaluationRepository = new InMemoryEvaluationRepository(); $this->gradeRepository = new InMemoryGradeRepository(); $this->clock = new class implements Clock { public function now(): DateTimeImmutable { return new DateTimeImmutable('2026-03-31 10:00:00'); } }; $this->seedEvaluationAndGrade(); } #[Test] public function itSavesAppreciation(): void { $handler = $this->createHandler(); $grade = $handler(new SaveAppreciationCommand( tenantId: self::TENANT_ID, gradeId: $this->gradeId, teacherId: self::TEACHER_ID, appreciation: 'Très bon travail', )); self::assertSame('Très bon travail', $grade->appreciation); self::assertNotNull($grade->appreciationUpdatedAt); } #[Test] public function itClearsAppreciation(): void { $handler = $this->createHandler(); $handler(new SaveAppreciationCommand( tenantId: self::TENANT_ID, gradeId: $this->gradeId, teacherId: self::TEACHER_ID, appreciation: 'Bon travail', )); $grade = $handler(new SaveAppreciationCommand( tenantId: self::TENANT_ID, gradeId: $this->gradeId, teacherId: self::TEACHER_ID, appreciation: null, )); self::assertNull($grade->appreciation); } #[Test] public function itThrowsWhenTeacherNotOwner(): void { $handler = $this->createHandler(); $this->expectException(NonProprietaireDeLEvaluationException::class); $handler(new SaveAppreciationCommand( tenantId: self::TENANT_ID, gradeId: $this->gradeId, teacherId: '550e8400-e29b-41d4-a716-446655440099', appreciation: 'Test', )); } #[Test] public function itThrowsWhenGradeNotFound(): void { $handler = $this->createHandler(); $this->expectException(GradeNotFoundException::class); $handler(new SaveAppreciationCommand( tenantId: self::TENANT_ID, gradeId: '550e8400-e29b-41d4-a716-446655440099', teacherId: self::TEACHER_ID, appreciation: 'Test', )); } #[Test] public function itThrowsWhenAppreciationTooLong(): void { $handler = $this->createHandler(); $this->expectException(AppreciationTropLongueException::class); $handler(new SaveAppreciationCommand( tenantId: self::TENANT_ID, gradeId: $this->gradeId, teacherId: self::TEACHER_ID, appreciation: str_repeat('a', 501), )); } private function createHandler(): SaveAppreciationHandler { return new SaveAppreciationHandler( $this->evaluationRepository, $this->gradeRepository, $this->clock, ); } private function seedEvaluationAndGrade(): void { $tenantId = TenantId::fromString(self::TENANT_ID); $evaluation = Evaluation::reconstitute( id: EvaluationId::fromString(self::EVALUATION_ID), tenantId: $tenantId, classId: ClassId::fromString('550e8400-e29b-41d4-a716-446655440020'), subjectId: SubjectId::fromString('550e8400-e29b-41d4-a716-446655440030'), teacherId: UserId::fromString(self::TEACHER_ID), title: 'Contrôle', description: null, evaluationDate: new DateTimeImmutable('2026-04-15'), gradeScale: new GradeScale(20), coefficient: new Coefficient(1.0), status: EvaluationStatus::PUBLISHED, createdAt: new DateTimeImmutable('2026-03-12 10:00:00'), updatedAt: new DateTimeImmutable('2026-03-12 10:00:00'), ); $this->evaluationRepository->save($evaluation); $grade = Grade::saisir( tenantId: $tenantId, evaluationId: EvaluationId::fromString(self::EVALUATION_ID), studentId: UserId::fromString(self::STUDENT_ID), value: new GradeValue(15.5), status: GradeStatus::GRADED, gradeScale: new GradeScale(20), createdBy: UserId::fromString(self::TEACHER_ID), now: new DateTimeImmutable('2026-03-27 10:00:00'), ); $this->gradeRepository->save($grade); $this->gradeId = (string) $grade->id; } }