diff --git a/src/ProjectOrigin.Vault/Activities/AllocateActivity.cs b/src/ProjectOrigin.Vault/Activities/AllocateActivity.cs index 2669e303..7b641967 100644 --- a/src/ProjectOrigin.Vault/Activities/AllocateActivity.cs +++ b/src/ProjectOrigin.Vault/Activities/AllocateActivity.cs @@ -20,8 +20,7 @@ public record AllocateArguments public required Guid ProductionSliceId { get; init; } public Guid? ChroniclerRequestId { get; init; } public required FederatedStreamId CertificateId { get; init; } - public required Guid RequestId { get; init; } - public required string Owner { get; init; } + public required RequestStatusArgs RequestStatusArgs { get; init; } } public class AllocateActivity : IExecuteActivity @@ -44,7 +43,7 @@ public async Task Execute(ExecuteContext con { try { - _logger.LogInformation("Starting Activity: {Activity}, RequestId: {RequestId} ", nameof(SendRegistryTransactionActivity), context.Arguments.RequestId); + _logger.LogInformation("Starting Activity: {Activity}, RequestId: {RequestId} ", nameof(SendRegistryTransactionActivity), context.Arguments.RequestStatusArgs.RequestId); var cons = await _unitOfWork.CertificateRepository.GetWalletSlice(context.Arguments.ConsumptionSliceId); var prod = await _unitOfWork.CertificateRepository.GetWalletSlice(context.Arguments.ProductionSliceId); @@ -70,6 +69,11 @@ public async Task Execute(ExecuteContext con catch (Exception ex) { _logger.LogError(ex, "Error registering claim intent with Chronicler"); + await _unitOfWork.RequestStatusRepository.SetRequestStatus(context.Arguments.RequestStatusArgs.RequestId, + context.Arguments.RequestStatusArgs.Owner, + RequestStatusState.Failed, + "Error registering claim intent with Chronicler"); + _unitOfWork.Commit(); return context.Faulted(ex); } } diff --git a/src/ProjectOrigin.Vault/Activities/UpdateClaimStateActivity.cs b/src/ProjectOrigin.Vault/Activities/UpdateClaimStateActivity.cs index f93c371d..e73d78b7 100644 --- a/src/ProjectOrigin.Vault/Activities/UpdateClaimStateActivity.cs +++ b/src/ProjectOrigin.Vault/Activities/UpdateClaimStateActivity.cs @@ -57,7 +57,7 @@ public async Task Execute(ExecuteContext context) { _unitOfWork.Rollback(); _logger.LogWarning(ex, "Claim is not allowed."); + await _unitOfWork.RequestStatusRepository.SetRequestStatus(context.Message.ClaimId, context.Message.Owner, RequestStatusState.Failed, failedReason: "Claim is not allowed."); + _unitOfWork.Commit(); } catch (QuantityNotYetAvailableToReserveException ex) { @@ -72,6 +74,8 @@ public async Task Consume(ConsumeContext context) { _unitOfWork.Rollback(); _logger.LogError(ex, "failed to handle claim"); + await _unitOfWork.RequestStatusRepository.SetRequestStatus(context.Message.ClaimId, context.Message.Owner, RequestStatusState.Failed, failedReason: "failed to handle claim"); + _unitOfWork.Commit(); } } diff --git a/src/ProjectOrigin.Vault/RegistryProcessBuilder/Claim.cs b/src/ProjectOrigin.Vault/RegistryProcessBuilder/Claim.cs index 6fa265c5..51e4f6f4 100644 --- a/src/ProjectOrigin.Vault/RegistryProcessBuilder/Claim.cs +++ b/src/ProjectOrigin.Vault/RegistryProcessBuilder/Claim.cs @@ -24,8 +24,11 @@ public async Task Claim(WalletSlice productionSlice, WalletSlice consumptionSlic ProductionSliceId = productionSlice.Id, ConsumptionSliceId = consumptionSlice.Id, ChroniclerRequestId = await GetClaimIntentId(productionSlice), - Owner = _owner, - RequestId = _routingSlipId + RequestStatusArgs = new RequestStatusArgs + { + Owner = _owner, + RequestId = _routingSlipId + } }); AddActivity(new AllocateArguments @@ -35,8 +38,11 @@ public async Task Claim(WalletSlice productionSlice, WalletSlice consumptionSlic ProductionSliceId = productionSlice.Id, ConsumptionSliceId = consumptionSlice.Id, ChroniclerRequestId = await GetClaimIntentId(consumptionSlice), - Owner = _owner, - RequestId = _routingSlipId + RequestStatusArgs = new RequestStatusArgs + { + Owner = _owner, + RequestId = _routingSlipId + } }); await _unitOfWork.ClaimRepository.InsertClaim(new Claim diff --git a/test/ProjectOrigin.Vault.Tests/ActivityTests/ChroniclerExecutionTest.cs b/test/ProjectOrigin.Vault.Tests/ActivityTests/ChroniclerExecutionTest.cs index fa6fb7e8..f4edee87 100644 --- a/test/ProjectOrigin.Vault.Tests/ActivityTests/ChroniclerExecutionTest.cs +++ b/test/ProjectOrigin.Vault.Tests/ActivityTests/ChroniclerExecutionTest.cs @@ -113,8 +113,11 @@ public async Task ChroniclerAndAllocate_ShouldRevise() AllocationId = Guid.NewGuid(), ChroniclerRequestId = chroniclerId, CertificateId = certId, - Owner = "", - RequestId = Guid.NewGuid(), + RequestStatusArgs = new RequestStatusArgs + { + Owner = "", + RequestId = Guid.NewGuid() + }, ConsumptionSliceId = Guid.NewGuid(), ProductionSliceId = Guid.NewGuid(), }); diff --git a/test/ProjectOrigin.Vault.Tests/ActivityTests/UpdateClaimStateActivityTests.cs b/test/ProjectOrigin.Vault.Tests/ActivityTests/UpdateClaimStateActivityTests.cs index 2f00a994..b3e05ef7 100644 --- a/test/ProjectOrigin.Vault.Tests/ActivityTests/UpdateClaimStateActivityTests.cs +++ b/test/ProjectOrigin.Vault.Tests/ActivityTests/UpdateClaimStateActivityTests.cs @@ -98,12 +98,10 @@ public async Task Execute_WhenSetClaimThrowsException_ShouldThrowException() _unitOfWork.ClaimRepository.When(x => x.SetClaimState(Arg.Any(), Arg.Any())).Do(x => throw exceptionToBeThrown); // Act - await _activity.Execute(_context); + await Assert.ThrowsAsync(async () => await _activity.Execute(_context)); // Assert - _context.Received(1).Faulted(Arg.Is(exceptionToBeThrown)); _unitOfWork.Received(1).Rollback(); - _unitOfWork.DidNotReceive().Commit(); _context.DidNotReceive().Completed(); } } diff --git a/test/ProjectOrigin.Vault.Tests/Extensions/HttpClientExtensions.cs b/test/ProjectOrigin.Vault.Tests/Extensions/HttpClientExtensions.cs index f7c4818f..ef968273 100644 --- a/test/ProjectOrigin.Vault.Tests/Extensions/HttpClientExtensions.cs +++ b/test/ProjectOrigin.Vault.Tests/Extensions/HttpClientExtensions.cs @@ -30,6 +30,9 @@ public static Task CreateExternalEndpoint(this H public static Task> GetCertificates(this HttpClient client) => client.GetAsync($"v1/certificates").ParseJson>(); + public static Task GetRequestStatus(this HttpClient client, Guid requestId) => + client.GetAsync($"v1/request-status/{requestId}").ParseJson(); + public static Task> GetCertificatesWithTimeout(this HttpClient client, int count, TimeSpan timeout) => Timeout(async () => { diff --git a/test/ProjectOrigin.Vault.Tests/FlowTests/ClaimTests.cs b/test/ProjectOrigin.Vault.Tests/FlowTests/ClaimTests.cs index 1a2b8690..855598bd 100644 --- a/test/ProjectOrigin.Vault.Tests/FlowTests/ClaimTests.cs +++ b/test/ProjectOrigin.Vault.Tests/FlowTests/ClaimTests.cs @@ -97,4 +97,63 @@ public async Task CannotAllocateAfterExpired() allocateEventStatus.Status.Should().Be(TransactionState.Failed); allocateEventStatus.Message.Should().Be("Certificate has expired"); } + + [Fact] + public async Task WhenClaimingMoreThanPossible_ClaimRequestSetToFailed() + { + var position = 1; + var endDate = DateTimeOffset.UtcNow; + var startDate = endDate.AddHours(-1); + + var client = WalletTestFixture.ServerFixture.CreateHttpClient(); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", WalletTestFixture.JwtTokenIssuerFixture.GenerateRandomToken()); + + var wallet = await client.CreateWallet(); + var endpoint = await client.CreateWalletEndpoint(wallet.WalletId); + + var productionId = await IssueCertificateToEndpoint(endpoint.WalletReference, Electricity.V1.GranularCertificateType.Production, new SecretCommitmentInfo(200), position++, startDate, endDate); + var consumptionId = await IssueCertificateToEndpoint(endpoint.WalletReference, Electricity.V1.GranularCertificateType.Consumption, new SecretCommitmentInfo(300), position++, startDate, endDate); + + await client.GetCertificatesWithTimeout(2, TimeSpan.FromMinutes(1)); + + var response = await client.CreateClaim( + consumptionId, + productionId, + 400u); + + await Task.Delay(TimeSpan.FromSeconds(5)); + + var requestStatus = await client.GetRequestStatus(response.ClaimRequestId); + + Assert.Equal(RequestStatus.Failed, requestStatus.Status); + } + + [Fact] + public async Task WhenTryingToClaimUnknownCertificate_ClaimRequestSetToFailed() + { + var position = 1; + var endDate = DateTimeOffset.UtcNow; + var startDate = endDate.AddHours(-1); + + var client = WalletTestFixture.ServerFixture.CreateHttpClient(); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", WalletTestFixture.JwtTokenIssuerFixture.GenerateRandomToken()); + + var wallet = await client.CreateWallet(); + var endpoint = await client.CreateWalletEndpoint(wallet.WalletId); + + var consumptionId = await IssueCertificateToEndpoint(endpoint.WalletReference, Electricity.V1.GranularCertificateType.Consumption, new SecretCommitmentInfo(300), position++, startDate, endDate); + + await client.GetCertificatesWithTimeout(1, TimeSpan.FromMinutes(1)); + + var response = await client.CreateClaim( + consumptionId, + consumptionId with { StreamId = Guid.NewGuid() }, + 400u); + + await Task.Delay(TimeSpan.FromSeconds(5)); + + var requestStatus = await client.GetRequestStatus(response.ClaimRequestId); + + Assert.Equal(RequestStatus.Failed, requestStatus.Status); + } } diff --git a/test/ProjectOrigin.Vault.Tests/RegistryProcessBuilder/ClaimTests.cs b/test/ProjectOrigin.Vault.Tests/RegistryProcessBuilder/ClaimTests.cs index 40711b21..2f1f7438 100644 --- a/test/ProjectOrigin.Vault.Tests/RegistryProcessBuilder/ClaimTests.cs +++ b/test/ProjectOrigin.Vault.Tests/RegistryProcessBuilder/ClaimTests.cs @@ -82,8 +82,8 @@ public async Task TestClaimEqualSize_WithoutChronicler() x.ChroniclerRequestId == null && x.CertificateId.Registry == prodCert.RegistryName && x.CertificateId.StreamId.Value == prodCert.Id.ToString() && - x.RequestId != Guid.Empty && - x.Owner != string.Empty); + x.RequestStatusArgs.RequestId != Guid.Empty && + x.RequestStatusArgs.Owner != string.Empty); var allocationId = slip.Itinerary[0].ShouldBeActivity().AllocationId.ToString(); @@ -95,8 +95,8 @@ public async Task TestClaimEqualSize_WithoutChronicler() x.ChroniclerRequestId == null && x.CertificateId.Registry == consCert.RegistryName && x.CertificateId.StreamId.Value == consCert.Id.ToString() && - x.RequestId != Guid.Empty && - x.Owner != string.Empty); + x.RequestStatusArgs.RequestId != Guid.Empty && + x.RequestStatusArgs.Owner != string.Empty); var (t3, _) = slip.Itinerary[2].ShouldBeTransactionWithEvent( transaction => @@ -201,8 +201,8 @@ public async Task TestClaimEqualSize_WithChronicler() x.ChroniclerRequestId.Equals(chronId) && x.CertificateId.Registry == prodCert.RegistryName && x.CertificateId.StreamId.Value == prodCert.Id.ToString() && - x.RequestId != Guid.Empty && - x.Owner != string.Empty); + x.RequestStatusArgs.RequestId != Guid.Empty && + x.RequestStatusArgs.Owner != string.Empty); var allocationId = slip.Itinerary[1].ShouldBeActivity().AllocationId.ToString(); @@ -224,8 +224,8 @@ public async Task TestClaimEqualSize_WithChronicler() x.ChroniclerRequestId.Equals(chronId2) && x.CertificateId.Registry == consCert.RegistryName && x.CertificateId.StreamId.Value == consCert.Id.ToString() && - x.RequestId != Guid.Empty && - x.Owner != string.Empty); + x.RequestStatusArgs.RequestId != Guid.Empty && + x.RequestStatusArgs.Owner != string.Empty); var (t3, _) = slip.Itinerary[4].ShouldBeTransactionWithEvent( transaction =>