Skip to content

Commit

Permalink
feat(apps): added endpoint to fetch app active documents (#266)
Browse files Browse the repository at this point in the history
* feat(apps): added endpoint to fetch app active documents
---------
Ref: CPLP-3087
Co-authored-by: Norbert Truchsess <[email protected]>
Reviewed-by: Norbert Truchsess <[email protected]>
  • Loading branch information
VPrasannaK94 authored Oct 9, 2023
1 parent 071751c commit f93caaa
Show file tree
Hide file tree
Showing 13 changed files with 253 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Identities;
using Org.Eclipse.TractusX.Portal.Backend.Provisioning.Library;
using System.Text.Json;

Expand All @@ -48,6 +49,7 @@ public class AppChangeBusinessLogic : IAppChangeBusinessLogic
private readonly INotificationService _notificationService;
private readonly IProvisioningManager _provisioningManager;
private readonly IOfferService _offerService;
private readonly IIdentityService _identityService;

/// <summary>
/// Constructor.
Expand All @@ -56,14 +58,22 @@ public class AppChangeBusinessLogic : IAppChangeBusinessLogic
/// <param name="notificationService">the notification service</param>
/// <param name="provisioningManager">The provisioning manager</param>
/// <param name="settings">Settings for the app change bl</param>
/// <param name="identityService">Identity</param>
/// <param name="offerService">Offer Servicel</param>
public AppChangeBusinessLogic(IPortalRepositories portalRepositories, INotificationService notificationService, IProvisioningManager provisioningManager, IOfferService offerService, IOptions<AppsSettings> settings)
public AppChangeBusinessLogic(
IPortalRepositories portalRepositories,
INotificationService notificationService,
IProvisioningManager provisioningManager,
IOfferService offerService,
IOptions<AppsSettings> settings,
IIdentityService identityService)
{
_portalRepositories = portalRepositories;
_notificationService = notificationService;
_provisioningManager = provisioningManager;
_settings = settings.Value;
_offerService = offerService;
_identityService = identityService;
}

/// <inheritdoc/>
Expand Down Expand Up @@ -283,4 +293,25 @@ private async Task UpdateTenantUrlAsyncInternal(Guid offerId, Guid subscriptionI

await _portalRepositories.SaveAsync().ConfigureAwait(false);
}

/// <inheritdoc />
public async Task<ActiveAppDocumentData> GetActiveAppDocumentTypeDataAsync(Guid appId)
{
var appDocTypeData = await _portalRepositories.GetInstance<IOfferRepository>()
.GetActiveOfferDocumentTypeDataOrderedAsync(appId, _identityService.IdentityData.CompanyId, OfferTypeId.APP, _settings.ActiveAppDocumentTypeIds)
.PreSortedGroupBy(result => result.DocumentTypeId)
.ToDictionaryAsync(
group => group.Key,
group => group.Select(result =>
new DocumentData(
result.DocumentId,
result.DocumentName)))
.ConfigureAwait(false);
return new ActiveAppDocumentData(
_settings.ActiveAppDocumentTypeIds.ToDictionary(
documentTypeId => documentTypeId,
documentTypeId => appDocTypeData.TryGetValue(documentTypeId, out var data)
? data
: Enumerable.Empty<DocumentData>()));
}
}
8 changes: 8 additions & 0 deletions src/marketplace/Apps.Service/BusinessLogic/AppsSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,14 @@ public class AppsSettings
/// </summary>
[Required(AllowEmptyStrings = false)]
public string ActivationPortalAddress { get; init; } = null!;

/// <summary>
/// Active Document Types
/// </summary>
[Required]
[EnumEnumeration]
[DistinctValues]
public IEnumerable<DocumentTypeId> ActiveAppDocumentTypeIds { get; set; } = null!;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,10 @@ public interface IAppChangeBusinessLogic
/// <param name="data">the data to update the url</param>
/// <param name="companyId"></param>
Task UpdateTenantUrlAsync(Guid offerId, Guid subscriptionId, UpdateTenantData data, Guid companyId);

