From dab75e58d9b9cecef5eefa3a7397830f602b68e4 Mon Sep 17 00:00:00 2001 From: Phil Schneider Date: Tue, 27 Aug 2024 13:30:34 +0200 Subject: [PATCH 1/2] fix(technicalUser): adjust deletion process * adjust deletion for external technical user * adjust state of deleted users Refs: #949 #950 #809 --- .../ServiceAccountBusinessLogic.cs | 111 ++++++++++++------ .../Controllers/IdentityProviderController.cs | 1 + .../Controllers/ServiceAccountController.cs | 7 +- ...tionServiceAccountErrorMessageContainer.cs | 4 +- .../Models/OwnServiceAccountData.cs | 5 +- .../Repositories/IServiceAccountRepository.cs | 2 +- .../Repositories/ServiceAccountRepository.cs | 15 ++- .../ServiceAccountBusinessLogicTests.cs | 65 +++++----- .../ServiceAccountRespotitoryTests.cs | 6 +- 9 files changed, 137 insertions(+), 79 deletions(-) diff --git a/src/administration/Administration.Service/BusinessLogic/ServiceAccountBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/ServiceAccountBusinessLogic.cs index 9c88b2a324..c72b52234c 100644 --- a/src/administration/Administration.Service/BusinessLogic/ServiceAccountBusinessLogic.cs +++ b/src/administration/Administration.Service/BusinessLogic/ServiceAccountBusinessLogic.cs @@ -28,6 +28,7 @@ using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Entities; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Identities; using Org.Eclipse.TractusX.Portal.Backend.Processes.Library; @@ -98,8 +99,14 @@ public async Task DeleteOwnCompanyServiceAccountAsync(Guid serviceAccountId { var serviceAccountRepository = portalRepositories.GetInstance(); var companyId = _identityData.CompanyId; - var result = await serviceAccountRepository.GetOwnCompanyServiceAccountWithIamServiceAccountRolesAsync(serviceAccountId, companyId).ConfigureAwait(ConfigureAwaitOptions.None) - ?? throw ConflictException.Create(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_CONFLICT, [new("serviceAccountId", serviceAccountId.ToString()), new(CompanyId, companyId.ToString())]); + var technicalUserCreationSteps = new[] + { + ProcessStepTypeId.CREATE_DIM_TECHNICAL_USER, ProcessStepTypeId.RETRIGGER_CREATE_DIM_TECHNICAL_USER, + ProcessStepTypeId.AWAIT_CREATE_DIM_TECHNICAL_USER_RESPONSE, + ProcessStepTypeId.RETRIGGER_AWAIT_CREATE_DIM_TECHNICAL_USER_RESPONSE + }; + var result = await serviceAccountRepository.GetOwnCompanyServiceAccountWithIamServiceAccountRolesAsync(serviceAccountId, companyId, technicalUserCreationSteps).ConfigureAwait(ConfigureAwaitOptions.None) + ?? throw NotFoundException.Create(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_FOUND, [new("serviceAccountId", serviceAccountId.ToString()), new(CompanyId, companyId.ToString())]); if (result.StatusId is ConnectorStatusId.ACTIVE or ConnectorStatusId.PENDING) { @@ -112,41 +119,57 @@ public async Task DeleteOwnCompanyServiceAccountAsync(Guid serviceAccountId } // serviceAccount - if (result.IsDimServiceAccount) + var userStatus = UserStatusId.DELETED; + switch (result) { - var processId = result.ProcessId ?? throw ConflictException.Create(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_LINKED_TO_PROCESS, [new("serviceAccountId", serviceAccountId.ToString())]); - - var processData = await portalRepositories.GetInstance() - .GetProcessDataForServiceAccountDeletionCallback(processId, null) - .ConfigureAwait(ConfigureAwaitOptions.None); - - var context = processData.ProcessData.CreateManualProcessData(null, - portalRepositories, () => $"externalId {processId}"); + case { IsDimServiceAccount: true, DimServiceAccountWasCreated: true }: + userStatus = await CreateDeletionProcess(serviceAccountId, result).ConfigureAwait(ConfigureAwaitOptions.None); + break; + case { IsDimServiceAccount: true, DimServiceAccountWasCreated: false }: + HandleCreationProcessSkip(result); + break; + default: + if (!string.IsNullOrWhiteSpace(result.ClientClientId)) + { + await provisioningManager.DeleteCentralClientAsync(result.ClientClientId).ConfigureAwait(ConfigureAwaitOptions.None); + } - context.ProcessSteps.Where(step => step.ProcessStepTypeId != ProcessStepTypeId.DELETE_DIM_TECHNICAL_USER).IfAny(pending => - throw ConflictException.Create(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_PENDING_PROCESS_STEPS, [new("serviceAccountId", serviceAccountId.ToString()), new("processStepTypeIds", string.Join(",", pending))])); + break; + } - if (!context.ProcessSteps.Any(step => step.ProcessStepTypeId == ProcessStepTypeId.DELETE_DIM_TECHNICAL_USER)) + portalRepositories.GetInstance().AttachAndModifyIdentity( + serviceAccountId, + i => { - portalRepositories.GetInstance().AttachAndModifyIdentity(serviceAccountId, null, i => - { - i.UserStatusId = UserStatusId.PENDING_DELETION; - }); - context.ScheduleProcessSteps([ProcessStepTypeId.DELETE_DIM_TECHNICAL_USER]); - context.FinalizeProcessStep(); - } - } - else if (!string.IsNullOrWhiteSpace(result.ClientClientId)) - { - await provisioningManager.DeleteCentralClientAsync(result.ClientClientId).ConfigureAwait(ConfigureAwaitOptions.None); - portalRepositories.GetInstance().AttachAndModifyIdentity(serviceAccountId, null, i => + i.UserStatusId = UserStatusId.PENDING; + }, + i => { - i.UserStatusId = UserStatusId.DELETED; + i.UserStatusId = userStatus; }); - } - portalRepositories.GetInstance().DeleteCompanyUserAssignedRoles(result.UserRoleIds.Select(userRoleId => (serviceAccountId, userRoleId))); + ModifyConnectorForDeleteServiceAccount(serviceAccountId, result); + + return await portalRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None); + } + private void HandleCreationProcessSkip(OwnServiceAccountData result) + { + if (result.ProcessData != null) + { + portalRepositories.GetInstance().AttachAndModifyProcessSteps(result.ProcessData.ProcessStepIds.Select(x => new ValueTuple?, Action>( + x, + ps => ps.ProcessStepStatusId = ProcessStepStatusId.TODO, + ps => + { + ps.ProcessStepStatusId = ProcessStepStatusId.SKIPPED; + ps.Message = "Skipped due to service account deletion"; + }))); + } + } + + private void ModifyConnectorForDeleteServiceAccount(Guid serviceAccountId, OwnServiceAccountData result) + { if (result.ConnectorId != null) { portalRepositories.GetInstance().AttachAndModifyConnector(result.ConnectorId.Value, @@ -159,8 +182,28 @@ public async Task DeleteOwnCompanyServiceAccountAsync(Guid serviceAccountId connector.CompanyServiceAccountId = null; }); } + } - return await portalRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None); + private async Task CreateDeletionProcess(Guid serviceAccountId, OwnServiceAccountData result) + { + var processId = result.ProcessData?.ProcessId ?? throw ConflictException.Create(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_LINKED_TO_PROCESS, [new("serviceAccountId", serviceAccountId.ToString())]); + + var processData = await portalRepositories.GetInstance() + .GetProcessDataForServiceAccountDeletionCallback(processId, null) + .ConfigureAwait(ConfigureAwaitOptions.None); + + var context = processData.ProcessData.CreateManualProcessData(null, + portalRepositories, () => $"externalId {processId}"); + + context.ProcessSteps.Where(step => step.ProcessStepTypeId != ProcessStepTypeId.DELETE_DIM_TECHNICAL_USER).IfAny(pending => + throw ConflictException.Create(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_PENDING_PROCESS_STEPS, [new("serviceAccountId", serviceAccountId.ToString()), new("processStepTypeIds", string.Join(",", pending))])); + + if (context.ProcessSteps.Any(step => step.ProcessStepTypeId == ProcessStepTypeId.DELETE_DIM_TECHNICAL_USER)) + return UserStatusId.DELETED; + + context.ScheduleProcessSteps([ProcessStepTypeId.DELETE_DIM_TECHNICAL_USER]); + context.FinalizeProcessStep(); + return UserStatusId.PENDING_DELETION; } public async Task GetOwnCompanyServiceAccountDetailsAsync(Guid serviceAccountId) @@ -170,7 +213,7 @@ public async Task GetOwnCompanyServiceAccountD if (result == null) { - throw NotFoundException.Create(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_CONFLICT, [new("serviceAccountId", serviceAccountId.ToString()), new(CompanyId, companyId.ToString())]); + throw NotFoundException.Create(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_FOUND, [new("serviceAccountId", serviceAccountId.ToString()), new(CompanyId, companyId.ToString())]); } IamClientAuthMethod? iamClientAuthMethod; @@ -220,7 +263,7 @@ public async Task ResetOwnCompanyServiceAccountSecretAsyn var result = await portalRepositories.GetInstance().GetOwnCompanyServiceAccountDetailedDataUntrackedAsync(serviceAccountId, companyId); if (result == null) { - throw ConflictException.Create(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_CONFLICT, [new("serviceAccountId", serviceAccountId.ToString()), new(CompanyId, companyId.ToString())]); + throw ConflictException.Create(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_FOUND, [new("serviceAccountId", serviceAccountId.ToString()), new(CompanyId, companyId.ToString())]); } if (result.ClientClientId == null) @@ -259,7 +302,7 @@ public async Task UpdateOwnCompanyServiceAccountDetailsAs var result = await serviceAccountRepository.GetOwnCompanyServiceAccountWithIamClientIdAsync(serviceAccountId, companyId).ConfigureAwait(ConfigureAwaitOptions.None); if (result == null) { - throw ConflictException.Create(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_CONFLICT, [new("serviceAccountId", serviceAccountId.ToString()), new(CompanyId, companyId.ToString())]); + throw ConflictException.Create(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_FOUND, [new("serviceAccountId", serviceAccountId.ToString()), new(CompanyId, companyId.ToString())]); } if (result.UserStatusId == UserStatusId.INACTIVE) @@ -398,7 +441,7 @@ public async Task HandleServiceAccountDeletionCallback(Guid processId) null, i => { - i.UserStatusId = UserStatusId.INACTIVE; + i.UserStatusId = UserStatusId.DELETED; }); context.FinalizeProcessStep(); diff --git a/src/administration/Administration.Service/Controllers/IdentityProviderController.cs b/src/administration/Administration.Service/Controllers/IdentityProviderController.cs index 57f3f3a713..8adb9f2d00 100644 --- a/src/administration/Administration.Service/Controllers/IdentityProviderController.cs +++ b/src/administration/Administration.Service/Controllers/IdentityProviderController.cs @@ -48,6 +48,7 @@ public IdentityProviderController(IIdentityProviderBusinessLogic identityProvide } /// + /// /// Gets the details of the own company identity provider /// /// Returns the details of the own company identity provider diff --git a/src/administration/Administration.Service/Controllers/ServiceAccountController.cs b/src/administration/Administration.Service/Controllers/ServiceAccountController.cs index 90640c362e..de85dbfe16 100644 --- a/src/administration/Administration.Service/Controllers/ServiceAccountController.cs +++ b/src/administration/Administration.Service/Controllers/ServiceAccountController.cs @@ -84,8 +84,11 @@ public Task> ExecuteCompanyUserCreation([From [ProducesResponseType(typeof(int), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status409Conflict)] - public Task DeleteServiceAccount([FromRoute] Guid serviceAccountId) => - _logic.DeleteOwnCompanyServiceAccountAsync(serviceAccountId); + public async Task DeleteServiceAccount([FromRoute] Guid serviceAccountId) + { + await _logic.DeleteOwnCompanyServiceAccountAsync(serviceAccountId).ConfigureAwait(ConfigureAwaitOptions.None); + return NoContent(); + } /// /// Gets the service account details for the given id diff --git a/src/administration/Administration.Service/ErrorHandling/AdministrationServiceAccountErrorMessageContainer.cs b/src/administration/Administration.Service/ErrorHandling/AdministrationServiceAccountErrorMessageContainer.cs index 1b5c4d5b66..326d39f2ad 100644 --- a/src/administration/Administration.Service/ErrorHandling/AdministrationServiceAccountErrorMessageContainer.cs +++ b/src/administration/Administration.Service/ErrorHandling/AdministrationServiceAccountErrorMessageContainer.cs @@ -30,7 +30,7 @@ public class AdministrationServiceAccountErrorMessageContainer : IErrorMessageCo { AdministrationServiceAccountErrors.SERVICE_COMPANY_NOT_EXIST_CONFLICT, "company {companyId} does not exist"}, { AdministrationServiceAccountErrors.SERVICE_BPN_NOT_SET_CONFLICT, "bpn not set for company {companyId}"}, { AdministrationServiceAccountErrors.SERVICE_ROLES_NOT_ASSIGN_ARGUMENT, "The roles {unassignable} are not assignable to a service account, {userRoleIds}"}, - { AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_CONFLICT, "serviceAccount {serviceAccountId} not found for company {companyId}"}, + { AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_FOUND, "serviceAccount {serviceAccountId} not found for company {companyId}"}, { AdministrationServiceAccountErrors.SERVICE_USERID_ACTIVATION_PENDING_CONFLICT, "Technical User is linked to an active connector. Change the link or deactivate the connector to delete the technical user."}, { AdministrationServiceAccountErrors.SERVICE_USERID_ACTIVATION_ACTIVE_CONFLICT, "Technical User is linked to an active subscription. Deactivate the subscription to delete the technical user."}, { AdministrationServiceAccountErrors.SERVICE_UNDEFINED_CLIENTID_CONFLICT, "undefined clientId for serviceAccount {serviceAccountId}"}, @@ -52,7 +52,7 @@ public enum AdministrationServiceAccountErrors SERVICE_COMPANY_NOT_EXIST_CONFLICT, SERVICE_BPN_NOT_SET_CONFLICT, SERVICE_ROLES_NOT_ASSIGN_ARGUMENT, - SERVICE_ACCOUNT_NOT_CONFLICT, + SERVICE_ACCOUNT_NOT_FOUND, SERVICE_USERID_ACTIVATION_PENDING_CONFLICT, SERVICE_USERID_ACTIVATION_ACTIVE_CONFLICT, SERVICE_UNDEFINED_CLIENTID_CONFLICT, diff --git a/src/portalbackend/PortalBackend.DBAccess/Models/OwnServiceAccountData.cs b/src/portalbackend/PortalBackend.DBAccess/Models/OwnServiceAccountData.cs index 2551ccd7f3..cf185a05de 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Models/OwnServiceAccountData.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Models/OwnServiceAccountData.cs @@ -30,5 +30,8 @@ public record OwnServiceAccountData( ConnectorStatusId? StatusId, OfferSubscriptionStatusId? OfferStatusId, bool IsDimServiceAccount, - Guid? ProcessId + bool DimServiceAccountWasCreated, + ProcessData? ProcessData ); + +public record ProcessData(Guid ProcessId, IEnumerable ProcessStepIds); diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/IServiceAccountRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/IServiceAccountRepository.cs index a059863f68..8cb40a1ee8 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/IServiceAccountRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/IServiceAccountRepository.cs @@ -36,7 +36,7 @@ CompanyServiceAccount CreateCompanyServiceAccount(Guid identityId, void AttachAndModifyCompanyServiceAccount(Guid id, Guid version, Action? initialize, Action modify); Task GetOwnCompanyServiceAccountWithIamClientIdAsync(Guid serviceAccountId, Guid userCompanyId); - Task GetOwnCompanyServiceAccountWithIamServiceAccountRolesAsync(Guid serviceAccountId, Guid companyId); + Task GetOwnCompanyServiceAccountWithIamServiceAccountRolesAsync(Guid serviceAccountId, Guid companyId, IEnumerable processStepsToFilter); Task GetOwnCompanyServiceAccountDetailedDataUntrackedAsync(Guid serviceAccountId, Guid companyId); Func?>> GetOwnCompanyServiceAccountsUntracked(Guid userCompanyId, string? clientId, bool? isOwner, IEnumerable userStatusIds); Task CheckActiveServiceAccountExistsForCompanyAsync(Guid technicalUserId, Guid companyId); diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/ServiceAccountRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/ServiceAccountRepository.cs index 00bd547861..ef5c487626 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/ServiceAccountRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/ServiceAccountRepository.cs @@ -94,11 +94,11 @@ public void AttachAndModifyCompanyServiceAccount( userRole.UserRoleText)))) .SingleOrDefaultAsync(); - public Task GetOwnCompanyServiceAccountWithIamServiceAccountRolesAsync(Guid serviceAccountId, Guid companyId) => + public Task GetOwnCompanyServiceAccountWithIamServiceAccountRolesAsync(Guid serviceAccountId, Guid companyId, IEnumerable processStepsToFilter) => portalDbContext.CompanyServiceAccounts .Where(serviceAccount => serviceAccount.Id == serviceAccountId && - serviceAccount.Identity!.UserStatusId == UserStatusId.ACTIVE && + (serviceAccount.Identity!.UserStatusId == UserStatusId.ACTIVE || serviceAccount.Identity!.UserStatusId == UserStatusId.PENDING) && (serviceAccount.CompaniesLinkedServiceAccount!.Owners == companyId || serviceAccount.CompaniesLinkedServiceAccount!.Provider == companyId)) .Select(sa => new OwnServiceAccountData( sa.Identity!.IdentityAssignedRoles.Select(r => r.UserRoleId), @@ -108,8 +108,15 @@ public void AttachAndModifyCompanyServiceAccount( sa.ClientClientId, sa.Connector!.StatusId, sa.OfferSubscription!.OfferSubscriptionStatusId, - sa.DimCompanyServiceAccount != null, - sa.DimUserCreationData!.ProcessId)) + sa.CompanyServiceAccountKindId == CompanyServiceAccountKindId.EXTERNAL, + sa.DimUserCreationData != null, + sa.DimUserCreationData == null ? null : new ProcessData( + sa.DimUserCreationData.ProcessId, + sa.DimUserCreationData!.Process!.ProcessSteps + .Where(ps => + ps.ProcessStepStatusId == ProcessStepStatusId.TODO && + processStepsToFilter.Contains(ps.ProcessStepTypeId)) + .Select(x => x.Id)))) .SingleOrDefaultAsync(); public Task GetOwnCompanyServiceAccountDetailedDataUntrackedAsync(Guid serviceAccountId, Guid companyId) => diff --git a/tests/administration/Administration.Service.Tests/BusinessLogic/ServiceAccountBusinessLogicTests.cs b/tests/administration/Administration.Service.Tests/BusinessLogic/ServiceAccountBusinessLogicTests.cs index b882b18f98..5d440391cc 100644 --- a/tests/administration/Administration.Service.Tests/BusinessLogic/ServiceAccountBusinessLogicTests.cs +++ b/tests/administration/Administration.Service.Tests/BusinessLogic/ServiceAccountBusinessLogicTests.cs @@ -248,7 +248,7 @@ public async Task GetOwnCompanyServiceAccountDetailsAsync_WithInvalidCompany_Not // Assert var exception = await Assert.ThrowsAsync(Act); - exception.Message.Should().Be(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_CONFLICT.ToString()); + exception.Message.Should().Be(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_FOUND.ToString()); } [Fact] @@ -264,7 +264,7 @@ public async Task GetOwnCompanyServiceAccountDetailsAsync_WithInvalidServiceAcco // Assert var exception = await Assert.ThrowsAsync(Act); - exception.Message.Should().Be(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_CONFLICT.ToString()); + exception.Message.Should().Be(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_FOUND.ToString()); } #endregion @@ -300,7 +300,7 @@ public async Task ResetOwnCompanyServiceAccountSecretAsync_WithInvalidUser_NotFo // Assert var exception = await Assert.ThrowsAsync(Act); - exception.Message.Should().Be(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_CONFLICT.ToString()); + exception.Message.Should().Be(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_FOUND.ToString()); } [Fact] @@ -316,7 +316,7 @@ public async Task ResetOwnCompanyServiceAccountSecretAsync_WithInvalidServiceAcc // Assert var exception = await Assert.ThrowsAsync(Act); - exception.Message.Should().Be(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_CONFLICT.ToString()); + exception.Message.Should().Be(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_FOUND.ToString()); } #endregion @@ -411,7 +411,7 @@ public async Task UpdateOwnCompanyServiceAccountDetailsAsync_WithNotExistingServ // Assert var exception = await Assert.ThrowsAsync(Act); - exception.Message.Should().Be(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_CONFLICT.ToString()); + exception.Message.Should().Be(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_FOUND.ToString()); } [Fact] @@ -513,8 +513,8 @@ public async Task DeleteOwnCompanyServiceAccountAsync_WithNotExistingServiceAcco async Task Act() => await sut.DeleteOwnCompanyServiceAccountAsync(serviceAccountId); // Assert - var ex = await Assert.ThrowsAsync(Act); - ex.Message.Should().Be(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_CONFLICT.ToString()); + var ex = await Assert.ThrowsAsync(Act); + ex.Message.Should().Be(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_FOUND.ToString()); } [Fact] @@ -550,13 +550,14 @@ public async Task DeleteOwnCompanyServiceAccountAsync_WithInvalidConnectorStatus } [Theory] - [InlineData(true, false, false)] - [InlineData(false, false, true)] - [InlineData(false, true, true)] - [InlineData(true, false, true)] - [InlineData(false, false, false)] - [InlineData(false, true, false)] - public async Task DeleteOwnCompanyServiceAccountAsync_WithoutClient_CallsExpected(bool withClient, bool isDimServiceAccount, bool withServiceAccount) + [InlineData(true, false, false, false)] + [InlineData(false, false, true, false)] + [InlineData(false, true, true, false)] + [InlineData(true, false, true, false)] + [InlineData(false, false, false, false)] + [InlineData(false, true, false, false)] + [InlineData(false, true, false, true)] + public async Task DeleteOwnCompanyServiceAccountAsync_WithoutClient_CallsExpected(bool withClient, bool isDimServiceAccount, bool withServiceAccount, bool withCreationStepsInTodo) { // Arrange var identity = _fixture.Build() @@ -567,7 +568,7 @@ public async Task DeleteOwnCompanyServiceAccountAsync_WithoutClient_CallsExpecte .With(x => x.CompanyServiceAccountId, ValidServiceAccountId) .Create(); var processId = Guid.NewGuid(); - SetupDeleteOwnCompanyServiceAccount(withServiceAccount, isDimServiceAccount, withClient, connector, identity, processId); + SetupDeleteOwnCompanyServiceAccount(withServiceAccount, isDimServiceAccount, withClient, connector, identity, processId, withCreationStepsInTodo); var sut = new ServiceAccountBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService); // Act @@ -576,11 +577,11 @@ public async Task DeleteOwnCompanyServiceAccountAsync_WithoutClient_CallsExpecte // Assert if (isDimServiceAccount) { - A.CallTo(() => _processStepRepository.CreateProcessStepRange(A>.That.Matches(x => x.Count() == 1 && x.First().ProcessStepTypeId == ProcessStepTypeId.DELETE_DIM_TECHNICAL_USER && x.First().ProcessStepStatusId == ProcessStepStatusId.TODO && x.First().ProcessId == processId))).MustHaveHappenedOnceExactly(); - A.CallTo(() => _processStepRepository.AttachAndModifyProcessSteps(A?, Action)>>._)).MustNotHaveHappened(); + A.CallTo(() => _processStepRepository.CreateProcessStepRange(A>.That.Matches(x => x.Count() == 1 && x.First().ProcessStepTypeId == ProcessStepTypeId.DELETE_DIM_TECHNICAL_USER && x.First().ProcessStepStatusId == ProcessStepStatusId.TODO && x.First().ProcessId == processId))).MustHaveHappened(withCreationStepsInTodo ? 0 : 1, Times.Exactly); + A.CallTo(() => _processStepRepository.AttachAndModifyProcessSteps(A?, Action)>>._)).MustHaveHappened(withCreationStepsInTodo ? 1 : 0, Times.Exactly); A.CallTo(() => _userRepository.AttachAndModifyIdentity(A._, A>._, A>._)).MustHaveHappenedOnceExactly(); A.CallTo(() => _provisioningManager.DeleteCentralClientAsync(A._)).MustNotHaveHappened(); - identity.UserStatusId.Should().Be(UserStatusId.PENDING_DELETION); + identity.UserStatusId.Should().Be(withCreationStepsInTodo ? UserStatusId.DELETED : UserStatusId.PENDING_DELETION); } else if (withClient) { @@ -797,29 +798,29 @@ private void SetupGetOwnCompanyServiceAccount() .Returns(null); } - private void SetupDeleteOwnCompanyServiceAccount(bool withServiceAccount, bool isDimServiceAccount, bool withClient, Connector? connector = null, Identity? identity = null, Guid? processId = null) + private void SetupDeleteOwnCompanyServiceAccount(bool withServiceAccount, bool isDimServiceAccount, bool withClient, Connector? connector = null, Identity? identity = null, Guid? processId = null, bool withCreateStepsInTodo = false) { var serviceAccount = new CompanyServiceAccount(Guid.NewGuid(), Guid.NewGuid(), "test-sa", "test", CompanyServiceAccountTypeId.OWN, CompanyServiceAccountKindId.INTERNAL); - A.CallTo(() => _serviceAccountRepository.GetOwnCompanyServiceAccountWithIamServiceAccountRolesAsync(ValidServiceAccountId, ValidCompanyId)) - .Returns(new OwnServiceAccountData(_userRoleIds, serviceAccount.Id, serviceAccount.Version, withServiceAccount ? ValidConnectorId : null, withClient ? ClientId : null, ConnectorStatusId.INACTIVE, OfferSubscriptionStatusId.PENDING, isDimServiceAccount, processId)); - A.CallTo(() => _serviceAccountRepository.GetOwnCompanyServiceAccountWithIamServiceAccountRolesAsync(A.That.Not.Matches(x => x == ValidServiceAccountId), A._)) + A.CallTo(() => _serviceAccountRepository.GetOwnCompanyServiceAccountWithIamServiceAccountRolesAsync(ValidServiceAccountId, ValidCompanyId, A>._)) + .Returns(new OwnServiceAccountData(_userRoleIds, serviceAccount.Id, serviceAccount.Version, withServiceAccount ? ValidConnectorId : null, withClient ? ClientId : null, ConnectorStatusId.INACTIVE, OfferSubscriptionStatusId.PENDING, isDimServiceAccount, !withCreateStepsInTodo, processId == null ? null : new ProcessData(processId.Value, withCreateStepsInTodo ? Enumerable.Repeat(Guid.NewGuid(), 1) : Enumerable.Empty()))); + A.CallTo(() => _serviceAccountRepository.GetOwnCompanyServiceAccountWithIamServiceAccountRolesAsync(A.That.Not.Matches(x => x == ValidServiceAccountId), A._, A>._)) .Returns(null); if (isDimServiceAccount) { A.CallTo(() => _processStepRepository.GetProcessDataForServiceAccountDeletionCallback(A._, A>._)) - .ReturnsLazily((Guid processId, IEnumerable? processStepTypeIds) => + .ReturnsLazily((Guid id, IEnumerable? processStepTypeIds) => ( ProcessTypeId.OFFER_SUBSCRIPTION, new VerifyProcessData( - new Process(processId, ProcessTypeId.OFFER_SUBSCRIPTION, Guid.NewGuid()), - processStepTypeIds?.Select(stepTypeId => new ProcessStep(Guid.NewGuid(), stepTypeId, ProcessStepStatusId.TODO, processId, _fixture.Create())) ?? Enumerable.Empty()), + new Process(id, ProcessTypeId.OFFER_SUBSCRIPTION, Guid.NewGuid()), + processStepTypeIds?.Select(stepTypeId => new ProcessStep(Guid.NewGuid(), stepTypeId, ProcessStepStatusId.TODO, id, _fixture.Create())) ?? Enumerable.Empty()), Guid.NewGuid())); } if (identity != null) { - A.CallTo(() => _userRepository.AttachAndModifyIdentity(ValidServiceAccountId, null, A>._)) + A.CallTo(() => _userRepository.AttachAndModifyIdentity(ValidServiceAccountId, A>._, A>._)) .Invokes((Guid _, Action? _, Action modify) => { modify.Invoke(identity); @@ -846,9 +847,9 @@ private void SetupDeleteOwnCompanyServiceAccount(bool withServiceAccount, bool i private void SetupDeleteOwnCompanyServiceAccountForInvalidConnectorStatus() { var serviceAccount = new CompanyServiceAccount(Guid.NewGuid(), Guid.NewGuid(), "test-sa", "test", CompanyServiceAccountTypeId.OWN, CompanyServiceAccountKindId.INTERNAL); - A.CallTo(() => _serviceAccountRepository.GetOwnCompanyServiceAccountWithIamServiceAccountRolesAsync(ValidServiceAccountId, ValidCompanyId)) - .Returns(new OwnServiceAccountData(_userRoleIds, serviceAccount.Id, serviceAccount.Version, null, null, ConnectorStatusId.ACTIVE, OfferSubscriptionStatusId.PENDING, false, null)); - A.CallTo(() => _serviceAccountRepository.GetOwnCompanyServiceAccountWithIamServiceAccountRolesAsync(A.That.Not.Matches(x => x == ValidServiceAccountId), A._)) + A.CallTo(() => _serviceAccountRepository.GetOwnCompanyServiceAccountWithIamServiceAccountRolesAsync(ValidServiceAccountId, ValidCompanyId, A>._)) + .Returns(new OwnServiceAccountData(_userRoleIds, serviceAccount.Id, serviceAccount.Version, null, null, ConnectorStatusId.ACTIVE, OfferSubscriptionStatusId.PENDING, false, false, null)); + A.CallTo(() => _serviceAccountRepository.GetOwnCompanyServiceAccountWithIamServiceAccountRolesAsync(A.That.Not.Matches(x => x == ValidServiceAccountId), A._, A>._)) .Returns(null); A.CallTo(() => _portalRepositories.GetInstance()).Returns(_serviceAccountRepository); @@ -858,9 +859,9 @@ private void SetupDeleteOwnCompanyServiceAccountForInvalidConnectorStatus() private void SetupDeleteOwnCompanyServiceAccountForValidOfferSubscription() { var serviceAccount = new CompanyServiceAccount(Guid.NewGuid(), Guid.NewGuid(), "test-sa", "test", CompanyServiceAccountTypeId.OWN, CompanyServiceAccountKindId.INTERNAL); - A.CallTo(() => _serviceAccountRepository.GetOwnCompanyServiceAccountWithIamServiceAccountRolesAsync(ValidServiceAccountId, ValidCompanyId)) - .Returns(new OwnServiceAccountData(_userRoleIds, serviceAccount.Id, serviceAccount.Version, null, null, ConnectorStatusId.INACTIVE, OfferSubscriptionStatusId.ACTIVE, false, null)); - A.CallTo(() => _serviceAccountRepository.GetOwnCompanyServiceAccountWithIamServiceAccountRolesAsync(A.That.Not.Matches(x => x == ValidServiceAccountId), A._)) + A.CallTo(() => _serviceAccountRepository.GetOwnCompanyServiceAccountWithIamServiceAccountRolesAsync(ValidServiceAccountId, ValidCompanyId, A>._)) + .Returns(new OwnServiceAccountData(_userRoleIds, serviceAccount.Id, serviceAccount.Version, null, null, ConnectorStatusId.INACTIVE, OfferSubscriptionStatusId.ACTIVE, false, false, null)); + A.CallTo(() => _serviceAccountRepository.GetOwnCompanyServiceAccountWithIamServiceAccountRolesAsync(A.That.Not.Matches(x => x == ValidServiceAccountId), A._, A>._)) .Returns(null); A.CallTo(() => _portalRepositories.GetInstance()).Returns(_serviceAccountRepository); diff --git a/tests/portalbackend/PortalBackend.DBAccess.Tests/ServiceAccountRespotitoryTests.cs b/tests/portalbackend/PortalBackend.DBAccess.Tests/ServiceAccountRespotitoryTests.cs index 9f56c0f2f1..491439e487 100644 --- a/tests/portalbackend/PortalBackend.DBAccess.Tests/ServiceAccountRespotitoryTests.cs +++ b/tests/portalbackend/PortalBackend.DBAccess.Tests/ServiceAccountRespotitoryTests.cs @@ -122,7 +122,7 @@ public async Task GetOwnCompanyServiceAccountWithIamServiceAccountRolesAsync_Ret var (sut, _) = await CreateSut(); // Act - var result = await sut.GetOwnCompanyServiceAccountWithIamServiceAccountRolesAsync(_validServiceAccountId, _validCompanyId); + var result = await sut.GetOwnCompanyServiceAccountWithIamServiceAccountRolesAsync(_validServiceAccountId, _validCompanyId, Enumerable.Empty()); // Assert result.Should().NotBe(default); @@ -136,7 +136,7 @@ public async Task GetOwnCompanyServiceAccountWithIamServiceAccountRolesAsync_Wit var (sut, _) = await CreateSut(); // Act - var result = await sut.GetOwnCompanyServiceAccountWithIamServiceAccountRolesAsync(Guid.NewGuid(), _validCompanyId); + var result = await sut.GetOwnCompanyServiceAccountWithIamServiceAccountRolesAsync(Guid.NewGuid(), _validCompanyId, Enumerable.Empty()); // Assert result.Should().Be(default); @@ -150,7 +150,7 @@ public async Task GetOwnCompanyServiceAccountWithIamServiceAccountRolesAsync_Wit Guid companyServiceAccountId = new("93eecd4e-ca47-4dd2-85bf-775ea72eb000"); Guid companyId = new("41fd2ab8-71cd-4546-9bef-a388d91b2542"); // Act - var result = await sut.GetOwnCompanyServiceAccountWithIamServiceAccountRolesAsync(companyServiceAccountId, companyId); + var result = await sut.GetOwnCompanyServiceAccountWithIamServiceAccountRolesAsync(companyServiceAccountId, companyId, Enumerable.Empty()); // Assert result.Should().NotBeNull(); } From f1421935a115f52f5790efe07a9b0c12932b2beb Mon Sep 17 00:00:00 2001 From: Phil Schneider Date: Thu, 29 Aug 2024 10:07:07 +0200 Subject: [PATCH 2/2] feat(sa): adjust delete technical user logic Refs: #950 --- .../ServiceAccountBusinessLogic.cs | 24 ++-------- ...tionServiceAccountErrorMessageContainer.cs | 6 ++- .../Models/OwnServiceAccountData.cs | 4 +- .../Repositories/ServiceAccountRepository.cs | 13 ++--- .../ServiceAccountBusinessLogicTests.cs | 47 ++++++++++++++----- 5 files changed, 49 insertions(+), 45 deletions(-) diff --git a/src/administration/Administration.Service/BusinessLogic/ServiceAccountBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/ServiceAccountBusinessLogic.cs index c72b52234c..971667785d 100644 --- a/src/administration/Administration.Service/BusinessLogic/ServiceAccountBusinessLogic.cs +++ b/src/administration/Administration.Service/BusinessLogic/ServiceAccountBusinessLogic.cs @@ -122,12 +122,11 @@ public async Task DeleteOwnCompanyServiceAccountAsync(Guid serviceAccountId var userStatus = UserStatusId.DELETED; switch (result) { - case { IsDimServiceAccount: true, DimServiceAccountWasCreated: true }: + case { IsDimServiceAccount: true, CreationProcessInProgress: false }: userStatus = await CreateDeletionProcess(serviceAccountId, result).ConfigureAwait(ConfigureAwaitOptions.None); break; - case { IsDimServiceAccount: true, DimServiceAccountWasCreated: false }: - HandleCreationProcessSkip(result); - break; + case { IsDimServiceAccount: true, CreationProcessInProgress: true }: + throw ConflictException.Create(AdministrationServiceAccountErrors.TECHNICAL_USER_CREATION_IN_PROGRESS); default: if (!string.IsNullOrWhiteSpace(result.ClientClientId)) { @@ -153,21 +152,6 @@ public async Task DeleteOwnCompanyServiceAccountAsync(Guid serviceAccountId return await portalRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None); } - private void HandleCreationProcessSkip(OwnServiceAccountData result) - { - if (result.ProcessData != null) - { - portalRepositories.GetInstance().AttachAndModifyProcessSteps(result.ProcessData.ProcessStepIds.Select(x => new ValueTuple?, Action>( - x, - ps => ps.ProcessStepStatusId = ProcessStepStatusId.TODO, - ps => - { - ps.ProcessStepStatusId = ProcessStepStatusId.SKIPPED; - ps.Message = "Skipped due to service account deletion"; - }))); - } - } - private void ModifyConnectorForDeleteServiceAccount(Guid serviceAccountId, OwnServiceAccountData result) { if (result.ConnectorId != null) @@ -186,7 +170,7 @@ private void ModifyConnectorForDeleteServiceAccount(Guid serviceAccountId, OwnSe private async Task CreateDeletionProcess(Guid serviceAccountId, OwnServiceAccountData result) { - var processId = result.ProcessData?.ProcessId ?? throw ConflictException.Create(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_LINKED_TO_PROCESS, [new("serviceAccountId", serviceAccountId.ToString())]); + var processId = result.ProcessId ?? throw ConflictException.Create(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_LINKED_TO_PROCESS, [new("serviceAccountId", serviceAccountId.ToString())]); var processData = await portalRepositories.GetInstance() .GetProcessDataForServiceAccountDeletionCallback(processId, null) diff --git a/src/administration/Administration.Service/ErrorHandling/AdministrationServiceAccountErrorMessageContainer.cs b/src/administration/Administration.Service/ErrorHandling/AdministrationServiceAccountErrorMessageContainer.cs index 326d39f2ad..6983f07645 100644 --- a/src/administration/Administration.Service/ErrorHandling/AdministrationServiceAccountErrorMessageContainer.cs +++ b/src/administration/Administration.Service/ErrorHandling/AdministrationServiceAccountErrorMessageContainer.cs @@ -38,7 +38,8 @@ public class AdministrationServiceAccountErrorMessageContainer : IErrorMessageCo { AdministrationServiceAccountErrors.SERVICE_INACTIVE_CONFLICT, "serviceAccount {serviceAccountId} is already INACTIVE"}, { AdministrationServiceAccountErrors.SERVICE_CLIENTID_NOT_NULL_CONFLICT, "clientClientId of serviceAccount {serviceAccountId} should not be null"}, { AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_LINKED_TO_PROCESS, "Service Account {serviceAccountId} is not linked to a process" }, - { AdministrationServiceAccountErrors.SERVICE_ACCOUNT_PENDING_PROCESS_STEPS, "Service Account {serviceAccountId} has pending process steps {processStepTypeIds}"} + { AdministrationServiceAccountErrors.SERVICE_ACCOUNT_PENDING_PROCESS_STEPS, "Service Account {serviceAccountId} has pending process steps {processStepTypeIds}"}, + { AdministrationServiceAccountErrors.TECHNICAL_USER_CREATION_IN_PROGRESS, "Technical user can't be deleted because the creation progress is still running"} }.ToImmutableDictionary(x => (int)x.Key, x => x.Value); public Type Type { get => typeof(AdministrationServiceAccountErrors); } @@ -60,5 +61,6 @@ public enum AdministrationServiceAccountErrors SERVICE_INACTIVE_CONFLICT, SERVICE_CLIENTID_NOT_NULL_CONFLICT, SERVICE_ACCOUNT_NOT_LINKED_TO_PROCESS, - SERVICE_ACCOUNT_PENDING_PROCESS_STEPS + SERVICE_ACCOUNT_PENDING_PROCESS_STEPS, + TECHNICAL_USER_CREATION_IN_PROGRESS } diff --git a/src/portalbackend/PortalBackend.DBAccess/Models/OwnServiceAccountData.cs b/src/portalbackend/PortalBackend.DBAccess/Models/OwnServiceAccountData.cs index cf185a05de..99c59d7936 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Models/OwnServiceAccountData.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Models/OwnServiceAccountData.cs @@ -30,8 +30,8 @@ public record OwnServiceAccountData( ConnectorStatusId? StatusId, OfferSubscriptionStatusId? OfferStatusId, bool IsDimServiceAccount, - bool DimServiceAccountWasCreated, - ProcessData? ProcessData + bool CreationProcessInProgress, + Guid? ProcessId ); public record ProcessData(Guid ProcessId, IEnumerable ProcessStepIds); diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/ServiceAccountRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/ServiceAccountRepository.cs index ef5c487626..c0ef79fc70 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/ServiceAccountRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/ServiceAccountRepository.cs @@ -98,7 +98,7 @@ public void AttachAndModifyCompanyServiceAccount( portalDbContext.CompanyServiceAccounts .Where(serviceAccount => serviceAccount.Id == serviceAccountId && - (serviceAccount.Identity!.UserStatusId == UserStatusId.ACTIVE || serviceAccount.Identity!.UserStatusId == UserStatusId.PENDING) && + serviceAccount.Identity!.UserStatusId == UserStatusId.ACTIVE && (serviceAccount.CompaniesLinkedServiceAccount!.Owners == companyId || serviceAccount.CompaniesLinkedServiceAccount!.Provider == companyId)) .Select(sa => new OwnServiceAccountData( sa.Identity!.IdentityAssignedRoles.Select(r => r.UserRoleId), @@ -109,14 +109,11 @@ public void AttachAndModifyCompanyServiceAccount( sa.Connector!.StatusId, sa.OfferSubscription!.OfferSubscriptionStatusId, sa.CompanyServiceAccountKindId == CompanyServiceAccountKindId.EXTERNAL, - sa.DimUserCreationData != null, - sa.DimUserCreationData == null ? null : new ProcessData( - sa.DimUserCreationData.ProcessId, - sa.DimUserCreationData!.Process!.ProcessSteps - .Where(ps => + sa.DimUserCreationData!.Process!.ProcessSteps + .Any(ps => ps.ProcessStepStatusId == ProcessStepStatusId.TODO && - processStepsToFilter.Contains(ps.ProcessStepTypeId)) - .Select(x => x.Id)))) + processStepsToFilter.Contains(ps.ProcessStepTypeId)), + sa.DimUserCreationData == null ? null : sa.DimUserCreationData!.ProcessId)) .SingleOrDefaultAsync(); public Task GetOwnCompanyServiceAccountDetailedDataUntrackedAsync(Guid serviceAccountId, Guid companyId) => diff --git a/tests/administration/Administration.Service.Tests/BusinessLogic/ServiceAccountBusinessLogicTests.cs b/tests/administration/Administration.Service.Tests/BusinessLogic/ServiceAccountBusinessLogicTests.cs index 5d440391cc..e1f768ef04 100644 --- a/tests/administration/Administration.Service.Tests/BusinessLogic/ServiceAccountBusinessLogicTests.cs +++ b/tests/administration/Administration.Service.Tests/BusinessLogic/ServiceAccountBusinessLogicTests.cs @@ -550,14 +550,13 @@ public async Task DeleteOwnCompanyServiceAccountAsync_WithInvalidConnectorStatus } [Theory] - [InlineData(true, false, false, false)] - [InlineData(false, false, true, false)] - [InlineData(false, true, true, false)] - [InlineData(true, false, true, false)] - [InlineData(false, false, false, false)] - [InlineData(false, true, false, false)] - [InlineData(false, true, false, true)] - public async Task DeleteOwnCompanyServiceAccountAsync_WithoutClient_CallsExpected(bool withClient, bool isDimServiceAccount, bool withServiceAccount, bool withCreationStepsInTodo) + [InlineData(true, false, false)] + [InlineData(false, false, true)] + [InlineData(false, true, true)] + [InlineData(true, false, true)] + [InlineData(false, false, false)] + [InlineData(false, true, false)] + public async Task DeleteOwnCompanyServiceAccountAsync_WithoutClient_CallsExpected(bool withClient, bool isDimServiceAccount, bool withServiceAccount) { // Arrange var identity = _fixture.Build() @@ -568,7 +567,7 @@ public async Task DeleteOwnCompanyServiceAccountAsync_WithoutClient_CallsExpecte .With(x => x.CompanyServiceAccountId, ValidServiceAccountId) .Create(); var processId = Guid.NewGuid(); - SetupDeleteOwnCompanyServiceAccount(withServiceAccount, isDimServiceAccount, withClient, connector, identity, processId, withCreationStepsInTodo); + SetupDeleteOwnCompanyServiceAccount(withServiceAccount, isDimServiceAccount, withClient, connector, identity, processId); var sut = new ServiceAccountBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService); // Act @@ -577,11 +576,10 @@ public async Task DeleteOwnCompanyServiceAccountAsync_WithoutClient_CallsExpecte // Assert if (isDimServiceAccount) { - A.CallTo(() => _processStepRepository.CreateProcessStepRange(A>.That.Matches(x => x.Count() == 1 && x.First().ProcessStepTypeId == ProcessStepTypeId.DELETE_DIM_TECHNICAL_USER && x.First().ProcessStepStatusId == ProcessStepStatusId.TODO && x.First().ProcessId == processId))).MustHaveHappened(withCreationStepsInTodo ? 0 : 1, Times.Exactly); - A.CallTo(() => _processStepRepository.AttachAndModifyProcessSteps(A?, Action)>>._)).MustHaveHappened(withCreationStepsInTodo ? 1 : 0, Times.Exactly); + A.CallTo(() => _processStepRepository.CreateProcessStepRange(A>.That.Matches(x => x.Count() == 1 && x.First().ProcessStepTypeId == ProcessStepTypeId.DELETE_DIM_TECHNICAL_USER && x.First().ProcessStepStatusId == ProcessStepStatusId.TODO && x.First().ProcessId == processId))).MustHaveHappenedOnceExactly(); A.CallTo(() => _userRepository.AttachAndModifyIdentity(A._, A>._, A>._)).MustHaveHappenedOnceExactly(); A.CallTo(() => _provisioningManager.DeleteCentralClientAsync(A._)).MustNotHaveHappened(); - identity.UserStatusId.Should().Be(withCreationStepsInTodo ? UserStatusId.DELETED : UserStatusId.PENDING_DELETION); + identity.UserStatusId.Should().Be(UserStatusId.PENDING_DELETION); } else if (withClient) { @@ -601,6 +599,29 @@ public async Task DeleteOwnCompanyServiceAccountAsync_WithoutClient_CallsExpecte A.CallTo(() => _userRolesRepository.DeleteCompanyUserAssignedRoles(A>.That.IsSameSequenceAs(validServiceAccountUserRoleIds))).MustHaveHappenedOnceExactly(); } + [Fact] + public async Task DeleteOwnCompanyServiceAccountAsync_WithCreationProcessInProgress_ThrowsException() + { + // Arrange + var identity = _fixture.Build() + .With(x => x.Id, ValidServiceAccountId) + .With(x => x.UserStatusId, UserStatusId.ACTIVE) + .Create(); + var connector = _fixture.Build() + .With(x => x.CompanyServiceAccountId, ValidServiceAccountId) + .Create(); + var processId = Guid.NewGuid(); + SetupDeleteOwnCompanyServiceAccount(true, true, true, connector, identity, processId, true); + var sut = new ServiceAccountBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService); + Task Act() => sut.DeleteOwnCompanyServiceAccountAsync(ValidServiceAccountId); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Be("TECHNICAL_USER_CREATION_IN_PROGRESS"); + } + #endregion #region GetServiceAccountRolesAsync @@ -802,7 +823,7 @@ private void SetupDeleteOwnCompanyServiceAccount(bool withServiceAccount, bool i { var serviceAccount = new CompanyServiceAccount(Guid.NewGuid(), Guid.NewGuid(), "test-sa", "test", CompanyServiceAccountTypeId.OWN, CompanyServiceAccountKindId.INTERNAL); A.CallTo(() => _serviceAccountRepository.GetOwnCompanyServiceAccountWithIamServiceAccountRolesAsync(ValidServiceAccountId, ValidCompanyId, A>._)) - .Returns(new OwnServiceAccountData(_userRoleIds, serviceAccount.Id, serviceAccount.Version, withServiceAccount ? ValidConnectorId : null, withClient ? ClientId : null, ConnectorStatusId.INACTIVE, OfferSubscriptionStatusId.PENDING, isDimServiceAccount, !withCreateStepsInTodo, processId == null ? null : new ProcessData(processId.Value, withCreateStepsInTodo ? Enumerable.Repeat(Guid.NewGuid(), 1) : Enumerable.Empty()))); + .Returns(new OwnServiceAccountData(_userRoleIds, serviceAccount.Id, serviceAccount.Version, withServiceAccount ? ValidConnectorId : null, withClient ? ClientId : null, ConnectorStatusId.INACTIVE, OfferSubscriptionStatusId.PENDING, isDimServiceAccount, withCreateStepsInTodo, processId)); A.CallTo(() => _serviceAccountRepository.GetOwnCompanyServiceAccountWithIamServiceAccountRolesAsync(A.That.Not.Matches(x => x == ValidServiceAccountId), A._, A>._)) .Returns(null);