From 0532efdd323ae77f1132a7b417aa0a3183a200e0 Mon Sep 17 00:00:00 2001 From: Phil Schneider Date: Wed, 8 Jan 2025 12:09:37 +0100 Subject: [PATCH] feat(provider): adjust provider callbackurl handling when the provider callback url is removed the trigger provider callback steps are set to done and the await auto setup step is created Refs: #1175 --- .../SubscriptionConfigurationBusinessLogic.cs | 74 ++++++++++++------ .../Repositories/CompanyRepository.cs | 16 +++- .../Repositories/ICompanyRepository.cs | 4 +- ...criptionConfigurationBusinessLogicTests.cs | 78 ++++++++++++++++--- 4 files changed, 133 insertions(+), 39 deletions(-) diff --git a/src/administration/Administration.Service/BusinessLogic/SubscriptionConfigurationBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/SubscriptionConfigurationBusinessLogic.cs index 14fb78277f..2f5c909bbe 100644 --- a/src/administration/Administration.Service/BusinessLogic/SubscriptionConfigurationBusinessLogic.cs +++ b/src/administration/Administration.Service/BusinessLogic/SubscriptionConfigurationBusinessLogic.cs @@ -21,6 +21,8 @@ using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; using Org.Eclipse.TractusX.Portal.Backend.Framework.Identity; using Org.Eclipse.TractusX.Portal.Backend.Framework.IO; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Processes.Library.Enums; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Processes.Library.Extensions; 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; @@ -30,30 +32,26 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.BusinessLogic; -public class SubscriptionConfigurationBusinessLogic : ISubscriptionConfigurationBusinessLogic +public class SubscriptionConfigurationBusinessLogic( + IOfferSubscriptionProcessService offerSubscriptionProcessService, + IPortalRepositories portalRepositories, + IIdentityService identityService) + : ISubscriptionConfigurationBusinessLogic { - private readonly IOfferSubscriptionProcessService _offerSubscriptionProcessService; - private readonly IPortalRepositories _portalRepositories; - private readonly IIdentityData _identityData; - - public SubscriptionConfigurationBusinessLogic(IOfferSubscriptionProcessService offerSubscriptionProcessService, IPortalRepositories portalRepositories, IIdentityService identityService) - { - _offerSubscriptionProcessService = offerSubscriptionProcessService; - _portalRepositories = portalRepositories; - _identityData = identityService.IdentityData; - } + private readonly IIdentityData _identityData = identityService.IdentityData; /// public async Task GetProviderCompanyDetailsAsync() { var companyId = _identityData.CompanyId; - var result = await _portalRepositories.GetInstance() + var result = await portalRepositories.GetInstance() .GetProviderCompanyDetailAsync(CompanyRoleId.SERVICE_PROVIDER, companyId) .ConfigureAwait(ConfigureAwaitOptions.None); if (result == default) { throw ConflictException.Create(AdministrationSubscriptionConfigurationErrors.SUBSCRIPTION_CONFLICT_COMPANY_NOT_FOUND, new ErrorParameter[] { new(nameof(companyId), companyId.ToString()) }); } + if (!result.IsProviderCompany) { throw ForbiddenException.Create(AdministrationSubscriptionConfigurationErrors.SUBSCRIPTION_FORBIDDEN_COMPANY_NOT_SERVICE_PROVIDER, new ErrorParameter[] { new(nameof(companyId), companyId.ToString()) }); @@ -78,7 +76,7 @@ public Task SetProviderCompanyDetailsAsync(ProviderDetailData data) private async Task SetOfferProviderCompanyDetailsInternalAsync(ProviderDetailData data, Guid companyId) { - var companyRepository = _portalRepositories.GetInstance(); + var companyRepository = portalRepositories.GetInstance(); var providerDetailData = await companyRepository .GetProviderCompanyDetailsExistsForUser(companyId) .ConfigureAwait(ConfigureAwaitOptions.None); @@ -92,9 +90,14 @@ private async Task SetOfferProviderCompanyDetailsInternalAsync(ProviderDetailDat { companyRepository.AttachAndModifyProviderCompanyDetails( providerDetailData.ProviderCompanyDetailId, - details => { details.AutoSetupUrl = providerDetailData.Url; }, details => { + details.AutoSetupUrl = providerDetailData.Url; + details.AutoSetupCallbackUrl = providerDetailData.CallbackUrl; + }, + details => + { + details.AutoSetupCallbackUrl = data.CallbackUrl; details.AutoSetupUrl = data.Url; details.DateLastChanged = DateTimeOffset.UtcNow; }); @@ -106,9 +109,35 @@ private async Task SetOfferProviderCompanyDetailsInternalAsync(ProviderDetailDat hasChanges = true; } + if (providerDetailData.CallbackUrl is not null && data.CallbackUrl is null) + { + await HandleOfferSetupProcesses(companyId, companyRepository).ConfigureAwait(ConfigureAwaitOptions.None); + hasChanges = true; + } + if (hasChanges) { - await _portalRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None); + await portalRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None); + } + } + + private async Task HandleOfferSetupProcesses(Guid companyId, ICompanyRepository companyRepository) + { + var processData = await companyRepository + .GetOfferSubscriptionProcessesForCompanyId(companyId) + .ToListAsync() + .ConfigureAwait(false); + + foreach (var context in processData + .Where(x => x.Process != null && x.ProcessSteps?.Any(ps => ps is + { + ProcessStepStatusId: ProcessStepStatusId.TODO, + ProcessStepTypeId: ProcessStepTypeId.RETRIGGER_PROVIDER + }) == true) + .Select(data => data.CreateManualProcessData(ProcessStepTypeId.RETRIGGER_PROVIDER, portalRepositories, () => $"processId {data.Process!.Id}"))) + { + context.FinalizeProcessStep(); + context.ScheduleProcessSteps(Enumerable.Repeat(ProcessStepTypeId.AWAIT_START_AUTOSETUP, 1)); } } @@ -129,11 +158,7 @@ private static async Task HandleCreateProviderCompanyDetails(ProviderDetailData companyRepository.CreateProviderCompanyDetail(companyId, data.Url!, providerDetails => { - if (data.CallbackUrl != null) - { - providerDetails.AutoSetupCallbackUrl = data.CallbackUrl; - } - + providerDetails.AutoSetupCallbackUrl = data.CallbackUrl; providerDetails.DateLastChanged = DateTimeOffset.UtcNow; }); } @@ -158,18 +183,17 @@ public Task RetriggerProviderCallback(Guid offerSubscriptionId) => public Task RetriggerCreateDimTechnicalUser(Guid offerSubscriptionId) => TriggerProcessStep(offerSubscriptionId, ProcessStepTypeId.RETRIGGER_OFFERSUBSCRIPTION_CREATE_DIM_TECHNICAL_USER, true); - /// private async Task TriggerProcessStep(Guid offerSubscriptionId, ProcessStepTypeId stepToTrigger, bool mustBePending) { var nextStep = stepToTrigger.GetOfferSubscriptionStepToRetrigger(); - var context = await _offerSubscriptionProcessService.VerifySubscriptionAndProcessSteps(offerSubscriptionId, stepToTrigger, null, mustBePending) + var context = await offerSubscriptionProcessService.VerifySubscriptionAndProcessSteps(offerSubscriptionId, stepToTrigger, null, mustBePending) .ConfigureAwait(ConfigureAwaitOptions.None); - _offerSubscriptionProcessService.FinalizeProcessSteps(context, Enumerable.Repeat(nextStep, 1)); - await _portalRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None); + offerSubscriptionProcessService.FinalizeProcessSteps(context, Enumerable.Repeat(nextStep, 1)); + await portalRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None); } /// public IAsyncEnumerable GetProcessStepsForSubscription(Guid offerSubscriptionId) => - _portalRepositories.GetInstance().GetProcessStepsForSubscription(offerSubscriptionId); + portalRepositories.GetInstance().GetProcessStepsForSubscription(offerSubscriptionId); } diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/CompanyRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/CompanyRepository.cs index 82f7a05aae..4b3f65d0ba 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/CompanyRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/CompanyRepository.cs @@ -21,6 +21,8 @@ using Org.Eclipse.TractusX.Portal.Backend.Framework.DBAccess; using Org.Eclipse.TractusX.Portal.Backend.Framework.Identity; using Org.Eclipse.TractusX.Portal.Backend.Framework.Models; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Processes.Library.Enums; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Processes.Library.Models; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Entities; @@ -133,10 +135,10 @@ public IAsyncEnumerable GetAllMemberCompaniesBPNAsync(IEnumerable - public Task<(Guid ProviderCompanyDetailId, string Url)> GetProviderCompanyDetailsExistsForUser(Guid companyId) => + public Task<(Guid ProviderCompanyDetailId, string Url, string? CallbackUrl)> GetProviderCompanyDetailsExistsForUser(Guid companyId) => context.ProviderCompanyDetails.AsNoTracking() .Where(details => details.CompanyId == companyId) - .Select(details => new ValueTuple(details.Id, details.AutoSetupUrl)) + .Select(details => new ValueTuple(details.Id, details.AutoSetupUrl, details.AutoSetupCallbackUrl)) .SingleOrDefaultAsync(); /// @@ -459,4 +461,14 @@ public Task IsExistingCompany(Guid companyId) => .Where(x => x.BusinessPartnerNumber == bpn) .Select(x => new ValueTuple>(true, x.Id, x.CompanyApplications.Where(a => a.ApplicationStatusId == CompanyApplicationStatusId.SUBMITTED).Select(a => a.Id))) .SingleOrDefaultAsync(); + + public IAsyncEnumerable> GetOfferSubscriptionProcessesForCompanyId(Guid companyId) => + context.Companies + .Where(c => c.Id == companyId) + .SelectMany(c => c.ProvidedOffers.SelectMany(po => + po.OfferSubscriptions.Select(os => + new VerifyProcessData( + os.Process, + os.Process!.ProcessSteps.Where(ps => ps.ProcessStepStatusId == ProcessStepStatusId.TODO))))) + .ToAsyncEnumerable(); } diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/ICompanyRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/ICompanyRepository.cs index 55eecc9b07..8f3432439d 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/ICompanyRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/ICompanyRepository.cs @@ -18,6 +18,7 @@ ********************************************************************************/ using Org.Eclipse.TractusX.Portal.Backend.Framework.Models; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Processes.Library.Models; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Entities; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums; @@ -64,7 +65,7 @@ public interface ICompanyRepository /// true if the company exists for the given user, otherwise false Task<(bool IsValidCompanyId, bool IsCompanyRoleOwner)> IsValidCompanyRoleOwner(Guid companyId, IEnumerable companyRoleIds); - Task<(Guid ProviderCompanyDetailId, string Url)> GetProviderCompanyDetailsExistsForUser(Guid companyId); + Task<(Guid ProviderCompanyDetailId, string Url, string? CallbackUrl)> GetProviderCompanyDetailsExistsForUser(Guid companyId); /// /// Creates service provider company details @@ -183,4 +184,5 @@ public interface ICompanyRepository Task<(Guid Id, IEnumerable<(UniqueIdentifierId Id, string Value)> UniqueIdentifiers, string? BusinessPartnerNumber, string CountryCode)> GetCompanyByProcessId(Guid processId); Task IsExistingCompany(Guid companyId); Task<(bool Exists, Guid CompanyId, IEnumerable SubmittedCompanyApplicationId)> GetCompanyIdByBpn(string bpn); + IAsyncEnumerable> GetOfferSubscriptionProcessesForCompanyId(Guid companyId); } diff --git a/tests/administration/Administration.Service.Tests/BusinessLogic/SubscriptionConfigurationBusinessLogicTests.cs b/tests/administration/Administration.Service.Tests/BusinessLogic/SubscriptionConfigurationBusinessLogicTests.cs index 1d5215955c..7c7ef25cbe 100644 --- a/tests/administration/Administration.Service.Tests/BusinessLogic/SubscriptionConfigurationBusinessLogicTests.cs +++ b/tests/administration/Administration.Service.Tests/BusinessLogic/SubscriptionConfigurationBusinessLogicTests.cs @@ -22,6 +22,8 @@ using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; using Org.Eclipse.TractusX.Portal.Backend.Framework.Identity; using Org.Eclipse.TractusX.Portal.Backend.Framework.Processes.Library.Concrete.Entities; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Processes.Library.DBAccess; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Processes.Library.Entities; using Org.Eclipse.TractusX.Portal.Backend.Framework.Processes.Library.Enums; using Org.Eclipse.TractusX.Portal.Backend.Framework.Processes.Library.Models; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess; @@ -41,6 +43,7 @@ public class SubscriptionConfigurationBusinessLogicTests private readonly IIdentityData _identity; private readonly ICompanyRepository _companyRepository; + private readonly IProcessStepRepository _processStepRepository; private readonly ICollection _serviceProviderDetails; private static readonly Guid OfferSubscriptionId = Guid.NewGuid(); @@ -49,7 +52,6 @@ public class SubscriptionConfigurationBusinessLogicTests private readonly IPortalRepositories _portalRepositories; private readonly IFixture _fixture; private readonly ISubscriptionConfigurationBusinessLogic _sut; - private readonly IIdentityService _identityService; public SubscriptionConfigurationBusinessLogicTests() { @@ -58,23 +60,25 @@ public SubscriptionConfigurationBusinessLogicTests() _offerSubscriptionsRepository = A.Fake(); _companyRepository = A.Fake(); + _processStepRepository = A.Fake(); _portalRepositories = A.Fake(); _offerSubscriptionProcessService = A.Fake(); _serviceProviderDetails = new HashSet(); _identity = A.Fake(); - _identityService = A.Fake(); + var identityService = A.Fake(); A.CallTo(() => _identity.IdentityId).Returns(Guid.NewGuid()); A.CallTo(() => _identity.IdentityTypeId).Returns(IdentityTypeId.COMPANY_USER); A.CallTo(() => _identity.CompanyId).Returns(ExistingCompanyId); - A.CallTo(() => _identityService.IdentityData).Returns(_identity); + A.CallTo(() => identityService.IdentityData).Returns(_identity); A.CallTo(() => _portalRepositories.GetInstance()).Returns(_companyRepository); + A.CallTo(() => _portalRepositories.GetInstance>()).Returns(_processStepRepository); A.CallTo(() => _portalRepositories.GetInstance()) .Returns(_offerSubscriptionsRepository); - _sut = new SubscriptionConfigurationBusinessLogic(_offerSubscriptionProcessService, _portalRepositories, _identityService); + _sut = new SubscriptionConfigurationBusinessLogic(_offerSubscriptionProcessService, _portalRepositories, identityService); } #region GetProcessStepsForSubscription @@ -192,9 +196,9 @@ public async Task SetProviderCompanyDetailsAsync_EmptyProviderDetailsId_ReturnsE { // Arrange SetupProviderCompanyDetails(); - var providerDetailData = new ProviderDetailData("https://www.service-url.com", "https://www.test.com"); + var providerDetailData = new ProviderDetailData("https://www.service-url.com", "https://example.org/callback"); A.CallTo(() => _companyRepository.GetProviderCompanyDetailsExistsForUser(ExistingCompanyId)) - .Returns((Guid.Empty, null!)); + .Returns((Guid.Empty, null!, null)); // Act await _sut.SetProviderCompanyDetailsAsync(providerDetailData); @@ -213,7 +217,7 @@ public async Task SetProviderCompanyDetailsAsync_WithNotExistingAndUrlNull_DoesN SetupProviderCompanyDetails(); var providerDetailData = new ProviderDetailData(null, null); A.CallTo(() => _companyRepository.GetProviderCompanyDetailsExistsForUser(ExistingCompanyId)) - .Returns((Guid.Empty, null!)); + .Returns((Guid.Empty, null!, null)); // Act await _sut.SetProviderCompanyDetailsAsync(providerDetailData); @@ -232,9 +236,23 @@ public async Task SetProviderCompanyDetailsAsync_WithProviderDetailsAndNoUrl_Rem // Arrange SetupProviderCompanyDetails(); var providerCompanyId = Guid.NewGuid(); + var process1Id = Guid.NewGuid(); + var process2Id = Guid.NewGuid(); + var processStep1Id = Guid.NewGuid(); + var processStep2Id = Guid.NewGuid(); var providerDetailData = new ProviderDetailData(null, null); A.CallTo(() => _companyRepository.GetProviderCompanyDetailsExistsForUser(ExistingCompanyId)) - .Returns((providerCompanyId, null!)); + .Returns((providerCompanyId, null!, null)); + A.CallTo(() => _companyRepository.GetOfferSubscriptionProcessesForCompanyId(providerCompanyId)) + .Returns(new List> + { + new( + new Process(process1Id, ProcessTypeId.OFFER_SUBSCRIPTION, Guid.NewGuid()), + Enumerable.Repeat(new ProcessStep(processStep1Id, ProcessStepTypeId.RETRIGGER_PROVIDER, ProcessStepStatusId.TODO, process1Id, DateTimeOffset.UtcNow), 1)), + new( + new Process(process2Id, ProcessTypeId.OFFER_SUBSCRIPTION, Guid.NewGuid()), + Enumerable.Repeat(new ProcessStep(processStep2Id, ProcessStepTypeId.AWAIT_START_AUTOSETUP, ProcessStepStatusId.TODO, process2Id, DateTimeOffset.UtcNow), 1)) + }.ToAsyncEnumerable()); // Act await _sut.SetProviderCompanyDetailsAsync(providerDetailData); @@ -247,6 +265,44 @@ public async Task SetProviderCompanyDetailsAsync_WithProviderDetailsAndNoUrl_Rem _serviceProviderDetails.Should().BeEmpty(); } + [Fact] + public async Task SetProviderCompanyDetailsAsync_WithCallbackUrlChanged_HandlesProcessSteps() + { + // Arrange + SetupProviderCompanyDetails(); + var process1Id = Guid.NewGuid(); + var process2Id = Guid.NewGuid(); + var processStep1Id = Guid.NewGuid(); + var processStep2Id = Guid.NewGuid(); + var providerDetailData = new ProviderDetailData(null, null); + A.CallTo(() => _companyRepository.GetProviderCompanyDetailsExistsForUser(ExistingCompanyId)) + .Returns((ExistingCompanyId, "https://example.org", "https://example.org/callback")); + A.CallTo(() => _companyRepository.GetOfferSubscriptionProcessesForCompanyId(ExistingCompanyId)) + .Returns(new List> + { + new( + new Process(process1Id, ProcessTypeId.OFFER_SUBSCRIPTION, Guid.NewGuid()), + Enumerable.Repeat(new ProcessStep(processStep1Id, ProcessStepTypeId.RETRIGGER_PROVIDER, ProcessStepStatusId.TODO, process1Id, DateTimeOffset.UtcNow), 1)), + new( + new Process(process2Id, ProcessTypeId.OFFER_SUBSCRIPTION, Guid.NewGuid()), + Enumerable.Repeat(new ProcessStep(processStep2Id, ProcessStepTypeId.AWAIT_START_AUTOSETUP, ProcessStepStatusId.TODO, process2Id, DateTimeOffset.UtcNow), 1)) + }.ToAsyncEnumerable()); + + // Act + await _sut.SetProviderCompanyDetailsAsync(providerDetailData); + + // Assert + A.CallTo(() => _companyRepository.RemoveProviderCompanyDetails(ExistingCompanyId)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _companyRepository.CreateProviderCompanyDetail(A._, A._, A>._)).MustNotHaveHappened(); + A.CallTo(() => _companyRepository.AttachAndModifyProviderCompanyDetails(A._, A>._, A>._)).MustNotHaveHappened(); + A.CallTo(() => _processStepRepository.AttachAndModifyProcessSteps(A>? Initialize, Action> Modify)>>._)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _processStepRepository.AttachAndModifyProcessSteps(A>? Initialize, Action> Modify)>>.That.Matches(x => x.Count() == 1 && x.Single().ProcessStepId == processStep1Id))).MustHaveHappenedOnceExactly(); + A.CallTo(() => _processStepRepository.CreateProcessStepRange(A>._)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _processStepRepository.CreateProcessStepRange(A>.That.Matches(x => x.Count() == 1 && x.Single().ProcessStepTypeId == ProcessStepTypeId.AWAIT_START_AUTOSETUP))).MustHaveHappenedOnceExactly(); + A.CallTo(() => _portalRepositories.SaveAsync()).MustHaveHappenedOnceExactly(); + _serviceProviderDetails.Should().BeEmpty(); + } + [Fact] public async Task SetProviderCompanyDetailsAsync_WithServiceProviderDetailsId_ReturnsExpectedResult() { @@ -261,7 +317,7 @@ public async Task SetProviderCompanyDetailsAsync_WithServiceProviderDetailsId_Re ProviderCompanyDetail? modifyDetail = null; A.CallTo(() => _companyRepository.GetProviderCompanyDetailsExistsForUser(ExistingCompanyId)) - .Returns((detailsId, existingUrl)); + .Returns((detailsId, existingUrl, null)); A.CallTo(() => _companyRepository.AttachAndModifyProviderCompanyDetails(A._, A>._, A>._)) .Invokes((Guid id, Action initialize, Action modifiy) => @@ -432,9 +488,9 @@ private void SetupProviderCompanyDetails() .Returns<(ProviderDetailReturnData, bool)>(default); A.CallTo(() => _companyRepository.GetProviderCompanyDetailsExistsForUser(A.That.Matches(x => x == ExistingCompanyId))) - .Returns((Guid.NewGuid(), _fixture.Create())); + .Returns((Guid.NewGuid(), _fixture.Create(), "https://example.org/callback")); A.CallTo(() => _companyRepository.GetProviderCompanyDetailsExistsForUser(A.That.Not.Matches(x => x == ExistingCompanyId))) - .Returns((Guid.Empty, null!)); + .Returns((Guid.Empty, null!, null)); } #endregion