repository = new InMemoryStudentGuardianRepository(); $this->tenantContext = new TenantContext(); $this->tenantId = TenantId::generate(); $this->tenantContext->setCurrentTenant(new TenantConfig( tenantId: InfraTenantId::fromString((string) $this->tenantId), subdomain: 'test', databaseUrl: 'sqlite:///:memory:', )); $this->voter = new StudentGuardianVoter($this->repository, $this->tenantContext); } #[Test] public function itAbstainsForUnrelatedAttributes(): void { $token = $this->tokenWithSecurityUser('ROLE_ADMIN'); $result = $this->voter->vote($token, 'some-student-id', ['SOME_OTHER_ATTRIBUTE']); self::assertSame(Voter::ACCESS_ABSTAIN, $result); } #[Test] public function itAbstainsWhenSubjectIsNotAString(): void { $token = $this->tokenWithSecurityUser('ROLE_ADMIN'); $result = $this->voter->vote($token, null, [StudentGuardianVoter::VIEW_STUDENT]); self::assertSame(Voter::ACCESS_ABSTAIN, $result); } #[Test] public function itDeniesAccessToUnauthenticatedUsers(): void { $token = $this->createMock(TokenInterface::class); $token->method('getUser')->willReturn(null); $result = $this->voter->vote($token, 'some-student-id', [StudentGuardianVoter::VIEW_STUDENT]); self::assertSame(Voter::ACCESS_DENIED, $result); } #[Test] public function itDeniesAccessToNonSecurityUser(): void { $user = $this->createMock(UserInterface::class); $user->method('getRoles')->willReturn(['ROLE_ADMIN']); $token = $this->createMock(TokenInterface::class); $token->method('getUser')->willReturn($user); $result = $this->voter->vote($token, 'some-student-id', [StudentGuardianVoter::VIEW_STUDENT]); self::assertSame(Voter::ACCESS_DENIED, $result); } #[Test] public function itGrantsViewToSuperAdmin(): void { $result = $this->voteWithRole('ROLE_SUPER_ADMIN'); self::assertSame(Voter::ACCESS_GRANTED, $result); } #[Test] public function itGrantsViewToAdmin(): void { $result = $this->voteWithRole('ROLE_ADMIN'); self::assertSame(Voter::ACCESS_GRANTED, $result); } #[Test] public function itGrantsViewToSecretariat(): void { $result = $this->voteWithRole('ROLE_SECRETARIAT'); self::assertSame(Voter::ACCESS_GRANTED, $result); } #[Test] public function itGrantsViewToProf(): void { $result = $this->voteWithRole('ROLE_PROF'); self::assertSame(Voter::ACCESS_GRANTED, $result); } #[Test] public function itGrantsViewToVieScolaire(): void { $result = $this->voteWithRole('ROLE_VIE_SCOLAIRE'); self::assertSame(Voter::ACCESS_GRANTED, $result); } #[Test] public function itGrantsViewToLinkedParent(): void { $parentId = UserId::generate(); $studentId = UserId::generate(); $link = StudentGuardian::lier( studentId: $studentId, guardianId: $parentId, relationshipType: RelationshipType::MOTHER, tenantId: $this->tenantId, createdAt: new DateTimeImmutable(), ); $this->repository->save($link); $token = $this->tokenWithSecurityUser('ROLE_PARENT', $parentId); $result = $this->voter->vote($token, (string) $studentId, [StudentGuardianVoter::VIEW_STUDENT]); self::assertSame(Voter::ACCESS_GRANTED, $result); } #[Test] public function itDeniesViewToUnlinkedParent(): void { $parentId = UserId::generate(); $otherStudentId = UserId::generate(); $token = $this->tokenWithSecurityUser('ROLE_PARENT', $parentId); $result = $this->voter->vote($token, (string) $otherStudentId, [StudentGuardianVoter::VIEW_STUDENT]); self::assertSame(Voter::ACCESS_DENIED, $result); } #[Test] public function itDeniesViewToEleve(): void { $result = $this->voteWithRole('ROLE_ELEVE'); self::assertSame(Voter::ACCESS_DENIED, $result); } #[Test] public function itGrantsViewToEachSeparatedParent(): void { $parent1Id = UserId::generate(); $parent2Id = UserId::generate(); $studentId = UserId::generate(); $link1 = StudentGuardian::lier( studentId: $studentId, guardianId: $parent1Id, relationshipType: RelationshipType::FATHER, tenantId: $this->tenantId, createdAt: new DateTimeImmutable(), ); $link2 = StudentGuardian::lier( studentId: $studentId, guardianId: $parent2Id, relationshipType: RelationshipType::MOTHER, tenantId: $this->tenantId, createdAt: new DateTimeImmutable(), ); $this->repository->save($link1); $this->repository->save($link2); $token1 = $this->tokenWithSecurityUser('ROLE_PARENT', $parent1Id); $token2 = $this->tokenWithSecurityUser('ROLE_PARENT', $parent2Id); self::assertSame(Voter::ACCESS_GRANTED, $this->voter->vote($token1, (string) $studentId, [StudentGuardianVoter::VIEW_STUDENT])); self::assertSame(Voter::ACCESS_GRANTED, $this->voter->vote($token2, (string) $studentId, [StudentGuardianVoter::VIEW_STUDENT])); } #[Test] public function itDeniesParentWhenNoTenantSet(): void { $parentId = UserId::generate(); $studentId = UserId::generate(); $tenantContext = new TenantContext(); $voter = new StudentGuardianVoter($this->repository, $tenantContext); $token = $this->tokenWithSecurityUser('ROLE_PARENT', $parentId); $result = $voter->vote($token, (string) $studentId, [StudentGuardianVoter::VIEW_STUDENT]); self::assertSame(Voter::ACCESS_DENIED, $result); } #[Test] public function itGrantsManageToAdmin(): void { $token = $this->tokenWithSecurityUser('ROLE_ADMIN'); $result = $this->voter->vote($token, null, [StudentGuardianVoter::MANAGE]); self::assertSame(Voter::ACCESS_GRANTED, $result); } #[Test] public function itDeniesManageToParent(): void { $token = $this->tokenWithSecurityUser('ROLE_PARENT'); $result = $this->voter->vote($token, null, [StudentGuardianVoter::MANAGE]); self::assertSame(Voter::ACCESS_DENIED, $result); } #[Test] public function itDeniesManageToEleve(): void { $token = $this->tokenWithSecurityUser('ROLE_ELEVE'); $result = $this->voter->vote($token, null, [StudentGuardianVoter::MANAGE]); self::assertSame(Voter::ACCESS_DENIED, $result); } private function voteWithRole(string $role): int { $token = $this->tokenWithSecurityUser($role); return $this->voter->vote($token, (string) UserId::generate(), [StudentGuardianVoter::VIEW_STUDENT]); } private function tokenWithSecurityUser(string $role, ?UserId $userId = null): TokenInterface { $securityUser = new SecurityUser( userId: $userId ?? UserId::generate(), email: 'test@example.com', hashedPassword: 'hashed', tenantId: $this->tenantId, roles: [$role], ); $token = $this->createMock(TokenInterface::class); $token->method('getUser')->willReturn($securityUser); return $token; } }