From 2f9ab332d1f0180b4bda751eddaeb362a65d8d77 Mon Sep 17 00:00:00 2001 From: VPrasannaK94 <117351802+VPrasannaK94@users.noreply.github.com> Date: Thu, 11 Apr 2024 22:04:58 +0530 Subject: [PATCH] feat(apps): create endpoint to fetch activeAppRoleDetails (#622) * create endpoint to fetch activeAppRoleDetails Ref : #573 --------- Co-authored-by: Norbert Truchsess --- .../BusinessLogic/AppChangeBusinessLogic.cs | 16 ++++ .../BusinessLogic/IAppChangeBusinessLogic.cs | 7 ++ .../Controllers/AppChangeController.cs | 18 +++++ .../Models/OfferRoleInfos.cs | 10 ++- .../Repositories/IUserRolesRepository.cs | 9 +++ .../Repositories/UserRolesRepository.cs | 26 +++++++ .../AppChangeBusinessLogicTest.cs | 73 +++++++++++++++++++ .../Controllers/AppChangeControllerTest.cs | 23 +++++- .../UserRolesRepositoryTests.cs | 58 ++++++++++++++- 9 files changed, 235 insertions(+), 5 deletions(-) diff --git a/src/marketplace/Apps.Service/BusinessLogic/AppChangeBusinessLogic.cs b/src/marketplace/Apps.Service/BusinessLogic/AppChangeBusinessLogic.cs index ca7a42598c..625580d193 100644 --- a/src/marketplace/Apps.Service/BusinessLogic/AppChangeBusinessLogic.cs +++ b/src/marketplace/Apps.Service/BusinessLogic/AppChangeBusinessLogic.cs @@ -25,6 +25,7 @@ using Org.Eclipse.TractusX.Portal.Backend.Framework.DateTimeProvider; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; using Org.Eclipse.TractusX.Portal.Backend.Framework.IO; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Models; using Org.Eclipse.TractusX.Portal.Backend.Framework.Web; using Org.Eclipse.TractusX.Portal.Backend.Notifications.Library; using Org.Eclipse.TractusX.Portal.Backend.Offers.Library.Service; @@ -362,4 +363,19 @@ public async Task DeleteActiveAppDocumentAsync(Guid appId, Guid documentId) /// public async Task CreateActiveAppDocumentAsync(Guid appId, DocumentTypeId documentTypeId, IFormFile document, CancellationToken cancellationToken) => await _offerDocumentService.UploadDocumentAsync(appId, documentTypeId, document, OfferTypeId.APP, _settings.UploadActiveAppDocumentTypeIds, OfferStatusId.ACTIVE, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + + /// + public async Task> GetActiveAppRolesAsync(Guid appId, string? languageShortName) + { + var (isValid, isActive, roleDetails) = await _portalRepositories.GetInstance().GetActiveAppRolesAsync(appId, OfferTypeId.APP, languageShortName, Constants.DefaultLanguage).ConfigureAwait(ConfigureAwaitOptions.None); + if (!isValid) + { + throw new NotFoundException($"App {appId} does not exist"); + } + if (!isActive) + { + throw new ConflictException($"App {appId} is not Active"); + } + return roleDetails ?? throw new UnexpectedConditionException("roleDetails should never be null here"); + } } diff --git a/src/marketplace/Apps.Service/BusinessLogic/IAppChangeBusinessLogic.cs b/src/marketplace/Apps.Service/BusinessLogic/IAppChangeBusinessLogic.cs index ecc8332d5e..f3d4e68a01 100644 --- a/src/marketplace/Apps.Service/BusinessLogic/IAppChangeBusinessLogic.cs +++ b/src/marketplace/Apps.Service/BusinessLogic/IAppChangeBusinessLogic.cs @@ -93,4 +93,11 @@ public interface IAppChangeBusinessLogic /// /// Task CreateActiveAppDocumentAsync(Guid appId, DocumentTypeId documentTypeId, IFormFile document, CancellationToken cancellationToken); + + /// + /// Gets user roles for an active app id + /// + /// Id of the offer + /// + Task> GetActiveAppRolesAsync(Guid appId, string? languageShortName); } diff --git a/src/marketplace/Apps.Service/Controllers/AppChangeController.cs b/src/marketplace/Apps.Service/Controllers/AppChangeController.cs index d03fe29c66..265ce31969 100644 --- a/src/marketplace/Apps.Service/Controllers/AppChangeController.cs +++ b/src/marketplace/Apps.Service/Controllers/AppChangeController.cs @@ -256,4 +256,22 @@ public async Task CreateActiveAppDocumentAsync([FromRoute] Guid await _businessLogic.CreateActiveAppDocumentAsync(appId, documentTypeId, document, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); return NoContent(); } + + /// + /// Gets the client roles for the given active app. + /// + /// Id of the app which roles should be returned. + /// OPTIONAL: The language short name. + /// Returns the client roles for the given active app. + /// Example: GET: /api/apps/AppChange/D3B1ECA2-6148-4008-9E6C-C1C2AEA5C645/roles + /// Returns the client roles. + /// The language does not exist. + /// The app was not found. + [HttpGet] + [Authorize(Roles = "view_client_roles")] + [Authorize(Policy = PolicyTypes.ValidCompany)] + [Route("{appId}/roles")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public Task> GetActiveAppRolesAsync([FromRoute] Guid appId, [FromQuery] string? languageShortName = null) => + _businessLogic.GetActiveAppRolesAsync(appId, languageShortName); } diff --git a/src/portalbackend/PortalBackend.DBAccess/Models/OfferRoleInfos.cs b/src/portalbackend/PortalBackend.DBAccess/Models/OfferRoleInfos.cs index 0a2443d4c0..71ea34d2a4 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Models/OfferRoleInfos.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Models/OfferRoleInfos.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional @@ -29,3 +28,12 @@ public record OfferRoleInfo( public record OfferRoleInfos( [property: JsonPropertyName("offerId")] Guid OfferId, [property: JsonPropertyName("roles")] IEnumerable RoleInfos); + +public record ActiveAppRoleDetails( + [property: JsonPropertyName("role")] string Role, + [property: JsonPropertyName("descriptions")] IEnumerable Descriptions); + +public record ActiveAppUserRoleDescription( + [property: JsonPropertyName("languageCode")] string LanguageCode, + [property: JsonPropertyName("description")] string Description); + diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/IUserRolesRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/IUserRolesRepository.cs index 65b717c3e1..133a963370 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/IUserRolesRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/IUserRolesRepository.cs @@ -20,6 +20,7 @@ using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Configuration; 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; @@ -70,4 +71,12 @@ public interface IUserRolesRepository IAsyncEnumerable<(Guid CompanyUserId, IEnumerable UserRoleIds)> GetUserWithUserRolesForApplicationId(Guid applicationId, IEnumerable userRoleIds); IAsyncEnumerable GetRolesForClient(string technicalUserProfileClient); + + /// + /// Gets userRoles for an offerId + /// + /// + /// + /// + Task<(bool IsValid, bool IsActive, IEnumerable? AppRoleDetails)> GetActiveAppRolesAsync(Guid offerId, OfferTypeId offerTypeId, string? languageShortName, string defaultLanguageShortName); } diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/UserRolesRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/UserRolesRepository.cs index 58af520c19..a4383cf525 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/UserRolesRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/UserRolesRepository.cs @@ -275,4 +275,30 @@ public IAsyncEnumerable GetRolesForClient(string technicalUserProfileClien .Where(instance => technicalUserProfileClient == instance.IamClient!.ClientClientId) .SelectMany(instance => instance.App!.UserRoles.Select(role => role.Id)) .ToAsyncEnumerable(); + + /// + public Task<(bool IsValid, bool IsActive, IEnumerable? AppRoleDetails)> GetActiveAppRolesAsync(Guid offerId, OfferTypeId offerTypeId, string? languageShortName, string defaultLanguageShortName) => + _dbContext.Offers + .AsNoTracking() + .Where(offer => offer!.Id == offerId && offer.OfferTypeId == offerTypeId) + .Select(offer => new + { + Active = offer.OfferStatusId == OfferStatusId.ACTIVE, + Roles = offer.UserRoles + }) + .Select(x => new ValueTuple?>( + true, + x.Active, + x.Active + ? x.Roles.Select(role => + new ActiveAppRoleDetails( + role.UserRoleText, + role.UserRoleDescriptions.Where(description => + (languageShortName != null && description.LanguageShortName == languageShortName) || + description.LanguageShortName == defaultLanguageShortName) + .Select(description => new ActiveAppUserRoleDescription( + description.LanguageShortName, + description.Description)))) + : null)) + .SingleOrDefaultAsync(); } diff --git a/tests/marketplace/Apps.Service.Tests/BusinessLogic/AppChangeBusinessLogicTest.cs b/tests/marketplace/Apps.Service.Tests/BusinessLogic/AppChangeBusinessLogicTest.cs index d4bf6e2ecc..027065e34f 100644 --- a/tests/marketplace/Apps.Service.Tests/BusinessLogic/AppChangeBusinessLogicTest.cs +++ b/tests/marketplace/Apps.Service.Tests/BusinessLogic/AppChangeBusinessLogicTest.cs @@ -1147,4 +1147,77 @@ public async Task CreateActiveAppDocumentAsync_ReturnsExpected() #endregion + #region GetActiveAppRoles + + [Fact] + public async Task GetActiveAppRolesAsync_Throws_NotFoundException() + { + // Arrange + var appId = _fixture.Create(); + var activeAppRoleDetails = default((bool, bool, IEnumerable)); + A.CallTo(() => _userRolesRepository.GetActiveAppRolesAsync(A._, A._, A._, A._)) + .Returns(activeAppRoleDetails); + + // Act + Task Act() => _sut.GetActiveAppRolesAsync(appId, null); + + // Assert + var result = await Assert.ThrowsAsync(Act); + result.Message.Should().Be($"App {appId} does not exist"); + A.CallTo(() => _userRolesRepository.GetActiveAppRolesAsync(appId, OfferTypeId.APP, null, "en")) + .MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task GetActiveAppRolesAsync_Throws_ConflictException() + { + // Arrange + var appId = _fixture.Create(); + var activeAppRoleDetails = (true, false, _fixture.CreateMany()); + A.CallTo(() => _userRolesRepository.GetActiveAppRolesAsync(A._, A._, A._, A._)) + .Returns(activeAppRoleDetails); + + // Act + Task Act() => _sut.GetActiveAppRolesAsync(appId, "de"); + + // Assert + var result = await Assert.ThrowsAsync(Act); + result.Message.Should().Be($"App {appId} is not Active"); + A.CallTo(() => _userRolesRepository.GetActiveAppRolesAsync(appId, OfferTypeId.APP, "de", "en")) + .MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task GetActiveAppRolesAsync_ReturnsExpected() + { + // Arrange + var appId = _fixture.Create(); + var userRole1 = new ActiveAppRoleDetails("TestRole1", [ + new ActiveAppUserRoleDescription("en", "TestRole1 description") + ]); + var userRole2 = new ActiveAppRoleDetails("TestRole2", [ + new ActiveAppUserRoleDescription("en", "TestRole2 description") + ]); + var activeAppRoleDetails = (true, true, new[] { + userRole1, + userRole2 + }); + + A.CallTo(() => _userRolesRepository.GetActiveAppRolesAsync(A._, A._, A._, A._)) + .Returns(activeAppRoleDetails); + + // Act + var result = await _sut.GetActiveAppRolesAsync(appId, "de"); + + // Assert + result.Should().HaveCount(2) + .And.Satisfy( + x => x.Role == "TestRole1" && x.Descriptions.Count() == 1 && x.Descriptions.Single().Description == "TestRole1 description", + x => x.Role == "TestRole2" && x.Descriptions.Count() == 1 && x.Descriptions.Single().Description == "TestRole2 description"); + A.CallTo(() => _userRolesRepository.GetActiveAppRolesAsync(appId, OfferTypeId.APP, "de", "en")) + .MustHaveHappenedOnceExactly(); + } + + #endregion + } diff --git a/tests/marketplace/Apps.Service.Tests/Controllers/AppChangeControllerTest.cs b/tests/marketplace/Apps.Service.Tests/Controllers/AppChangeControllerTest.cs index 697a26d76e..be12e1f7fa 100644 --- a/tests/marketplace/Apps.Service.Tests/Controllers/AppChangeControllerTest.cs +++ b/tests/marketplace/Apps.Service.Tests/Controllers/AppChangeControllerTest.cs @@ -20,7 +20,6 @@ using AutoFixture; using FakeItEasy; using FluentAssertions; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Org.Eclipse.TractusX.Portal.Backend.Apps.Service.BusinessLogic; using Org.Eclipse.TractusX.Portal.Backend.Apps.Service.ViewModels; @@ -29,6 +28,7 @@ using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Identities; using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared; using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.Extensions; +using System.Collections.Immutable; using Xunit; namespace Org.Eclipse.TractusX.Portal.Backend.Apps.Service.Controllers.Tests; @@ -193,4 +193,25 @@ public async Task CreateActiveAppDocumentAsync_ReturnsExpected() // Assert A.CallTo(() => _logic.CreateActiveAppDocumentAsync(appId, documentTypeId, file, CancellationToken.None)).MustHaveHappened(); } + + [Fact] + public async Task GetActiveAppRolesAsync_ReturnsExpected() + { + // Arrange + var appId = _fixture.Create(); + var language = _fixture.Create(); + var activeAppRoleDetails = _fixture.CreateMany(5).ToImmutableArray(); + + A.CallTo(() => _logic.GetActiveAppRolesAsync(A._, A._)) + .Returns(activeAppRoleDetails); + + // Act + var result = await _controller.GetActiveAppRolesAsync(appId, language); + + // Assert + A.CallTo(() => _logic.GetActiveAppRolesAsync(appId, language)) + .MustHaveHappenedOnceExactly(); + result.Should().HaveSameCount(activeAppRoleDetails) + .And.ContainInOrder(activeAppRoleDetails); + } } diff --git a/tests/portalbackend/PortalBackend.DBAccess.Tests/UserRolesRepositoryTests.cs b/tests/portalbackend/PortalBackend.DBAccess.Tests/UserRolesRepositoryTests.cs index 0da0bf87fd..e272b2393e 100644 --- a/tests/portalbackend/PortalBackend.DBAccess.Tests/UserRolesRepositoryTests.cs +++ b/tests/portalbackend/PortalBackend.DBAccess.Tests/UserRolesRepositoryTests.cs @@ -21,6 +21,7 @@ using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Configuration; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Tests.Setup; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums; using Xunit.Extensions.AssemblyFixture; namespace Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Tests; @@ -167,10 +168,10 @@ public async Task GetUserRoleDataUntrackedAsync_WithNotMatchingClient_ReturnsEmp { // Arrange var userRoleConfig = new[]{ - new UserRoleConfig("not-existing-client", new [] - { + new UserRoleConfig("not-existing-client", + [ "Company Admin" - })}; + ])}; var sut = await CreateSut(); // Act @@ -182,6 +183,57 @@ public async Task GetUserRoleDataUntrackedAsync_WithNotMatchingClient_ReturnsEmp #endregion + #region GetActiveAppRoles + + [Fact] + public async Task GetActiveAppRolesAsync_NonExistingApp_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var data = await sut.GetActiveAppRolesAsync(new Guid("deadbeef-dead-beef-dead-beefdeadbeef"), OfferTypeId.APP, "de", Constants.DefaultLanguage); + + // Assert + data.IsValid.Should().BeFalse(); + data.IsActive.Should().BeFalse(); + data.AppRoleDetails.Should().BeNull(); + } + + [Fact] + public async Task GetActiveAppRolesAsync_InActiveApp_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var data = await sut.GetActiveAppRolesAsync(new Guid("99C5FD12-8085-4DE2-ABFD-215E1EE4BAA7"), OfferTypeId.APP, "de", Constants.DefaultLanguage); + + // Assert + data.IsValid.Should().BeTrue(); + data.IsActive.Should().BeFalse(); + data.AppRoleDetails.Should().BeNull(); + } + + [Fact] + public async Task GetActiveAppRolesAsync_ActiveApp_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var data = await sut.GetActiveAppRolesAsync(new Guid("ac1cf001-7fbc-1f2f-817f-bce05744000b"), OfferTypeId.APP, "de", Constants.DefaultLanguage); + + // Assert + data.IsValid.Should().BeTrue(); + data.IsActive.Should().BeTrue(); + data.AppRoleDetails.Should().HaveCount(2) + .And.Satisfy( + x => x.Role == "EarthCommerce.AdministratorRC_QAS2" && x.Descriptions.Count() == 2 && x.Descriptions.Any(x => x.LanguageCode == "de") && x.Descriptions.Any(x => x.LanguageCode == "en"), + x => x.Role == "EarthCommerce.Advanced.BuyerRC_QAS2" && x.Descriptions.Count() == 2 && x.Descriptions.Any(x => x.LanguageCode == "de") && x.Descriptions.Any(x => x.LanguageCode == "en")); + } + + #endregion private async Task CreateSut() { var context = await _dbTestDbFixture.GetPortalDbContext();