From 135de156febfe2d483fe78708984e452cd1a4514 Mon Sep 17 00:00:00 2001 From: Surya Srinivasan Date: Sun, 8 Feb 2026 02:44:39 +0530 Subject: [PATCH 1/8] Fix account deletion blocked by deleted project admin mappings --- .../com/cloud/user/AccountManagerImpl.java | 29 ++++++++++++++----- .../cloud/user/AccountManagerImplTest.java | 21 ++++++++++++++ 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java index 1f6e8d5b49ee..dbf9f9b67b58 100644 --- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java +++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java @@ -2098,16 +2098,31 @@ public boolean deleteUserAccount(long accountId) { return deleteAccount(account, callerUserId, caller); } - protected void checkIfAccountManagesProjects(long accountId) { - List managedProjectIds = _projectAccountDao.listAdministratedProjectIds(accountId); - if (!CollectionUtils.isEmpty(managedProjectIds)) { - throw new InvalidParameterValueException(String.format( - "Unable to delete account [%s], because it manages the following project(s): %s. Please, remove the account from these projects or demote it to a regular project role first.", - accountId, managedProjectIds - )); +protected void checkIfAccountManagesProjects(long accountId) { + List managedProjectIds = _projectAccountDao.listAdministratedProjectIds(accountId); + + if (CollectionUtils.isEmpty(managedProjectIds)) { + return; + } + + List activeManagedProjects = new ArrayList<>(); + + for (Long projectId : managedProjectIds) { + ProjectVO project = _projectDao.findById(projectId); + if (project != null && project.getRemoved() == null) { + activeManagedProjects.add(projectId); } } + if (!activeManagedProjects.isEmpty()) { + throw new InvalidParameterValueException(String.format( + "Unable to delete account [%s], because it manages the following project(s): %s. Please, remove the account from these projects or demote it to a regular project role first.", + accountId, activeManagedProjects + )); + } +} + + protected boolean isDeleteNeeded(AccountVO account, long accountId, Account caller) { if (account == null) { logger.info(String.format("The account, identified by id %d, doesn't exist", accountId )); diff --git a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java index 6f5fbb0fdc12..d8bd9b9a348c 100644 --- a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java +++ b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java @@ -26,6 +26,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Date; + import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.Role; @@ -73,6 +75,7 @@ import com.cloud.vm.UserVmVO; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.snapshot.VMSnapshotVO; +import com.cloud.projects.ProjectVO; public class AccountManagerImplTest extends AccountManagentImplTestBase { @@ -1581,4 +1584,22 @@ public void testcheckCallerApiPermissionsForUserOperationsNotAllowedApis() { accountManagerImpl.checkCallerApiPermissionsForUserOrAccountOperations(accountMock); } + + @Test + public void testCheckIfAccountManagesOnlyDeletedProjectsDoesNotThrow() { + long accountId = 42L; + long projectId = 100L; + + Mockito.when(projectAccountDao.listAdministratedProjectIds(accountId)) + .thenReturn(List.of(projectId)); + + ProjectVO deletedProject = Mockito.mock(ProjectVO.class); + Mockito.when(deletedProject.getRemoved()).thenReturn(new Date()); + + Mockito.when(projectDao.findById(projectId)) + .thenReturn(deletedProject); + + accountManager.checkIfAccountManagesProjects(accountId); + } + } From 57d50603393e509be9489d347c196999b4cc402a Mon Sep 17 00:00:00 2001 From: Surya Srinivasan Date: Mon, 9 Feb 2026 18:47:17 +0530 Subject: [PATCH 2/8] Fix indentation per review --- server/src/main/java/com/cloud/user/AccountManagerImpl.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java index dbf9f9b67b58..cec340686963 100644 --- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java +++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java @@ -2115,10 +2115,12 @@ protected void checkIfAccountManagesProjects(long accountId) { } if (!activeManagedProjects.isEmpty()) { - throw new InvalidParameterValueException(String.format( + throw new InvalidParameterValueException( + String.format( "Unable to delete account [%s], because it manages the following project(s): %s. Please, remove the account from these projects or demote it to a regular project role first.", accountId, activeManagedProjects - )); + ) + ); } } From f16eaa964224db4a057e0a0cdee33ee66625ab37 Mon Sep 17 00:00:00 2001 From: Surya Srinivasan Date: Mon, 9 Feb 2026 21:26:40 +0530 Subject: [PATCH 3/8] Update server/src/main/java/com/cloud/user/AccountManagerImpl.java Co-authored-by: dahn --- .../com/cloud/user/AccountManagerImpl.java | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java index cec340686963..e69680a1d86f 100644 --- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java +++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java @@ -2098,31 +2098,30 @@ public boolean deleteUserAccount(long accountId) { return deleteAccount(account, callerUserId, caller); } -protected void checkIfAccountManagesProjects(long accountId) { - List managedProjectIds = _projectAccountDao.listAdministratedProjectIds(accountId); + protected void checkIfAccountManagesProjects(long accountId) { + List managedProjectIds = _projectAccountDao.listAdministratedProjectIds(accountId); - if (CollectionUtils.isEmpty(managedProjectIds)) { - return; - } + if (CollectionUtils.isEmpty(managedProjectIds)) { + return; + } - List activeManagedProjects = new ArrayList<>(); + List activeManagedProjects = new ArrayList<>(); - for (Long projectId : managedProjectIds) { - ProjectVO project = _projectDao.findById(projectId); - if (project != null && project.getRemoved() == null) { - activeManagedProjects.add(projectId); + for (Long projectId : managedProjectIds) { + ProjectVO project = _projectDao.findById(projectId); + if (project != null && project.getRemoved() == null) { + activeManagedProjects.add(projectId); + } } - } - if (!activeManagedProjects.isEmpty()) { - throw new InvalidParameterValueException( - String.format( - "Unable to delete account [%s], because it manages the following project(s): %s. Please, remove the account from these projects or demote it to a regular project role first.", - accountId, activeManagedProjects - ) - ); + if (!activeManagedProjects.isEmpty()) { + throw new InvalidParameterValueException( + String.format( + "Unable to delete account [%s], because it manages the following project(s): %s. Please, remove the account from these projects or demote it to a regular project role first.", + accountId, activeManagedProjects + )); + } } -} protected boolean isDeleteNeeded(AccountVO account, long accountId, Account caller) { From cb1eb8d0fbe78230f3b74601e40bbc0441776133 Mon Sep 17 00:00:00 2001 From: Surya Srinivasan Date: Tue, 10 Feb 2026 18:14:49 +0530 Subject: [PATCH 4/8] Fix test mocks to use injected _projectAccountDao and _projectDao --- .../com/cloud/user/AccountManagerImplTest.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java index d8bd9b9a348c..298e78c6d24d 100644 --- a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java +++ b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java @@ -79,6 +79,9 @@ public class AccountManagerImplTest extends AccountManagentImplTestBase { + @InjectMocks + private AccountManagerImpl accountManagerImpl; + @Mock private UserVmManagerImpl _vmMgr; @Mock @@ -117,9 +120,16 @@ public class AccountManagerImplTest extends AccountManagentImplTestBase { @Mock private ProjectAccountVO projectAccountVO; + @Mock private Project project; + @Mock + private ProjectAccountDao _projectAccountDao; + + @Mock + private ProjectDao _projectDao; + @Mock PasswordPolicyImpl passwordPolicyMock; @@ -1590,16 +1600,17 @@ public void testCheckIfAccountManagesOnlyDeletedProjectsDoesNotThrow() { long accountId = 42L; long projectId = 100L; - Mockito.when(projectAccountDao.listAdministratedProjectIds(accountId)) + Mockito.when(_projectAccountDao.listAdministratedProjectIds(accountId)) .thenReturn(List.of(projectId)); ProjectVO deletedProject = Mockito.mock(ProjectVO.class); Mockito.when(deletedProject.getRemoved()).thenReturn(new Date()); - Mockito.when(projectDao.findById(projectId)) + Mockito.when(_projectDao.findById(projectId)) .thenReturn(deletedProject); - accountManager.checkIfAccountManagesProjects(accountId); + accountManagerImpl.checkIfAccountManagesProjects(accountId); + } } From 20a3151651139802f45086727c3858ba9f18cc78 Mon Sep 17 00:00:00 2001 From: Surya Srinivasan Date: Tue, 10 Feb 2026 19:18:35 +0530 Subject: [PATCH 5/8] Fix missing imports for ProjectAccountDao, ProjectDao and InjectMocks --- .../cloud/user/AccountManagerImplTest.java | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java index 298e78c6d24d..6f5fbb0fdc12 100644 --- a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java +++ b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java @@ -26,8 +26,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Date; - import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.Role; @@ -75,13 +73,9 @@ import com.cloud.vm.UserVmVO; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.snapshot.VMSnapshotVO; -import com.cloud.projects.ProjectVO; public class AccountManagerImplTest extends AccountManagentImplTestBase { - @InjectMocks - private AccountManagerImpl accountManagerImpl; - @Mock private UserVmManagerImpl _vmMgr; @Mock @@ -120,16 +114,9 @@ public class AccountManagerImplTest extends AccountManagentImplTestBase { @Mock private ProjectAccountVO projectAccountVO; - @Mock private Project project; - @Mock - private ProjectAccountDao _projectAccountDao; - - @Mock - private ProjectDao _projectDao; - @Mock PasswordPolicyImpl passwordPolicyMock; @@ -1594,23 +1581,4 @@ public void testcheckCallerApiPermissionsForUserOperationsNotAllowedApis() { accountManagerImpl.checkCallerApiPermissionsForUserOrAccountOperations(accountMock); } - - @Test - public void testCheckIfAccountManagesOnlyDeletedProjectsDoesNotThrow() { - long accountId = 42L; - long projectId = 100L; - - Mockito.when(_projectAccountDao.listAdministratedProjectIds(accountId)) - .thenReturn(List.of(projectId)); - - ProjectVO deletedProject = Mockito.mock(ProjectVO.class); - Mockito.when(deletedProject.getRemoved()).thenReturn(new Date()); - - Mockito.when(_projectDao.findById(projectId)) - .thenReturn(deletedProject); - - accountManagerImpl.checkIfAccountManagesProjects(accountId); - - } - } From ac30ebd8916f6e45856b5c03b3f510a20bd74c0e Mon Sep 17 00:00:00 2001 From: Surya Srinivasan Date: Fri, 13 Feb 2026 17:13:14 +0530 Subject: [PATCH 6/8] test: add missing BeforeClass import --- server/src/test/java/com/cloud/user/AccountManagerImplTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java index 6f5fbb0fdc12..055bc5cf9d60 100644 --- a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java +++ b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java @@ -45,6 +45,7 @@ import org.apache.cloudstack.resourcedetail.UserDetailVO; import org.apache.cloudstack.webhook.WebhookHelper; import org.junit.Assert; +import org.junit.BeforeClass; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; From 8d229708435ed14b22a52d0dd087d665069c16cd Mon Sep 17 00:00:00 2001 From: Surya Srinivasan Date: Mon, 16 Feb 2026 23:57:45 +0530 Subject: [PATCH 7/8] test: add JUnit @After import for CallContext cleanup --- server/src/test/java/com/cloud/user/AccountManagerImplTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java index 055bc5cf9d60..9f611fd9e0ec 100644 --- a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java +++ b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java @@ -54,6 +54,7 @@ import org.mockito.MockedStatic; import org.mockito.Mockito; import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.junit.After; import com.cloud.acl.DomainChecker; import com.cloud.api.auth.SetupUserTwoFactorAuthenticationCmd; From e2085e1c9864585b41359e927737f96ca9e084f5 Mon Sep 17 00:00:00 2001 From: Surya Srinivasan Date: Wed, 25 Feb 2026 01:30:11 +0530 Subject: [PATCH 8/8] Fix AccountManagerImplTest to mock active project correctly --- .../test/java/com/cloud/user/AccountManagerImplTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java index 9f611fd9e0ec..dcaa95e2b360 100644 --- a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java +++ b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java @@ -45,7 +45,6 @@ import org.apache.cloudstack.resourcedetail.UserDetailVO; import org.apache.cloudstack.webhook.WebhookHelper; import org.junit.Assert; -import org.junit.BeforeClass; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -54,7 +53,6 @@ import org.mockito.MockedStatic; import org.mockito.Mockito; import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.junit.After; import com.cloud.acl.DomainChecker; import com.cloud.api.auth.SetupUserTwoFactorAuthenticationCmd; @@ -75,6 +73,7 @@ import com.cloud.vm.UserVmVO; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.snapshot.VMSnapshotVO; +import com.cloud.projects.ProjectVO; public class AccountManagerImplTest extends AccountManagentImplTestBase { @@ -1390,6 +1389,9 @@ public void checkIfAccountManagesProjectsTestThrowExceptionWhenTheAccountIsAProj List managedProjectIds = List.of(1L); Mockito.when(_projectAccountDao.listAdministratedProjectIds(accountId)).thenReturn(managedProjectIds); + ProjectVO project = Mockito.mock(ProjectVO.class); + Mockito.when(project.getRemoved()).thenReturn(null); + Mockito.when(_projectDao.findById(1L)).thenReturn(project); accountManagerImpl.checkIfAccountManagesProjects(accountId); }