createMock(MailerInterface::class); $twig = $this->createMock(Environment::class); $homeworkRepo = $this->createMock(HomeworkRepository::class); $userRepo = $this->createMock(UserRepository::class); $homework = $this->createHomework(); $homeworkRepo->method('findById')->willReturn($homework); $teacher = $this->createUser('teacher@school.fr', [Role::PROF], 'Jean', 'Dupont'); $userRepo->method('findById')->willReturn($teacher); $userRepo->method('findAllByTenant')->willReturn([ $this->createUser('director@school.fr', [Role::ADMIN], 'Marie', 'Martin'), $this->createUser('teacher@school.fr', [Role::PROF], 'Jean', 'Dupont'), $this->createUser('parent@school.fr', [Role::PARENT], 'Pierre', 'Durand'), ]); $twig->method('render')->willReturn('notification'); $mailer->expects(self::exactly(1))->method('send'); $handler = new OnExceptionDevoirDemandeeHandler( $homeworkRepo, $userRepo, $mailer, $twig, new NullLogger(), ); ($handler)($this->createEvent()); } #[Test] public function itSkipsWhenNoDirectorsInTenant(): void { $mailer = $this->createMock(MailerInterface::class); $twig = $this->createMock(Environment::class); $homeworkRepo = $this->createMock(HomeworkRepository::class); $userRepo = $this->createMock(UserRepository::class); $homeworkRepo->method('findById')->willReturn($this->createHomework()); $userRepo->method('findById')->willReturn($this->createUser('teacher@school.fr', [Role::PROF])); $userRepo->method('findAllByTenant')->willReturn([ $this->createUser('teacher@school.fr', [Role::PROF]), ]); $mailer->expects(self::never())->method('send'); $handler = new OnExceptionDevoirDemandeeHandler( $homeworkRepo, $userRepo, $mailer, $twig, new NullLogger(), ); ($handler)($this->createEvent()); } #[Test] public function itHandlesMailerFailureGracefully(): void { $mailer = $this->createMock(MailerInterface::class); $twig = $this->createMock(Environment::class); $homeworkRepo = $this->createMock(HomeworkRepository::class); $userRepo = $this->createMock(UserRepository::class); $homeworkRepo->method('findById')->willReturn($this->createHomework()); $userRepo->method('findById')->willReturn($this->createUser('teacher@school.fr', [Role::PROF])); $userRepo->method('findAllByTenant')->willReturn([ $this->createUser('director@school.fr', [Role::ADMIN]), ]); $twig->method('render')->willReturn('notification'); $mailer->method('send')->willThrowException(new RuntimeException('SMTP error')); $handler = new OnExceptionDevoirDemandeeHandler( $homeworkRepo, $userRepo, $mailer, $twig, new NullLogger(), ); ($handler)($this->createEvent()); $this->addToAssertionCount(1); } #[Test] public function itPassesCorrectDataToTemplate(): void { $mailer = $this->createMock(MailerInterface::class); $twig = $this->createMock(Environment::class); $homeworkRepo = $this->createMock(HomeworkRepository::class); $userRepo = $this->createMock(UserRepository::class); $homeworkRepo->method('findById')->willReturn($this->createHomework()); $teacher = $this->createUser('teacher@school.fr', [Role::PROF], 'Jean', 'Dupont'); $userRepo->method('findById')->willReturn($teacher); $userRepo->method('findAllByTenant')->willReturn([ $this->createUser('director@school.fr', [Role::ADMIN]), ]); $twig->expects(self::once()) ->method('render') ->with( 'emails/homework_exception_notification.html.twig', self::callback(static function (array $params): bool { return $params['teacherName'] === 'Jean Dupont' && $params['homeworkTitle'] === 'Exercices chapitre 5' && $params['ruleTypes'] === ['minimum_delay'] && $params['justification'] === 'Sortie scolaire prévue, devoir urgent.' && $params['dueDate'] === '15/04/2026'; }), ) ->willReturn('notification'); $mailer->expects(self::once())->method('send'); $handler = new OnExceptionDevoirDemandeeHandler( $homeworkRepo, $userRepo, $mailer, $twig, new NullLogger(), ); ($handler)($this->createEvent()); } private function createEvent(): ExceptionDevoirDemandee { return new ExceptionDevoirDemandee( exceptionId: HomeworkRuleExceptionId::generate(), tenantId: TenantId::fromString(self::TENANT_ID), homeworkId: HomeworkId::fromString('550e8400-e29b-41d4-a716-446655440050'), ruleTypes: ['minimum_delay'], justification: 'Sortie scolaire prévue, devoir urgent.', createdBy: UserId::fromString(self::TEACHER_ID), occurredOn: new DateTimeImmutable('2026-03-19 10:00:00'), ); } private function createHomework(): Homework { return Homework::reconstitute( id: HomeworkId::fromString('550e8400-e29b-41d4-a716-446655440050'), tenantId: TenantId::fromString(self::TENANT_ID), classId: ClassId::fromString('550e8400-e29b-41d4-a716-446655440020'), subjectId: SubjectId::fromString('550e8400-e29b-41d4-a716-446655440030'), teacherId: UserId::fromString(self::TEACHER_ID), title: 'Exercices chapitre 5', description: 'Faire les exercices 1 à 10', dueDate: new DateTimeImmutable('2026-04-15'), status: \App\Scolarite\Domain\Model\Homework\HomeworkStatus::PUBLISHED, createdAt: new DateTimeImmutable('2026-03-19 10:00:00'), updatedAt: new DateTimeImmutable('2026-03-19 10:00:00'), ); } /** * @param Role[] $roles */ private function createUser( string $email, array $roles, string $firstName = 'Test', string $lastName = 'User', ): User { return User::reconstitute( id: UserId::generate(), email: new Email($email), roles: $roles, tenantId: TenantId::fromString(self::TENANT_ID), schoolName: 'École Test', statut: StatutCompte::ACTIF, dateNaissance: null, createdAt: new DateTimeImmutable('2026-01-01'), hashedPassword: 'hashed', activatedAt: new DateTimeImmutable('2026-01-02'), consentementParental: null, firstName: $firstName, lastName: $lastName, ); } }