Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(approval): send mail and notification to requester #101

Merged
merged 1 commit into from
May 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ public record SsiApprovalData(
VerifiedCredentialTypeId Type,
Guid? ProcessId,
VerifiedCredentialTypeKindId? Kind,
string? Bpn,
string Bpn,
string UserId,
JsonDocument? Schema,
DetailData? DetailData
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ public IAsyncEnumerable<OwnedVerifiedCredentialData> GetOwnCredentialDetails(str
x.ProcessId,
x.VerifiedCredentialType!.VerifiedCredentialTypeAssignedKind == null ? null : x.VerifiedCredentialType!.VerifiedCredentialTypeAssignedKind!.VerifiedCredentialTypeKindId,
x.Bpnl,
x.CreatorUserId,
x.CompanySsiProcessData!.Schema,
x.VerifiedCredentialExternalTypeDetailVersion == null ?
null :
Expand All @@ -213,13 +214,14 @@ public IAsyncEnumerable<OwnedVerifiedCredentialData> GetOwnCredentialDetails(str
.SingleOrDefaultAsync();

/// <inheritdoc />
public Task<(bool Exists, CompanySsiDetailStatusId Status, VerifiedCredentialTypeId Type, Guid? ProcessId, IEnumerable<Guid> ProcessStepIds)> GetSsiRejectionData(Guid credentialId) =>
public Task<(bool Exists, CompanySsiDetailStatusId Status, VerifiedCredentialTypeId Type, string UserId, Guid? ProcessId, IEnumerable<Guid> ProcessStepIds)> GetSsiRejectionData(Guid credentialId) =>
_context.CompanySsiDetails
.Where(x => x.Id == credentialId)
.Select(x => new ValueTuple<bool, CompanySsiDetailStatusId, VerifiedCredentialTypeId, Guid?, IEnumerable<Guid>>(
.Select(x => new ValueTuple<bool, CompanySsiDetailStatusId, VerifiedCredentialTypeId, string, Guid?, IEnumerable<Guid>>(
true,
x.CompanySsiDetailStatusId,
x.VerifiedCredentialTypeId,
x.CreatorUserId,
x.ProcessId,
x.Process!.ProcessSteps.Where(ps => ps.ProcessStepStatusId == ProcessStepStatusId.TODO).Select(p => p.Id)
))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public interface ICompanySsiDetailsRepository
IAsyncEnumerable<OwnedVerifiedCredentialData> GetOwnCredentialDetails(string bpnl);

Task<(bool exists, SsiApprovalData data)> GetSsiApprovalData(Guid credentialId);
Task<(bool Exists, CompanySsiDetailStatusId Status, VerifiedCredentialTypeId Type, Guid? ProcessId, IEnumerable<Guid> ProcessStepIds)> GetSsiRejectionData(Guid credentialId);
Task<(bool Exists, CompanySsiDetailStatusId Status, VerifiedCredentialTypeId Type, string UserId, Guid? ProcessId, IEnumerable<Guid> ProcessStepIds)> GetSsiRejectionData(Guid credentialId);
void AttachAndModifyCompanySsiDetails(Guid id, Action<CompanySsiDetail>? initialize, Action<CompanySsiDetail> updateFields);
IAsyncEnumerable<VerifiedCredentialTypeId> GetCertificateTypes(string bpnl);
IAsyncEnumerable<CredentialExpiryData> GetExpiryData(DateTimeOffset now, DateTimeOffset inactiveVcsToDelete, DateTimeOffset expiredVcsToDelete);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,9 +215,14 @@ public async Task ApproveCredential(Guid credentialId, CancellationToken cancell
new("credentialType", typeValue),
new("expiryDate", expiry.ToString("o", CultureInfo.InvariantCulture))
};
await _portalService.TriggerMail("CredentialApproval", _identity.CompanyUserId.Value, mailParameters, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);
var content = JsonSerializer.Serialize(new { data.Type, CredentialId = credentialId }, Options);
await _portalService.AddNotification(content, _identity.CompanyUserId.Value, NotificationTypeId.CREDENTIAL_APPROVAL, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);

if (Guid.TryParse(data.UserId, out var companyUserId))
{
await _portalService.TriggerMail("CredentialApproval", companyUserId, mailParameters, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);
var content = JsonSerializer.Serialize(new { data.Type, CredentialId = credentialId }, Options);
await _portalService.AddNotification(content, companyUserId, NotificationTypeId.CREDENTIAL_APPROVAL, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);
}

await _repositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None);
}

Expand Down Expand Up @@ -257,11 +262,6 @@ private static void ValidateApprovalData(Guid credentialId, bool exists, SsiAppr
throw ConflictException.Create(IssuerErrors.CREDENTIAL_NOT_PENDING, new ErrorParameter[] { new("credentialId", credentialId.ToString()), new("status", CompanySsiDetailStatusId.PENDING.ToString()) });
}

if (string.IsNullOrWhiteSpace(data.Bpn))
{
throw UnexpectedConditionException.Create(IssuerErrors.BPN_NOT_SET);
}

ValidateFrameworkCredential(data);

if (Enum.GetValues<VerifiedCredentialTypeKindId>().All(x => x != data.Kind))
Expand Down Expand Up @@ -321,7 +321,7 @@ public async Task RejectCredential(Guid credentialId, CancellationToken cancella
}

var companySsiRepository = _repositories.GetInstance<ICompanySsiDetailsRepository>();
var (exists, status, type, processId, processStepIds) = await companySsiRepository.GetSsiRejectionData(credentialId).ConfigureAwait(ConfigureAwaitOptions.None);
var (exists, status, type, userId, processId, processStepIds) = await companySsiRepository.GetSsiRejectionData(credentialId).ConfigureAwait(ConfigureAwaitOptions.None);
if (!exists)
{
throw NotFoundException.Create(IssuerErrors.SSI_DETAILS_NOT_FOUND, new ErrorParameter[] { new("credentialId", credentialId.ToString()) });
Expand All @@ -333,16 +333,17 @@ public async Task RejectCredential(Guid credentialId, CancellationToken cancella
}

var typeValue = type.GetEnumValue() ?? throw UnexpectedConditionException.Create(IssuerErrors.CREDENTIAL_TYPE_NOT_FOUND, new ErrorParameter[] { new("verifiedCredentialType", type.ToString()) });
var content = JsonSerializer.Serialize(new { Type = type, CredentialId = credentialId }, Options);
await _portalService.AddNotification(content, _identity.CompanyUserId.Value, NotificationTypeId.CREDENTIAL_REJECTED, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);

var mailParameters = new MailParameter[]
if (Guid.TryParse(userId, out var companyUserId))
{
new("requestName", typeValue),
new("reason", "Declined by the Operator")
};

await _portalService.TriggerMail("CredentialRejected", _identity.CompanyUserId.Value, mailParameters, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);
var content = JsonSerializer.Serialize(new { Type = type, CredentialId = credentialId }, Options);
await _portalService.AddNotification(content, companyUserId, NotificationTypeId.CREDENTIAL_REJECTED, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);
var mailParameters = new MailParameter[]
{
new("requestName", typeValue),
new("reason", "Declined by the Operator")
};
await _portalService.TriggerMail("CredentialRejected", companyUserId, mailParameters, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);
}

companySsiRepository.AttachAndModifyCompanySsiDetails(credentialId, c =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public class IssuerBusinessLogicTests
private static readonly Guid CredentialId = Guid.NewGuid();
private static readonly string Bpnl = "BPNL00000001TEST";
private static readonly string IssuerBpnl = "BPNL000001ISSUER";
private static readonly Guid CompanyUserId = Guid.NewGuid();

private readonly IFixture _fixture;
private readonly ICompanySsiDetailsRepository _companySsiDetailsRepository;
Expand Down Expand Up @@ -203,28 +204,6 @@ public async Task ApproveCredential_WithStatusNotPending_ThrowsConflictException
A.CallTo(() => _issuerRepositories.SaveAsync()).MustNotHaveHappened();
}

[Fact]
public async Task ApproveCredential_WithBpnNotSetActiveSsiDetail_ThrowsConflictException()
{
// Arrange
var alreadyActiveId = Guid.NewGuid();
var approvalData = _fixture.Build<SsiApprovalData>()
.With(x => x.Status, CompanySsiDetailStatusId.PENDING)
.With(x => x.Bpn, (string?)null)
.Create();
A.CallTo(() => _companySsiDetailsRepository.GetSsiApprovalData(alreadyActiveId))
.Returns((true, approvalData));
Task Act() => _sut.ApproveCredential(alreadyActiveId, CancellationToken.None);

// Act
var ex = await Assert.ThrowsAsync<UnexpectedConditionException>(Act);

// Assert
ex.Message.Should().Be(IssuerErrors.BPN_NOT_SET.ToString());
A.CallTo(() => _portalService.TriggerMail("CredentialApproval", A<Guid>._, A<IEnumerable<MailParameter>>._, A<CancellationToken>._)).MustNotHaveHappened();
A.CallTo(() => _issuerRepositories.SaveAsync()).MustNotHaveHappened();
}

[Fact]
public async Task ApproveCredential_WithExpiryInThePast_ReturnsExpected()
{
Expand All @@ -245,6 +224,7 @@ public async Task ApproveCredential_WithExpiryInThePast_ReturnsExpected()
null,
VerifiedCredentialTypeKindId.FRAMEWORK,
Bpnl,
CompanyUserId.ToString(),
JsonDocument.Parse(schema),
detailData
);
Expand Down Expand Up @@ -284,6 +264,7 @@ public async Task ApproveCredential_WithInvalidCredentialType_ThrowsException()
null,
VerifiedCredentialTypeKindId.FRAMEWORK,
Bpnl,
CompanyUserId.ToString(),
JsonDocument.Parse(schema),
useCaseData
);
Expand Down Expand Up @@ -311,6 +292,7 @@ public async Task ApproveCredential_WithDetailVersionNotSet_ThrowsConflictExcept
null,
VerifiedCredentialTypeKindId.FRAMEWORK,
Bpnl,
CompanyUserId.ToString(),
null,
null
);
Expand Down Expand Up @@ -343,6 +325,7 @@ public async Task ApproveCredential_WithAlreadyLinkedProcess_ThrowsConflictExcep
Guid.NewGuid(),
VerifiedCredentialTypeKindId.FRAMEWORK,
Bpnl,
CompanyUserId.ToString(),
null,
new DetailData(
VerifiedCredentialExternalTypeId.TRACEABILITY_CREDENTIAL,
Expand All @@ -369,32 +352,32 @@ public async Task ApproveCredential_WithAlreadyLinkedProcess_ThrowsConflictExcep
ex.Message.Should().Be(IssuerErrors.ALREADY_LINKED_PROCESS.ToString());
}

[Theory]
[InlineData(VerifiedCredentialTypeKindId.FRAMEWORK, VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, VerifiedCredentialExternalTypeId.TRACEABILITY_CREDENTIAL)]
public async Task ApproveCredential_WithValid_ReturnsExpected(VerifiedCredentialTypeKindId kindId, VerifiedCredentialTypeId typeId, VerifiedCredentialExternalTypeId externalTypeId)
[Fact]
public async Task ApproveCredential_WithValid_ReturnsExpected()
{
// Arrange
var schema = CreateSchema();
var processData = new CompanySsiProcessData(CredentialId, JsonDocument.Parse(schema), VerifiedCredentialTypeKindId.FRAMEWORK);
var now = DateTimeOffset.UtcNow;
var detailData = new DetailData(
externalTypeId,
VerifiedCredentialExternalTypeId.TRACEABILITY_CREDENTIAL,
"test",
"1.0.0",
DateTimeOffset.UtcNow
);

var data = new SsiApprovalData(
CompanySsiDetailStatusId.PENDING,
typeId,
VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK,
null,
kindId,
VerifiedCredentialTypeKindId.FRAMEWORK,
Bpnl,
CompanyUserId.ToString(),
JsonDocument.Parse(schema),
detailData
);

var detail = new CompanySsiDetail(CredentialId, _identity.Bpnl, typeId, CompanySsiDetailStatusId.PENDING, "", Guid.NewGuid().ToString(), DateTimeOffset.Now);
var detail = new CompanySsiDetail(CredentialId, _identity.Bpnl, VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, CompanySsiDetailStatusId.PENDING, "", Guid.NewGuid().ToString(), DateTimeOffset.Now);
A.CallTo(() => _dateTimeProvider.OffsetNow).Returns(now);
A.CallTo(() => _companySsiDetailsRepository.GetSsiApprovalData(CredentialId))
.Returns((true, data));
Expand Down Expand Up @@ -426,6 +409,63 @@ public async Task ApproveCredential_WithValid_ReturnsExpected(VerifiedCredential
processData.Schema.Deserialize<FrameworkCredential>()!.IssuanceDate.Should().Be(now);
}

[Fact]
public async Task ApproveCredential_WithValidWithoutCompanyUserRequester_DoesNotSendMailAndNotification()
{
// Arrange
var schema = CreateSchema();
var processData = new CompanySsiProcessData(CredentialId, JsonDocument.Parse(schema), VerifiedCredentialTypeKindId.FRAMEWORK);
var now = DateTimeOffset.UtcNow;
var detailData = new DetailData(
VerifiedCredentialExternalTypeId.TRACEABILITY_CREDENTIAL,
"test",
"1.0.0",
DateTimeOffset.UtcNow
);

var data = new SsiApprovalData(
CompanySsiDetailStatusId.PENDING,
VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK,
null,
VerifiedCredentialTypeKindId.FRAMEWORK,
Bpnl,
"test123",
JsonDocument.Parse(schema),
detailData
);

var detail = new CompanySsiDetail(CredentialId, _identity.Bpnl, VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, CompanySsiDetailStatusId.PENDING, "", "test123", DateTimeOffset.Now);
A.CallTo(() => _dateTimeProvider.OffsetNow).Returns(now);
A.CallTo(() => _companySsiDetailsRepository.GetSsiApprovalData(CredentialId))
.Returns((true, data));
A.CallTo(() => _companySsiDetailsRepository.AttachAndModifyCompanySsiDetails(CredentialId, A<Action<CompanySsiDetail>?>._, A<Action<CompanySsiDetail>>._!))
.Invokes((Guid _, Action<CompanySsiDetail>? initialize, Action<CompanySsiDetail> updateFields) =>
{
initialize?.Invoke(detail);
updateFields.Invoke(detail);
});
A.CallTo(() => _companySsiDetailsRepository.AttachAndModifyProcessData(CredentialId, A<Action<CompanySsiProcessData>?>._, A<Action<CompanySsiProcessData>>._!))
.Invokes((Guid _, Action<CompanySsiProcessData>? initialize, Action<CompanySsiProcessData> updateFields) =>
{
initialize?.Invoke(processData);
updateFields.Invoke(processData);
});

// Act
await _sut.ApproveCredential(CredentialId, CancellationToken.None);

// Assert
A.CallTo(() => _portalService.AddNotification(A<string>._, A<Guid>._, NotificationTypeId.CREDENTIAL_APPROVAL, A<CancellationToken>._)).MustNotHaveHappened();
A.CallTo(() => _portalService.TriggerMail("CredentialApproval", A<Guid>._, A<IEnumerable<MailParameter>>._, A<CancellationToken>._)).MustNotHaveHappened();
A.CallTo(() => _issuerRepositories.SaveAsync()).MustHaveHappenedOnceExactly();
A.CallTo(() => _processStepRepository.CreateProcess(ProcessTypeId.CREATE_CREDENTIAL))
.MustHaveHappenedOnceExactly();

detail.CompanySsiDetailStatusId.Should().Be(CompanySsiDetailStatusId.ACTIVE);
detail.DateLastChanged.Should().Be(now);
processData.Schema.Deserialize<FrameworkCredential>()!.IssuanceDate.Should().Be(now);
}

private static string CreateSchema()
{
var schemaData = new FrameworkCredential(
Expand Down Expand Up @@ -464,7 +504,7 @@ public async Task RejectCredential_WithoutExistingSsiDetail_ThrowsNotFoundExcept
// Arrange
var notExistingId = Guid.NewGuid();
A.CallTo(() => _companySsiDetailsRepository.GetSsiRejectionData(notExistingId))
.Returns(default((bool, CompanySsiDetailStatusId, VerifiedCredentialTypeId, Guid?, IEnumerable<Guid>)));
.Returns(default((bool, CompanySsiDetailStatusId, VerifiedCredentialTypeId, string, Guid?, IEnumerable<Guid>)));
Task Act() => _sut.RejectCredential(notExistingId, CancellationToken.None);

// Act
Expand All @@ -488,6 +528,7 @@ public async Task RejectCredential_WithNotPendingSsiDetail_ThrowsNotFoundExcepti
true,
status,
VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK,
CompanyUserId.ToString(),
null,
Enumerable.Empty<Guid>()
));
Expand All @@ -514,6 +555,7 @@ public async Task RejectCredential_WithValidRequest_ReturnsExpected()
true,
CompanySsiDetailStatusId.PENDING,
VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK,
CompanyUserId.ToString(),
null,
Enumerable.Empty<Guid>()));
A.CallTo(() => _companySsiDetailsRepository.AttachAndModifyCompanySsiDetails(CredentialId, A<Action<CompanySsiDetail>?>._, A<Action<CompanySsiDetail>>._!))
Expand Down Expand Up @@ -547,6 +589,7 @@ public async Task RejectCredential_WithValidRequestAndPendingProcessStepIds_Retu
true,
CompanySsiDetailStatusId.PENDING,
VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK,
CompanyUserId.ToString(),
Guid.NewGuid(),
Enumerable.Repeat<Guid>(Guid.NewGuid(), 1)));
A.CallTo(() => _companySsiDetailsRepository.AttachAndModifyCompanySsiDetails(CredentialId, A<Action<CompanySsiDetail>?>._, A<Action<CompanySsiDetail>>._!))
Expand All @@ -569,6 +612,41 @@ public async Task RejectCredential_WithValidRequestAndPendingProcessStepIds_Retu
detail.DateLastChanged.Should().Be(now);
}

