repository = new InMemoryTeacherReplacementRepository(); $this->classRepository = new InMemoryClassRepository(); $this->subjectRepository = new InMemorySubjectRepository(); $this->tenantContext = new TenantContext(); $this->clock = new class implements Clock { public function now(): DateTimeImmutable { return new DateTimeImmutable('2026-03-15 10:00:00'); } }; $this->seedClassesAndSubjects(); } #[Test] public function itProvidesReplacedClassesForAuthenticatedTeacher(): void { $provider = $this->createProvider(self::REPLACEMENT_TEACHER_ID); $this->setTenant(); $replacement = $this->createReplacement(); $this->repository->save($replacement); $result = $provider->provide(new GetCollection()); self::assertCount(1, $result); self::assertContainsOnlyInstancesOf(ReplacedClassResource::class, $result); self::assertSame((string) $replacement->id, $result[0]->replacementId); self::assertSame(self::REPLACED_TEACHER_ID, $result[0]->replacedTeacherId); self::assertSame(self::CLASS_ID, $result[0]->classId); self::assertSame(self::SUBJECT_ID, $result[0]->subjectId); } #[Test] public function itReturnsEmptyWhenNoReplacements(): void { $provider = $this->createProvider(self::REPLACEMENT_TEACHER_ID); $this->setTenant(); $result = $provider->provide(new GetCollection()); self::assertSame([], $result); } #[Test] public function itThrowsWhenUserNotAuthenticated(): void { $security = $this->createMock(Security::class); $security->method('getUser')->willReturn(null); $handler = new GetReplacedClassesForTeacherHandler( $this->repository, $this->classRepository, $this->subjectRepository, $this->clock, ); $provider = new ReplacedClassesProvider($handler, $this->tenantContext, $security); $this->setTenant(); $this->expectException(UnauthorizedHttpException::class); $this->expectExceptionMessage('Authentification requise.'); $provider->provide(new GetCollection()); } #[Test] public function itThrowsWhenTenantNotSet(): void { $provider = $this->createProvider(self::REPLACEMENT_TEACHER_ID); $this->expectException(UnauthorizedHttpException::class); $this->expectExceptionMessage('Tenant non défini.'); $provider->provide(new GetCollection()); } private function setTenant(): void { $this->tenantContext->setCurrentTenant(new TenantConfig( tenantId: InfraTenantId::fromString(self::TENANT_UUID), subdomain: 'test', databaseUrl: 'sqlite:///:memory:', )); } private function createProvider(string $userId): ReplacedClassesProvider { $securityUser = new SecurityUser( UserId::fromString($userId), 'teacher@test.com', 'hashed', TenantId::fromString(self::TENANT_UUID), ['ROLE_PROF'], ); $security = $this->createMock(Security::class); $security->method('getUser')->willReturn($securityUser); $handler = new GetReplacedClassesForTeacherHandler( $this->repository, $this->classRepository, $this->subjectRepository, $this->clock, ); return new ReplacedClassesProvider($handler, $this->tenantContext, $security); } private function seedClassesAndSubjects(): void { $tenantId = TenantId::fromString(self::TENANT_UUID); $schoolId = SchoolId::fromString(self::SCHOOL_ID); $academicYearId = AcademicYearId::fromString(self::ACADEMIC_YEAR_ID); $now = new DateTimeImmutable('2026-01-01'); $class = SchoolClass::reconstitute( ClassId::fromString(self::CLASS_ID), $tenantId, $schoolId, $academicYearId, new ClassName('6ème A'), null, null, ClassStatus::ACTIVE, null, $now, $now, null, ); $this->classRepository->save($class); $subject = Subject::reconstitute( SubjectId::fromString(self::SUBJECT_ID), $tenantId, $schoolId, new SubjectName('Mathématiques'), new SubjectCode('MATH'), null, SubjectStatus::ACTIVE, null, $now, $now, null, ); $this->subjectRepository->save($subject); } private function createReplacement(): TeacherReplacement { return TeacherReplacement::designer( tenantId: TenantId::fromString(self::TENANT_UUID), replacedTeacherId: UserId::fromString(self::REPLACED_TEACHER_ID), replacementTeacherId: UserId::fromString(self::REPLACEMENT_TEACHER_ID), startDate: new DateTimeImmutable('2026-03-01'), endDate: new DateTimeImmutable('2026-03-31'), classes: [ new ClassSubjectPair( ClassId::fromString(self::CLASS_ID), SubjectId::fromString(self::SUBJECT_ID), ), ], reason: null, createdBy: UserId::fromString(self::CREATED_BY_ID), now: new DateTimeImmutable('2026-02-15 10:00:00'), ); } }