/// <summary>
/// Gets the Active App Documents
/// </summary>
/// <param name="appId">Id of the offer</param>
Task<ActiveAppDocumentData> GetActiveAppDocumentTypeDataAsync(Guid appId);
}
16 changes: 16 additions & 0 deletions src/marketplace/Apps.Service/Controllers/AppChangeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -186,4 +186,20 @@ public async Task<NoContentResult> UpdateTenantUrl([FromRoute] Guid appId, [From
await this.WithCompanyId(companyId => _businessLogic.UpdateTenantUrlAsync(appId, subscriptionId, data, companyId)).ConfigureAwait(false);
return NoContent();
}

/// <summary>
/// Returns the Active App Documents
/// </summary>
/// <param name="appId" example="092bdae3-a044-4314-94f4-85c65a09e31b">Id of the app.</param>
/// <remarks>Example: GET /apps/appchange/{appId}/documents</remarks>
/// <response code="200">Gets the Active Apps documents</response>
/// <response code="404">If App does not exists</response>
[HttpGet]
[Route("{appId}/documents")]
[Authorize(Roles = "edit_apps")]
[Authorize(Policy = PolicyTypes.ValidCompany)]
[ProducesResponseType(typeof(ActiveAppDocumentData), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status404NotFound)]
public async Task<ActiveAppDocumentData> GetActiveAppDocuments([FromRoute] Guid appId) =>
await _businessLogic.GetActiveAppDocumentTypeDataAsync(appId).ConfigureAwait(false);
}
7 changes: 7 additions & 0 deletions src/marketplace/Apps.Service/ViewModels/AppData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/

using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums;

namespace Org.Eclipse.TractusX.Portal.Backend.Apps.Service.ViewModels;
Expand All @@ -43,3 +44,9 @@ public record AppData(
string Price,
Guid LeadPictureId,
IEnumerable<string> UseCases);

/// <summary>
/// View model of an Active App Documents
/// </summary>
/// <param name="Documents">Id of the App.</param>
public record ActiveAppDocumentData(IDictionary<DocumentTypeId, IEnumerable<DocumentData>> Documents);
Original file line number Diff line number Diff line change
Expand Up @@ -487,4 +487,13 @@ public interface IOfferRepository
/// <param name="subscriptionId"></param>
/// <returns></returns>
Task<(bool IsSingleInstance, IEnumerable<IEnumerable<UserRoleData>> ServiceAccountProfiles, string? OfferName)> GetServiceAccountProfileDataForSubscription(Guid subscriptionId);

/// <summary>
/// Gets the Active Offer DocumentType Data
/// </summary>
/// <param name="offerId"></param>
/// <param name="offerTypeId"></param>
/// <param name="documentTypeIds"></param>
/// <returns></returns>
IAsyncEnumerable<DocumentTypeData> GetActiveOfferDocumentTypeDataOrderedAsync(Guid offerId, Guid userCompanyId, OfferTypeId offerTypeId, IEnumerable<DocumentTypeId> documentTypeIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -822,4 +822,20 @@ public void AttachAndModifyAppInstanceSetup(Guid appInstanceSetupId, Guid offerI
o.Offer.Name
))
.SingleOrDefaultAsync();

/// <inheritdoc />
public IAsyncEnumerable<DocumentTypeData> GetActiveOfferDocumentTypeDataOrderedAsync(Guid offerId, Guid userCompanyId, OfferTypeId offerTypeId, IEnumerable<DocumentTypeId> documentTypeIds) =>
_context.OfferAssignedDocuments
.Where(oad => oad.OfferId == offerId &&
oad.Offer!.OfferStatusId == OfferStatusId.ACTIVE &&
oad.Offer.OfferTypeId == offerTypeId &&
oad.Offer.ProviderCompanyId == userCompanyId &&
oad.Document!.DocumentStatusId != DocumentStatusId.INACTIVE &&
documentTypeIds.Contains(oad.Document!.DocumentTypeId))
.OrderBy(oad => oad.Document!.DocumentTypeId)
.Select(oad => new DocumentTypeData(
oad.Document!.DocumentTypeId,
oad.Document.Id,
oad.Document.DocumentName))
.ToAsyncEnumerable();
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
using AutoFixture.AutoFakeItEasy;
using FakeItEasy;
using FluentAssertions;
using Flurl.Util;
using Microsoft.Extensions.Options;
using MimeKit.Encodings;
using Org.BouncyCastle.Utilities.Collections;
using Org.Eclipse.TractusX.Portal.Backend.Apps.Service.ViewModels;
using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling;
using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Configuration;
Expand Down Expand Up @@ -60,6 +63,7 @@ public class AppChangeBusinessLogicTest
private readonly IDocumentRepository _documentRepository;
private readonly INotificationService _notificationService;
private readonly IOfferService _offerService;
private readonly IIdentityService _identityService;
private readonly AppChangeBusinessLogic _sut;

