repository = new InMemoryTeacherReplacementRepository(); $this->tenantContext = new TenantContext(); } #[Test] public function itProvidesSingleReplacementById(): void { $provider = $this->createProvider(granted: true); $this->setTenant(); $replacement = $this->createReplacement(); $this->repository->save($replacement); $result = $provider->provide( new Delete(), ['id' => (string) $replacement->id], ); self::assertInstanceOf(TeacherReplacementResource::class, $result); self::assertSame((string) $replacement->id, $result->id); self::assertSame(self::REPLACED_TEACHER_ID, $result->replacedTeacherId); self::assertSame(self::REPLACEMENT_TEACHER_ID, $result->replacementTeacherId); self::assertSame('active', $result->status); } #[Test] public function itThrowsNotFoundWhenReplacementDoesNotExist(): void { $provider = $this->createProvider(granted: true); $this->setTenant(); $this->expectException(NotFoundHttpException::class); $this->expectExceptionMessage('Remplacement non trouvé.'); $provider->provide( new Delete(), ['id' => '550e8400-e29b-41d4-a716-446655440099'], ); } #[Test] public function itRejectsUnauthorizedAccess(): void { $provider = $this->createProvider(granted: false); $this->setTenant(); $this->expectException(AccessDeniedHttpException::class); $provider->provide( new Delete(), ['id' => '550e8400-e29b-41d4-a716-446655440099'], ); } #[Test] public function itRejectsRequestWithoutTenant(): void { $provider = $this->createProvider(granted: true); $this->expectException(UnauthorizedHttpException::class); $provider->provide( new Delete(), ['id' => '550e8400-e29b-41d4-a716-446655440099'], ); } private function setTenant(): void { $this->tenantContext->setCurrentTenant(new TenantConfig( tenantId: InfraTenantId::fromString(self::TENANT_UUID), subdomain: 'test', databaseUrl: 'sqlite:///:memory:', )); } private function createProvider(bool $granted): TeacherReplacementItemProvider { $authChecker = new class($granted) implements AuthorizationCheckerInterface { public function __construct(private readonly bool $granted) { } public function isGranted(mixed $attribute, mixed $subject = null, ?AccessDecision $accessDecision = null): bool { return $this->granted; } }; return new TeacherReplacementItemProvider($this->repository, $this->tenantContext, $authChecker); } 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'), ); } }