*/ #[CoversClass(TimesheetTeamAccess::class)] #[CoversClass(TimesheetTeamAccessValidator::class)] class TimesheetTeamAccessValidatorTest extends ConstraintValidatorTestCase { protected function createValidator(): TimesheetTeamAccessValidator { return $this->createMyValidator(); } /** * @param array $originalData */ protected function createMyValidator( array $originalData = [], ?User $user = null ): TimesheetTeamAccessValidator { $security = $this->createMock(Security::class); $security->method('getUser')->willReturn($user ?? new User()); $permissionService = $this->createMock(PermissionService::class); $permissionService->method('getPermissions')->willReturn([]); $permissionManager = new RolePermissionManager($permissionService, [], []); $unitOfWork = $this->createMock(UnitOfWork::class); $unitOfWork->method('getOriginalEntityData')->willReturn($originalData); $entityManager = $this->createMock(EntityManagerInterface::class); $entityManager->method('getUnitOfWork')->willReturn($unitOfWork); $registry = $this->createMock(ManagerRegistry::class); $registry->method('getManagerForClass')->willReturn($entityManager); return new TimesheetTeamAccessValidator($security, $permissionManager, $registry); } public function testConstraintIsInvalid(): void { $this->expectException(UnexpectedTypeException::class); $this->validator->validate(new Timesheet(), new NotBlank()); } public function testInvalidValueThrowsException(): void { $this->expectException(UnexpectedTypeException::class); $this->validator->validate(new NotBlank(), new TimesheetTeamAccess(['message' => 'myMessage'])); } public function testTriggersForNewTimesheetWithInaccessibleProject(): void { $this->validator = $this->createMyValidator(); $this->validator->initialize($this->context); $timesheet = new Timesheet(); $timesheet->setProject($this->createRestrictedProject('restricted')); $this->validator->validate($timesheet, new TimesheetTeamAccess()); $this->buildViolation('You are not allowed to use this project.') ->atPath('property.path.project') ->setCode(TimesheetTeamAccess::PROJECT_ACCESS_ERROR) ->assertRaised(); } public function testDoesNotReadOriginalDataForNewTimesheet(): void { $security = $this->createMock(Security::class); $security->method('getUser')->willReturn(new User()); $permissionService = $this->createMock(PermissionService::class); $permissionService->method('getPermissions')->willReturn([]); $permissionManager = new RolePermissionManager($permissionService, [], []); $registry = $this->createMock(ManagerRegistry::class); $registry->expects(self::never())->method('getManagerForClass'); $this->validator = new TimesheetTeamAccessValidator($security, $permissionManager, $registry); $this->validator->initialize($this->context); $timesheet = new Timesheet(); $timesheet->setProject($this->createRestrictedProject('new-timesheet')); $this->validator->validate($timesheet, new TimesheetTeamAccess()); $this->buildViolation('You are not allowed to use this project.') ->atPath('property.path.project') ->setCode(TimesheetTeamAccess::PROJECT_ACCESS_ERROR) ->assertRaised(); } public function testDoesNotTriggerForExistingTimesheetWithUnchangedProject(): void { $originalProject = $this->createRestrictedProject('restricted'); $this->validator = $this->createMyValidator(['project' => $originalProject]); $this->validator->initialize($this->context); $timesheet = $this->createPersistedTimesheet(); $timesheet->setProject($originalProject); $this->validator->validate($timesheet, new TimesheetTeamAccess()); $this->assertNoViolation(); } public function testTriggersForExistingTimesheetWithChangedProject(): void { $this->validator = $this->createMyValidator(['project' => $this->createProject('old')]); $this->validator->initialize($this->context); $timesheet = $this->createPersistedTimesheet(); $timesheet->setProject($this->createRestrictedProject('new')); $this->validator->validate($timesheet, new TimesheetTeamAccess()); $this->buildViolation('You are not allowed to use this project.') ->atPath('property.path.project') ->setCode(TimesheetTeamAccess::PROJECT_ACCESS_ERROR) ->assertRaised(); } public function testTriggersForExistingTimesheetWithChangedActivity(): void { $this->validator = $this->createMyValidator(['activity' => $this->createActivity('old')]); $this->validator->initialize($this->context); $timesheet = $this->createPersistedTimesheet(); $timesheet->setActivity($this->createRestrictedActivity('new')); $this->validator->validate($timesheet, new TimesheetTeamAccess()); $this->buildViolation('You are not allowed to use this activity.') ->atPath('property.path.activity') ->setCode(TimesheetTeamAccess::ACTIVITY_ACCESS_ERROR) ->assertRaised(); } public function testDoesNotTriggerForSuperAdmin(): void { $user = new User(); $user->setRoles([User::ROLE_SUPER_ADMIN]); $this->validator = $this->createMyValidator([], $user); $this->validator->initialize($this->context); $timesheet = new Timesheet(); $timesheet ->setProject($this->createRestrictedProject('restricted')) ->setActivity($this->createRestrictedActivity('restricted')) ; $this->validator->validate($timesheet, new TimesheetTeamAccess()); $this->assertNoViolation(); } public function testGetTargets(): void { $constraint = new TimesheetTeamAccess(); self::assertEquals('class', $constraint->getTargets()); } private function createPersistedTimesheet(): Timesheet { $timesheet = new Timesheet(); $reflection = new \ReflectionClass($timesheet); $property = $reflection->getProperty('id'); $property->setAccessible(true); $property->setValue($timesheet, 1); $property->setAccessible(false); return $timesheet; } private function createProject(string $name): Project { $project = new Project(); $project->setName($name); $project->setCustomer(new Customer('customer-' . $name)); return $project; } private function createActivity(string $name): Activity { $activity = new Activity(); $activity->setName($name); return $activity; } private function createRestrictedProject(string $name): Project { $project = $this->createProject($name); $project->getCustomer()?->addTeam(new Team('customer-team-' . $name)); return $project; } private function createRestrictedActivity(string $name): Activity { $activity = $this->createActivity($name); $activity->addTeam(new Team('activity-team-' . $name)); return $activity; } }