diff --git a/tests/Service/UserOrganizationAppServiceTest.php b/tests/Service/UserOrganizationAppServiceTest.php new file mode 100644 index 0000000..49dbf19 --- /dev/null +++ b/tests/Service/UserOrganizationAppServiceTest.php @@ -0,0 +1,320 @@ +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 UserOrganizatonApp(); + $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 UserOrganizatonApp(); + $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 UserOrganizatonApp(); + $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([ + [UserOrganizatonApp::class, $uoaRepo], + [Roles::class, $roleRepo], + ]); + + // Expect creation + $this->entityManager->expects($this->once())->method('persist')->with($this->isInstanceOf(UserOrganizatonApp::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 UserOrganizatonApp(); + $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([ + [UserOrganizatonApp::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([ + [UserOrganizatonApp::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(UserOrganizatonApp::class)); + + // Run + $this->service->syncRolesForUserOrganizationApp($uo, $app, ['100'], $actingUser); + } +} \ No newline at end of file