diff --git a/CHANGELOG.md b/CHANGELOG.md index 52c25d100b..7d24faf1cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ New features, fixed bugs, known defects and other noteworthy changes to each release of the Catena-X Portal Backend. +## 2.2.0-RC2 + +### Change + +* **Network Registration (Administration Service)** + * enhanced endpoint `GET /api/administration/registration/network/companies` with additional fields and filter possibilities [#916](https://github.com/eclipse-tractusx/portal-backend/pull/916) + +### Bugfixes + +* **Application Activation** + * adjusted the set of the theme only for shared idps [#852](https://github.com/eclipse-tractusx/portal-backend/pull/852) +* **App Roles** + * added a duplication check for roles before adding them [#877](https://github.com/eclipse-tractusx/portal-backend/pull/877) +* **BPDM** + * adjusted the structure of the bpdm request for the `input/business-partners` request [#928](https://github.com/eclipse-tractusx/portal-backend/pull/928) + ## 2.2.0-RC1 ### Change diff --git a/src/Directory.Build.props b/src/Directory.Build.props index e79c6b364f..0a1bbb7255 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -20,6 +20,6 @@ 2.2.0 - RC1 + RC2 diff --git a/src/administration/Administration.Service/BusinessLogic/IRegistrationBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/IRegistrationBusinessLogic.cs index 1ce740c945..41b1581324 100644 --- a/src/administration/Administration.Service/BusinessLogic/IRegistrationBusinessLogic.cs +++ b/src/administration/Administration.Service/BusinessLogic/IRegistrationBusinessLogic.cs @@ -32,7 +32,7 @@ public interface IRegistrationBusinessLogic { Task GetCompanyWithAddressAsync(Guid applicationId); Task> GetCompanyApplicationDetailsAsync(int page, int size, CompanyApplicationStatusFilter? companyApplicationStatusFilter, string? companyName); - Task> GetOspCompanyDetailsAsync(int page, int size, CompanyApplicationStatusFilter? companyApplicationStatusFilter, string? companyName); + Task> GetOspCompanyDetailsAsync(int page, int size, CompanyApplicationStatusFilter? companyApplicationStatusFilter, string? companyName, string? externalId, DateCreatedOrderFilter? dateCreatedOrderFilter); Task> GetAllCompanyApplicationsDetailsAsync(int page, int size, string? companyName); Task UpdateCompanyBpn(Guid applicationId, string bpn); diff --git a/src/administration/Administration.Service/BusinessLogic/RegistrationBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/RegistrationBusinessLogic.cs index 9b1b59e811..8d78e3abb0 100644 --- a/src/administration/Administration.Service/BusinessLogic/RegistrationBusinessLogic.cs +++ b/src/administration/Administration.Service/BusinessLogic/RegistrationBusinessLogic.cs @@ -161,33 +161,37 @@ private async Task GetCompanyWithAddressAsyncInternal(Gu .AsAsyncEnumerable())); } - public Task> GetOspCompanyDetailsAsync(int page, int size, CompanyApplicationStatusFilter? companyApplicationStatusFilter, string? companyName) + public Task> GetOspCompanyDetailsAsync(int page, int size, CompanyApplicationStatusFilter? companyApplicationStatusFilter, string? companyName, string? externalId, DateCreatedOrderFilter? dateCreatedOrderFilter) { if (companyName != null && !companyName.IsValidCompanyName()) { throw ControllerArgumentException.Create(ValidationExpressionErrors.INCORRECT_COMPANY_NAME, [new("name", "CompanyName")]); } - var applications = portalRepositories.GetInstance() + var applicationsQuery = portalRepositories.GetInstance() .GetExternalCompanyApplicationsFilteredQuery(_identityData.CompanyId, - companyName?.Length >= 3 ? companyName : null, + companyName?.Length >= 3 ? companyName : null, externalId, GetCompanyApplicationStatusIds(companyApplicationStatusFilter)); + var orderedQuery = dateCreatedOrderFilter == null || dateCreatedOrderFilter.Value == DateCreatedOrderFilter.DESC + ? applicationsQuery.AsSplitQuery().OrderByDescending(application => application.DateCreated) + : applicationsQuery.AsSplitQuery().OrderBy(application => application.DateCreated); + return Pagination.CreateResponseAsync( page, size, _settings.ApplicationsMaxPageSize, (skip, take) => new Pagination.AsyncSource( - applications.CountAsync(), - applications - .AsSplitQuery() - .OrderByDescending(application => application.DateCreated) + applicationsQuery.CountAsync(), + orderedQuery .Skip(skip) .Take(take) .Select(application => new CompanyDetailsOspOnboarding( application.CompanyId, + application.NetworkRegistration!.ExternalId, application.Id, application.ApplicationStatusId, application.DateCreated, + application.Company!.DateCreated, application.DateLastChanged, application.Company!.Name, application.Company.CompanyAssignedRoles.Select(companyAssignedRoles => companyAssignedRoles.CompanyRoleId), @@ -210,7 +214,7 @@ private async Task GetCompanyWithAddressAsyncInternal(Gu _settings.ApplicationsMaxPageSize, (skip, take) => new Pagination.AsyncSource( applications.CountAsync(), - applications.OrderByDescending(application => application.DateCreated) + applications.OrderByDescending(application => application.Company!.DateCreated) .Skip(skip) .Take(take) .Select(application => new @@ -394,7 +398,7 @@ public Task TriggerChecklistAsync(Guid applicationId, ApplicationChecklistEntryT throw new ControllerArgumentException($"The processStep {processStepTypeId} is not retriggerable"); } - var nextStepData = processStepTypeId.GetNextProcessStepDataForManualTriggerProcessStepId(_settings.ClearinghouseConnectDisabled); + var nextStepData = processStepTypeId.GetNextProcessStepDataForManualTriggerProcessStepId(); if (nextStepData == default) { throw new UnexpectedConditionException($"While the processStep {processStepTypeId} is configured to be retriggerable there is no next step configured"); diff --git a/src/administration/Administration.Service/BusinessLogic/RegistrationSettings.cs b/src/administration/Administration.Service/BusinessLogic/RegistrationSettings.cs index 2ab34cac97..ffa234b85e 100644 --- a/src/administration/Administration.Service/BusinessLogic/RegistrationSettings.cs +++ b/src/administration/Administration.Service/BusinessLogic/RegistrationSettings.cs @@ -40,11 +40,6 @@ public class RegistrationSettings public string HelpAddress { get; set; } = null!; public bool UseDimWallet { get; set; } - - /// - /// If true all sd factory calls are disabled and won't be called. The respective process steps will be skipped. - /// - public bool ClearinghouseConnectDisabled { get; set; } } public static class RegistrationSettingsExtension diff --git a/src/administration/Administration.Service/BusinessLogic/UserRolesBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/UserRolesBusinessLogic.cs index 783ee597be..ab620c7008 100644 --- a/src/administration/Administration.Service/BusinessLogic/UserRolesBusinessLogic.cs +++ b/src/administration/Administration.Service/BusinessLogic/UserRolesBusinessLogic.cs @@ -154,7 +154,7 @@ private async Task> ModifyUserRolesInternal( var distinctRoles = roles.Where(role => !string.IsNullOrWhiteSpace(role)).Distinct().ToImmutableList(); var existingRoles = await getUserRoleModificationData(companyUserId, distinctRoles, offerId).ToListAsync().ConfigureAwait(false); existingRoles.DuplicatesBy(x => x.CompanyUserRoleText).IfAny( - duplicateRoles => throw new ConflictException($"roles {string.Join(",", $"{duplicateRoles.Select(role => $"[{role.CompanyUserRoleText}, {role.CompanyUserRoleId}]")}")} are ambigous")); + duplicateRoles => throw new ConflictException($"roles {string.Join(",", duplicateRoles.Select(role => $"[{role.CompanyUserRoleText}, {role.CompanyUserRoleId}]"))} are ambigous")); distinctRoles.Except(existingRoles.Where(r => r.IsAssignable).Select(r => r.CompanyUserRoleText)).IfAny( nonExistingRoles => throw new ControllerArgumentException($"Invalid roles {string.Join(",", nonExistingRoles)}", nameof(roles))); diff --git a/src/administration/Administration.Service/Controllers/RegistrationController.cs b/src/administration/Administration.Service/Controllers/RegistrationController.cs index 6dfd31b77a..c84b2fd643 100644 --- a/src/administration/Administration.Service/Controllers/RegistrationController.cs +++ b/src/administration/Administration.Service/Controllers/RegistrationController.cs @@ -568,6 +568,8 @@ public async Task RetriggerDeleteCentralUser([FromRoute] Guid p /// size to get number of records /// Search by company applicationstatus /// search by company name + /// search by external Id + /// sort result by dateCreated ascending or descending /// OSp Company Application Details /// /// Example: GET: api/administration/registration/network/companies?companyName=Car&page=0&size=4&companyApplicationStatus=Closed
@@ -579,6 +581,7 @@ public async Task RetriggerDeleteCentralUser([FromRoute] Guid p [Authorize(Policy = PolicyTypes.ValidCompany)] [Route("network/companies")] [ProducesResponseType(typeof(Pagination.Response), StatusCodes.Status200OK)] - public Task> GetOspCompanyDetailsAsync([FromQuery] int page, [FromQuery] int size, [FromQuery] CompanyApplicationStatusFilter? companyApplicationStatusFilter = null, [FromQuery] string? companyName = null) => - _logic.GetOspCompanyDetailsAsync(page, size, companyApplicationStatusFilter, companyName); + public Task> GetOspCompanyDetailsAsync([FromQuery] int page, [FromQuery] int size, [FromQuery] CompanyApplicationStatusFilter? companyApplicationStatusFilter = null, [FromQuery] string? companyName = null, [FromQuery] string? externalId = null, [FromQuery] DateCreatedOrderFilter? dateCreatedOrderFilter = null) => + _logic.GetOspCompanyDetailsAsync(page, size, companyApplicationStatusFilter, companyName, externalId, dateCreatedOrderFilter); } + diff --git a/src/portalbackend/PortalBackend.PortalEntities/Enums/CompanyApplicationStatusFilter.cs b/src/administration/Administration.Service/Models/CompanyApplicationStatusFilter.cs similarity index 93% rename from src/portalbackend/PortalBackend.PortalEntities/Enums/CompanyApplicationStatusFilter.cs rename to src/administration/Administration.Service/Models/CompanyApplicationStatusFilter.cs index 19f71d7284..9e8bac9a38 100644 --- a/src/portalbackend/PortalBackend.PortalEntities/Enums/CompanyApplicationStatusFilter.cs +++ b/src/administration/Administration.Service/Models/CompanyApplicationStatusFilter.cs @@ -17,7 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -namespace Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums; +namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Models; /// /// Filter operations for the CompanyApplicationStatus diff --git a/src/administration/Administration.Service/Models/CompanyDetailsOspOnboarding.cs b/src/administration/Administration.Service/Models/CompanyDetailsOspOnboarding.cs index d6088add81..f51f9c1e32 100644 --- a/src/administration/Administration.Service/Models/CompanyDetailsOspOnboarding.cs +++ b/src/administration/Administration.Service/Models/CompanyDetailsOspOnboarding.cs @@ -25,9 +25,11 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Models; public record CompanyDetailsOspOnboarding( [property: JsonPropertyName("companyId")] Guid CompanyId, + [property: JsonPropertyName("externalId")] string? ExternalId, [property: JsonPropertyName("applicationId")] Guid ApplicationId, [property: JsonPropertyName("applicationStatus")] CompanyApplicationStatusId CompanyApplicationStatusId, - [property: JsonPropertyName("applicationDateCreated")] DateTimeOffset DateCreated, + [property: JsonPropertyName("applicationDateCreated")] DateTimeOffset ApplicationDateCreated, + [property: JsonPropertyName("dateCreated")] DateTimeOffset DateCreated, [property: JsonPropertyName("lastChangedDate")] DateTimeOffset? DateLastChanged, [property: JsonPropertyName("companyName")] string CompanyName, [property: JsonPropertyName("companyRoles")] IEnumerable CompanyRoles, diff --git a/src/administration/Administration.Service/Models/DateCreatedOrderFilter.cs b/src/administration/Administration.Service/Models/DateCreatedOrderFilter.cs new file mode 100644 index 0000000000..cd6a15903d --- /dev/null +++ b/src/administration/Administration.Service/Models/DateCreatedOrderFilter.cs @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2022 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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Models; + +public enum DateCreatedOrderFilter +{ + ASC, + DESC +} diff --git a/src/administration/Administration.Service/appsettings.json b/src/administration/Administration.Service/appsettings.json index 380e0959b4..e5736f627a 100644 --- a/src/administration/Administration.Service/appsettings.json +++ b/src/administration/Administration.Service/appsettings.json @@ -259,8 +259,7 @@ "Registration": { "ApplicationsMaxPageSize": 20, "DocumentTypeIds": [], - "UseDimWallet": false, - "ClearinghouseConnectDisabled": false + "UseDimWallet": false }, "UserManagement": { "ApplicationsMaxPageSize": 20, diff --git a/src/externalsystems/Bpdm.Library/Models/BpdmLegalEntityData.cs b/src/externalsystems/Bpdm.Library/Models/BpdmLegalEntityData.cs index 9023eae207..97d302b667 100644 --- a/src/externalsystems/Bpdm.Library/Models/BpdmLegalEntityData.cs +++ b/src/externalsystems/Bpdm.Library/Models/BpdmLegalEntityData.cs @@ -111,5 +111,5 @@ public record BpdmLegalEntityData( BpdmLegalEntity LegalEntity, BpdmSite? Site, BpdmAddress Address, - bool OwnCompanyData + bool IsOwnCompanyData ); diff --git a/src/marketplace/Apps.Service/BusinessLogic/AppChangeBusinessLogic.cs b/src/marketplace/Apps.Service/BusinessLogic/AppChangeBusinessLogic.cs index 03cdaf939b..760f57d7e8 100644 --- a/src/marketplace/Apps.Service/BusinessLogic/AppChangeBusinessLogic.cs +++ b/src/marketplace/Apps.Service/BusinessLogic/AppChangeBusinessLogic.cs @@ -111,11 +111,19 @@ private async Task> InsertActiveAppUserRoleAsync(Guid a { throw new ForbiddenException($"Company {_identityData.CompanyId} is not the provider company of app {appId}"); } + var roleData = await AppExtensions.CreateUserRolesWithDescriptions(_portalRepositories.GetInstance(), appId, userRoles); + + // When user will try to upload the same role names which are already attched to an APP. + // so, no role will be added against the given appId so, no need to procced further + // No need to Add roles to client + // No need to update the Offer entity + // When nothing has happened, no need to send notifications + if (!roleData.Any()) + return roleData; - var roleData = AppExtensions.CreateUserRolesWithDescriptions(_portalRepositories.GetInstance(), appId, userRoles); foreach (var clientId in result.ClientClientIds) { - await _provisioningManager.AddRolesToClientAsync(clientId, userRoles.Select(x => x.Role)).ConfigureAwait(ConfigureAwaitOptions.None); + await _provisioningManager.AddRolesToClientAsync(clientId, roleData.Select(x => x.RoleName)).ConfigureAwait(ConfigureAwaitOptions.None); } _portalRepositories.GetInstance().AttachAndModifyOffer(appId, offer => diff --git a/src/marketplace/Apps.Service/BusinessLogic/AppReleaseBusinessLogic.cs b/src/marketplace/Apps.Service/BusinessLogic/AppReleaseBusinessLogic.cs index 9c6ee51664..84b2e24c4b 100644 --- a/src/marketplace/Apps.Service/BusinessLogic/AppReleaseBusinessLogic.cs +++ b/src/marketplace/Apps.Service/BusinessLogic/AppReleaseBusinessLogic.cs @@ -91,7 +91,13 @@ private async Task> InsertAppUserRoleAsync(Guid appId, { throw new ForbiddenException($"Company {companyId} is not the provider company of app {appId}"); } - var roleData = AppExtensions.CreateUserRolesWithDescriptions(_portalRepositories.GetInstance(), appId, userRoles); + var roleData = await AppExtensions.CreateUserRolesWithDescriptions(_portalRepositories.GetInstance(), appId, userRoles); + + // When user will try to upload the same role names which are already attched to an APP. + // so, no role will be added against the given appId so, no need to procced further + // No need to update the Offer entity + if (!roleData.Any()) + return roleData; _portalRepositories.GetInstance().AttachAndModifyOffer(appId, offer => offer.DateLastChanged = DateTimeOffset.UtcNow); diff --git a/src/marketplace/Apps.Service/Extensions/AppExtensions.cs b/src/marketplace/Apps.Service/Extensions/AppExtensions.cs index 622930827e..69ba2f2542 100644 --- a/src/marketplace/Apps.Service/Extensions/AppExtensions.cs +++ b/src/marketplace/Apps.Service/Extensions/AppExtensions.cs @@ -19,6 +19,7 @@ using Org.Eclipse.TractusX.Portal.Backend.Apps.Service.ViewModels; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Linq; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Entities; @@ -46,6 +47,7 @@ public static void ValidateAppUserRole(Guid appId, IEnumerable appU { throw new ControllerArgumentException("Language Code must not be empty"); } + appUserRolesDescription.DuplicatesBy(x => x.Role).IfAny(duplicateRoles => throw new ControllerArgumentException($"Roles are ambiguous: {string.Join(",", duplicateRoles.Select(x => x.Role))}")); } /// @@ -56,8 +58,10 @@ public static void ValidateAppUserRole(Guid appId, IEnumerable appU /// id of the app to create the roles for /// the user roles to add /// returns the created appRoleData - public static IEnumerable CreateUserRolesWithDescriptions(IUserRolesRepository userRolesRepository, Guid appId, IEnumerable userRoles) => - userRoles.Zip( + public static async Task> CreateUserRolesWithDescriptions(IUserRolesRepository userRolesRepository, Guid appId, IEnumerable userRoles) + { + userRoles = await GetUniqueAppUserRoles(userRolesRepository, appId, userRoles); + return userRoles.Zip( userRolesRepository.CreateAppUserRoles(userRoles.Select(x => (appId, x.Role))), (AppUserRole appUserRole, UserRole userRole) => { @@ -65,4 +69,19 @@ public static IEnumerable CreateUserRolesWithDescriptions(IUserRole return new AppRoleData(userRole.Id, appUserRole.Role); }) .ToList(); + } + + /// + /// Get unique roles by eleminating the duplicate roles from the request (client) and existing roles from the Database + /// + /// + /// repository + /// id of the app + /// the app user roles + /// returns the filtered and unique roles + private static async Task> GetUniqueAppUserRoles(IUserRolesRepository userRolesRepository, Guid appId, IEnumerable userRoles) + { + var existingRoles = await userRolesRepository.GetUserRolesForOfferIdAsync(appId).ToListAsync().ConfigureAwait(false); + return userRoles.ExceptBy(existingRoles, userRole => userRole.Role); + } } diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/ApplicationRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/ApplicationRepository.cs index a4d2f39cc2..fe7a37738a 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/ApplicationRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/ApplicationRepository.cs @@ -134,12 +134,13 @@ public IQueryable GetCompanyApplicationsFilteredQuery(string (companyName == null || EF.Functions.ILike(application.Company!.Name, $"{companyName.EscapeForILike()}%")) && (applicationStatusIds == null || applicationStatusIds.Contains(application.ApplicationStatusId))); - public IQueryable GetExternalCompanyApplicationsFilteredQuery(Guid onboardingServiceProviderId, string? companyName, IEnumerable applicationStatusIds) => + public IQueryable GetExternalCompanyApplicationsFilteredQuery(Guid onboardingServiceProviderId, string? companyName, string? externalId, IEnumerable applicationStatusIds) => portalDbContext.CompanyApplications.AsNoTracking() .Where(application => application.CompanyApplicationTypeId == CompanyApplicationTypeId.EXTERNAL && application.OnboardingServiceProviderId == onboardingServiceProviderId && (companyName == null || EF.Functions.ILike(application.Company!.Name, $"{companyName.EscapeForILike()}%")) && + (externalId == null || application.NetworkRegistration!.ExternalId == externalId) && applicationStatusIds.Contains(application.ApplicationStatusId)); public Task GetCompanyApplicationDetailDataAsync(Guid applicationId, Guid userCompanyId, Guid? companyId) => @@ -205,7 +206,7 @@ public IQueryable GetExternalCompanyApplicationsFilteredQuer .SingleOrDefaultAsync(); /// - public Task<(Guid CompanyId, string CompanyName, string? BusinessPartnerNumber, IEnumerable IamIdpAliasse, CompanyApplicationTypeId ApplicationTypeId, Guid? NetworkRegistrationProcessId)> GetCompanyAndApplicationDetailsForApprovalAsync(Guid applicationId) => + public Task<(Guid CompanyId, string CompanyName, string? BusinessPartnerNumber, IEnumerable SharedIdpAliase, CompanyApplicationTypeId ApplicationTypeId, Guid? NetworkRegistrationProcessId)> GetCompanyAndApplicationDetailsForApprovalAsync(Guid applicationId) => portalDbContext.CompanyApplications.Where(companyApplication => companyApplication.Id == applicationId && companyApplication.ApplicationStatusId == CompanyApplicationStatusId.SUBMITTED) @@ -213,7 +214,7 @@ public IQueryable GetExternalCompanyApplicationsFilteredQuer ca.CompanyId, ca.Company!.Name, ca.Company.BusinessPartnerNumber, - ca.Company.IdentityProviders.Select(x => x.IamIdentityProvider!.IamIdpAlias), + ca.Company.IdentityProviders.Where(x => x.IdentityProviderTypeId == IdentityProviderTypeId.SHARED && x.IamIdentityProvider != null).Select(x => x.IamIdentityProvider!.IamIdpAlias), ca.CompanyApplicationTypeId, ca.CompanyApplicationTypeId == CompanyApplicationTypeId.EXTERNAL ? ca.Company.NetworkRegistration!.ProcessId : diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/IApplicationRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/IApplicationRepository.cs index 0c9bf25cdb..15d068306c 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/IApplicationRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/IApplicationRepository.cs @@ -34,11 +34,11 @@ public interface IApplicationRepository Task<(bool Exists, bool IsUserOfCompany, CompanyApplicationStatusId ApplicationStatus)> GetOwnCompanyApplicationStatusUserDataUntrackedAsync(Guid applicationId, Guid companyId); Task GetOwnCompanyApplicationUserEmailDataAsync(Guid applicationId, Guid companyUserId, IEnumerable submitDocumentTypeIds); IQueryable GetCompanyApplicationsFilteredQuery(string? companyName, IEnumerable applicationStatusIds); - IQueryable GetExternalCompanyApplicationsFilteredQuery(Guid onboardingServiceProviderId, string? companyName, IEnumerable applicationStatusIds); + IQueryable GetExternalCompanyApplicationsFilteredQuery(Guid onboardingServiceProviderId, string? companyName, string? externalId, IEnumerable applicationStatusIds); Task GetCompanyApplicationDetailDataAsync(Guid applicationId, Guid userCompanyId, Guid? companyId); Task<(string CompanyName, string? FirstName, string? LastName, string? Email, IEnumerable<(Guid ApplicationId, CompanyApplicationStatusId ApplicationStatusId, IEnumerable<(string? FirstName, string? LastName, string? Email)> InvitedUsers)> Applications)> GetCompanyApplicationsDeclineData(Guid companyUserId, IEnumerable applicationStatusIds); Task<(bool IsValidApplicationId, Guid CompanyId, bool IsSubmitted)> GetCompanyIdSubmissionStatusForApplication(Guid applicationId); - Task<(Guid CompanyId, string CompanyName, string? BusinessPartnerNumber, IEnumerable IamIdpAliasse, CompanyApplicationTypeId ApplicationTypeId, Guid? NetworkRegistrationProcessId)> GetCompanyAndApplicationDetailsForApprovalAsync(Guid applicationId); + Task<(Guid CompanyId, string CompanyName, string? BusinessPartnerNumber, IEnumerable SharedIdpAliase, CompanyApplicationTypeId ApplicationTypeId, Guid? NetworkRegistrationProcessId)> GetCompanyAndApplicationDetailsForApprovalAsync(Guid applicationId); Task<(Guid CompanyId, string CompanyName, string? BusinessPartnerNumber)> GetCompanyAndApplicationDetailsForCreateWalletAsync(Guid applicationId); IAsyncEnumerable GetInvitedUsersDataByApplicationIdUntrackedAsync(Guid applicationId); IAsyncEnumerable GetEmailDataUntrackedAsync(Guid applicationId); diff --git a/src/portalbackend/PortalBackend.PortalEntities/Entities/CompanyApplication.cs b/src/portalbackend/PortalBackend.PortalEntities/Entities/CompanyApplication.cs index 170514cd1d..e400325c99 100644 --- a/src/portalbackend/PortalBackend.PortalEntities/Entities/CompanyApplication.cs +++ b/src/portalbackend/PortalBackend.PortalEntities/Entities/CompanyApplication.cs @@ -66,7 +66,7 @@ public CompanyApplication(Guid id, Guid companyId, CompanyApplicationStatusId ap public virtual Process? ChecklistProcess { get; set; } public virtual CompanyApplicationType? CompanyApplicationType { get; set; } public virtual Company? OnboardingServiceProvider { get; set; } - public virtual NetworkRegistration? NetworkRegistration { get; private set; } + public virtual NetworkRegistration? NetworkRegistration { get; set; } public virtual Identity? LastEditor { get; private set; } public virtual CompanyInvitation? CompanyInvitation { get; private set; } public virtual ICollection Invitations { get; private set; } diff --git a/src/processes/ApplicationChecklist.Library/ApplicationChecklistEntryTypeIdExtensions.cs b/src/processes/ApplicationChecklist.Library/ApplicationChecklistEntryTypeIdExtensions.cs index 80ed0ecb54..353ceb28cc 100644 --- a/src/processes/ApplicationChecklist.Library/ApplicationChecklistEntryTypeIdExtensions.cs +++ b/src/processes/ApplicationChecklist.Library/ApplicationChecklistEntryTypeIdExtensions.cs @@ -39,15 +39,14 @@ public static IEnumerable GetManualTriggerProcessStepIds(this public static IEnumerable GetManualTriggerProcessStepIds(this IEnumerable entryTypeIds) => ManualProcessStepIds.IntersectBy(entryTypeIds, x => x.Key).SelectMany(x => x.Value).Distinct(); - public static (ProcessStepTypeId ProcessStepTypeId, ApplicationChecklistEntryStatusId ChecklistEntryStatusId) GetNextProcessStepDataForManualTriggerProcessStepId(this ProcessStepTypeId processStepTypeId, bool clearinghouseConnectDisabled) => + public static (ProcessStepTypeId ProcessStepTypeId, ApplicationChecklistEntryStatusId ChecklistEntryStatusId) GetNextProcessStepDataForManualTriggerProcessStepId(this ProcessStepTypeId processStepTypeId) => processStepTypeId switch { ProcessStepTypeId.RETRIGGER_CLEARING_HOUSE => (ProcessStepTypeId.START_CLEARING_HOUSE, ApplicationChecklistEntryStatusId.TO_DO), ProcessStepTypeId.RETRIGGER_IDENTITY_WALLET => (ProcessStepTypeId.CREATE_IDENTITY_WALLET, ApplicationChecklistEntryStatusId.TO_DO), ProcessStepTypeId.RETRIGGER_CREATE_DIM_WALLET => (ProcessStepTypeId.CREATE_DIM_WALLET, ApplicationChecklistEntryStatusId.TO_DO), ProcessStepTypeId.RETRIGGER_VALIDATE_DID_DOCUMENT => (ProcessStepTypeId.VALIDATE_DID_DOCUMENT, ApplicationChecklistEntryStatusId.TO_DO), - ProcessStepTypeId.RETRIGGER_SELF_DESCRIPTION_LP when clearinghouseConnectDisabled => (ProcessStepTypeId.ACTIVATE_APPLICATION, ApplicationChecklistEntryStatusId.SKIPPED), - ProcessStepTypeId.RETRIGGER_SELF_DESCRIPTION_LP when !clearinghouseConnectDisabled => (ProcessStepTypeId.START_SELF_DESCRIPTION_LP, ApplicationChecklistEntryStatusId.TO_DO), + ProcessStepTypeId.RETRIGGER_SELF_DESCRIPTION_LP => (ProcessStepTypeId.START_SELF_DESCRIPTION_LP, ApplicationChecklistEntryStatusId.TO_DO), ProcessStepTypeId.RETRIGGER_BUSINESS_PARTNER_NUMBER_PUSH => (ProcessStepTypeId.CREATE_BUSINESS_PARTNER_NUMBER_PUSH, ApplicationChecklistEntryStatusId.TO_DO), ProcessStepTypeId.RETRIGGER_BUSINESS_PARTNER_NUMBER_PULL => (ProcessStepTypeId.CREATE_BUSINESS_PARTNER_NUMBER_PULL, ApplicationChecklistEntryStatusId.IN_PROGRESS), ProcessStepTypeId.TRIGGER_OVERRIDE_CLEARING_HOUSE => (ProcessStepTypeId.START_OVERRIDE_CLEARING_HOUSE, ApplicationChecklistEntryStatusId.TO_DO), diff --git a/src/registration/ApplicationActivation.Library/ApplicationActivationService.cs b/src/registration/ApplicationActivation.Library/ApplicationActivationService.cs index 8672b2aedf..32ba5df5a0 100644 --- a/src/registration/ApplicationActivation.Library/ApplicationActivationService.cs +++ b/src/registration/ApplicationActivation.Library/ApplicationActivationService.cs @@ -90,7 +90,7 @@ public ApplicationActivationService( throw new ConflictException($"CompanyApplication {context.ApplicationId} is not in status SUBMITTED"); } - var (companyId, companyName, businessPartnerNumber, iamIdpAliasse, applicationTypeId, networkRegistrationProcessId) = result; + var (companyId, companyName, businessPartnerNumber, sharedIdpAliase, applicationTypeId, networkRegistrationProcessId) = result; if (string.IsNullOrWhiteSpace(businessPartnerNumber)) { throw new ConflictException($"BusinessPartnerNumber (bpn) for CompanyApplications {context.ApplicationId} company {companyId} is empty"); @@ -99,7 +99,7 @@ public ApplicationActivationService( var userRolesRepository = _portalRepositories.GetInstance(); var assignedRoles = await AssignRolesAndBpn(context.ApplicationId, userRolesRepository, applicationRepository, businessPartnerNumber).ConfigureAwait(ConfigureAwaitOptions.None); await RemoveRegistrationRoles(context.ApplicationId, userRolesRepository).ConfigureAwait(ConfigureAwaitOptions.None); - await SetTheme(iamIdpAliasse).ConfigureAwait(ConfigureAwaitOptions.None); + await SetTheme(sharedIdpAliase).ConfigureAwait(ConfigureAwaitOptions.None); applicationRepository.AttachAndModifyCompanyApplication(context.ApplicationId, ca => { diff --git a/tests/administration/Administration.Service.Tests/BusinessLogic/RegistrationBusinessLogicTest.cs b/tests/administration/Administration.Service.Tests/BusinessLogic/RegistrationBusinessLogicTest.cs index 0572449a24..fa6e6a68e2 100644 --- a/tests/administration/Administration.Service.Tests/BusinessLogic/RegistrationBusinessLogicTest.cs +++ b/tests/administration/Administration.Service.Tests/BusinessLogic/RegistrationBusinessLogicTest.cs @@ -193,12 +193,20 @@ public async Task GetCompanyApplicationDetailsAsync_WithClosedRequest_GetsExpect #region GetOSPCompanyApplicationDetailsAsync [Theory] - [InlineData(null)] - [InlineData(CompanyApplicationStatusFilter.Closed)] - [InlineData(CompanyApplicationStatusFilter.InReview)] - public async Task GetOspCompanyApplicationDetailsAsync_WithDefaultRequest_GetsExpectedEntries(CompanyApplicationStatusFilter? statusFilter) + [InlineData(null, null)] + [InlineData(null, DateCreatedOrderFilter.ASC)] + [InlineData(null, DateCreatedOrderFilter.DESC)] + [InlineData(CompanyApplicationStatusFilter.Closed, null)] + [InlineData(CompanyApplicationStatusFilter.InReview, null)] + [InlineData(CompanyApplicationStatusFilter.Closed, DateCreatedOrderFilter.ASC)] + [InlineData(CompanyApplicationStatusFilter.InReview, DateCreatedOrderFilter.ASC)] + [InlineData(CompanyApplicationStatusFilter.Closed, DateCreatedOrderFilter.DESC)] + [InlineData(CompanyApplicationStatusFilter.InReview, DateCreatedOrderFilter.DESC)] + public async Task GetOspCompanyApplicationDetailsAsync_WithDefaultRequest_GetsExpectedEntries(CompanyApplicationStatusFilter? statusFilter, DateCreatedOrderFilter? dateCreatedOrderFilter) { // Arrange + var companyName = _fixture.Create(); + var externalId = _fixture.Create(); var data = _fixture.CreateMany<(Guid Id, Guid CompanyId, CompanyApplicationStatusId CompanyApplicationStatusId, DateTimeOffset Created)>(10) .Select(x => new CompanyApplication(x.Id, x.CompanyId, x.CompanyApplicationStatusId, CompanyApplicationTypeId.EXTERNAL, x.Created) { @@ -207,16 +215,21 @@ public async Task GetOspCompanyApplicationDetailsAsync_WithDefaultRequest_GetsEx Name = _fixture.Create(), BusinessPartnerNumber = _fixture.Create(), }, + NetworkRegistration = new NetworkRegistration(Guid.NewGuid(), _fixture.Create(), x.CompanyId, Guid.NewGuid(), Guid.NewGuid(), x.Id, x.Created) + { + ExternalId = _fixture.Create(), + DateCreated = _fixture.Create(), + }, DateLastChanged = _fixture.Create() }).ToImmutableList(); var queryData = new AsyncEnumerableStub(data).AsQueryable(); - A.CallTo(() => _applicationRepository.GetExternalCompanyApplicationsFilteredQuery(A._, A._, A>._)) + A.CallTo(() => _applicationRepository.GetExternalCompanyApplicationsFilteredQuery(A._, A._, A._, A>._)) .Returns(queryData); // Act - var result = await _logic.GetOspCompanyDetailsAsync(0, 3, statusFilter, null); + var result = await _logic.GetOspCompanyDetailsAsync(0, 3, statusFilter, companyName, externalId, dateCreatedOrderFilter); // Assert Assert.IsType>(result); @@ -224,25 +237,41 @@ public async Task GetOspCompanyApplicationDetailsAsync_WithDefaultRequest_GetsEx switch (statusFilter) { case CompanyApplicationStatusFilter.Closed: - A.CallTo(() => _applicationRepository.GetExternalCompanyApplicationsFilteredQuery(CompanyId, null, A>.That.IsSameSequenceAs(new[] { CompanyApplicationStatusId.CONFIRMED, CompanyApplicationStatusId.DECLINED }))).MustHaveHappenedOnceExactly(); + A.CallTo(() => _applicationRepository.GetExternalCompanyApplicationsFilteredQuery(CompanyId, companyName, externalId, A>.That.IsSameSequenceAs(new[] { CompanyApplicationStatusId.CONFIRMED, CompanyApplicationStatusId.DECLINED }))).MustHaveHappenedOnceExactly(); break; case CompanyApplicationStatusFilter.InReview: - A.CallTo(() => _applicationRepository.GetExternalCompanyApplicationsFilteredQuery(CompanyId, null, A>.That.IsSameSequenceAs(new[] { CompanyApplicationStatusId.SUBMITTED }))).MustHaveHappenedOnceExactly(); + A.CallTo(() => _applicationRepository.GetExternalCompanyApplicationsFilteredQuery(CompanyId, companyName, externalId, A>.That.IsSameSequenceAs(new[] { CompanyApplicationStatusId.SUBMITTED }))).MustHaveHappenedOnceExactly(); break; default: - A.CallTo(() => _applicationRepository.GetExternalCompanyApplicationsFilteredQuery(CompanyId, null, A>.That.IsSameSequenceAs(new[] { CompanyApplicationStatusId.SUBMITTED, CompanyApplicationStatusId.CONFIRMED, CompanyApplicationStatusId.DECLINED }))).MustHaveHappenedOnceExactly(); + A.CallTo(() => _applicationRepository.GetExternalCompanyApplicationsFilteredQuery(CompanyId, companyName, externalId, A>.That.IsSameSequenceAs(new[] { CompanyApplicationStatusId.SUBMITTED, CompanyApplicationStatusId.CONFIRMED, CompanyApplicationStatusId.DECLINED }))).MustHaveHappenedOnceExactly(); break; } result.Meta.NumberOfElements.Should().Be(10); - var sorted = data.OrderByDescending(application => application.DateCreated).ToImmutableArray(); + var sorted = dateCreatedOrderFilter switch + { + DateCreatedOrderFilter.ASC => data.OrderBy(application => application.Company!.DateCreated).Take(3).ToImmutableArray(), + DateCreatedOrderFilter.DESC => data.OrderByDescending(application => application.Company!.DateCreated).Take(3).ToImmutableArray(), + _ => data.OrderByDescending(application => application.Company!.DateCreated).Take(3).ToImmutableArray() + }; result.Content.Should().HaveCount(3).And.Satisfy( x => x.ApplicationId == sorted[0].Id && x.CompanyApplicationStatusId == sorted[0].ApplicationStatusId && x.DateCreated == sorted[0].DateCreated && x.DateLastChanged == sorted[0].DateLastChanged && x.CompanyId == sorted[0].CompanyId && x.CompanyName == sorted[0].Company!.Name && x.BusinessPartnerNumber == sorted[0].Company!.BusinessPartnerNumber, x => x.ApplicationId == sorted[1].Id && x.CompanyApplicationStatusId == sorted[1].ApplicationStatusId && x.DateCreated == sorted[1].DateCreated && x.DateLastChanged == sorted[1].DateLastChanged && x.CompanyId == sorted[1].CompanyId && x.CompanyName == sorted[1].Company!.Name && x.BusinessPartnerNumber == sorted[1].Company!.BusinessPartnerNumber, x => x.ApplicationId == sorted[2].Id && x.CompanyApplicationStatusId == sorted[2].ApplicationStatusId && x.DateCreated == sorted[2].DateCreated && x.DateLastChanged == sorted[2].DateLastChanged && x.CompanyId == sorted[2].CompanyId && x.CompanyName == sorted[2].Company!.Name && x.BusinessPartnerNumber == sorted[2].Company!.BusinessPartnerNumber ); + + switch (dateCreatedOrderFilter) + { + case DateCreatedOrderFilter.ASC: + result.Content.Should().BeInAscendingOrder(x => x.DateCreated); + break; + case null: + case DateCreatedOrderFilter.DESC: + result.Content.Should().BeInDescendingOrder(x => x.DateCreated); + break; + } } #endregion @@ -752,15 +781,14 @@ public async Task TriggerChecklistAsync_WithFailingChecklistServiceCall_ReturnsE } [Theory] - [InlineData(ApplicationChecklistEntryTypeId.CLEARING_HOUSE, ProcessStepTypeId.RETRIGGER_CLEARING_HOUSE, ProcessStepTypeId.START_CLEARING_HOUSE, ApplicationChecklistEntryStatusId.TO_DO, false)] - [InlineData(ApplicationChecklistEntryTypeId.IDENTITY_WALLET, ProcessStepTypeId.RETRIGGER_IDENTITY_WALLET, ProcessStepTypeId.CREATE_IDENTITY_WALLET, ApplicationChecklistEntryStatusId.TO_DO, false)] - [InlineData(ApplicationChecklistEntryTypeId.IDENTITY_WALLET, ProcessStepTypeId.RETRIGGER_CREATE_DIM_WALLET, ProcessStepTypeId.CREATE_DIM_WALLET, ApplicationChecklistEntryStatusId.TO_DO, false)] - [InlineData(ApplicationChecklistEntryTypeId.IDENTITY_WALLET, ProcessStepTypeId.RETRIGGER_VALIDATE_DID_DOCUMENT, ProcessStepTypeId.VALIDATE_DID_DOCUMENT, ApplicationChecklistEntryStatusId.TO_DO, false)] - [InlineData(ApplicationChecklistEntryTypeId.SELF_DESCRIPTION_LP, ProcessStepTypeId.RETRIGGER_SELF_DESCRIPTION_LP, ProcessStepTypeId.START_SELF_DESCRIPTION_LP, ApplicationChecklistEntryStatusId.TO_DO, false)] - [InlineData(ApplicationChecklistEntryTypeId.SELF_DESCRIPTION_LP, ProcessStepTypeId.RETRIGGER_SELF_DESCRIPTION_LP, ProcessStepTypeId.ACTIVATE_APPLICATION, ApplicationChecklistEntryStatusId.SKIPPED, true)] - [InlineData(ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, ProcessStepTypeId.RETRIGGER_BUSINESS_PARTNER_NUMBER_PUSH, ProcessStepTypeId.CREATE_BUSINESS_PARTNER_NUMBER_PUSH, ApplicationChecklistEntryStatusId.TO_DO, false)] - [InlineData(ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, ProcessStepTypeId.RETRIGGER_BUSINESS_PARTNER_NUMBER_PULL, ProcessStepTypeId.CREATE_BUSINESS_PARTNER_NUMBER_PULL, ApplicationChecklistEntryStatusId.IN_PROGRESS, false)] - public async Task TriggerChecklistAsync_WithValidData_ReturnsExpected(ApplicationChecklistEntryTypeId typeId, ProcessStepTypeId stepId, ProcessStepTypeId nextStepId, ApplicationChecklistEntryStatusId statusId, bool clearinghouseConnectDisabled) + [InlineData(ApplicationChecklistEntryTypeId.CLEARING_HOUSE, ProcessStepTypeId.RETRIGGER_CLEARING_HOUSE, ProcessStepTypeId.START_CLEARING_HOUSE, ApplicationChecklistEntryStatusId.TO_DO)] + [InlineData(ApplicationChecklistEntryTypeId.IDENTITY_WALLET, ProcessStepTypeId.RETRIGGER_IDENTITY_WALLET, ProcessStepTypeId.CREATE_IDENTITY_WALLET, ApplicationChecklistEntryStatusId.TO_DO)] + [InlineData(ApplicationChecklistEntryTypeId.IDENTITY_WALLET, ProcessStepTypeId.RETRIGGER_CREATE_DIM_WALLET, ProcessStepTypeId.CREATE_DIM_WALLET, ApplicationChecklistEntryStatusId.TO_DO)] + [InlineData(ApplicationChecklistEntryTypeId.IDENTITY_WALLET, ProcessStepTypeId.RETRIGGER_VALIDATE_DID_DOCUMENT, ProcessStepTypeId.VALIDATE_DID_DOCUMENT, ApplicationChecklistEntryStatusId.TO_DO)] + [InlineData(ApplicationChecklistEntryTypeId.SELF_DESCRIPTION_LP, ProcessStepTypeId.RETRIGGER_SELF_DESCRIPTION_LP, ProcessStepTypeId.START_SELF_DESCRIPTION_LP, ApplicationChecklistEntryStatusId.TO_DO)] + [InlineData(ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, ProcessStepTypeId.RETRIGGER_BUSINESS_PARTNER_NUMBER_PUSH, ProcessStepTypeId.CREATE_BUSINESS_PARTNER_NUMBER_PUSH, ApplicationChecklistEntryStatusId.TO_DO)] + [InlineData(ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, ProcessStepTypeId.RETRIGGER_BUSINESS_PARTNER_NUMBER_PULL, ProcessStepTypeId.CREATE_BUSINESS_PARTNER_NUMBER_PULL, ApplicationChecklistEntryStatusId.IN_PROGRESS)] + public async Task TriggerChecklistAsync_WithValidData_ReturnsExpected(ApplicationChecklistEntryTypeId typeId, ProcessStepTypeId stepId, ProcessStepTypeId nextStepId, ApplicationChecklistEntryStatusId statusId) { // Arrange var checklistEntry = new ApplicationChecklistEntry(Guid.NewGuid(), typeId, @@ -795,7 +823,6 @@ public async Task TriggerChecklistAsync_WithValidData_ReturnsExpected(Applicatio //Act var settings = A.Fake(); - settings.ClearinghouseConnectDisabled = clearinghouseConnectDisabled; A.CallTo(() => _options.Value).Returns(settings); var logic = new RegistrationBusinessLogic(_portalRepositories, _options, _checklistService, _clearinghouseBusinessLogic, _sdFactoryBusinessLogic, _dimBusinessLogic, _issuerComponentBusinessLogic, _provisioningManager, _mailingProcessCreation, _identityService, _logger); await logic.TriggerChecklistAsync(applicationId, typeId, stepId); diff --git a/tests/administration/Administration.Service.Tests/Controllers/RegistrationControllerTest.cs b/tests/administration/Administration.Service.Tests/Controllers/RegistrationControllerTest.cs index 2b5035c287..4fd1557ad5 100644 --- a/tests/administration/Administration.Service.Tests/Controllers/RegistrationControllerTest.cs +++ b/tests/administration/Administration.Service.Tests/Controllers/RegistrationControllerTest.cs @@ -74,15 +74,22 @@ public async Task GetCompanyApplicationDetailsAsync_ReturnsCompanyApplicationDet public async Task GetOspCompanyApplicationDetailsAsync_ReturnsCompanyApplicationDetails() { //Arrange + var page = _fixture.Create(); + var size = _fixture.Create(); + var companyApplicationStatusFilter = _fixture.Create(); + var companyName = _fixture.Create(); + var externalId = _fixture.Create(); + var dateCreatedOrderFilter = _fixture.Create(); + var paginationResponse = new Pagination.Response(new Pagination.Metadata(15, 1, 1, 15), _fixture.CreateMany(5)); - A.CallTo(() => _logic.GetOspCompanyDetailsAsync(0, 15, null, null)) + A.CallTo(() => _logic.GetOspCompanyDetailsAsync(A._, A._, A._, A._, A._, A._)) .Returns(paginationResponse); //Act - var result = await _controller.GetOspCompanyDetailsAsync(0, 15, null, null); + var result = await _controller.GetOspCompanyDetailsAsync(page, size, companyApplicationStatusFilter, companyName, externalId, dateCreatedOrderFilter); //Assert - A.CallTo(() => _logic.GetOspCompanyDetailsAsync(0, 15, null, null)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _logic.GetOspCompanyDetailsAsync(page, size, companyApplicationStatusFilter, companyName, externalId, dateCreatedOrderFilter)).MustHaveHappenedOnceExactly(); Assert.IsType>(result); result.Content.Should().HaveCount(5); } diff --git a/tests/marketplace/Apps.Service.Tests/BusinessLogic/AppChangeBusinessLogicTest.cs b/tests/marketplace/Apps.Service.Tests/BusinessLogic/AppChangeBusinessLogicTest.cs index 0ffc884c67..98900b5997 100644 --- a/tests/marketplace/Apps.Service.Tests/BusinessLogic/AppChangeBusinessLogicTest.cs +++ b/tests/marketplace/Apps.Service.Tests/BusinessLogic/AppChangeBusinessLogicTest.cs @@ -149,6 +149,9 @@ public async Task AddActiveAppUserRoleAsync_ExecutesSuccessfully() A.CallTo(() => _portalRepositories.GetInstance().GetInsertActiveAppUserRoleDataAsync(appId, OfferTypeId.APP)) .Returns((true, appName, _identity.CompanyId, clientIds)); + A.CallTo(() => _userRolesRepository.GetUserRolesForOfferIdAsync(appId)) + .Returns(new[] { "Exsiting Role" }.ToAsyncEnumerable()); + IEnumerable? userRoles = null; A.CallTo(() => _userRolesRepository.CreateAppUserRoles(A>._)) .ReturnsLazily((IEnumerable<(Guid AppId, string Role)> appRoles) => @@ -226,6 +229,89 @@ public async Task AddActiveAppUserRoleAsync_ExecutesSuccessfully() ); } + [Fact] + public async Task AddActiveAppUserRoleAsync_WithExistingData_NoRowEffecting() + { + //Arrange + var appId = _fixture.Create(); + var appName = _fixture.Create(); + var appAssignedRoleDesc = _fixture.CreateMany(3).Select(role => new AppUserRole(role, _fixture.CreateMany(2).ToImmutableArray())).ToImmutableArray(); + var clientIds = new[] { "client" }; + + A.CallTo(() => _portalRepositories.GetInstance().GetInsertActiveAppUserRoleDataAsync(appId, OfferTypeId.APP)) + .Returns((true, appName, _identity.CompanyId, clientIds)); + + A.CallTo(() => _userRolesRepository.GetUserRolesForOfferIdAsync(appId)) + .Returns(appAssignedRoleDesc.Select(x => x.Role).ToAsyncEnumerable()); + + IEnumerable? userRoles = null; + A.CallTo(() => _userRolesRepository.CreateAppUserRoles(A>._)) + .ReturnsLazily((IEnumerable<(Guid AppId, string Role)> appRoles) => + { + userRoles = appRoles.Select(x => new UserRole(Guid.NewGuid(), x.Role, x.AppId)).ToImmutableArray(); + return userRoles; + }); + + var userRoleDescriptions = new List>(); + A.CallTo(() => _userRolesRepository.CreateAppUserRoleDescriptions(A>._)) + .ReturnsLazily((IEnumerable<(Guid RoleId, string LanguageCode, string Description)> roleLanguageDescriptions) => + { + var createdUserRoleDescriptions = roleLanguageDescriptions.Select(x => new UserRoleDescription(x.RoleId, x.LanguageCode, x.Description)).ToImmutableArray(); + userRoleDescriptions.Add(createdUserRoleDescriptions); + return createdUserRoleDescriptions; + }); + var existingOffer = _fixture.Create(); + existingOffer.DateLastChanged = DateTimeOffset.UtcNow; + A.CallTo(() => _offerRepository.AttachAndModifyOffer(appId, A>._, A?>._)) + .Invokes((Guid _, Action setOptionalParameters, Action? initializeParemeters) => + { + initializeParemeters?.Invoke(existingOffer); + setOptionalParameters(existingOffer); + }); + A.CallTo(() => _notificationService.CreateNotifications(A>._, A._, A>._, A._, A._)) + .Returns(_fixture.CreateMany(4).AsFakeIAsyncEnumerable(out var createNotificationsResultAsyncEnumerator)); + + //Act + var result = await _sut.AddActiveAppUserRoleAsync(appId, appAssignedRoleDesc); + + //Assert + A.CallTo(() => _offerRepository.GetInsertActiveAppUserRoleDataAsync(appId, OfferTypeId.APP)).MustHaveHappened(); + + A.CallTo(() => _userRolesRepository.CreateAppUserRoles(A>._)).MustHaveHappenedOnceExactly(); + userRoles.Should().NotBeNull() + .And.HaveCount(0); + + A.CallTo(() => _userRolesRepository.CreateAppUserRoleDescriptions(A>._)).MustNotHaveHappened(); + userRoleDescriptions.Should().NotBeNull() + .And.HaveCount(0); + A.CallTo(() => _offerRepository.AttachAndModifyOffer(appId, A>._, A?>._)).MustNotHaveHappened(); + A.CallTo(() => _notificationService.CreateNotifications(A>._, A._, A>._, A._, A._)) + .MustNotHaveHappened(); + A.CallTo(() => createNotificationsResultAsyncEnumerator.MoveNextAsync()) + .MustNotHaveHappened(); + A.CallTo(() => _provisioningManager.AddRolesToClientAsync("client", A>.That.IsSameSequenceAs(appAssignedRoleDesc.Select(x => x.Role)))) + .MustNotHaveHappened(); + + result.Should().NotBeNull() + .And.HaveCount(0); + } + + [Fact] + public async Task AddDuplicateActiveAppUserRoleAsync_ThrowsControllerArgumentException() + { + //Arrange + var appId = _fixture.Create(); + var roleId = _fixture.Create(); + var appAssignedRoleDesc = _fixture.CreateMany(3).Select(role => new AppUserRole(roleId, _fixture.CreateMany(2).ToImmutableArray())).ToImmutableArray(); + + //Act + async Task Act() => await _sut.AddActiveAppUserRoleAsync(appId, appAssignedRoleDesc); + + //Assert + var error = await Assert.ThrowsAsync(Act); + error.Message.Should().Be($"Roles are ambiguous: {roleId},{roleId}"); + } + [Fact] public async Task AddActiveAppUserRoleAsync_WithCompanyUserIdNotSet_ThrowsForbiddenException() { diff --git a/tests/marketplace/Apps.Service.Tests/BusinessLogic/AppReleaseBusinessLogicTest.cs b/tests/marketplace/Apps.Service.Tests/BusinessLogic/AppReleaseBusinessLogicTest.cs index 6c846dc548..cf16cd3f72 100644 --- a/tests/marketplace/Apps.Service.Tests/BusinessLogic/AppReleaseBusinessLogicTest.cs +++ b/tests/marketplace/Apps.Service.Tests/BusinessLogic/AppReleaseBusinessLogicTest.cs @@ -145,6 +145,9 @@ public async Task CreateServiceOffering_WithValidDataAndEmptyDescriptions_Return A.CallTo(() => _offerRepository.IsProviderCompanyUserAsync(appId, _identity.CompanyId, OfferTypeId.APP)) .Returns((true, true)); + A.CallTo(() => _userRolesRepository.GetUserRolesForOfferIdAsync(appId)) + .Returns(new[] { "Exsiting Role" }.ToAsyncEnumerable()); + IEnumerable? userRoles = null; A.CallTo(() => _userRolesRepository.CreateAppUserRoles(A>._)) .ReturnsLazily((IEnumerable<(Guid AppId, string Role)> appRoles) => @@ -214,6 +217,78 @@ public async Task CreateServiceOffering_WithValidDataAndEmptyDescriptions_Return ); } + [Fact] + public async Task CreateServiceOffering_WithExistingData_NoRowEffecting() + { + // Arrange + var appId = _fixture.Create(); + var appUserRoles = _fixture.CreateMany(3).Select(role => new AppUserRole(role, _fixture.CreateMany(2).ToImmutableArray())).ToImmutableArray(); + + A.CallTo(() => _offerRepository.IsProviderCompanyUserAsync(appId, _identity.CompanyId, OfferTypeId.APP)) + .Returns((true, true)); + + A.CallTo(() => _userRolesRepository.GetUserRolesForOfferIdAsync(appId)) + .Returns(appUserRoles.Select(x => x.Role).ToAsyncEnumerable()); + + IEnumerable? userRoles = null; + A.CallTo(() => _userRolesRepository.CreateAppUserRoles(A>._)) + .ReturnsLazily((IEnumerable<(Guid AppId, string Role)> appRoles) => + { + userRoles = appRoles.Select(x => new UserRole(Guid.NewGuid(), x.Role, x.AppId)).ToImmutableArray(); + return userRoles; + }); + + var userRoleDescriptions = new List>(); + A.CallTo(() => _userRolesRepository.CreateAppUserRoleDescriptions(A>._)) + .ReturnsLazily((IEnumerable<(Guid RoleId, string LanguageCode, string Description)> roleLanguageDescriptions) => + { + var createdUserRoleDescriptions = roleLanguageDescriptions.Select(x => new UserRoleDescription(x.RoleId, x.LanguageCode, x.Description)).ToImmutableArray(); + userRoleDescriptions.Add(createdUserRoleDescriptions); + return createdUserRoleDescriptions; + }); + var existingOffer = _fixture.Create(); + existingOffer.DateLastChanged = DateTimeOffset.UtcNow; + A.CallTo(() => _offerRepository.AttachAndModifyOffer(appId, A>._, A?>._)) + .Invokes((Guid _, Action setOptionalParameters, Action? initializeParemeters) => + { + initializeParemeters?.Invoke(existingOffer); + setOptionalParameters(existingOffer); + }); + // Act + var result = await _sut.AddAppUserRoleAsync(appId, appUserRoles); + + // Assert + A.CallTo(() => _offerRepository.IsProviderCompanyUserAsync(A._, A._, A._)).MustHaveHappened(); + + A.CallTo(() => _userRolesRepository.CreateAppUserRoles(A>._)).MustHaveHappenedOnceExactly(); + userRoles.Should().NotBeNull() + .And.HaveCount(0); + + A.CallTo(() => _offerRepository.AttachAndModifyOffer(appId, A>._, A?>._)).MustNotHaveHappened(); + A.CallTo(() => _userRolesRepository.CreateAppUserRoleDescriptions(A>._)).MustNotHaveHappened(); + userRoleDescriptions.Should().NotBeNull() + .And.HaveCount(0); + + result.Should().NotBeNull() + .And.HaveCount(0); + } + + [Fact] + public async Task CreateServiceOffering_WithValidButDuplicateData_ThrowsControllerArgumentException() + { + // Arrange + var appId = _fixture.Create(); + var roleId = _fixture.Create(); + var appUserRoles = _fixture.CreateMany(3).Select(role => new AppUserRole(roleId, _fixture.CreateMany(2).ToImmutableArray())).ToImmutableArray(); + + // Act + async Task Act() => await _sut.AddAppUserRoleAsync(appId, appUserRoles); + + // Assert + var error = await Assert.ThrowsAsync(Act); + error.Message.Should().Be($"Roles are ambiguous: {roleId},{roleId}"); + } + #region AddAppAsync [Fact]