entityManager = $this->createMock(EntityManagerInterface::class); $this->actionService = $this->createMock(ActionService::class); $this->security = $this->createMock(Security::class); $this->userService = $this->createMock(UserService::class); $this->psrLogger = $this->createMock(LoggerInterface::class); $this->loggerService = $this->createMock(LoggerService::class); $this->service = new UserOrganizationAppService( $this->entityManager, $this->actionService, $this->security, $this->userService, $this->psrLogger, $this->loggerService ); } /** * Helper to set private ID property on entities. */ private function setEntityId(object $entity, int $id): void { $reflection = new \ReflectionClass($entity); if ($reflection->hasProperty('id')) { $property = $reflection->getProperty('id'); // $property->setAccessible(true); // Needed for PHP < 8.1 $property->setValue($entity, $id); } } // ========================================== // TEST: groupUserOrganizationAppsByApplication // ========================================== public function testGroupUserOrganizationAppsByApplication(): void { // 1. Setup Apps $app1 = new Apps(); $this->setEntityId($app1, 1); $app2 = new Apps(); $this->setEntityId($app2, 2); // No roles for this one // 2. Setup Existing Link $role = new Roles(); $this->setEntityId($role, 10); $uo = new UsersOrganizations(); $this->setEntityId($uo, 99); $uoa = new UserOrganizationApp(); $this->setEntityId($uoa, 500); $uoa->setApplication($app1); $uoa->setRole($role); $uoa->setUserOrganization($uo); // 3. Run $result = $this->service->groupUserOrganizationAppsByApplication( [$uoa], [$app1, $app2], null ); // 4. Assert $this->assertArrayHasKey(1, $result); $this->assertArrayHasKey(2, $result); // Check App 1 (Has existing link) $this->assertEquals(99, $result[1]['uoId']); $this->assertEquals([10], $result[1]['selectedRoleIds']); // Check App 2 (Empty default) $this->assertNull($result[2]['uoId']); $this->assertEmpty($result[2]['selectedRoleIds']); } // ========================================== // TEST: deactivateAllUserOrganizationsAppLinks // ========================================== public function testDeactivateAllLinksSuccess(): void { $uo = new UsersOrganizations(); $user = new User(); $org = new Organizations(); $uo->setUsers($user); $uo->setOrganization($org); $app = new Apps(); $this->setEntityId($app, 1); $role = new Roles(); $this->setEntityId($role, 10); $uoa = new UserOrganizationApp(); $this->setEntityId($uoa, 555); $uoa->setApplication($app); $uoa->setRole($role); $uoa->setIsActive(true); // Mock Repository $repo = $this->createMock(EntityRepository::class); $repo->method('findBy')->willReturn([$uoa]); $this->entityManager->method('getRepository')->willReturn($repo); // Expectations $this->actionService->expects($this->once())->method('createAction'); $this->entityManager->expects($this->once())->method('persist')->with($uoa); $this->loggerService->expects($this->once())->method('logUOALinkDeactivated'); $this->service->deactivateAllUserOrganizationsAppLinks($uo, null); $this->assertFalse($uoa->isActive()); } public function testDeactivateHandlesException(): void { $uo = new UsersOrganizations(); // The service needs a User to create an Action log $user = new User(); $this->setEntityId($user, 99); $uo->setUsers($user); // <--- Assign the user! // Also needs an Org for the Action log $org = new Organizations(); $this->setEntityId($org, 88); $uo->setOrganization($org); $app = new Apps(); $this->setEntityId($app, 1); $role = new Roles(); $this->setEntityId($role, 1); $realUoa = new UserOrganizationApp(); $this->setEntityId($realUoa, 100); $realUoa->setApplication($app); $realUoa->setRole($role); $realUoa->setIsActive(true); $repo = $this->createMock(EntityRepository::class); $repo->method('findBy')->willReturn([$realUoa]); $this->entityManager->method('getRepository')->willReturn($repo); // Throw exception on persist $this->entityManager->method('persist')->willThrowException(new \Exception('DB Error')); // Expect Logger Critical $this->loggerService->expects($this->once())->method('logCritical'); $this->service->deactivateAllUserOrganizationsAppLinks($uo); } // ========================================== // TEST: syncRolesForUserOrganizationApp // ========================================== public function testSyncRolesAddsNewRole(): void { // Setup $actingUser = new User(); $this->setEntityId($actingUser, 1); $targetUser = new User(); $this->setEntityId($targetUser, 2); $org = new Organizations(); $this->setEntityId($org, 10); $uo = new UsersOrganizations(); $uo->setOrganization($org); $uo->setUsers($targetUser); $app = new Apps(); $this->setEntityId($app, 5); $app->setName('App1'); $roleId = 20; $role = new Roles(); $role->setName('EDITOR'); $this->setEntityId($role, $roleId); // Mock Repositories $uoaRepo = $this->createMock(EntityRepository::class); $uoaRepo->method('findBy')->willReturn([]); // No existing roles $roleRepo = $this->createMock(EntityRepository::class); $roleRepo->method('find')->with($roleId)->willReturn($role); $this->entityManager->method('getRepository')->willReturnMap([ [UserOrganizationApp::class, $uoaRepo], [Roles::class, $roleRepo], ]); // Expect creation $this->entityManager->expects($this->once())->method('persist')->with($this->isInstanceOf(UserOrganizationApp::class)); $this->entityManager->expects($this->once())->method('flush'); $this->actionService->expects($this->once())->method('createAction'); // Run $this->service->syncRolesForUserOrganizationApp($uo, $app, [(string)$roleId], $actingUser); } public function testSyncRolesDeactivatesUnselectedRole(): void { $actingUser = new User(); $this->setEntityId($actingUser, 1); $targetUser = new User(); $this->setEntityId($targetUser, 2); $org = new Organizations(); $this->setEntityId($org, 10); $uo = new UsersOrganizations(); $uo->setOrganization($org); $uo->setUsers($targetUser); $app = new Apps(); $this->setEntityId($app, 5); $app->setName('App1'); // Existing active role $role = new Roles(); $this->setEntityId($role, 30); $role->setName('VIEWER'); $existingUoa = new UserOrganizationApp(); $this->setEntityId($existingUoa, 999); $existingUoa->setRole($role); $existingUoa->setApplication($app); $existingUoa->setUserOrganization($uo); $existingUoa->setIsActive(true); // Repos $uoaRepo = $this->createMock(EntityRepository::class); $uoaRepo->method('findBy')->willReturn([$existingUoa]); $this->entityManager->method('getRepository')->willReturnMap([ [UserOrganizationApp::class, $uoaRepo], ]); // We pass empty array [] as selected roles -> expect deactivation $this->service->syncRolesForUserOrganizationApp($uo, $app, [], $actingUser); $this->assertFalse($existingUoa->isActive()); } public function testSyncRolesHandlesSuperAdminLogic(): void { // Setup $actingUser = new User(); $this->setEntityId($actingUser, 1); $targetUser = new User(); $this->setEntityId($targetUser, 2); $uo = new UsersOrganizations(); $uo->setUsers($targetUser); $org = new Organizations(); $this->setEntityId($org, 500); // <--- Give the Org an ID! $uo->setOrganization($org); $app = new Apps(); $this->setEntityId($app, 1); $app->setName('Portal'); // Roles $superAdminRole = new Roles(); $superAdminRole->setName('SUPER ADMIN'); $this->setEntityId($superAdminRole, 100); $adminRole = new Roles(); $adminRole->setName('ADMIN'); $this->setEntityId($adminRole, 101); // Repositories Configuration $uoaRepo = $this->createMock(EntityRepository::class); // 1. findBy (initial check) -> returns empty // 2. findOneBy (inside ensureAdminRoleForSuperAdmin) -> returns null (Admin link doesn't exist yet) $uoaRepo->method('findBy')->willReturn([]); $uoaRepo->method('findOneBy')->willReturn(null); $roleRepo = $this->createMock(EntityRepository::class); $roleRepo->method('find')->with(100)->willReturn($superAdminRole); $roleRepo->method('findOneBy')->with(['name' => 'ADMIN'])->willReturn($adminRole); $this->entityManager->method('getRepository')->willReturnMap([ [UserOrganizationApp::class, $uoaRepo], [Roles::class, $roleRepo], ]); // Expectations // 1. UserService should be called to sync SUPER ADMIN $this->userService->expects($this->once()) ->method('syncUserRoles') ->with($targetUser, 'SUPER ADMIN', true); // 2. EntityManager should persist: // - The new SUPER ADMIN link // - The new ADMIN link (automatically created) $this->entityManager->expects($this->exactly(2)) ->method('persist') ->with($this->isInstanceOf(UserOrganizationApp::class)); // Run $this->service->syncRolesForUserOrganizationApp($uo, $app, ['100'], $actingUser); } }