From 006050852174d2e3f5af42b90d11b335bec0bf26 Mon Sep 17 00:00:00 2001
From: leandro-cavalcante
<162341380+leandro-cavalcante@users.noreply.github.com>
Date: Mon, 11 Nov 2024 09:01:22 +0100
Subject: [PATCH] feat: Internal issuer component enhance get credential
endpoint by supporting filters
---
docs/api/issuer-service.yaml | 13 ++-
.../Models/StatusType.cs | 8 ++
.../CompanySsiDetailsRepository.cs | 7 +-
.../ICompanySsiDetailsRepository.cs | 3 +-
.../BusinessLogic/IIssuerBusinessLogic.cs | 2 +-
.../BusinessLogic/IssuerBusinessLogic.cs | 18 +++-
.../Controllers/IssuerController.cs | 6 +-
.../CompanySsiDetailsRepositoryTests.cs | 61 ++++++++++++-
.../BusinessLogic/IssuerBusinessLogicTests.cs | 91 ++++++++++++++++++-
9 files changed, 194 insertions(+), 15 deletions(-)
create mode 100644 src/database/SsiCredentialIssuer.DbAccess/Models/StatusType.cs
diff --git a/docs/api/issuer-service.yaml b/docs/api/issuer-service.yaml
index d0c9460b..964921eb 100644
--- a/docs/api/issuer-service.yaml
+++ b/docs/api/issuer-service.yaml
@@ -8,7 +8,12 @@ paths:
tags:
- 'Org.Eclipse.TractusX.SsiCredentialIssuer.Service, Version=1.2.0.0, Culture=neutral, PublicKeyToken=null'
summary: Gets all use case frameworks and the participation status of the acting company
- description: 'Example: GET: api/issuer/useCaseParticipation'
+ description: 'Example: GET: api/issuer/useCaseParticipation
Available values:
All, Active, Expired'
+ parameters:
+ - name: status
+ in: query
+ schema:
+ type: string
responses:
'200':
description: OK
@@ -30,6 +35,12 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
+ '400':
+ description: Bad Request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
'409':
description: Conflict
content:
diff --git a/src/database/SsiCredentialIssuer.DbAccess/Models/StatusType.cs b/src/database/SsiCredentialIssuer.DbAccess/Models/StatusType.cs
new file mode 100644
index 00000000..39de1391
--- /dev/null
+++ b/src/database/SsiCredentialIssuer.DbAccess/Models/StatusType.cs
@@ -0,0 +1,8 @@
+namespace Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Models;
+
+public enum StatusType
+{
+ Active,
+ Expired,
+ All
+}
diff --git a/src/database/SsiCredentialIssuer.DbAccess/Repositories/CompanySsiDetailsRepository.cs b/src/database/SsiCredentialIssuer.DbAccess/Repositories/CompanySsiDetailsRepository.cs
index 220585f8..5cf0aff1 100644
--- a/src/database/SsiCredentialIssuer.DbAccess/Repositories/CompanySsiDetailsRepository.cs
+++ b/src/database/SsiCredentialIssuer.DbAccess/Repositories/CompanySsiDetailsRepository.cs
@@ -30,7 +30,7 @@ public class CompanySsiDetailsRepository(IssuerDbContext context)
: ICompanySsiDetailsRepository
{
///
- public IAsyncEnumerable GetUseCaseParticipationForCompany(string bpnl, DateTimeOffset minExpiry) =>
+ public IAsyncEnumerable GetUseCaseParticipationForCompany(string bpnl, DateTimeOffset minExpiry, StatusType? statusType) =>
context.VerifiedCredentialTypes
.Where(t => t.VerifiedCredentialTypeAssignedKind!.VerifiedCredentialTypeKindId == VerifiedCredentialTypeKindId.FRAMEWORK)
.Select(t => new
@@ -38,6 +38,11 @@ public IAsyncEnumerable GetUseCaseParticipationForComp
t.VerifiedCredentialTypeAssignedUseCase!.UseCase,
TypeId = t.Id,
ExternalTypeDetails = t.VerifiedCredentialTypeAssignedExternalType!.VerifiedCredentialExternalType!.VerifiedCredentialExternalTypeDetailVersions
+ .Where(x => !statusType.HasValue || statusType == StatusType.All
+ || (statusType == StatusType.Active
+ && (x.Expiry > minExpiry))
+ || (statusType == StatusType.Expired && x.Expiry <= DateTimeOffset.UtcNow)
+ )
})
.Select(x => new UseCaseParticipationData(
x.UseCase!.Name,
diff --git a/src/database/SsiCredentialIssuer.DbAccess/Repositories/ICompanySsiDetailsRepository.cs b/src/database/SsiCredentialIssuer.DbAccess/Repositories/ICompanySsiDetailsRepository.cs
index 6291c544..0be2af40 100644
--- a/src/database/SsiCredentialIssuer.DbAccess/Repositories/ICompanySsiDetailsRepository.cs
+++ b/src/database/SsiCredentialIssuer.DbAccess/Repositories/ICompanySsiDetailsRepository.cs
@@ -31,8 +31,9 @@ public interface ICompanySsiDetailsRepository
///
/// Bpnl of the company
/// The minimum datetime the expiry date should have
+ /// the status type on how the credentials should be filtered
/// AsyncEnumerable of UseCaseParticipation
- IAsyncEnumerable GetUseCaseParticipationForCompany(string bpnl, DateTimeOffset minExpiry);
+ IAsyncEnumerable GetUseCaseParticipationForCompany(string bpnl, DateTimeOffset minExpiry, StatusType? statusType);
///
/// Gets the company credential details for the given company id
diff --git a/src/issuer/SsiCredentialIssuer.Service/BusinessLogic/IIssuerBusinessLogic.cs b/src/issuer/SsiCredentialIssuer.Service/BusinessLogic/IIssuerBusinessLogic.cs
index f3721e57..40d79778 100644
--- a/src/issuer/SsiCredentialIssuer.Service/BusinessLogic/IIssuerBusinessLogic.cs
+++ b/src/issuer/SsiCredentialIssuer.Service/BusinessLogic/IIssuerBusinessLogic.cs
@@ -26,7 +26,7 @@ namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.BusinessLogic;
public interface IIssuerBusinessLogic
{
- IAsyncEnumerable GetUseCaseParticipationAsync();
+ IAsyncEnumerable GetUseCaseParticipationAsync(string? status);
IAsyncEnumerable GetSsiCertificatesAsync();
diff --git a/src/issuer/SsiCredentialIssuer.Service/BusinessLogic/IssuerBusinessLogic.cs b/src/issuer/SsiCredentialIssuer.Service/BusinessLogic/IssuerBusinessLogic.cs
index d9a03a3a..76b76636 100644
--- a/src/issuer/SsiCredentialIssuer.Service/BusinessLogic/IssuerBusinessLogic.cs
+++ b/src/issuer/SsiCredentialIssuer.Service/BusinessLogic/IssuerBusinessLogic.cs
@@ -85,10 +85,22 @@ public IssuerBusinessLogic(
}
///
- public IAsyncEnumerable GetUseCaseParticipationAsync() =>
- _repositories
+ public IAsyncEnumerable GetUseCaseParticipationAsync(string? status)
+ {
+ StatusType? statusTypeResult = null;
+ if (!string.IsNullOrEmpty(status))
+ {
+ if (!(Enum.TryParse(status, ignoreCase: true, out var statusType) || !Enum.IsDefined(typeof(StatusType), statusType)))
+ {
+ throw new ArgumentException($"Status value {status} is not valid; please use Active, Expired or All");
+ }
+ statusTypeResult = statusType;
+ }
+
+ return _repositories
.GetInstance()
- .GetUseCaseParticipationForCompany(_identity.Bpnl, _dateTimeProvider.OffsetNow);
+ .GetUseCaseParticipationForCompany(_identity.Bpnl, _dateTimeProvider.OffsetNow, statusTypeResult);
+ }
///
public IAsyncEnumerable GetSsiCertificatesAsync() =>
diff --git a/src/issuer/SsiCredentialIssuer.Service/Controllers/IssuerController.cs b/src/issuer/SsiCredentialIssuer.Service/Controllers/IssuerController.cs
index 55aec7e5..1d12b849 100644
--- a/src/issuer/SsiCredentialIssuer.Service/Controllers/IssuerController.cs
+++ b/src/issuer/SsiCredentialIssuer.Service/Controllers/IssuerController.cs
@@ -42,9 +42,10 @@ public static RouteGroupBuilder MapIssuerApi(this RouteGroupBuilder group)
{
var issuer = group.MapGroup("/issuer");
- issuer.MapGet("useCaseParticipation", (IIssuerBusinessLogic logic) => logic.GetUseCaseParticipationAsync())
+ issuer.MapGet("useCaseParticipation", (IIssuerBusinessLogic logic,
+ [FromQuery(Name = "status")] string? status) => logic.GetUseCaseParticipationAsync(status))
.WithSwaggerDescription("Gets all use case frameworks and the participation status of the acting company",
- "Example: GET: api/issuer/useCaseParticipation")
+ "Example: GET: api/issuer/useCaseParticipation
Available values:
All, Active, Expired")
.RequireAuthorization(r =>
{
r.RequireRole("view_use_case_participation");
@@ -52,6 +53,7 @@ public static RouteGroupBuilder MapIssuerApi(this RouteGroupBuilder group)
})
.WithDefaultResponses()
.Produces(StatusCodes.Status200OK, typeof(IEnumerable), Constants.JsonContentType)
+ .Produces(StatusCodes.Status400BadRequest, typeof(ErrorResponse), Constants.JsonContentType)
.Produces(StatusCodes.Status409Conflict, typeof(ErrorResponse), Constants.JsonContentType);
issuer.MapGet("certificates", (IIssuerBusinessLogic logic) => logic.GetSsiCertificatesAsync())
diff --git a/tests/database/SsiCredentialIssuer.DbAccess.Tests/CompanySsiDetailsRepositoryTests.cs b/tests/database/SsiCredentialIssuer.DbAccess.Tests/CompanySsiDetailsRepositoryTests.cs
index c9ed176a..cc12f4dd 100644
--- a/tests/database/SsiCredentialIssuer.DbAccess.Tests/CompanySsiDetailsRepositoryTests.cs
+++ b/tests/database/SsiCredentialIssuer.DbAccess.Tests/CompanySsiDetailsRepositoryTests.cs
@@ -22,6 +22,7 @@
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Org.Eclipse.TractusX.SsiCredentialIssuer.DbAccess.Tests.Setup;
+using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Models;
using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Repositories;
using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities;
using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities;
@@ -49,14 +50,16 @@ public CompanySsiDetailsRepositoryTests(TestDbFixture testDbFixture)
#region GetDetailsForCompany
- [Fact]
- public async Task GetDetailsForCompany_WithValidData_ReturnsExpected()
+ [Theory]
+ [InlineData(null)]
+ [InlineData(StatusType.All)]
+ public async Task GetDetailsForCompany_WithValidData_And_StatusType_ReturnsExpected(StatusType? statusType)
{
// Arrange
var sut = await CreateSut();
// Act
- var result = await sut.GetUseCaseParticipationForCompany(ValidBpnl, DateTimeOffset.MinValue).ToListAsync();
+ var result = await sut.GetUseCaseParticipationForCompany(ValidBpnl, DateTimeOffset.MinValue, statusType).ToListAsync();
// Assert
result.Should().HaveCount(10);
@@ -84,7 +87,7 @@ public async Task GetDetailsForCompany_WithExpiryFilter_ReturnsExpected()
var sut = await CreateSut();
// Act
- var result = await sut.GetUseCaseParticipationForCompany(ValidBpnl, dt).ToListAsync();
+ var result = await sut.GetUseCaseParticipationForCompany(ValidBpnl, dt, null).ToListAsync();
// Assert
result.Should().HaveCount(10);
@@ -104,6 +107,56 @@ public async Task GetDetailsForCompany_WithExpiryFilter_ReturnsExpected()
x => x.ExternalDetailData.Version == "3.0" && !x.SsiDetailData.Any());
}
+ [Fact]
+ public async Task GetAllExternalTypeDetailDataWithValidData_WithValidData_and_StatusType_Active_ReturnsExpected()
+ {
+ // Arrange
+ var (sut, context) = await CreateSutWithContext();
+
+ //Act
+
+ var result = await sut.GetUseCaseParticipationForCompany(ValidBpnl, DateTimeOffset.UtcNow, StatusType.Active).ToListAsync();
+
+ // Assert
+ result.Should().HaveCount(10);
+ result.SelectMany(x => x.VerifiedCredentials)
+ .Select(x => x.ExternalDetailData)
+ .Should().HaveCount(1);
+ }
+
+ [Fact]
+ public async Task GetAllExternalTypeDetailDataWithValidData_and_StatusType_All_ReturnsExpected()
+ {
+ // Arrange
+ var sut = await CreateSut();
+
+ //Act
+
+ var result = await sut.GetUseCaseParticipationForCompany(ValidBpnl, DateTimeOffset.UtcNow, StatusType.All).ToListAsync();
+
+ // Assert
+ result.Should().HaveCount(10);
+ result.SelectMany(x => x.VerifiedCredentials)
+ .Select(x => x.ExternalDetailData)
+ .Should().HaveCount(11);
+ }
+
+ [Fact]
+ public async Task GetAllExternalTypeDetailDataWithValidData_WithValidData_and_StatusType_Expired_ReturnsExpected()
+ {
+ // Arrange
+ var (sut, context) = await CreateSutWithContext();
+ //Act
+
+ var result = await sut.GetUseCaseParticipationForCompany(ValidBpnl, DateTimeOffset.UtcNow, StatusType.Expired).ToListAsync();
+
+ // Assert
+ result.Should().HaveCount(10);
+ result.SelectMany(x => x.VerifiedCredentials)
+ .Select(x => x.ExternalDetailData)
+ .Should().HaveCount(10);
+ }
+
#endregion
#region GetAllCredentialDetails
diff --git a/tests/issuer/SsiCredentialIssuer.Service.Tests/BusinessLogic/IssuerBusinessLogicTests.cs b/tests/issuer/SsiCredentialIssuer.Service.Tests/BusinessLogic/IssuerBusinessLogicTests.cs
index f159cc07..78d26411 100644
--- a/tests/issuer/SsiCredentialIssuer.Service.Tests/BusinessLogic/IssuerBusinessLogicTests.cs
+++ b/tests/issuer/SsiCredentialIssuer.Service.Tests/BusinessLogic/IssuerBusinessLogicTests.cs
@@ -123,7 +123,7 @@ public async Task GetUseCaseParticipationAsync_ReturnsExpected()
Setup_GetUseCaseParticipationAsync();
// Act
- var result = await _sut.GetUseCaseParticipationAsync().ToListAsync();
+ var result = await _sut.GetUseCaseParticipationAsync(null).ToListAsync();
// Assert
result.Should().HaveCount(5);
@@ -163,6 +163,93 @@ public async Task GetCredentialsForBpn_ReturnsExpected()
result.Should().HaveCount(5);
}
+ [Fact]
+ public async Task GetUseCaseParticipationAsync_WithValidStatus_ReturnsExpectedResults()
+ {
+ // Arrange
+ var status = "Active";
+ var now = DateTimeOffset.Now;
+ var verifiedCredentials = _fixture.Build()
+ .With(x => x.SsiDetailData, _fixture.CreateMany(1))
+ .CreateMany(5);
+ var expectedData = new List
+ {
+ new UseCaseParticipationData("Test", "Test", VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, verifiedCredentials)
+ }.ToAsyncEnumerable();
+
+ A.CallTo(() => _dateTimeProvider.OffsetNow).Returns(now);
+ A.CallTo(() => _companySsiDetailsRepository.GetUseCaseParticipationForCompany(_identity.Bpnl, now, StatusType.Active))
+ .Returns(expectedData);
+
+ // Act
+ var result = await _sut.GetUseCaseParticipationAsync(status).ToListAsync();
+
+ // Assert
+ result.Should().BeEquivalentTo(expectedData.ToEnumerable());
+ }
+
+ [Fact]
+ public async Task GetUseCaseParticipationAsync_WithInvalidStatus_ThrowsArgumentException()
+ {
+ // Arrange
+ var status = "InvalidStatus";
+
+ // Act
+ Func act = async () => await _sut.GetUseCaseParticipationAsync(status).ToListAsync();
+
+ // Assert
+ await act.Should().ThrowAsync()
+ .WithMessage("Status value InvalidStatus is not valid; please use Active, Expired or All");
+ }
+
+ [Fact]
+ public async Task GetUseCaseParticipationAsync_WithNullStatus_ReturnsExpectedResults()
+ {
+ // Arrange
+ var now = DateTimeOffset.Now;
+ var verifiedCredentials = _fixture.Build()
+ .With(x => x.SsiDetailData, _fixture.CreateMany(1))
+ .CreateMany(5);
+ var expectedData = new List
+ {
+ new UseCaseParticipationData("Test", "Test", VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, verifiedCredentials)
+ }.ToAsyncEnumerable();
+
+ A.CallTo(() => _dateTimeProvider.OffsetNow).Returns(now);
+ A.CallTo(() => _companySsiDetailsRepository.GetUseCaseParticipationForCompany(_identity.Bpnl, now, null))
+ .Returns(expectedData);
+
+ // Act
+ var result = await _sut.GetUseCaseParticipationAsync(null).ToListAsync();
+
+ // Assert
+ result.Should().BeEquivalentTo(expectedData.ToEnumerable());
+ }
+
+ [Fact]
+ public async Task GetUseCaseParticipationAsync_WithAllStatus_ReturnsExpectedResults()
+ {
+ // Arrange
+ var now = DateTimeOffset.Now;
+ var verifiedCredentials = _fixture.Build()
+ .With(x => x.SsiDetailData, _fixture.CreateMany(1))
+ .CreateMany(5);
+ var expectedData = new List
+ {
+ new UseCaseParticipationData("Test", "Test", VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, verifiedCredentials)
+ }.ToAsyncEnumerable();
+
+ A.CallTo(() => _dateTimeProvider.OffsetNow).Returns(now);
+ A.CallTo(() => _companySsiDetailsRepository.GetUseCaseParticipationForCompany(_identity.Bpnl, now, StatusType.All))
+ .Returns(expectedData);
+
+ // Act
+ var result = await _sut.GetUseCaseParticipationAsync("All").ToListAsync();
+
+ // Assert
+ result.Should().BeEquivalentTo(expectedData.ToEnumerable());
+ }
+
#endregion
#region ApproveCredential
@@ -1081,7 +1168,7 @@ private void Setup_GetUseCaseParticipationAsync()
var verifiedCredentials = _fixture.Build()
.With(x => x.SsiDetailData, _fixture.CreateMany(1))
.CreateMany(5);
- A.CallTo(() => _companySsiDetailsRepository.GetUseCaseParticipationForCompany(Bpnl, A._))
+ A.CallTo(() => _companySsiDetailsRepository.GetUseCaseParticipationForCompany(Bpnl, A._, null))
.Returns(_fixture.Build().With(x => x.VerifiedCredentials, verifiedCredentials).CreateMany(5).ToAsyncEnumerable());
}