public AppChangeBusinessLogicTest()
Expand All @@ -78,6 +82,7 @@ public AppChangeBusinessLogicTest()
_documentRepository = A.Fake<IDocumentRepository>();
_notificationService = A.Fake<INotificationService>();
_offerService = A.Fake<IOfferService>();
_identityService = A.Fake<IIdentityService>();

var settings = new AppsSettings
{
Expand All @@ -92,14 +97,21 @@ public AppChangeBusinessLogicTest()
CompanyAdminRoles = new[]
{
new UserRoleConfig(ClientId, new [] { "Company Admin" })
},
ActiveAppDocumentTypeIds = new[]
{
DocumentTypeId.APP_IMAGE,
DocumentTypeId.APP_TECHNICAL_INFORMATION,
DocumentTypeId.APP_CONTRACT,
DocumentTypeId.ADDITIONAL_DETAILS
}
};
A.CallTo(() => _portalRepositories.GetInstance<INotificationRepository>()).Returns(_notificationRepository);
A.CallTo(() => _portalRepositories.GetInstance<IOfferRepository>()).Returns(_offerRepository);
A.CallTo(() => _portalRepositories.GetInstance<IOfferSubscriptionsRepository>()).Returns(_offerSubscriptionsRepository);
A.CallTo(() => _portalRepositories.GetInstance<IUserRolesRepository>()).Returns(_userRolesRepository);
A.CallTo(() => _portalRepositories.GetInstance<IDocumentRepository>()).Returns(_documentRepository);
_sut = new AppChangeBusinessLogic(_portalRepositories, _notificationService, _provisioningManager, _offerService, Options.Create(settings));
_sut = new AppChangeBusinessLogic(_portalRepositories, _notificationService, _provisioningManager, _offerService, Options.Create(settings), _identityService);
}

#region AddActiveAppUserRole
Expand Down Expand Up @@ -941,4 +953,42 @@ public async Task UpdateTenantUrlAsync_WithoutSubscriptionDetails_ThrowsConflict
}

#endregion

#region GetActiveAppDocumentTypeDataAsync

[Fact]
public async Task GetActiveAppDocumentTypeDataAsync_ReturnsExpected()
{
// Arrange
var appId = _fixture.Create<Guid>();
var documentId1 = _fixture.Create<Guid>();
var documentId2 = _fixture.Create<Guid>();
var documentId3 = _fixture.Create<Guid>();
var documentId4 = _fixture.Create<Guid>();
var documentId5 = _fixture.Create<Guid>();
var documentData = new[] {
new DocumentTypeData(DocumentTypeId.ADDITIONAL_DETAILS, documentId1, "TestDoc1"),
new DocumentTypeData(DocumentTypeId.ADDITIONAL_DETAILS, documentId2, "TestDoc2"),
new DocumentTypeData(DocumentTypeId.APP_IMAGE, documentId3, "TestDoc3"),
new DocumentTypeData(DocumentTypeId.APP_IMAGE, documentId4, "TestDoc4"),
new DocumentTypeData(DocumentTypeId.APP_TECHNICAL_INFORMATION, documentId5, "TestDoc5"),
}.ToAsyncEnumerable();

A.CallTo(() => _offerRepository.GetActiveOfferDocumentTypeDataOrderedAsync(A<Guid>._, A<Guid>._, OfferTypeId.APP, A<IEnumerable<DocumentTypeId>>._))
.Returns(documentData);

// Act
var result = await _sut.GetActiveAppDocumentTypeDataAsync(appId).ConfigureAwait(false);

// Assert
A.CallTo(() => _offerRepository.GetActiveOfferDocumentTypeDataOrderedAsync(A<Guid>._, A<Guid>._, OfferTypeId.APP, A<IEnumerable<DocumentTypeId>>._)).MustHaveHappened();
result.Documents.Should().NotBeNull().And.HaveCount(4).And.Satisfy(
x => x.Key == DocumentTypeId.APP_IMAGE && x.Value.SequenceEqual(new DocumentData[] { new(documentId3, "TestDoc3"), new(documentId4, "TestDoc4") }),
x => x.Key == DocumentTypeId.APP_TECHNICAL_INFORMATION && x.Value.SequenceEqual(new DocumentData[] { new(documentId5, "TestDoc5") }),
x => x.Key == DocumentTypeId.APP_CONTRACT && !x.Value.Any(),
x => x.Key == DocumentTypeId.ADDITIONAL_DETAILS && x.Value.SequenceEqual(new DocumentData[] { new(documentId1, "TestDoc1"), new(documentId2, "TestDoc2") })
);
}

