entityManager = $this->createMock(EntityManagerInterface::class); $this->security = $this->createMock(Security::class); $this->actionService = $this->createMock(ActionService::class); $this->emailService = $this->createMock(EmailService::class); $this->organizationsService = $this->createMock(OrganizationsService::class); $this->loggerService = $this->createMock(LoggerService::class); $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); $this->userService = new UserService( $this->entityManager, $this->security, $this->loggerService, $this->actionService, $this->emailService, $this->organizationsService, $this->eventDispatcher ); } public function testGenerateRandomPassword(): void { $password = $this->userService->generateRandomPassword(); $this->assertEquals(64, strlen($password)); $this->assertMatchesRegularExpression('/[a-zA-Z0-9!@#$%^&*()_+]+/', $password); } public function testIsUserConnectedReturnsTrueIfTokenValid(): void { $userIdentifier = 'test@example.com'; // Mock the Repository for AccessToken $repo = $this->createMock(EntityRepository::class); // Mock a token that expires in the future $token = $this->createMock(AccessToken::class); $token->method('getExpiry')->willReturn(new \DateTimeImmutable('+1 hour')); $repo->expects($this->once()) ->method('findBy') ->with(['userIdentifier' => $userIdentifier, 'revoked' => false]) ->willReturn([$token]); $this->entityManager->expects($this->once()) ->method('getRepository') ->with(AccessToken::class) ->willReturn($repo); $result = $this->userService->isUserConnected($userIdentifier); $this->assertTrue($result); } public function testIsUserConnectedReturnsFalseIfTokenExpired(): void { $userIdentifier = 'test@example.com'; $repo = $this->createMock(EntityRepository::class); $token = $this->createMock(AccessToken::class); $token->method('getExpiry')->willReturn(new \DateTimeImmutable('-1 hour')); $repo->method('findBy')->willReturn([$token]); $this->entityManager->method('getRepository')->willReturn($repo); $result = $this->userService->isUserConnected($userIdentifier); $this->assertFalse($result); } public function testGetUserByIdentifierFound(): void { $identifier = 'user@test.com'; $user = new User(); $user->setEmail($identifier); $repo = $this->createMock(EntityRepository::class); $repo->expects($this->once()) ->method('findOneBy') ->with(['email' => $identifier]) ->willReturn($user); $this->entityManager->method('getRepository')->with(User::class)->willReturn($repo); $result = $this->userService->getUserByIdentifier($identifier); $this->assertSame($user, $result); } public function testGetUserByIdentifierNotFound(): void { $identifier = 'unknown@test.com'; $repo = $this->createMock(EntityRepository::class); $repo->method('findOneBy')->willReturn(null); $this->entityManager->method('getRepository')->with(User::class)->willReturn($repo); // Expect Logger to be called $this->loggerService->expects($this->once()) ->method('logEntityNotFound') ->with('User', ['user_identifier' => $identifier], null); $this->expectException(EntityNotFoundException::class); $this->expectExceptionMessage(UserService::NOT_FOUND); $this->userService->getUserByIdentifier($identifier); } public function testHasAccessToReturnsTrueForSuperAdmin(): void { $this->security->method('isGranted')->with('ROLE_SUPER_ADMIN')->willReturn(true); $user = new User(); // Dummy user $this->assertTrue($this->userService->hasAccessTo($user)); } public function testHasAccessToReturnsTrueForSelf(): void { $this->security->method('isGranted')->willReturn(false); $currentUser = new User(); $currentUser->setEmail('me@test.com'); $targetUser = new User(); $targetUser->setEmail('me@test.com'); $this->security->method('getUser')->willReturn($currentUser); // skipSelfCheck = false (default) $this->assertTrue($this->userService->hasAccessTo($targetUser)); } public function testHandleProfilePictureUploadsAndLogs(): void { $user = new User(); $user->setName('John'); $user->setSurname('Doe'); // Mock UploadedFile $file = $this->createMock(UploadedFile::class); $file->method('guessExtension')->willReturn('jpg'); $this->userService->handleProfilePicture($user, $file); $this->assertStringContainsString('uploads/profile_pictures/JohnDoe_', $user->getPictureUrl()); } public function testSyncUserRolesAddsRole(): void { $user = new User(); $user->setRoles(['ROLE_USER']); $this->loggerService->expects($this->once())->method('logRoleAssignment'); $this->userService->syncUserRoles($user, 'ADMIN', true); $this->assertContains('ROLE_ADMIN', $user->getRoles()); } public function testSyncUserRolesRemovesRole(): void { $user = new User(); $user->setRoles(['ROLE_USER', 'ROLE_ADMIN']); // Mock repositories to ensure no other org gives this role $repoUO = $this->createMock(EntityRepository::class); $repoUO->method('findBy')->willReturn([]); // No active org links $this->entityManager->method('getRepository') ->willReturnMap([ [UsersOrganizations::class, $repoUO] ]); $this->userService->syncUserRoles($user, 'ADMIN', false); $this->assertNotContains('ROLE_ADMIN', $user->getRoles()); } public function testIsPasswordStrong(): void { $this->assertTrue($this->userService->isPasswordStrong('StrongP@ss1')); // Chars + Digits + Special + Length $this->assertFalse($this->userService->isPasswordStrong('weak')); // Too short $this->assertFalse($this->userService->isPasswordStrong('123456789')); // No letters } public function testCreateNewUserSuccess(): void { $newUser = new User(); $newUser->setName('jane'); $newUser->setSurname('doe'); $newUser->setEmail('jane@doe.com'); $actingUser = new User(); $this->setEntityId($actingUser, 99); $actingUser->setEmail('admin@test.com'); // 1. Expect the Entity Manager to save the user $this->entityManager->expects($this->atLeastOnce()) ->method('persist') ->with($newUser) ->willReturnCallback(function ($entity) { $this->setEntityId($entity, 123); }); $this->entityManager->expects($this->atLeastOnce())->method('flush'); // 2. IMPORTANT: Expect the Event Dispatcher to be called // We check that it receives an instance of UserCreatedEvent $this->eventDispatcher->expects($this->once()) ->method('dispatch') ->with($this->isInstanceOf(UserCreatedEvent::class)) ->willReturn(new UserCreatedEvent($newUser, $actingUser)); // 3. REMOVE direct expectations for emailService, loggerService, and actionService // Because UserService no longer calls them—the Subscriber does. // (If you want to test the Subscriber, that should be in a separate UserSubscriberTest) // Execute $this->userService->createNewUser($newUser, $actingUser, null); // 4. Assertions $this->assertEquals('Jane', $newUser->getName()); // Verify formatting (formatUserData) $this->assertEquals(123, $newUser->getId()); $this->assertFalse($newUser->isActive()); } public function testLinkUserToOrganization(): void { $user = new User(); $this->setEntityId($user, 10); // Pre-set ID for existing user $org = new Organizations(); $this->setEntityId($org, 50); // Pre-set ID for org $actingUser = new User(); $this->setEntityId($actingUser, 99); // Capture the UsersOrganizations entity when it is persisted to give it an ID $this->entityManager->expects($this->exactly(2)) ->method('persist') ->willReturnCallback(function ($entity) use ($user) { if ($entity instanceof UsersOrganizations) { // This is the UO entity link (Call 1) $this->setEntityId($entity, 555); } elseif ($entity instanceof User && $entity === $user) { // This is the User entity inside generatePasswordToken (Call 2) // The ID is already set, so we do nothing here. } }); $this->entityManager->expects($this->exactly(2))->method('flush'); // Now the logger will receive valid Integers instead of null $this->loggerService->expects($this->once()) ->method('logUserOrganizationLinkCreated') ->with(10, 50, 99, 555); $this->emailService->expects($this->once())->method('sendPasswordSetupEmail'); $this->organizationsService->expects($this->once())->method('notifyOrganizationAdmins'); $result = $this->userService->linkUserToOrganization($user, $org, $actingUser); $this->assertInstanceOf(UsersOrganizations::class, $result); $this->assertEquals(555, $result->getId()); } public function testIsAdminOfOrganizationReturnsTrue(): void { $org = new Organizations(); $currentUser = new User(); $currentUser->setEmail('admin@test.com'); // Mock Security User $this->security->method('getUser')->willReturn($currentUser); $this->security->method('isGranted')->with('ROLE_ADMIN')->willReturn(true); // 1. getUserByIdentifier (internal call) mocks $userRepo = $this->createMock(EntityRepository::class); $userRepo->method('findOneBy')->with(['email' => 'admin@test.com'])->willReturn($currentUser); // 2. UsersOrganizations mock $uoRepo = $this->createMock(EntityRepository::class); $uo = new UsersOrganizations(); $uoRepo->method('findOneBy')->willReturn($uo); // 3. Roles mock $rolesRepo = $this->createMock(EntityRepository::class); $adminRole = new Roles(); $adminRole->setName('ADMIN'); $rolesRepo->method('findOneBy')->with(['name' => 'ADMIN'])->willReturn($adminRole); // 4. UserOrganizatonApp mock (The link checking if they are admin active) $uoaRepo = $this->createMock(EntityRepository::class); $uoa = new UserOrganizatonApp(); $uoaRepo->method('findOneBy')->willReturn($uoa); // Returns an object, so true // Configure EntityManager to return these repos based on class $this->entityManager->method('getRepository')->willReturnMap([ [User::class, $userRepo], [UsersOrganizations::class, $uoRepo], [Roles::class, $rolesRepo], [UserOrganizatonApp::class, $uoaRepo], ]); $result = $this->userService->isAdminOfOrganization($org); $this->assertTrue($result); } private function setEntityId(object $entity, int $id): void { $reflection = new \ReflectionClass($entity); $property = $reflection->getProperty('id'); // $property->setAccessible(true); // Required for PHP < 8.1 $property->setValue($entity, $id); } }