diff --git a/Directory.Packages.props b/Directory.Packages.props index 01b27f5..b542dbe 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,8 +4,8 @@ - - + + @@ -16,7 +16,7 @@ - + @@ -39,14 +39,14 @@ - - - - - - - - + + + + + + + + diff --git a/FileProcessor.BusinessLogic/Services/FileProcessorDomainService.cs b/FileProcessor.BusinessLogic/Services/FileProcessorDomainService.cs index 4fdc0ec..4c61174 100644 --- a/FileProcessor.BusinessLogic/Services/FileProcessorDomainService.cs +++ b/FileProcessor.BusinessLogic/Services/FileProcessorDomainService.cs @@ -398,55 +398,13 @@ public async Task ProcessTransactionForFileLine(FileCommands.ProcessTran return stateResult; } - Int32 transactionNumber = Interlocked.Increment(ref TransactionNumber); - - Result<(Guid ContractId, Guid OperatorId, Guid ProductId, String MerchantDevice)> detailsForTransaction = await this.GetDetailsForTransaction(cancellationToken, - fileDetails, - fileProfile, - processingContext.OperatorName, - processingContext); - if (detailsForTransaction.IsFailed) - return ResultHelpers.CreateFailure(detailsForTransaction); - - processingContext.Stage = FileLineProcessingStage.TransactionDetailsResolved; - Result saleResult = await this.SendSaleTransaction(fileDetails, - detailsForTransaction, - metadataResult.Data, - transactionNumber, - processingContext, - cancellationToken); - processingContext.TransactionDispatchSucceeded = saleResult.IsSuccess; - if (saleResult.IsSuccess) - processingContext.ResponseCode = saleResult.Data.ResponseCode; - - // A failed dispatch has no definitive processor response. Leave the line unmodified so - // the file processing workflow can retry it with the same file and line identity. - if (saleResult.IsFailed) - { - stateResult = fileAggregate.RecordTransactionDispatchFailure(command.LineNumber, - transactionNumber, - processingContext.TransactionDispatchFailureType, - DateTime.UtcNow); - if (stateResult.IsFailed) - return stateResult; - - dispatchFailureResult = ResultHelpers.CreateFailure(saleResult); - processingContext.Stage = FileLineProcessingStage.TransactionDispatchAttemptPersisted; - return Result.Success(); - } - - processingContext.Stage = FileLineProcessingStage.FileLineStateUpdate; - stateResult = saleResult.Data.ResponseCode == "0000" - ? fileAggregate.RecordFileLineAsSuccessful(command.LineNumber, saleResult.Data.TransactionId) - : fileAggregate.RecordFileLineAsFailed(command.LineNumber, saleResult.Data.TransactionId, saleResult.Data.ResponseCode, saleResult.Data.ResponseMessage); - - ProcessingResult processingResult = saleResult.IsSuccess && saleResult.Data.ResponseCode == "0000" - ? ProcessingResult.Successful - : ProcessingResult.Failed; - FileLineProcessingDiagnostics.LineStateDetermined(processingContext, processingResult); - if (stateResult.IsSuccess) - processingContext.Stage = FileLineProcessingStage.FileLineStatePersisted; - + (stateResult, dispatchFailureResult) = await this.ProcessTransaction(fileAggregate, + command, + fileDetails, + fileProfile, + metadataResult.Data, + processingContext, + cancellationToken); return stateResult; }, command.FileId, cancellationToken); @@ -466,6 +424,65 @@ public async Task ProcessTransactionForFileLine(FileCommands.ProcessTran return result; } + private async Task<(Result StateResult, Result DispatchFailureResult)> ProcessTransaction(FileAggregate fileAggregate, + FileCommands.ProcessTransactionForFileLineCommand command, + FileDetails fileDetails, + FileProfileModel fileProfile, + Dictionary transactionMetadata, + FileLineProcessingContext processingContext, + CancellationToken cancellationToken) + { + Int32 transactionNumber = Interlocked.Increment(ref TransactionNumber); + + Result<(Guid ContractId, Guid OperatorId, Guid ProductId, String MerchantDevice)> detailsForTransaction = await this.GetDetailsForTransaction(cancellationToken, + fileDetails, + fileProfile, + processingContext.OperatorName, + processingContext); + if (detailsForTransaction.IsFailed) + return (ResultHelpers.CreateFailure(detailsForTransaction), null); + + processingContext.Stage = FileLineProcessingStage.TransactionDetailsResolved; + Result saleResult = await this.SendSaleTransaction(fileDetails, + detailsForTransaction, + transactionMetadata, + transactionNumber, + processingContext, + cancellationToken); + processingContext.TransactionDispatchSucceeded = saleResult.IsSuccess; + if (saleResult.IsSuccess) + processingContext.ResponseCode = saleResult.Data.ResponseCode; + + // A failed dispatch has no definitive processor response. Leave the line unmodified so + // the file processing workflow can retry it with the same file and line identity. + if (saleResult.IsFailed) + { + Result stateResult = fileAggregate.RecordTransactionDispatchFailure(command.LineNumber, + transactionNumber, + processingContext.TransactionDispatchFailureType, + DateTime.UtcNow); + if (stateResult.IsFailed) + return (stateResult, null); + + processingContext.Stage = FileLineProcessingStage.TransactionDispatchAttemptPersisted; + return (Result.Success(), ResultHelpers.CreateFailure(saleResult)); + } + + processingContext.Stage = FileLineProcessingStage.FileLineStateUpdate; + Result stateUpdateResult = saleResult.Data.ResponseCode == "0000" + ? fileAggregate.RecordFileLineAsSuccessful(command.LineNumber, saleResult.Data.TransactionId) + : fileAggregate.RecordFileLineAsFailed(command.LineNumber, saleResult.Data.TransactionId, saleResult.Data.ResponseCode, saleResult.Data.ResponseMessage); + + ProcessingResult processingResult = saleResult.Data.ResponseCode == "0000" + ? ProcessingResult.Successful + : ProcessingResult.Failed; + FileLineProcessingDiagnostics.LineStateDetermined(processingContext, processingResult); + if (stateUpdateResult.IsSuccess) + processingContext.Stage = FileLineProcessingStage.FileLineStatePersisted; + + return (stateUpdateResult, null); + } + private Result> BuildTransactionMetadata(FileCommands.ProcessTransactionForFileLineCommand command, FileLine fileLine, FileProfileModel fileProfile, diff --git a/FileProcessor.IntegrationTests/Common/DockerHelper.cs b/FileProcessor.IntegrationTests/Common/DockerHelper.cs index 8896398..262c883 100644 --- a/FileProcessor.IntegrationTests/Common/DockerHelper.cs +++ b/FileProcessor.IntegrationTests/Common/DockerHelper.cs @@ -11,6 +11,7 @@ namespace FileProcessor.IntegrationTests.Common using Shared.IntegrationTesting; using Shared.Serialisation; using System.Net.Http; + using System.Threading; using TestHosts.Clients; using TransactionProcessor.Client; @@ -79,7 +80,9 @@ public override ContainerBuilder SetupFileProcessorContainer() { this.AdditionalVariables[ContainerType.FileProcessor] = variables; return base.SetupFileProcessorContainer(); } - + + public String AccessToken; + public override async Task StartContainersForScenarioRun(String scenarioName, DockerServices dockerServices){ await base.StartContainersForScenarioRun(scenarioName, dockerServices); @@ -116,6 +119,13 @@ public override async Task StartContainersForScenarioRun(String scenarioName, Do this.AgencyBankingClient = new AgencyBankingClient(TestHostServiceBaseAddressResolver, httpClient, Serialise_CamelCase, this.Deserialise_CamelCase); this.ProjectionManagementClient = new EventStoreProjectionManagementClient(ConfigureEventStoreSettings()); + + SimpleResults.Result bootstrapToken = await this.SecurityServiceClient.GetToken("management-bootstrap", "management-bootstrap-secret", CancellationToken.None); + if (bootstrapToken.IsFailed || String.IsNullOrWhiteSpace(bootstrapToken.Data?.AccessToken)) + { + throw new InvalidOperationException("Unable to obtain the integration-test management bootstrap token."); + } + this.AccessToken = bootstrapToken.Data.AccessToken; } String Serialise(Object arg) diff --git a/FileProcessor.IntegrationTests/Steps/SharedSteps.cs b/FileProcessor.IntegrationTests/Steps/SharedSteps.cs index 2c83ebb..1af950b 100644 --- a/FileProcessor.IntegrationTests/Steps/SharedSteps.cs +++ b/FileProcessor.IntegrationTests/Steps/SharedSteps.cs @@ -48,7 +48,7 @@ public SharedSteps(ScenarioContext scenarioContext, { ScenarioContext = scenarioContext; TestingContext = testingContext; - this.SecurityServiceSteps = new SecurityServiceSteps(testingContext.DockerHelper.SecurityServiceClient); + this.SecurityServiceSteps = new SecurityServiceSteps(testingContext.DockerHelper.SecurityServiceClient, testingContext.DockerHelper.AccessToken); } private TransactionProcessorSteps GetTransactionProcessorSteps() diff --git a/FileProcessor/Startup.cs b/FileProcessor/Startup.cs index acfd704..9735ac5 100644 --- a/FileProcessor/Startup.cs +++ b/FileProcessor/Startup.cs @@ -8,26 +8,25 @@ using System; using System.Collections.Generic; using System.Linq; +using FileProcessor.Bootstrapper; +using FileProcessor.Common; +using HealthChecks.UI.Client; +using Lamar; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Shared.DomainDrivenDesign.EventSourcing; +using Shared.EventStore.Aggregate; +using Shared.Extensions; +using Shared.General; +using Shared.Logger; +using Shared.Middleware; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using ILogger = Microsoft.Extensions.Logging.ILogger; namespace FileProcessor { - using Bootstrapper; - using Common; - using HealthChecks.UI.Client; - using Lamar; - using Microsoft.AspNetCore.Diagnostics.HealthChecks; - using Microsoft.Extensions.Configuration; - using Microsoft.Extensions.Logging; - using Shared.DomainDrivenDesign.EventSourcing; - using Shared.EventStore.Aggregate; - using Shared.Extensions; - using Shared.General; - using Shared.Logger; - using Shared.Middleware; - using System.Diagnostics.CodeAnalysis; - using System.Reflection; - using ILogger = Microsoft.Extensions.Logging.ILogger; - [ExcludeFromCodeCoverage] public class Startup {