From 721525dde761204a2e116eb9fd605a9799437669 Mon Sep 17 00:00:00 2001 From: Norbert Truchsess Date: Mon, 16 Sep 2024 21:58:26 +0200 Subject: [PATCH] fix compile-errors after rebase and review findings --- .../RegistrationBusinessLogic.cs | 24 +--- ...ompanyApplicationStatusFilterExtensions.cs | 16 +-- .../ClearinghouseBusinessLogic.cs | 30 ++--- .../IClearinghouseBusinessLogic.cs | 3 +- .../Clearinghouse.Library.csproj | 1 + .../Framework.Async/Directory.Build.props | 2 +- .../ToAsyncEnumerableExtensions.cs | 34 ++++++ .../Framework.Cors/Directory.Build.props | 2 +- .../Framework.DBAccess/Directory.Build.props | 2 +- .../Directory.Build.props | 2 +- .../Directory.Build.props | 2 +- .../Directory.Build.props | 2 +- .../Directory.Build.props | 2 +- .../Directory.Build.props | 2 +- .../Directory.Build.props | 2 +- .../Framework.IO/Directory.Build.props | 2 +- .../Framework.Linq/Directory.Build.props | 2 +- .../Framework.Logging/Directory.Build.props | 2 +- .../Framework.Models/Directory.Build.props | 2 +- .../Framework.Seeding/Directory.Build.props | 2 +- .../Framework.Swagger/Directory.Build.props | 2 +- .../Framework.Token/Directory.Build.props | 2 +- .../Framework.Web/Directory.Build.props | 2 +- .../Services/BatchDeleteService.cs | 20 +++- .../Repositories/AgreementRepository.cs | 13 ++ .../ApplicationChecklistRepository.cs | 3 +- .../Repositories/DocumentRepository.cs | 11 +- .../Repositories/IAgreementRepository.cs | 3 + .../Repositories/IDocumentRepository.cs | 8 +- .../ClearinghouseBusinessLogicTests.cs | 112 +++++++++--------- .../BatchDeleteServiceTests.cs | 47 ++++++-- 31 files changed, 209 insertions(+), 150 deletions(-) create mode 100644 src/framework/Framework.Async/ToAsyncEnumerableExtensions.cs diff --git a/src/administration/Administration.Service/BusinessLogic/RegistrationBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/RegistrationBusinessLogic.cs index e29195c0a9..3db4a91b2d 100644 --- a/src/administration/Administration.Service/BusinessLogic/RegistrationBusinessLogic.cs +++ b/src/administration/Administration.Service/BusinessLogic/RegistrationBusinessLogic.cs @@ -20,6 +20,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using Org.Eclipse.TractusX.Portal.Backend.Administration.Service.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Extensions; using Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Models; using Org.Eclipse.TractusX.Portal.Backend.Clearinghouse.Library.BusinessLogic; using Org.Eclipse.TractusX.Portal.Backend.Clearinghouse.Library.Models; @@ -130,7 +131,7 @@ private async Task GetCompanyWithAddressAsyncInternal(Gu var applications = portalRepositories.GetInstance() .GetCompanyApplicationsFilteredQuery( companyName?.Length >= 3 ? companyName : null, - GetCompanyApplicationStatusIds(companyApplicationStatusFilter)); + companyApplicationStatusFilter.GetCompanyApplicationStatusIds()); return Pagination.CreateResponseAsync( page, @@ -170,7 +171,7 @@ private async Task GetCompanyWithAddressAsyncInternal(Gu var applicationsQuery = portalRepositories.GetInstance() .GetExternalCompanyApplicationsFilteredQuery(_identityData.CompanyId, companyName?.Length >= 3 ? companyName : null, externalId, - GetCompanyApplicationStatusIds(companyApplicationStatusFilter)); + companyApplicationStatusFilter.GetCompanyApplicationStatusIds()); var orderedQuery = dateCreatedOrderFilter == null || dateCreatedOrderFilter.Value == DateCreatedOrderFilter.DESC ? applicationsQuery.AsSplitQuery().OrderByDescending(application => application.DateCreated) @@ -606,25 +607,6 @@ private void PostRegistrationCancelEmailAsync(ICollection emailData, } } - private static IEnumerable GetCompanyApplicationStatusIds(CompanyApplicationStatusFilter? companyApplicationStatusFilter) - { - switch (companyApplicationStatusFilter) - { - case CompanyApplicationStatusFilter.Closed: - { - return [CompanyApplicationStatusId.CONFIRMED, CompanyApplicationStatusId.DECLINED]; - } - case CompanyApplicationStatusFilter.InReview: - { - return [CompanyApplicationStatusId.SUBMITTED]; - } - default: - { - return [CompanyApplicationStatusId.SUBMITTED, CompanyApplicationStatusId.CONFIRMED, CompanyApplicationStatusId.DECLINED]; - } - } - } - /// public async Task<(string fileName, byte[] content, string contentType)> GetDocumentAsync(Guid documentId) { diff --git a/src/administration/Administration.Service/Extensions/CompanyApplicationStatusFilterExtensions.cs b/src/administration/Administration.Service/Extensions/CompanyApplicationStatusFilterExtensions.cs index 1220e5b3b3..d405603f75 100644 --- a/src/administration/Administration.Service/Extensions/CompanyApplicationStatusFilterExtensions.cs +++ b/src/administration/Administration.Service/Extensions/CompanyApplicationStatusFilterExtensions.cs @@ -27,14 +27,16 @@ public static class CompanyApplicationStatusFilterExtensions public static IEnumerable GetCompanyApplicationStatusIds(this CompanyApplicationStatusFilter? companyApplicationStatusFilter) => companyApplicationStatusFilter switch { - CompanyApplicationStatusFilter.Closed => - [ - CompanyApplicationStatusId.CONFIRMED, CompanyApplicationStatusId.DECLINED + CompanyApplicationStatusFilter.Closed => [ + CompanyApplicationStatusId.CONFIRMED, + CompanyApplicationStatusId.DECLINED + ], + CompanyApplicationStatusFilter.InReview => [ + CompanyApplicationStatusId.SUBMITTED ], - CompanyApplicationStatusFilter.InReview => [CompanyApplicationStatusId.SUBMITTED], - _ => - [ - CompanyApplicationStatusId.SUBMITTED, CompanyApplicationStatusId.CONFIRMED, + _ => [ + CompanyApplicationStatusId.SUBMITTED, + CompanyApplicationStatusId.CONFIRMED, CompanyApplicationStatusId.DECLINED ] }; diff --git a/src/externalsystems/Clearinghouse.Library/BusinessLogic/ClearinghouseBusinessLogic.cs b/src/externalsystems/Clearinghouse.Library/BusinessLogic/ClearinghouseBusinessLogic.cs index d9ee9b1115..ccffde8768 100644 --- a/src/externalsystems/Clearinghouse.Library/BusinessLogic/ClearinghouseBusinessLogic.cs +++ b/src/externalsystems/Clearinghouse.Library/BusinessLogic/ClearinghouseBusinessLogic.cs @@ -20,6 +20,7 @@ using Microsoft.Extensions.Options; using Org.Eclipse.TractusX.Portal.Backend.Clearinghouse.Library.Models; using Org.Eclipse.TractusX.Portal.Backend.Custodian.Library.BusinessLogic; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Async; using Org.Eclipse.TractusX.Portal.Backend.Framework.DateTimeProvider; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess; @@ -139,22 +140,27 @@ public async Task ProcessEndClearinghouse(Guid applicationId, ClearinghouseRespo : [ProcessStepTypeId.START_SELF_DESCRIPTION_LP]); } - public async Task CheckEndClearinghouseProcesses(CancellationToken stoppingToken) + public async Task CheckEndClearinghouseProcesses(CancellationToken cancellationToken) { var applicationIds = await portalRepositories.GetInstance() .GetApplicationsForClearinghouseRetrigger(dateTimeProvider.OffsetNow.AddDays(-_settings.RetriggerEndClearinghouseIntervalInDays)) - .ToListAsync(stoppingToken) + .ToListAsync(cancellationToken) .ConfigureAwait(false); - var hasChanges = false; - foreach (var context in applicationIds.Select(applicationId => checklistService.VerifyChecklistEntryAndProcessSteps( - applicationId, - ApplicationChecklistEntryTypeId.CLEARING_HOUSE, - [ApplicationChecklistEntryStatusId.IN_PROGRESS], - ProcessStepTypeId.AWAIT_CLEARING_HOUSE_RESPONSE))) + if (applicationIds.Count == 0) + return; + + await foreach (var context in applicationIds + .Select(applicationId => + checklistService.VerifyChecklistEntryAndProcessSteps( + applicationId, + ApplicationChecklistEntryTypeId.CLEARING_HOUSE, + [ApplicationChecklistEntryStatusId.IN_PROGRESS], + ProcessStepTypeId.AWAIT_CLEARING_HOUSE_RESPONSE)) + .TasksToAsyncEnumerable().WithCancellation(cancellationToken)) { checklistService.FinalizeChecklistEntryAndProcessSteps( - await context.ConfigureAwait(ConfigureAwaitOptions.None), + context, null, item => { @@ -162,12 +168,8 @@ await context.ConfigureAwait(ConfigureAwaitOptions.None), item.Comment = "Reset to retrigger clearinghouse"; }, [ProcessStepTypeId.START_OVERRIDE_CLEARING_HOUSE]); - hasChanges = true; } - if (hasChanges) - { - await portalRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None); - } + await portalRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None); } } diff --git a/src/externalsystems/Clearinghouse.Library/BusinessLogic/IClearinghouseBusinessLogic.cs b/src/externalsystems/Clearinghouse.Library/BusinessLogic/IClearinghouseBusinessLogic.cs index bc880a38fa..d3a087e6d2 100644 --- a/src/externalsystems/Clearinghouse.Library/BusinessLogic/IClearinghouseBusinessLogic.cs +++ b/src/externalsystems/Clearinghouse.Library/BusinessLogic/IClearinghouseBusinessLogic.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2022 Microsoft and BMW Group AG * Copyright (c) 2022 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional @@ -27,5 +26,5 @@ public interface IClearinghouseBusinessLogic { Task HandleClearinghouse(IApplicationChecklistService.WorkerChecklistProcessStepData context, CancellationToken cancellationToken); Task ProcessEndClearinghouse(Guid applicationId, ClearinghouseResponseData data, CancellationToken cancellationToken); - Task CheckEndClearinghouseProcesses(CancellationToken stoppingToken); + Task CheckEndClearinghouseProcesses(CancellationToken cancellationToken); } diff --git a/src/externalsystems/Clearinghouse.Library/Clearinghouse.Library.csproj b/src/externalsystems/Clearinghouse.Library/Clearinghouse.Library.csproj index f2b188c869..c29ec0ea95 100644 --- a/src/externalsystems/Clearinghouse.Library/Clearinghouse.Library.csproj +++ b/src/externalsystems/Clearinghouse.Library/Clearinghouse.Library.csproj @@ -31,6 +31,7 @@ + diff --git a/src/framework/Framework.Async/Directory.Build.props b/src/framework/Framework.Async/Directory.Build.props index 0f881739fd..e909f97822 100644 --- a/src/framework/Framework.Async/Directory.Build.props +++ b/src/framework/Framework.Async/Directory.Build.props @@ -19,7 +19,7 @@ - 2.7.0 + 2.8.0 diff --git a/src/framework/Framework.Async/ToAsyncEnumerableExtensions.cs b/src/framework/Framework.Async/ToAsyncEnumerableExtensions.cs new file mode 100644 index 0000000000..1a83290abf --- /dev/null +++ b/src/framework/Framework.Async/ToAsyncEnumerableExtensions.cs @@ -0,0 +1,34 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using System.Runtime.CompilerServices; + +namespace Org.Eclipse.TractusX.Portal.Backend.Framework.Async; + +public static class ToAsyncEnumerableExtensions +{ + public static async IAsyncEnumerable TasksToAsyncEnumerable(this IEnumerable> tasks, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + foreach (var task in tasks) + { + cancellationToken.ThrowIfCancellationRequested(); + yield return await task.ConfigureAwait(ConfigureAwaitOptions.None); + } + } +} diff --git a/src/framework/Framework.Cors/Directory.Build.props b/src/framework/Framework.Cors/Directory.Build.props index 0f881739fd..e909f97822 100644 --- a/src/framework/Framework.Cors/Directory.Build.props +++ b/src/framework/Framework.Cors/Directory.Build.props @@ -19,7 +19,7 @@ - 2.7.0 + 2.8.0 diff --git a/src/framework/Framework.DBAccess/Directory.Build.props b/src/framework/Framework.DBAccess/Directory.Build.props index 0f881739fd..e909f97822 100644 --- a/src/framework/Framework.DBAccess/Directory.Build.props +++ b/src/framework/Framework.DBAccess/Directory.Build.props @@ -19,7 +19,7 @@ - 2.7.0 + 2.8.0 diff --git a/src/framework/Framework.DateTimeProvider/Directory.Build.props b/src/framework/Framework.DateTimeProvider/Directory.Build.props index 0f881739fd..e909f97822 100644 --- a/src/framework/Framework.DateTimeProvider/Directory.Build.props +++ b/src/framework/Framework.DateTimeProvider/Directory.Build.props @@ -19,7 +19,7 @@ - 2.7.0 + 2.8.0 diff --git a/src/framework/Framework.DependencyInjection/Directory.Build.props b/src/framework/Framework.DependencyInjection/Directory.Build.props index 0f881739fd..e909f97822 100644 --- a/src/framework/Framework.DependencyInjection/Directory.Build.props +++ b/src/framework/Framework.DependencyInjection/Directory.Build.props @@ -19,7 +19,7 @@ - 2.7.0 + 2.8.0 diff --git a/src/framework/Framework.ErrorHandling.Controller/Directory.Build.props b/src/framework/Framework.ErrorHandling.Controller/Directory.Build.props index 0f881739fd..e909f97822 100644 --- a/src/framework/Framework.ErrorHandling.Controller/Directory.Build.props +++ b/src/framework/Framework.ErrorHandling.Controller/Directory.Build.props @@ -19,7 +19,7 @@ - 2.7.0 + 2.8.0 diff --git a/src/framework/Framework.ErrorHandling.Web/Directory.Build.props b/src/framework/Framework.ErrorHandling.Web/Directory.Build.props index 0f881739fd..e909f97822 100644 --- a/src/framework/Framework.ErrorHandling.Web/Directory.Build.props +++ b/src/framework/Framework.ErrorHandling.Web/Directory.Build.props @@ -19,7 +19,7 @@ - 2.7.0 + 2.8.0 diff --git a/src/framework/Framework.ErrorHandling/Directory.Build.props b/src/framework/Framework.ErrorHandling/Directory.Build.props index 0f881739fd..e909f97822 100644 --- a/src/framework/Framework.ErrorHandling/Directory.Build.props +++ b/src/framework/Framework.ErrorHandling/Directory.Build.props @@ -19,7 +19,7 @@ - 2.7.0 + 2.8.0 diff --git a/src/framework/Framework.HttpClientExtensions/Directory.Build.props b/src/framework/Framework.HttpClientExtensions/Directory.Build.props index 0f881739fd..e909f97822 100644 --- a/src/framework/Framework.HttpClientExtensions/Directory.Build.props +++ b/src/framework/Framework.HttpClientExtensions/Directory.Build.props @@ -19,7 +19,7 @@ - 2.7.0 + 2.8.0 diff --git a/src/framework/Framework.IO/Directory.Build.props b/src/framework/Framework.IO/Directory.Build.props index 0f881739fd..e909f97822 100644 --- a/src/framework/Framework.IO/Directory.Build.props +++ b/src/framework/Framework.IO/Directory.Build.props @@ -19,7 +19,7 @@ - 2.7.0 + 2.8.0 diff --git a/src/framework/Framework.Linq/Directory.Build.props b/src/framework/Framework.Linq/Directory.Build.props index 0f881739fd..e909f97822 100644 --- a/src/framework/Framework.Linq/Directory.Build.props +++ b/src/framework/Framework.Linq/Directory.Build.props @@ -19,7 +19,7 @@ - 2.7.0 + 2.8.0 diff --git a/src/framework/Framework.Logging/Directory.Build.props b/src/framework/Framework.Logging/Directory.Build.props index 0f881739fd..e909f97822 100644 --- a/src/framework/Framework.Logging/Directory.Build.props +++ b/src/framework/Framework.Logging/Directory.Build.props @@ -19,7 +19,7 @@ - 2.7.0 + 2.8.0 diff --git a/src/framework/Framework.Models/Directory.Build.props b/src/framework/Framework.Models/Directory.Build.props index 0f881739fd..e909f97822 100644 --- a/src/framework/Framework.Models/Directory.Build.props +++ b/src/framework/Framework.Models/Directory.Build.props @@ -19,7 +19,7 @@ - 2.7.0 + 2.8.0 diff --git a/src/framework/Framework.Seeding/Directory.Build.props b/src/framework/Framework.Seeding/Directory.Build.props index 0f881739fd..e909f97822 100644 --- a/src/framework/Framework.Seeding/Directory.Build.props +++ b/src/framework/Framework.Seeding/Directory.Build.props @@ -19,7 +19,7 @@ - 2.7.0 + 2.8.0 diff --git a/src/framework/Framework.Swagger/Directory.Build.props b/src/framework/Framework.Swagger/Directory.Build.props index 0f881739fd..e909f97822 100644 --- a/src/framework/Framework.Swagger/Directory.Build.props +++ b/src/framework/Framework.Swagger/Directory.Build.props @@ -19,7 +19,7 @@ - 2.7.0 + 2.8.0 diff --git a/src/framework/Framework.Token/Directory.Build.props b/src/framework/Framework.Token/Directory.Build.props index 0f881739fd..e909f97822 100644 --- a/src/framework/Framework.Token/Directory.Build.props +++ b/src/framework/Framework.Token/Directory.Build.props @@ -19,7 +19,7 @@ - 2.7.0 + 2.8.0 diff --git a/src/framework/Framework.Web/Directory.Build.props b/src/framework/Framework.Web/Directory.Build.props index 0f881739fd..e909f97822 100644 --- a/src/framework/Framework.Web/Directory.Build.props +++ b/src/framework/Framework.Web/Directory.Build.props @@ -19,7 +19,7 @@ - 2.7.0 + 2.8.0 diff --git a/src/maintenance/Maintenance.App/Services/BatchDeleteService.cs b/src/maintenance/Maintenance.App/Services/BatchDeleteService.cs index 97895877e8..5b946ae958 100644 --- a/src/maintenance/Maintenance.App/Services/BatchDeleteService.cs +++ b/src/maintenance/Maintenance.App/Services/BatchDeleteService.cs @@ -42,9 +42,11 @@ public async Task CleanupDocuments(CancellationToken cancellationToken) { logger.LogInformation("Getting documents and assignments older {Days} days", _settings.DeleteIntervalInDays); var documentRepository = portalRepositories.GetInstance(); + var documentData = await documentRepository .GetDocumentDataForCleanup(dateTimeProvider.OffsetNow.AddDays(-_settings.DeleteIntervalInDays)) - .ConfigureAwait(ConfigureAwaitOptions.None); + .ToListAsync(cancellationToken) + .ConfigureAwait(false); if (documentData.Count == 0) { logger.LogInformation("No documents to cleanup"); @@ -53,11 +55,19 @@ public async Task CleanupDocuments(CancellationToken cancellationToken) logger.LogInformation("Cleaning up {DocumentCount} Documents and {OfferIdCount} OfferAssignedDocuments", documentData.Count, documentData.SelectMany(x => x.OfferIds).Count()); - var agreementsToDeleteDocumentId = documentData.SelectMany(data => data.AgreementIds.Select(agreementId => new Agreement(agreementId, default, null!, default, default, default) { DocumentId = data.DocumentId })).ToList(); - portalRepositories.AttachRange(agreementsToDeleteDocumentId); - agreementsToDeleteDocumentId.ForEach(agreement => agreement.DocumentId = null); - documentRepository.RemoveOfferAssignedDocuments(documentData.SelectMany(data => data.OfferIds.Select(offerId => new OfferAssignedDocument(offerId, data.DocumentId)))); + portalRepositories.GetInstance().AttachAndModifyAgreements( + documentData.SelectMany(data => data.AgreementIds.Select?, Action)>(agreementId => ( + agreementId, + agreement => agreement.DocumentId = data.DocumentId, + agreement => agreement.DocumentId = null)))); + + portalRepositories.GetInstance().RemoveOfferAssignedDocuments( + documentData.SelectMany(data => data.OfferIds.Select(offerId => ( + offerId, + data.DocumentId)))); + documentRepository.RemoveDocuments(documentData.Select(x => x.DocumentId)); + await portalRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None); logger.LogInformation("Documents older than {Days} days and depending consents successfully cleaned up", _settings.DeleteIntervalInDays); } diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/AgreementRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/AgreementRepository.cs index 2351c602ce..5923459b3a 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/AgreementRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/AgreementRepository.cs @@ -21,6 +21,7 @@ using Microsoft.EntityFrameworkCore; 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; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums; namespace Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories; @@ -129,4 +130,16 @@ public IAsyncEnumerable GetAgreementIdsForOfferAsync(Guid o .Where(assigned => assigned.OfferId == offerId) .Select(assigned => new AgreementStatusData(assigned.AgreementId, assigned.Agreement!.AgreementStatusId)) .AsAsyncEnumerable(); + + public void AttachAndModifyAgreements(IEnumerable<(Guid AgreementId, Action? Initialize, Action Modify)> agreementModificationIds) + { + var items = agreementModificationIds.Select(agreementModificationId => + { + var agreement = new Agreement(agreementModificationId.AgreementId, default, null!, default, default, default); + agreementModificationId.Initialize?.Invoke(agreement); + return (Agreement: agreement, agreementModificationId.Modify); + }).ToList(); + _context.AttachRange(items.Select(item => item.Agreement)); + items.ForEach(item => item.Modify(item.Agreement)); + } } diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/ApplicationChecklistRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/ApplicationChecklistRepository.cs index 51fe492b00..c14797340d 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/ApplicationChecklistRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/ApplicationChecklistRepository.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2022 BMW Group AG * Copyright (c) 2022 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional @@ -106,7 +105,7 @@ public IAsyncEnumerable GetApplicationsForClearinghouseRetrigger(DateTimeO ce.DateCreated < dateCreatedThreshold) && ca.ChecklistProcess!.ProcessTypeId == ProcessTypeId.APPLICATION_CHECKLIST && ca.ChecklistProcess!.ProcessSteps.Any(ps => - ps.ProcessStepTypeId == ProcessStepTypeId.END_CLEARING_HOUSE && + ps.ProcessStepTypeId == ProcessStepTypeId.AWAIT_CLEARING_HOUSE_RESPONSE && ps.ProcessStepStatusId == ProcessStepStatusId.TODO && ps.DateCreated < dateCreatedThreshold)) .Select(x => x.Id) diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/DocumentRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/DocumentRepository.cs index e6a7e9a861..1fc4debca4 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/DocumentRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/DocumentRepository.cs @@ -231,17 +231,16 @@ public void AttachAndModifyDocuments(IEnumerable<(Guid DocumentId, Action documentIds) => dbContext.Documents.RemoveRange(documentIds.Select(documentId => new Document(documentId, null!, null!, null!, default, default, default, default))); - public void RemoveOfferAssignedDocuments(IEnumerable offerAssignedDocuments) => - dbContext.OfferAssignedDocuments.RemoveRange(offerAssignedDocuments); - public Task<(byte[] Content, string FileName, bool IsDocumentTypeMatch, MediaTypeId MediaTypeId)> GetDocumentAsync(Guid documentId, IEnumerable documentTypeIds) => dbContext.Documents .Where(x => x.Id == documentId) .Select(x => new ValueTuple(x.DocumentContent, x.DocumentName, documentTypeIds.Contains(x.DocumentTypeId), x.MediaTypeId)) .SingleOrDefaultAsync(); - public Task AgreementIds, IEnumerable OfferIds)>> GetDocumentDataForCleanup(DateTimeOffset dateCreated) => - dbContext.Documents.Where(x => + public IAsyncEnumerable<(Guid DocumentId, IEnumerable AgreementIds, IEnumerable OfferIds)> GetDocumentDataForCleanup(DateTimeOffset dateCreated) => + dbContext.Documents + .AsSplitQuery() + .Where(x => x.DateCreated < dateCreated && !x.Companies.Any() && x.Connector == null && @@ -252,5 +251,5 @@ public void RemoveOfferAssignedDocuments(IEnumerable offe doc.Agreements.Select(x => x.Id), doc.Offers.Select(x => x.Id) )) - .ToListAsync(); + .AsAsyncEnumerable(); } diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/IAgreementRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/IAgreementRepository.cs index a039d5759b..bbf608cafc 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/IAgreementRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/IAgreementRepository.cs @@ -19,6 +19,7 @@ ********************************************************************************/ 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; namespace Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories; @@ -92,4 +93,6 @@ public interface IAgreementRepository /// Id of the offer the agreement must be associated with /// IAsyncEnumerable GetAgreementIdsForOfferAsync(Guid offerId); + + void AttachAndModifyAgreements(IEnumerable<(Guid AgreementId, Action? Initialize, Action Modify)> agreementModificationIds); } diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/IDocumentRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/IDocumentRepository.cs index e3e2c84485..9337874f11 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/IDocumentRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/IDocumentRepository.cs @@ -146,12 +146,6 @@ public interface IDocumentRepository /// void RemoveDocuments(IEnumerable documentIds); - /// - /// Delete List Of Document - /// - /// - void RemoveOfferAssignedDocuments(IEnumerable offerAssignedDocuments); - /// /// Gets the registration document with the given id /// @@ -160,5 +154,5 @@ public interface IDocumentRepository /// Task<(byte[] Content, string FileName, bool IsDocumentTypeMatch, MediaTypeId MediaTypeId)> GetDocumentAsync(Guid documentId, IEnumerable documentTypeIds); - Task AgreementIds, IEnumerable OfferIds)>> GetDocumentDataForCleanup(DateTimeOffset dateCreated); + IAsyncEnumerable<(Guid DocumentId, IEnumerable AgreementIds, IEnumerable OfferIds)> GetDocumentDataForCleanup(DateTimeOffset dateCreated); } diff --git a/tests/externalsystems/Clearinghouse.Library.Tests/ClearinghouseBusinessLogicTests.cs b/tests/externalsystems/Clearinghouse.Library.Tests/ClearinghouseBusinessLogicTests.cs index 3a03967099..081feb5958 100644 --- a/tests/externalsystems/Clearinghouse.Library.Tests/ClearinghouseBusinessLogicTests.cs +++ b/tests/externalsystems/Clearinghouse.Library.Tests/ClearinghouseBusinessLogicTests.cs @@ -120,14 +120,14 @@ public async Task HandleStartClearingHouse_WithNotExistingApplication_ThrowsConf { // Arrange var applicationId = Guid.NewGuid(); - var checklist = new Dictionary - { - { ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION, ApplicationChecklistEntryStatusId.DONE }, - { ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, ApplicationChecklistEntryStatusId.DONE }, - { ApplicationChecklistEntryTypeId.IDENTITY_WALLET, ApplicationChecklistEntryStatusId.DONE }, - { ApplicationChecklistEntryTypeId.CLEARING_HOUSE, ApplicationChecklistEntryStatusId.TO_DO } - } - .ToImmutableDictionary(); + + var checklist = ImmutableDictionary.CreateRange([ + new(ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION, ApplicationChecklistEntryStatusId.DONE), + new(ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, ApplicationChecklistEntryStatusId.DONE), + new(ApplicationChecklistEntryTypeId.IDENTITY_WALLET, ApplicationChecklistEntryStatusId.DONE), + new(ApplicationChecklistEntryTypeId.CLEARING_HOUSE, ApplicationChecklistEntryStatusId.TO_DO) + ]); + var context = new IApplicationChecklistService.WorkerChecklistProcessStepData(applicationId, ProcessStepTypeId.START_CLEARING_HOUSE, checklist, Enumerable.Empty()); SetupForHandleStartClearingHouse(); @@ -143,14 +143,13 @@ public async Task HandleStartClearingHouse_WithNotExistingApplication_ThrowsConf public async Task HandleStartClearingHouse_WithCreatedApplication_ThrowsConflictException() { // Arrange - var checklist = new Dictionary - { - {ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION, ApplicationChecklistEntryStatusId.DONE}, - {ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, ApplicationChecklistEntryStatusId.DONE}, - {ApplicationChecklistEntryTypeId.IDENTITY_WALLET, ApplicationChecklistEntryStatusId.DONE}, - {ApplicationChecklistEntryTypeId.CLEARING_HOUSE, ApplicationChecklistEntryStatusId.TO_DO} - } - .ToImmutableDictionary(); + var checklist = ImmutableDictionary.CreateRange([ + new(ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION, ApplicationChecklistEntryStatusId.DONE), + new(ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, ApplicationChecklistEntryStatusId.DONE), + new(ApplicationChecklistEntryTypeId.IDENTITY_WALLET, ApplicationChecklistEntryStatusId.DONE), + new(ApplicationChecklistEntryTypeId.CLEARING_HOUSE, ApplicationChecklistEntryStatusId.TO_DO) + ]); + var context = new IApplicationChecklistService.WorkerChecklistProcessStepData(IdWithApplicationCreated, ProcessStepTypeId.START_CLEARING_HOUSE, checklist, Enumerable.Empty()); SetupForHandleStartClearingHouse(); @@ -166,14 +165,13 @@ public async Task HandleStartClearingHouse_WithCreatedApplication_ThrowsConflict public async Task HandleStartClearingHouse_WithBpnNull_ThrowsConflictException() { // Arrange - var checklist = new Dictionary - { - {ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION, ApplicationChecklistEntryStatusId.DONE}, - {ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, ApplicationChecklistEntryStatusId.DONE}, - {ApplicationChecklistEntryTypeId.IDENTITY_WALLET, ApplicationChecklistEntryStatusId.DONE}, - {ApplicationChecklistEntryTypeId.CLEARING_HOUSE, ApplicationChecklistEntryStatusId.TO_DO} - } - .ToImmutableDictionary(); + var checklist = ImmutableDictionary.CreateRange([ + new(ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION, ApplicationChecklistEntryStatusId.DONE), + new(ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, ApplicationChecklistEntryStatusId.DONE), + new(ApplicationChecklistEntryTypeId.IDENTITY_WALLET, ApplicationChecklistEntryStatusId.DONE), + new(ApplicationChecklistEntryTypeId.CLEARING_HOUSE, ApplicationChecklistEntryStatusId.TO_DO) + ]); + var context = new IApplicationChecklistService.WorkerChecklistProcessStepData(IdWithoutBpn, ProcessStepTypeId.START_CLEARING_HOUSE, checklist, Enumerable.Empty()); SetupForHandleStartClearingHouse(); @@ -192,14 +190,14 @@ public async Task HandleStartClearingHouse_WithValidData_CallsExpected(ProcessSt { // Arrange var entry = new ApplicationChecklistEntry(Guid.NewGuid(), ApplicationChecklistEntryTypeId.CLEARING_HOUSE, ApplicationChecklistEntryStatusId.TO_DO, DateTimeOffset.UtcNow); - var checklist = new Dictionary - { - {ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION, ApplicationChecklistEntryStatusId.DONE}, - {ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, ApplicationChecklistEntryStatusId.DONE}, - {ApplicationChecklistEntryTypeId.IDENTITY_WALLET, ApplicationChecklistEntryStatusId.DONE}, - {ApplicationChecklistEntryTypeId.CLEARING_HOUSE, ApplicationChecklistEntryStatusId.TO_DO} - } - .ToImmutableDictionary(); + + var checklist = ImmutableDictionary.CreateRange([ + new(ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION, ApplicationChecklistEntryStatusId.DONE), + new(ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, ApplicationChecklistEntryStatusId.DONE), + new(ApplicationChecklistEntryTypeId.IDENTITY_WALLET, ApplicationChecklistEntryStatusId.DONE), + new(ApplicationChecklistEntryTypeId.CLEARING_HOUSE, ApplicationChecklistEntryStatusId.TO_DO) + ]); + var context = new IApplicationChecklistService.WorkerChecklistProcessStepData(IdWithBpn, stepTypeId, checklist, Enumerable.Empty()); SetupForHandleStartClearingHouse(); @@ -222,14 +220,13 @@ public async Task HandleStartClearingHouse_WithValidData_CallsExpected(ProcessSt public async Task HandleStartClearingHouse_WithDimActiveAndNonExistingApplication_ThrowsConflictException() { // Arrange - var checklist = new Dictionary - { - {ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION, ApplicationChecklistEntryStatusId.DONE}, - {ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, ApplicationChecklistEntryStatusId.DONE}, - {ApplicationChecklistEntryTypeId.IDENTITY_WALLET, ApplicationChecklistEntryStatusId.DONE}, - {ApplicationChecklistEntryTypeId.CLEARING_HOUSE, ApplicationChecklistEntryStatusId.TO_DO} - } - .ToImmutableDictionary(); + var checklist = ImmutableDictionary.CreateRange([ + new(ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION, ApplicationChecklistEntryStatusId.DONE), + new(ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, ApplicationChecklistEntryStatusId.DONE), + new(ApplicationChecklistEntryTypeId.IDENTITY_WALLET, ApplicationChecklistEntryStatusId.DONE), + new(ApplicationChecklistEntryTypeId.CLEARING_HOUSE, ApplicationChecklistEntryStatusId.TO_DO) + ]); + var context = new IApplicationChecklistService.WorkerChecklistProcessStepData(IdWithBpn, ProcessStepTypeId.START_CLEARING_HOUSE, checklist, Enumerable.Empty()); A.CallTo(() => _applicationRepository.GetDidForApplicationId(A._)) .Returns<(bool, string?)>(default); @@ -253,14 +250,13 @@ public async Task HandleStartClearingHouse_WithDimActiveAndNonExistingApplicatio public async Task HandleStartClearingHouse_WithDimActiveAndDidNotSet_ThrowsConflictException() { // Arrange - var checklist = new Dictionary - { - {ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION, ApplicationChecklistEntryStatusId.DONE}, - {ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, ApplicationChecklistEntryStatusId.DONE}, - {ApplicationChecklistEntryTypeId.IDENTITY_WALLET, ApplicationChecklistEntryStatusId.DONE}, - {ApplicationChecklistEntryTypeId.CLEARING_HOUSE, ApplicationChecklistEntryStatusId.TO_DO} - } - .ToImmutableDictionary(); + var checklist = ImmutableDictionary.CreateRange([ + new(ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION, ApplicationChecklistEntryStatusId.DONE), + new(ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, ApplicationChecklistEntryStatusId.DONE), + new(ApplicationChecklistEntryTypeId.IDENTITY_WALLET, ApplicationChecklistEntryStatusId.DONE), + new(ApplicationChecklistEntryTypeId.CLEARING_HOUSE, ApplicationChecklistEntryStatusId.TO_DO) + ]); + var context = new IApplicationChecklistService.WorkerChecklistProcessStepData(IdWithBpn, ProcessStepTypeId.START_CLEARING_HOUSE, checklist, Enumerable.Empty()); A.CallTo(() => _applicationRepository.GetDidForApplicationId(A._)) .Returns((true, null)); @@ -285,14 +281,14 @@ public async Task HandleStartClearingHouse_WithDimActive_CallsExpected() { // Arrange var entry = new ApplicationChecklistEntry(Guid.NewGuid(), ApplicationChecklistEntryTypeId.CLEARING_HOUSE, ApplicationChecklistEntryStatusId.TO_DO, DateTimeOffset.UtcNow); - var checklist = new Dictionary - { - {ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION, ApplicationChecklistEntryStatusId.DONE}, - {ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, ApplicationChecklistEntryStatusId.DONE}, - {ApplicationChecklistEntryTypeId.IDENTITY_WALLET, ApplicationChecklistEntryStatusId.DONE}, - {ApplicationChecklistEntryTypeId.CLEARING_HOUSE, ApplicationChecklistEntryStatusId.TO_DO}, - } - .ToImmutableDictionary(); + + var checklist = ImmutableDictionary.CreateRange([ + new(ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION, ApplicationChecklistEntryStatusId.DONE), + new(ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, ApplicationChecklistEntryStatusId.DONE), + new(ApplicationChecklistEntryTypeId.IDENTITY_WALLET, ApplicationChecklistEntryStatusId.DONE), + new(ApplicationChecklistEntryTypeId.CLEARING_HOUSE, ApplicationChecklistEntryStatusId.TO_DO) + ]); + var context = new IApplicationChecklistService.WorkerChecklistProcessStepData(IdWithBpn, ProcessStepTypeId.START_CLEARING_HOUSE, checklist, Enumerable.Empty()); A.CallTo(() => _applicationRepository.GetDidForApplicationId(A._)) .Returns((true, "did:web:test123456")); @@ -466,7 +462,7 @@ private void SetupForCheckEndClearinghouseProcesses(IEnumerable._, ApplicationChecklistEntryTypeId.CLEARING_HOUSE, A>._, - ProcessStepTypeId.END_CLEARING_HOUSE, + ProcessStepTypeId.AWAIT_CLEARING_HOUSE_RESPONSE, A?>._, A?>._)) .ReturnsLazily((Guid id, @@ -480,7 +476,7 @@ private void SetupForCheckEndClearinghouseProcesses(IEnumerable.Empty, - new List())); + [])); } #endregion diff --git a/tests/maintenance/Maintenance.App.Tests/BatchDeleteServiceTests.cs b/tests/maintenance/Maintenance.App.Tests/BatchDeleteServiceTests.cs index 6837e18a41..2e6ac4faa9 100644 --- a/tests/maintenance/Maintenance.App.Tests/BatchDeleteServiceTests.cs +++ b/tests/maintenance/Maintenance.App.Tests/BatchDeleteServiceTests.cs @@ -1,3 +1,22 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Org.Eclipse.TractusX.Portal.Backend.Framework.DateTimeProvider; @@ -14,6 +33,8 @@ public class BatchDeleteServiceTests { private readonly IPortalRepositories _portalRepositories; private readonly IDocumentRepository _documentRepository; + private readonly IAgreementRepository _agreementRepository; + private readonly IOfferRepository _offerRepository; private readonly BatchDeleteService _sut; private readonly IMockLogger _mockLogger; @@ -29,8 +50,12 @@ public BatchDeleteServiceTests() _portalRepositories = A.Fake(); _documentRepository = A.Fake(); + _agreementRepository = A.Fake(); + _offerRepository = A.Fake(); A.CallTo(() => _portalRepositories.GetInstance()).Returns(_documentRepository); + A.CallTo(() => _portalRepositories.GetInstance()).Returns(_agreementRepository); + A.CallTo(() => _portalRepositories.GetInstance()).Returns(_offerRepository); var dateTimeProvider = A.Fake(); A.CallTo(() => dateTimeProvider.OffsetNow).Returns(DateTimeOffset.UtcNow); @@ -44,19 +69,19 @@ public async Task CleanupDocuments_WithoutMatchingDocuments_DoesNothing() { // Arrange A.CallTo(() => _documentRepository.GetDocumentDataForCleanup(A._)) - .Returns([]); + .Returns(Array.Empty<(Guid, IEnumerable, IEnumerable)>().ToAsyncEnumerable()); // Act await _sut.CleanupDocuments(CancellationToken.None); // Assert - A.CallTo(() => _portalRepositories.SaveAsync()) + A.CallTo(() => _agreementRepository.AttachAndModifyAgreements(A?, Action)>>._)) .MustNotHaveHappened(); - A.CallTo(() => _portalRepositories.AttachRange(A>._)) + A.CallTo(() => _offerRepository.RemoveOfferAssignedDocuments(A>._)) .MustNotHaveHappened(); A.CallTo(() => _documentRepository.RemoveDocuments(A>._)) .MustNotHaveHappened(); - A.CallTo(() => _documentRepository.RemoveOfferAssignedDocuments(A>._)) + A.CallTo(() => _portalRepositories.SaveAsync()) .MustNotHaveHappened(); } @@ -70,19 +95,19 @@ public async Task CleanupDocuments_WithMatchingDocuments_RemovesExpected() var offerId1 = Guid.NewGuid(); var offerId2 = Guid.NewGuid(); A.CallTo(() => _documentRepository.GetDocumentDataForCleanup(A._)) - .Returns([new(documentId, new[] { agreementId1, agreementId2 }, new[] { offerId1, offerId2 })]); + .Returns(new (Guid, IEnumerable, IEnumerable)[] { (documentId, [agreementId1, agreementId2], [offerId1, offerId2]) }.ToAsyncEnumerable()); // Act await _sut.CleanupDocuments(CancellationToken.None); // Assert - A.CallTo(() => _portalRepositories.SaveAsync()) + A.CallTo(() => _agreementRepository.AttachAndModifyAgreements(A?, Action)>>.That.Matches(x => x.Count(a => a.Id == agreementId1) == 1 && x.Count(a => a.Id == agreementId2) == 1))) .MustHaveHappenedOnceExactly(); - A.CallTo(() => _portalRepositories.AttachRange(A>.That.Matches(x => x.Count(a => a.Id == agreementId1) == 1 && x.Count(a => a.Id == agreementId2) == 1))) + A.CallTo(() => _offerRepository.RemoveOfferAssignedDocuments(A>.That.Matches(x => x.Count(a => a.OfferId == offerId1 && a.DocumentId == documentId) == 1 && x.Count(a => a.OfferId == offerId2 && a.DocumentId == documentId) == 1))) .MustHaveHappenedOnceExactly(); A.CallTo(() => _documentRepository.RemoveDocuments(A>.That.Matches(x => x.Single() == documentId))) .MustHaveHappenedOnceExactly(); - A.CallTo(() => _documentRepository.RemoveOfferAssignedDocuments(A>.That.Matches(x => x.Count(a => a.OfferId == offerId1 && a.DocumentId == documentId) == 1 && x.Count(a => a.OfferId == offerId2 && a.DocumentId == documentId) == 1))) + A.CallTo(() => _portalRepositories.SaveAsync()) .MustHaveHappenedOnceExactly(); } @@ -97,13 +122,13 @@ public async Task CleanupDocuments_WithException_LogsException() await _sut.CleanupDocuments(CancellationToken.None); // Assert - A.CallTo(() => _portalRepositories.SaveAsync()) + A.CallTo(() => _agreementRepository.AttachAndModifyAgreements(A?, Action)>>._)) .MustNotHaveHappened(); - A.CallTo(() => _portalRepositories.AttachRange(A>._)) + A.CallTo(() => _offerRepository.RemoveOfferAssignedDocuments(A>._)) .MustNotHaveHappened(); A.CallTo(() => _documentRepository.RemoveDocuments(A>._)) .MustNotHaveHappened(); - A.CallTo(() => _documentRepository.RemoveOfferAssignedDocuments(A>._)) + A.CallTo(() => _portalRepositories.SaveAsync()) .MustNotHaveHappened(); A.CallTo(() => _mockLogger.Log(A.That.IsEqualTo(LogLevel.Error), A._, A._)).MustHaveHappenedOnceExactly(); }