#endregion
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public AppChangeControllerTest()
{
_fixture = new Fixture();
_logic = A.Fake<IAppChangeBusinessLogic>();
this._controller = new AppChangeController(_logic);
_controller = new AppChangeController(_logic);
_controller.AddControllerContextWithClaim(IamUserId, _identity);
}

Expand All @@ -60,7 +60,7 @@ public async Task AddActiveAppUserRole_ReturnsExpectedCount()
.Returns(appRoleData);

//Act
var result = await this._controller.AddActiveAppUserRole(appId, appUserRoles).ConfigureAwait(false);
var result = await _controller.AddActiveAppUserRole(appId, appUserRoles).ConfigureAwait(false);
foreach (var item in result)
{
//Assert
Expand All @@ -81,7 +81,7 @@ public async Task GetAppUpdateDescriptionsAsync_ReturnsExpected()
.Returns(offerDescriptionData);

//Act
var result = await this._controller.GetAppUpdateDescriptionsAsync(appId).ConfigureAwait(false);
var result = await _controller.GetAppUpdateDescriptionsAsync(appId).ConfigureAwait(false);

//Assert
A.CallTo(() => _logic.GetAppUpdateDescriptionByIdAsync(A<Guid>._, A<Guid>._)).MustHaveHappened();
Expand All @@ -95,7 +95,7 @@ public async Task CreateOrUpdateAppDescriptionsAsync_ReturnsExpected()
var offerDescriptionData = _fixture.CreateMany<LocalizedDescription>(3);

//Act
var result = await this._controller.CreateOrUpdateAppDescriptionsByIdAsync(appId, offerDescriptionData).ConfigureAwait(false);
var result = await _controller.CreateOrUpdateAppDescriptionsByIdAsync(appId, offerDescriptionData).ConfigureAwait(false);

//Assert
A.CallTo(() => _logic.CreateOrUpdateAppDescriptionByIdAsync(A<Guid>._, A<Guid>._, A<IEnumerable<LocalizedDescription>>._)).MustHaveHappened();
Expand All @@ -112,7 +112,7 @@ public async Task UploadOfferAssignedAppLeadImageDocumentByIdAsync_ReturnsExpect
.ReturnsLazily(() => Task.CompletedTask);

// Act
var result = await this._controller.UploadOfferAssignedAppLeadImageDocumentByIdAsync(appId, file, CancellationToken.None).ConfigureAwait(false);
var result = await _controller.UploadOfferAssignedAppLeadImageDocumentByIdAsync(appId, file, CancellationToken.None).ConfigureAwait(false);

// Assert
A.CallTo(() => _logic.UploadOfferAssignedAppLeadImageDocumentByIdAsync(appId, A<ValueTuple<Guid, Guid>>.That.Matches(x => x.Item1 == _identity.UserId && x.Item2 == _identity.CompanyId), file, CancellationToken.None)).MustHaveHappenedOnceExactly();
Expand All @@ -126,7 +126,7 @@ public async Task DeactivateApp_ReturnsNoContent()
var appId = _fixture.Create<Guid>();

//Act
var result = await this._controller.DeactivateApp(appId).ConfigureAwait(false);
var result = await _controller.DeactivateApp(appId).ConfigureAwait(false);

//Assert
A.CallTo(() => _logic.DeactivateOfferByAppIdAsync(appId)).MustHaveHappenedOnceExactly();
Expand All @@ -141,10 +141,23 @@ public async Task UpdateTenantUrl_ReturnsExpected()
var data = new UpdateTenantData("http://test.com");

//Act
var result = await this._controller.UpdateTenantUrl(appId, subscriptionId, data).ConfigureAwait(false);
var result = await _controller.UpdateTenantUrl(appId, subscriptionId, data).ConfigureAwait(false);

//Assert
A.CallTo(() => _logic.UpdateTenantUrlAsync(appId, subscriptionId, data, _identity.CompanyId)).MustHaveHappened();
result.Should().BeOfType<NoContentResult>();
}

