public void FileAggregate_RecordFileLineAsRejected_FileLineUpdatedAsRejected() { FileAggregate fileAggregate = FileAggregate.Create(TestData.FileId); fileAggregate.CreateFile(TestData.FileImportLogId, TestData.EstateId, TestData.MerchantId, TestData.UserId, TestData.FileProfileId, TestData.FileLocation, TestData.FileUploadedDateTime); fileAggregate.AddFileLine(TestData.FileLine); fileAggregate.RecordFileLineAsRejected(TestData.LineNumber, TestData.RejectionReason); FileDetails fileDetails = fileAggregate.GetFile(); fileDetails.FileLines.ShouldNotBeNull(); fileDetails.FileLines.ShouldNotBeEmpty(); fileDetails.FileLines.ShouldHaveSingleItem(); fileDetails.FileLines.Single().LineNumber.ShouldBe(1); fileDetails.FileLines.Single().LineData.ShouldBe(TestData.FileLine); fileDetails.FileLines.Single().ProcessingResult.ShouldBe(ProcessingResult.Rejected); fileDetails.FileLines.Single().RejectedReason.ShouldBe(TestData.RejectionReason); fileDetails.ProcessingCompleted.ShouldBeTrue(); fileDetails.ProcessingSummary.ShouldNotBeNull(); fileDetails.ProcessingSummary.TotalLines.ShouldBe(1); fileDetails.ProcessingSummary.NotProcessedLines.ShouldBe(0); fileDetails.ProcessingSummary.FailedLines.ShouldBe(0); fileDetails.ProcessingSummary.SuccessfullyProcessedLines.ShouldBe(0); fileDetails.ProcessingSummary.IgnoredLines.ShouldBe(0); fileDetails.ProcessingSummary.RejectedLines.ShouldBe(1); }
private void VerifyFile(FileAggregate source, FileDetails fileDetails) { var fileModel = source.GetFile(); fileDetails.FileId.ShouldBe(fileModel.FileId); fileDetails.FileImportLogId.ShouldBe(fileModel.FileImportLogId); fileDetails.FileLocation.ShouldBe(fileModel.FileLocation); fileDetails.FileProfileId.ShouldBe(fileModel.FileProfileId); fileDetails.MerchantId.ShouldBe(fileModel.MerchantId); fileDetails.ProcessingCompleted.ShouldBe(fileModel.ProcessingCompleted); fileDetails.UserId.ShouldBe(fileModel.UserId); fileDetails.EstateId.ShouldBe(fileModel.EstateId); fileDetails.ProcessingSummary.ShouldNotBeNull(); fileDetails.ProcessingSummary.FailedLines.ShouldBe(fileModel.ProcessingSummary.FailedLines); fileDetails.ProcessingSummary.IgnoredLines.ShouldBe(fileModel.ProcessingSummary.IgnoredLines); fileDetails.ProcessingSummary.NotProcessedLines.ShouldBe(fileModel.ProcessingSummary.NotProcessedLines); fileDetails.ProcessingSummary.SuccessfullyProcessedLines.ShouldBe(fileModel.ProcessingSummary.SuccessfullyProcessedLines); fileDetails.ProcessingSummary.TotalLines.ShouldBe(fileModel.ProcessingSummary.TotalLines); foreach (FileLine fileModelFileLine in fileModel.FileLines) { FileLine?fileLineToVerify = fileDetails.FileLines.SingleOrDefault(f => f.LineNumber == fileModelFileLine.LineNumber); fileLineToVerify.ShouldNotBeNull(); fileLineToVerify.LineData.ShouldBe(fileModelFileLine.LineData); fileLineToVerify.TransactionId.ShouldBe(fileModelFileLine.TransactionId); fileLineToVerify.ProcessingResult.ShouldBe(fileModelFileLine.ProcessingResult); } }
public static FileAggregate GetCreatedFileAggregate() { FileAggregate fileAggregate = new FileAggregate(); fileAggregate.CreateFile(TestData.FileImportLogId, TestData.EstateId, TestData.MerchantId, TestData.UserId, TestData.FileProfileId, TestData.OriginalFileName, TestData.FileUploadedDateTime); return(fileAggregate); }
public void FileAggregate_AddFileLine_FileNotCreated_FileLineAdded() { FileAggregate fileAggregate = FileAggregate.Create(TestData.FileId); Should.Throw <InvalidOperationException>(() => { fileAggregate.AddFileLine(TestData.FileLine); }); }
public void FileAggregate_RecordFileLineAsRejected_FileNotCreated_ErrorThrown() { FileAggregate fileAggregate = FileAggregate.Create(TestData.FileId); Should.Throw <InvalidOperationException>(() => { fileAggregate.RecordFileLineAsRejected(TestData.LineNumber, TestData.RejectionReason); }); }
public static FileAggregate GetFileAggregateWithBlankLine() { FileAggregate fileAggregate = new FileAggregate(); fileAggregate.CreateFile(TestData.FileImportLogId, TestData.EstateId, TestData.MerchantId, TestData.UserId, TestData.FileProfileId, TestData.OriginalFileName, TestData.FileUploadedDateTime); fileAggregate.AddFileLine(String.Empty); return(fileAggregate); }
public void FileAggregate_RecordFileLineAsFailed_FileNotCreated_ErrorThrown() { FileAggregate fileAggregate = FileAggregate.Create(TestData.FileId); Should.Throw <InvalidOperationException>(() => { fileAggregate.RecordFileLineAsFailed(TestData.LineNumber, TestData.TransactionId, TestData.ResponseCodeFailed, TestData.ResponseMessageFailed); }); }
public void FileAggregate_RecordFileLineAsSuccessful_FileNotCreated_ErrorThrown() { FileAggregate fileAggregate = FileAggregate.Create(TestData.FileId); Should.Throw <InvalidOperationException>(() => { fileAggregate.RecordFileLineAsSuccessful(TestData.LineNumber, TestData.TransactionId); }); }
public async Task <Unit> Handle(ProcessUploadedFileRequest request, CancellationToken cancellationToken) { // TODO: Should the file id be generated from the file uploaded to protect against duplicate files??? FileAggregate fileAggregate = await this.FileAggregateRepository.GetLatestVersion(request.FileId, cancellationToken); fileAggregate.CreateFile(request.FileImportLogId, request.EstateId, request.MerchantId, request.UserId, request.FileProfileId, request.FilePath, request.FileUploadedDateTime); await this.FileAggregateRepository.SaveChanges(fileAggregate, cancellationToken); return(new Unit()); }
public void FileAggregate_CanBeCreated_IsCreated() { FileAggregate fileAggregate = FileAggregate.Create(TestData.FileId); fileAggregate.ShouldNotBeNull(); FileDetails fileDetails = fileAggregate.GetFile(); fileDetails.ShouldNotBeNull(); fileDetails.FileId.ShouldBe(TestData.FileId); fileDetails.ProcessingCompleted.ShouldBeFalse(); }
public void FileAggregate_RecordFileLineAsRejected_LineNotFound_ErrorThrown() { FileAggregate fileAggregate = FileAggregate.Create(TestData.FileId); fileAggregate.CreateFile(TestData.FileImportLogId, TestData.EstateId, TestData.MerchantId, TestData.UserId, TestData.FileProfileId, TestData.FileLocation, TestData.FileUploadedDateTime); fileAggregate.AddFileLine(TestData.FileLine); Should.Throw <NotFoundException>(() => { fileAggregate.RecordFileLineAsRejected(TestData.NotFoundLineNumber, TestData.RejectionReason); }); }
public void FileAggregate_RecordFileLineAsFailed_FileHasNoLines_ErrorThrown() { FileAggregate fileAggregate = FileAggregate.Create(TestData.FileId); fileAggregate.CreateFile(TestData.FileImportLogId, TestData.EstateId, TestData.MerchantId, TestData.UserId, TestData.FileProfileId, TestData.FileLocation, TestData.FileUploadedDateTime); Should.Throw <InvalidOperationException>(() => { fileAggregate.RecordFileLineAsFailed(TestData.LineNumber, TestData.TransactionId, TestData.ResponseCodeFailed, TestData.ResponseMessageFailed); }); }
public void FileAggregate_RecordFileLineAsRejected_FileHasNoLine_ErrorThrown() { FileAggregate fileAggregate = FileAggregate.Create(TestData.FileId); fileAggregate.CreateFile(TestData.FileImportLogId, TestData.EstateId, TestData.MerchantId, TestData.UserId, TestData.FileProfileId, TestData.FileLocation, TestData.FileUploadedDateTime); Should.Throw <InvalidOperationException>(() => { fileAggregate.RecordFileLineAsRejected(TestData.LineNumber, TestData.RejectionReason); }); }
public static FileAggregate GetFileAggregateWithLinesAlreadyProcessed() { FileAggregate fileAggregate = new FileAggregate(); fileAggregate.CreateFile(TestData.FileImportLogId, TestData.EstateId, TestData.MerchantId, TestData.UserId, TestData.FileProfileId, TestData.OriginalFileName, TestData.FileUploadedDateTime); fileAggregate.AddFileLine("D,1,2"); fileAggregate.AddFileLine("D,1,2"); fileAggregate.AddFileLine("D,1,2"); fileAggregate.AddFileLine("D,1,2"); fileAggregate.RecordFileLineAsSuccessful(1, TestData.TransactionId); fileAggregate.RecordFileLineAsRejected(2, TestData.RejectionReason); fileAggregate.RecordFileLineAsFailed(3, TestData.TransactionId, "-1", "Failed"); fileAggregate.RecordFileLineAsIgnored(4); return(fileAggregate); }
public void FileAggregate_CreateFile_FileAlreadyCreated_NoErrorThrown() { FileAggregate fileAggregate = FileAggregate.Create(TestData.FileId); fileAggregate.CreateFile(TestData.FileImportLogId, TestData.EstateId, TestData.MerchantId, TestData.UserId, TestData.FileProfileId, TestData.FileLocation, TestData.FileUploadedDateTime); Should.NotThrow(() => { fileAggregate.CreateFile(TestData.FileImportLogId, TestData.EstateId, TestData.MerchantId, TestData.UserId, TestData.FileProfileId, TestData.FileLocation, TestData.FileUploadedDateTime); }); }
/// <summary> /// Gets the file. /// </summary> /// <param name="fileId">The file identifier.</param> /// <param name="estateId">The estate identifier.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns></returns> public async Task <FileDetails> GetFile(Guid fileId, Guid estateId, CancellationToken cancellationToken) { FileAggregate fileAggregate = await this.FileAggregateRepository.GetLatestVersion(fileId, cancellationToken); if (fileAggregate.IsCreated == false) { throw new NotFoundException($"File with Id [{fileId}] not found"); } FileDetails fileDetails = fileAggregate.GetFile(); EstateReportingGenericContext context = await this.DbContextFactory.GetContext(estateId, cancellationToken); Merchant merchant = await context.Merchants.AsAsyncEnumerable() .SingleOrDefaultAsync(m => m.MerchantId == fileDetails.MerchantId, cancellationToken); if (merchant != null) { fileDetails.MerchantName = merchant.Name; } EstateSecurityUser userDetails = await context.EstateSecurityUsers.AsAsyncEnumerable() .SingleOrDefaultAsync(u => u.SecurityUserId == fileDetails.UserId); if (userDetails != null) { fileDetails.UserEmailAddress = userDetails.EmailAddress; } FileProfile fileProfile = await this.GetFileProfile(fileDetails.FileProfileId, cancellationToken); if (fileProfile != null) { fileDetails.FileProfileName = fileProfile.Name; } return(fileDetails); }
public void FileAggregate_CreateFile_FileIsCreated() { FileAggregate fileAggregate = FileAggregate.Create(TestData.FileId); fileAggregate.CreateFile(TestData.FileImportLogId, TestData.EstateId, TestData.MerchantId, TestData.UserId, TestData.FileProfileId, TestData.FileLocation, TestData.FileUploadedDateTime); fileAggregate.IsCreated.ShouldBeTrue(); FileDetails fileDetails = fileAggregate.GetFile(); fileDetails.ShouldNotBeNull(); fileDetails.FileId.ShouldBe(TestData.FileId); fileDetails.FileImportLogId.ShouldBe(TestData.FileImportLogId); fileDetails.EstateId.ShouldBe(TestData.EstateId); fileDetails.MerchantId.ShouldBe(TestData.MerchantId); fileDetails.UserId.ShouldBe(TestData.UserId); fileDetails.FileProfileId.ShouldBe(TestData.FileProfileId); fileDetails.FileLocation.ShouldBe(TestData.FileLocation); fileDetails.FileLines.ShouldBeEmpty(); fileDetails.ProcessingCompleted.ShouldBeFalse(); }
public void FileAggregate_AddFileLine_FileLineAdded() { FileAggregate fileAggregate = FileAggregate.Create(TestData.FileId); fileAggregate.CreateFile(TestData.FileImportLogId, TestData.EstateId, TestData.MerchantId, TestData.UserId, TestData.FileProfileId, TestData.FileLocation, TestData.FileUploadedDateTime); fileAggregate.AddFileLine(TestData.FileLine); FileDetails fileDetails = fileAggregate.GetFile(); fileDetails.FileLines.ShouldNotBeNull(); fileDetails.FileLines.ShouldNotBeEmpty(); fileDetails.FileLines.ShouldHaveSingleItem(); fileDetails.FileLines.Single().LineData.ShouldBe(TestData.FileLine); fileDetails.ProcessingCompleted.ShouldBeFalse(); fileDetails.ProcessingSummary.ShouldNotBeNull(); fileDetails.ProcessingSummary.TotalLines.ShouldBe(1); fileDetails.ProcessingSummary.NotProcessedLines.ShouldBe(1); fileDetails.ProcessingSummary.FailedLines.ShouldBe(0); fileDetails.ProcessingSummary.SuccessfullyProcessedLines.ShouldBe(0); fileDetails.ProcessingSummary.IgnoredLines.ShouldBe(0); }
public void FileAggregate_CanBeCreated_InvalidFileId_IsCreated() { Should.Throw <ArgumentNullException>(() => { FileAggregate fileAggregate = FileAggregate.Create(Guid.Empty); }); }
/// <summary> /// Handles the specified request. /// </summary> /// <param name="request">The request.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns></returns> /// <exception cref="System.NotSupportedException">File Id [{request.FileId}] has no lines added</exception> /// <exception cref="NotFoundException"> /// File Line Number {request.LineNumber} not found in File Id {request.FileId} /// or /// No file profile found with Id {fileDetails.FileProfileId} /// or /// Merchant not found with Id {fileDetails.MerchantId} on estate Id {fileDetails.EstateId} /// or /// No contracts found for Merchant Id {fileDetails.MerchantId} on estate Id {fileDetails.EstateId} /// or /// No merchant contract for operator Id {fileProfile.OperatorName} found for Merchant Id {merchant.MerchantId} /// or /// No variable value product found on the merchant contract for operator Id {fileProfile.OperatorName} and Merchant Id {merchant.MerchantId} /// </exception> public async Task <Unit> Handle(ProcessTransactionForFileLineRequest request, CancellationToken cancellationToken) { // Get the file aggregate, this tells us the file profile information FileAggregate fileAggregate = await this.FileAggregateRepository.GetLatestVersion(request.FileId, cancellationToken); FileDetails fileDetails = fileAggregate.GetFile(); if (fileDetails.FileLines.Any() == false) { throw new NotSupportedException($"File Id [{request.FileId}] has no lines added"); } FileLine fileLine = fileDetails.FileLines.SingleOrDefault(f => f.LineNumber == request.LineNumber); if (fileLine == null) { throw new NotFoundException($"File Line Number {request.LineNumber} not found in File Id {request.FileId}"); } if (fileLine.ProcessingResult != ProcessingResult.NotProcessed) { // Line already processed return(new Unit()); } FileProfile fileProfile = await this.FileProcessorManager.GetFileProfile(fileDetails.FileProfileId, cancellationToken); if (fileProfile == null) { throw new NotFoundException($"No file profile found with Id {fileDetails.FileProfileId}"); } // Determine if we need to actually process this file line if (this.FileLineCanBeIgnored(fileLine.LineData, fileProfile.FileFormatHandler)) { // Write something to aggregate to say line was explicity ignored fileAggregate.RecordFileLineAsIgnored(fileLine.LineNumber); await this.FileAggregateRepository.SaveChanges(fileAggregate, cancellationToken); return(new Unit()); } // need to now parse the line (based on the file format), this builds the metadata Dictionary <String, String> transactionMetadata = this.ParseFileLine(fileLine.LineData, fileProfile.FileFormatHandler); if (transactionMetadata == null) { // Line failed to parse so record this fileAggregate.RecordFileLineAsRejected(fileLine.LineNumber, "Invalid Format"); await this.FileAggregateRepository.SaveChanges(fileAggregate, cancellationToken); return(new Unit()); } // Add the file data to the request metadata transactionMetadata.Add("FileId", request.FileId.ToString()); transactionMetadata.Add("FileLineNumber", fileLine.LineNumber.ToString()); String operatorName = fileProfile.OperatorName; if (transactionMetadata.ContainsKey("OperatorName")) { // extract the value operatorName = transactionMetadata["OperatorName"]; transactionMetadata = transactionMetadata.Where(x => x.Key != "OperatorName").ToDictionary(x => x.Key, x => x.Value); } this.TokenResponse = await this.GetToken(cancellationToken); Interlocked.Increment(ref FileRequestHandler.TransactionNumber); // Get the merchant details MerchantResponse merchant = await this.EstateClient.GetMerchant(this.TokenResponse.AccessToken, fileDetails.EstateId, fileDetails.MerchantId, cancellationToken); if (merchant == null) { throw new NotFoundException($"Merchant not found with Id {fileDetails.MerchantId} on estate Id {fileDetails.EstateId}"); } List <ContractResponse> contracts = await this.EstateClient.GetMerchantContracts(this.TokenResponse.AccessToken, fileDetails.EstateId, fileDetails.MerchantId, cancellationToken); if (contracts.Any() == false) { throw new NotFoundException($"No contracts found for Merchant Id {fileDetails.MerchantId} on estate Id {fileDetails.EstateId}"); } ContractResponse?contract = null; if (fileProfile.OperatorName == "Voucher") { contract = contracts.SingleOrDefault(c => c.Description.Contains(operatorName)); } else { contract = contracts.SingleOrDefault(c => c.OperatorName == operatorName); } if (contract == null) { throw new NotFoundException($"No merchant contract for operator Id {operatorName} found for Merchant Id {merchant.MerchantId}"); } ContractProduct?product = contract.Products.SingleOrDefault(p => p.Value == null); // TODO: Is this enough or should the name be used and stored in file profile?? if (product == null) { throw new NotFoundException($"No variable value product found on the merchant contract for operator Id {fileProfile.OperatorName} and Merchant Id {merchant.MerchantId}"); } // Build a transaction request message SaleTransactionRequest saleTransactionRequest = new SaleTransactionRequest { EstateId = fileDetails.EstateId, MerchantId = fileDetails.MerchantId, TransactionDateTime = fileDetails.FileReceivedDateTime, TransactionNumber = FileRequestHandler.TransactionNumber.ToString(), TransactionType = "Sale", ContractId = contract.ContractId, DeviceIdentifier = merchant.Devices.First().Value, OperatorIdentifier = contract.OperatorName, ProductId = product.ProductId, AdditionalTransactionMetadata = transactionMetadata, }; SerialisedMessage serialisedRequestMessage = new SerialisedMessage { Metadata = new Dictionary <String, String> { { "estate_id", fileDetails.EstateId.ToString() }, { "merchant_id", fileDetails.MerchantId.ToString() } }, SerialisedData = JsonConvert.SerializeObject(saleTransactionRequest, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }) }; Logger.LogInformation(serialisedRequestMessage.SerialisedData); // Send request to transaction processor SerialisedMessage serialisedResponseMessage = await this.TransactionProcessorClient.PerformTransaction(this.TokenResponse.AccessToken, serialisedRequestMessage, cancellationToken); // Get the sale transaction response SaleTransactionResponse saleTransactionResponse = JsonConvert.DeserializeObject <SaleTransactionResponse>(serialisedResponseMessage.SerialisedData); if (saleTransactionResponse.ResponseCode == "0000") { // record response against file line in file aggregate fileAggregate.RecordFileLineAsSuccessful(request.LineNumber, saleTransactionResponse.TransactionId); } else { fileAggregate.RecordFileLineAsFailed(request.LineNumber, saleTransactionResponse.TransactionId, saleTransactionResponse.ResponseCode, saleTransactionResponse.ResponseMessage); } // Save changes to file aggregate // TODO: Add retry round this save (maybe 3 retries) await this.FileAggregateRepository.SaveChanges(fileAggregate, cancellationToken); return(new Unit()); }
private async Task <Unit> ProcessFile(Guid fileId, Guid fileProfileId, String fileName, CancellationToken cancellationToken) { IFileInfo inProgressFile = null; FileProfile fileProfile = null; try { fileProfile = await this.FileProcessorManager.GetFileProfile(fileProfileId, cancellationToken); if (fileProfile == null) { throw new NotFoundException($"No file profile found with Id {fileProfileId}"); } // Check the processed/failed directories exist if (this.FileSystem.Directory.Exists(fileProfile.ProcessedDirectory) == false) { Logger.LogWarning($"Creating Directory {fileProfile.ProcessedDirectory} as not found"); this.FileSystem.Directory.CreateDirectory(fileProfile.ProcessedDirectory); } if (this.FileSystem.Directory.Exists(fileProfile.FailedDirectory) == false) { Logger.LogWarning($"Creating Directory {fileProfile.FailedDirectory} as not found"); this.FileSystem.Directory.CreateDirectory(fileProfile.FailedDirectory); } inProgressFile = this.FileSystem.FileInfo.FromFileName(fileName); if (inProgressFile.Exists == false) { throw new FileNotFoundException($"File {inProgressFile.FullName} not found"); } FileAggregate fileAggregate = await this.FileAggregateRepository.GetLatestVersion(fileId, cancellationToken); if (fileAggregate.IsCreated == false) { throw new InvalidOperationException($"File with Id {fileId} not created"); } String fileContent = null; //Open file for Read\Write using (Stream fs = inProgressFile.Open(FileMode.OpenOrCreate, FileAccess.Read, FileShare.Read)) { //Create object of StreamReader by passing FileStream object on which it needs to operates on using (StreamReader sr = new StreamReader(fs)) { //Use ReadToEnd method to read all the content from file fileContent = await sr.ReadToEndAsync(); } } if (String.IsNullOrEmpty(fileContent) == false) { String[] fileLines = fileContent.Split(fileProfile.LineTerminator); foreach (String fileLine in fileLines) { fileAggregate.AddFileLine(fileLine); } await this.FileAggregateRepository.SaveChanges(fileAggregate, cancellationToken); } Logger.LogInformation( $"About to move file {inProgressFile.Name} to [{fileProfile.ProcessedDirectory}]"); // TODO: Move file now inProgressFile.MoveTo($"{fileProfile.ProcessedDirectory}/{inProgressFile.Name}"); return(new Unit()); } catch (Exception e) { if (inProgressFile != null && fileProfile != null) { inProgressFile.MoveTo($"{fileProfile.FailedDirectory}/{inProgressFile.Name}"); } Logger.LogError(e); throw; } }