[Fact]
public async Task RejectCredential_WithValidWithoutCompanyUserRequester_DoesNotSendMailAndNotification()
{
// Arrange
var now = DateTimeOffset.UtcNow;
var detail = new CompanySsiDetail(CredentialId, _identity.Bpnl, VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, CompanySsiDetailStatusId.PENDING, IssuerBpnl, "test123", DateTimeOffset.Now);
A.CallTo(() => _dateTimeProvider.OffsetNow).Returns(now);
A.CallTo(() => _companySsiDetailsRepository.GetSsiRejectionData(CredentialId))
.Returns((
true,
CompanySsiDetailStatusId.PENDING,
VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK,
"test123",
Guid.NewGuid(),
Enumerable.Repeat(Guid.NewGuid(), 1)));
A.CallTo(() => _companySsiDetailsRepository.AttachAndModifyCompanySsiDetails(CredentialId, A<Action<CompanySsiDetail>?>._, A<Action<CompanySsiDetail>>._!))
.Invokes((Guid _, Action<CompanySsiDetail>? initialize, Action<CompanySsiDetail> updateFields) =>
{
initialize?.Invoke(detail);
updateFields.Invoke(detail);
});

// Act
await _sut.RejectCredential(CredentialId, CancellationToken.None);

// Assert
A.CallTo(() => _portalService.TriggerMail(A<string>._, A<Guid>._, A<IEnumerable<MailParameter>>._, A<CancellationToken>._)).MustNotHaveHappened();
A.CallTo(() => _portalService.AddNotification(A<string>._, A<Guid>._, A<NotificationTypeId>._, A<CancellationToken>._)).MustNotHaveHappened();
A.CallTo(() => _issuerRepositories.SaveAsync()).MustHaveHappenedOnceExactly();
A.CallTo(() => _processStepRepository.AttachAndModifyProcessSteps(A<IEnumerable<(Guid ProcessStepId, Action<ProcessStep>? Initialize, Action<ProcessStep> Modify)>>._)).MustHaveHappenedOnceExactly();

detail.CompanySsiDetailStatusId.Should().Be(CompanySsiDetailStatusId.INACTIVE);
detail.DateLastChanged.Should().Be(now);
}

#endregion

#region GetCertificateTypes
Expand Down
Loading