[Fact]
public async Task GetActiveAppDocuments_ReturnsExpected()
{
//Arrange
var appId = _fixture.Create<Guid>();

//Act
await _controller.GetActiveAppDocuments(appId).ConfigureAwait(false);

//Assert
A.CallTo(() => _logic.GetActiveAppDocumentTypeDataAsync(appId)).MustHaveHappened();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@
"APP_TECHNICAL_INFORMATION",
"CONFORMITY_APPROVAL_BUSINESS_APPS"
],
"ActiveAppDocumentTypeIds": [
"APP_IMAGE",
"APP_TECHNICAL_INFORMATION",
"APP_CONTRACT",
"ADDITIONAL_DETAILS"
],
"ITAdminRoles": [
{
"ClientId": "Cl2-CX-Portal",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1441,6 +1441,48 @@ public async Task GetServiceAccountProfileDataForSubscription_WithServiceAccount

#endregion

#region GetActiveOfferDocumentTypeData

[Fact]
public async Task GetActiveOfferDocumentTypeDataAsync_ReturnsExpectedResult()
{
// Arrange
var activeDocumentTypes = new[]{
DocumentTypeId.APP_IMAGE,
DocumentTypeId.APP_TECHNICAL_INFORMATION,
DocumentTypeId.APP_CONTRACT,
DocumentTypeId.ADDITIONAL_DETAILS
};
var sut = await CreateSut().ConfigureAwait(false);

// Act
var result = await sut.GetActiveOfferDocumentTypeDataOrderedAsync(
new("ac1cf001-7fbc-1f2f-817f-bce0572c0007"),
new("2dc4249f-b5ca-4d42-bef1-7a7a950a4f87"),
OfferTypeId.APP, activeDocumentTypes).ToListAsync().ConfigureAwait(false);

// Assert
result.Should().NotBeNull()
.And.BeInAscendingOrder(x => x.DocumentTypeId)
.And.HaveCount(4)
.And.Satisfy(
x => x.DocumentId == new Guid("e020787d-1e04-4c0b-9c06-bd1cd44724b2") &&
x.DocumentName == "Default_App_Image.png" &&
x.DocumentTypeId == DocumentTypeId.APP_IMAGE,
x => x.DocumentId == new Guid("0d68c68c-d689-474c-a3be-8493f99feab2") &&
x.DocumentName == "AdditionalServiceDetails.pdf" &&
x.DocumentTypeId == DocumentTypeId.ADDITIONAL_DETAILS,
x => x.DocumentId == new Guid("aaf53459-c36b-408e-a805-0b406ce9751e") &&
x.DocumentName == "AdditionalServiceDetails2.pdf" &&
x.DocumentTypeId == DocumentTypeId.ADDITIONAL_DETAILS,
x => x.DocumentId == new Guid("d9926bd9-bce0-4605-a083-7066ffe5147c") &&
x.DocumentName == "AdditionalTechnicalInfo.pdf" &&
x.DocumentTypeId == DocumentTypeId.APP_TECHNICAL_INFORMATION
);
}

#endregion

#region Setup

private async Task<OfferRepository> CreateSut()
Expand Down
Loading

0 comments on commit f93caaa

Please sign in to comment.