From 0cad0464574d70c329b6743b1eea400750192bc8 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Wed, 28 Oct 2026 14:59:02 +0000 Subject: [PATCH 1/2] changes to client interface --- .../Common/FileLineProcessingDiagnostics.cs | 1 + FileProcessor.Client/FileProcessorClient.cs | 197 +++++++----------- FileProcessor.Client/IFileProcessorClient.cs | 116 ----------- .../FileProcessorSteps.cs | 43 ++-- 4 files changed, 100 insertions(+), 257 deletions(-) delete mode 100644 FileProcessor.Client/IFileProcessorClient.cs diff --git a/FileProcessor.BusinessLogic/Common/FileLineProcessingDiagnostics.cs b/FileProcessor.BusinessLogic/Common/FileLineProcessingDiagnostics.cs index c5a6467..0cba6d4 100644 --- a/FileProcessor.BusinessLogic/Common/FileLineProcessingDiagnostics.cs +++ b/FileProcessor.BusinessLogic/Common/FileLineProcessingDiagnostics.cs @@ -46,6 +46,7 @@ public FileLineProcessingContext(FileCommands.ProcessTransactionForFileLineComma public Boolean TransactionDispatchSucceeded { get; set; } public String TransactionDispatchFailureType { get; set; } + public String TransactionDispatchFailureResult { get; set; } public Guid EstateId { get; set; } diff --git a/FileProcessor.Client/FileProcessorClient.cs b/FileProcessor.Client/FileProcessorClient.cs index fb9ef84..49b3cbe 100644 --- a/FileProcessor.Client/FileProcessorClient.cs +++ b/FileProcessor.Client/FileProcessorClient.cs @@ -1,7 +1,5 @@ using System.Linq; using System.Security.Cryptography; -using System.Net.Http.Headers; -using System.Text; using Shared.Results; namespace FileProcessor.Client { @@ -19,54 +17,89 @@ namespace FileProcessor.Client { using FileProcessor.Models; using SimpleResults; - /// - /// - /// - /// - /// - public class FileProcessorClient : ClientProxyBase, IFileProcessorClient { - #region Fields + public enum SortDirection { + Ascending, + Descending, + } + public record PagingRequest(Int32 PageNumber, Int32 PageSize, SortDirection Direction); - /// - /// The base address resolver - /// - private readonly Func BaseAddressResolver; + public interface IFileProcessorClient { - private readonly HttpClient HttpClient; + Task>> GetFileProfiles(String accessToken, + CancellationToken cancellationToken); - private readonly Func Serialise; + Task> GetFileProfile(String accessToken, + Guid fileProfileId, + CancellationToken cancellationToken); - private readonly Func Deserialise; + Task>> GetFileProfileVersions(String accessToken, + Guid fileProfileId, + CancellationToken cancellationToken); - #endregion + Task> GetFileProfileVersion(String accessToken, + Guid fileProfileId, + Guid fileProfileVersionId, + CancellationToken cancellationToken); + + Task> CreateFileProfile(String accessToken, + DataTransferObjects.Requests.CreateFileProfileRequest request, + CancellationToken cancellationToken); + + Task> UpdateFileProfile(String accessToken, + Guid fileProfileId, + DataTransferObjects.Requests.UpdateFileProfileRequest request, + CancellationToken cancellationToken); - #region Constructors + Task ArchiveFileProfile(String accessToken, + Guid fileProfileId, + CancellationToken cancellationToken); + + Task> GetFile(String accessToken, + Guid estateId, + Guid fileId, + CancellationToken cancellationToken); + + /// + Task> GetFileImportLog(String accessToken, + Guid fileImportLogId, + Guid estateId, + Guid? merchantId, + CancellationToken cancellationToken); + + Task> GetFileImportLogs(String accessToken, + Guid estateId, + DateOnly startDate, + DateOnly endDate, + Guid? merchantId, + PagingRequest pagingRequest, + CancellationToken cancellationToken); + + Task> UploadFile(String accessToken, + String fileName, + Byte[] fileData, + UploadFileRequest uploadFileRequest, + CancellationToken cancellationToken); + + } + + public class FileProcessorClient : ClientProxyBase, IFileProcessorClient { + + private readonly Func BaseAddressResolver; - /// - /// Initializes a new instance of the class. - /// - /// The base address resolver. - /// The HTTP client. public FileProcessorClient(Func baseAddressResolver, HttpClient httpClient, Func serialise, - Func deserialise) : base(httpClient, serialise, deserialise) - { + Func deserialise) : base(httpClient, serialise, deserialise) { this.BaseAddressResolver = baseAddressResolver; - this.HttpClient = httpClient; - this.Serialise = serialise; - this.Deserialise = deserialise; } - #endregion - #region Methods public async Task>> GetFileProfiles(String accessToken, CancellationToken cancellationToken) { String requestUri = this.BuildRequestUrl("/api/file-profiles"); - return await this.SendRequest>(HttpMethod.Get, requestUri, accessToken, null, cancellationToken); + return await this.Get>(requestUri, accessToken, null, cancellationToken); } public async Task> GetFileProfile(String accessToken, @@ -74,7 +107,7 @@ public async Task> GetFileProfile(String accessToken, CancellationToken cancellationToken) { String requestUri = this.BuildRequestUrl($"/api/file-profiles/{fileProfileId}"); - return await this.SendRequest(HttpMethod.Get, requestUri, accessToken, null, cancellationToken); + return await this.Get(requestUri, accessToken, null, cancellationToken); } public async Task>> GetFileProfileVersions(String accessToken, @@ -82,7 +115,7 @@ public async Task>> GetFileProfileVersions(String acces CancellationToken cancellationToken) { String requestUri = this.BuildRequestUrl($"/api/file-profiles/{fileProfileId}/versions"); - return await this.SendRequest>(HttpMethod.Get, requestUri, accessToken, null, cancellationToken); + return await this.Get>(requestUri, accessToken, null, cancellationToken); } public async Task> GetFileProfileVersion(String accessToken, @@ -91,7 +124,7 @@ public async Task> GetFileProfileVersion(String accessToken, CancellationToken cancellationToken) { String requestUri = this.BuildRequestUrl($"/api/file-profiles/{fileProfileId}/versions/{fileProfileVersionId}"); - return await this.SendRequest(HttpMethod.Get, requestUri, accessToken, null, cancellationToken); + return await this.Get(requestUri, accessToken, null, cancellationToken); } public async Task> CreateFileProfile(String accessToken, @@ -99,7 +132,7 @@ public async Task> CreateFileProfile(String accessToken, CancellationToken cancellationToken) { String requestUri = this.BuildRequestUrl("/api/file-profiles"); - return await this.SendRequest(HttpMethod.Post, requestUri, accessToken, request, cancellationToken); + return await this.Post(requestUri, request, accessToken, cancellationToken); } public async Task> UpdateFileProfile(String accessToken, @@ -108,7 +141,7 @@ public async Task> UpdateFileProfile(String accessToken, CancellationToken cancellationToken) { String requestUri = this.BuildRequestUrl($"/api/file-profiles/{fileProfileId}"); - return await this.SendRequest(HttpMethod.Patch, requestUri, accessToken, request, cancellationToken); + return await this.Patch(requestUri, request, accessToken, cancellationToken); } public async Task ArchiveFileProfile(String accessToken, @@ -116,7 +149,7 @@ public async Task ArchiveFileProfile(String accessToken, CancellationToken cancellationToken) { String requestUri = this.BuildRequestUrl($"/api/file-profiles/{fileProfileId}"); - Result result = await this.SendRequest(HttpMethod.Delete, requestUri, accessToken, null, cancellationToken); + Result result = await this.Delete(requestUri, accessToken, cancellationToken); if (result.IsFailed) { @@ -126,14 +159,6 @@ public async Task ArchiveFileProfile(String accessToken, return Result.Success(); } - /// - /// Gets the file. - /// - /// The access token. - /// The estate identifier. - /// The file identifier. - /// The cancellation token. - /// public async Task> GetFile(String accessToken, Guid estateId, Guid fileId, @@ -156,15 +181,6 @@ public async Task> GetFile(String accessToken, } } - /// - /// Gets the file import log. - /// - /// The access token. - /// The file import log identifier. - /// The estate identifier. - /// The merchant identifier. - /// The cancellation token. - /// public async Task> GetFileImportLog(String accessToken, Guid fileImportLogId, Guid estateId, @@ -192,27 +208,21 @@ public async Task> GetFileImportLog(String accessToken, } } - /// - /// Gets the file import logs. - /// - /// The access token. - /// The estate identifier. - /// The first calendar date. - /// The last calendar date. - /// The merchant identifier. - /// The cancellation token. - /// public async Task> GetFileImportLogs(String accessToken, Guid estateId, DateOnly startDate, DateOnly endDate, Guid? merchantId, - Int32 pageNumber, - Int32 pageSize, - Boolean descending, + PagingRequest pagingRequest, CancellationToken cancellationToken) { + + Boolean descending = pagingRequest.Direction switch { + SortDirection.Ascending => false, + _ => true + }; + String requestUri = this.BuildRequestUrl( - $"/api/fileImportLogs?estateId={estateId}&startDate={startDate:yyyy-MM-dd}&endDate={endDate:yyyy-MM-dd}&pageNumber={pageNumber}&pageSize={pageSize}&descending={descending}"); + $"/api/fileImportLogs?estateId={estateId}&startDate={startDate:yyyy-MM-dd}&endDate={endDate:yyyy-MM-dd}&pageNumber={pagingRequest.PageNumber}&pageSize={pagingRequest.PageSize}&descending={descending}"); if (merchantId.HasValue) requestUri += $"&merchantId={merchantId}"; @@ -221,15 +231,6 @@ public async Task> GetFileImportLogs(String accessToke return result.IsFailed ? ResultHelpers.CreateFailure(result) : result; } - /// - /// Uploads the file. - /// - /// The access token. - /// Name of the file. - /// The file data. - /// The upload file request. - /// The cancellation token. - /// public async Task> UploadFile(String accessToken, String fileName, Byte[] fileData, @@ -276,11 +277,6 @@ private Guid CreateGuidFromFileData(Byte[] fileContents) } } - /// - /// Builds the request URL. - /// - /// The route. - /// private String BuildRequestUrl(String route) { String baseAddress = this.BaseAddressResolver("FileProcessorApi"); @@ -289,43 +285,6 @@ private String BuildRequestUrl(String route) { return requestUri; } - private async Task> SendRequest(HttpMethod method, - String requestUri, - String accessToken, - Object body, - CancellationToken cancellationToken) - { - using HttpRequestMessage request = new(method, requestUri); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); - - if (body != null) - { - String payload = this.Serialise(body); - request.Content = new StringContent(payload, Encoding.UTF8, "application/json"); - } - - HttpResponseMessage response = await this.HttpClient.SendAsync(request, cancellationToken); - String responseContent = response.Content == null ? null : await response.Content.ReadAsStringAsync(cancellationToken); - - if (response.IsSuccessStatusCode == false) - { - String message = String.IsNullOrWhiteSpace(responseContent) - ? $"Request failed with status code {(Int32)response.StatusCode}" - : responseContent; - - return response.StatusCode == System.Net.HttpStatusCode.NotFound - ? Result.NotFound(message) - : Result.Failure(message); - } - - if (String.IsNullOrWhiteSpace(responseContent)) - { - return Result.Success(default(T)); - } - - Object deserialised = this.Deserialise(responseContent, typeof(T)); - return Result.Success((T)deserialised); - } #endregion } diff --git a/FileProcessor.Client/IFileProcessorClient.cs b/FileProcessor.Client/IFileProcessorClient.cs deleted file mode 100644 index caa0d19..0000000 --- a/FileProcessor.Client/IFileProcessorClient.cs +++ /dev/null @@ -1,116 +0,0 @@ -using SimpleResults; - -namespace FileProcessor.Client -{ - using System; - using System.Collections.Generic; - using System.Threading; - using System.Threading.Tasks; - using DataTransferObjects; - using ApiFileDetails = DataTransferObjects.Responses.FileDetails; - using ApiFileImportLog = DataTransferObjects.Responses.FileImportLog; - using DataTransferObjects.Responses; - using FileProcessor.Models; - - /// - /// - /// - public interface IFileProcessorClient - { - #region Methods - - Task>> GetFileProfiles(String accessToken, - CancellationToken cancellationToken); - - Task> GetFileProfile(String accessToken, - Guid fileProfileId, - CancellationToken cancellationToken); - - Task>> GetFileProfileVersions(String accessToken, - Guid fileProfileId, - CancellationToken cancellationToken); - - Task> GetFileProfileVersion(String accessToken, - Guid fileProfileId, - Guid fileProfileVersionId, - CancellationToken cancellationToken); - - Task> CreateFileProfile(String accessToken, - DataTransferObjects.Requests.CreateFileProfileRequest request, - CancellationToken cancellationToken); - - Task> UpdateFileProfile(String accessToken, - Guid fileProfileId, - DataTransferObjects.Requests.UpdateFileProfileRequest request, - CancellationToken cancellationToken); - - Task ArchiveFileProfile(String accessToken, - Guid fileProfileId, - CancellationToken cancellationToken); - - /// - /// Gets the file. - /// - /// The access token. - /// The estate identifier. - /// The file identifier. - /// The cancellation token. - /// - Task> GetFile(String accessToken, - Guid estateId, - Guid fileId, - CancellationToken cancellationToken); - - /// - /// Gets the file import log. - /// - /// The access token. - /// The file import log identifier. - /// The estate identifier. - /// The merchant identifier. - /// The cancellation token. - /// - Task> GetFileImportLog(String accessToken, - Guid fileImportLogId, - Guid estateId, - Guid? merchantId, - CancellationToken cancellationToken); - - /// - /// Gets the file import logs. - /// - /// The access token. - /// The estate identifier. - /// The first calendar date. - /// The last calendar date. - /// The merchant identifier. - /// The cancellation token. - /// - Task> GetFileImportLogs(String accessToken, - Guid estateId, - DateOnly startDate, - DateOnly endDate, - Guid? merchantId, - Int32 pageNumber, - Int32 pageSize, - Boolean descending, - CancellationToken cancellationToken); - - /// - /// Uploads the file. - /// - /// The access token. - /// Name of the file. - /// The file data. - /// The upload file request. - /// The cancellation token. - /// - Task> UploadFile(String accessToken, - String fileName, - Byte[] fileData, - UploadFileRequest uploadFileRequest, - CancellationToken cancellationToken); - - #endregion - } -} diff --git a/FileProcessor.IntegrationTesting.Helpers/FileProcessorSteps.cs b/FileProcessor.IntegrationTesting.Helpers/FileProcessorSteps.cs index 7eb6673..be6bbef 100644 --- a/FileProcessor.IntegrationTesting.Helpers/FileProcessorSteps.cs +++ b/FileProcessor.IntegrationTesting.Helpers/FileProcessorSteps.cs @@ -40,7 +40,7 @@ public async Task GivenIUploadThisFileForProcessing(String accessToken, St uploadFileRequest, CancellationToken.None); uploadFileResult.IsSuccess.ShouldBeTrue(); - var fileId = uploadFileResult.Data; + Guid fileId = uploadFileResult.Data; fileId.ShouldNotBe(Guid.Empty); estateDetails.AddUploadedFile(new FileImportLogFile @@ -124,7 +124,7 @@ public async Task GivenIUploadThisFileForProcessingAnErrorShouldBeReturnedIndica String filePath, Byte[] fileData, UploadFileRequest uploadFileRequest) { - var result = await this.FileProcessorClient.UploadFile(accessToken, Path.GetFileName(filePath), fileData, uploadFileRequest, CancellationToken.None); + Result result = await this.FileProcessorClient.UploadFile(accessToken, Path.GetFileName(filePath), fileData, uploadFileRequest, CancellationToken.None); result.IsFailed.ShouldBeTrue(); } @@ -135,15 +135,15 @@ public async Task WhenIGetTheImportLogsBetweenAndTheFollowingDataIsReturned(Stri EstateDetails1 estate = estateDetailsList.SingleOrDefault(e => e.EstateDetails.EstateName == estateName); estate.ShouldNotBeNull(); + PagingRequest pagingRequest = new(1, 50, Client.SortDirection.Descending); + await Retry.For(async () => { - var getFileImportLogsResult = await this.FileProcessorClient.GetFileImportLogs(accessToken, + Result getFileImportLogsResult = await this.FileProcessorClient.GetFileImportLogs(accessToken, estate.EstateDetails.EstateId, DateOnly.FromDateTime(queryStartDate), DateOnly.FromDateTime(queryEndDate), null, - 1, - 50, - false, + pagingRequest, CancellationToken.None); getFileImportLogsResult.IsSuccess.ShouldBeTrue(); FileImportLogList importLogList = getFileImportLogsResult.Data; @@ -165,23 +165,23 @@ public async Task WhenIGetTheImportLogForTheFollowingFileInformationIsReturned(S EstateDetails1 estate = estateDetailsList.SingleOrDefault(e => e.EstateDetails.EstateName == estateName); estate.ShouldNotBeNull(); + PagingRequest pagingRequest = new(1, 50, Client.SortDirection.Descending); + DateTime queryStartDate = ReqnrollTableHelper.GetDateForDateString(startDate, DateTime.Now); - var importLogListResult = await this.FileProcessorClient.GetFileImportLogs(accessToken, + Result importLogListResult = await this.FileProcessorClient.GetFileImportLogs(accessToken, estate.EstateDetails.EstateId, DateOnly.FromDateTime(queryStartDate), DateOnly.FromDateTime(queryStartDate), null, - 1, - 50, - false, + pagingRequest, CancellationToken.None); importLogListResult.IsSuccess.ShouldBeTrue(); - var importLogList = importLogListResult.Data; + FileImportLogList importLogList = importLogListResult.Data; importLogList.ShouldNotBeNull(); importLogList.FileImportLogs.ShouldHaveSingleItem(); Guid fileImportLogId = importLogList.FileImportLogs.Single().FileImportLogId; - var getImportLogResult = await this.FileProcessorClient.GetFileImportLog(accessToken, + Result getImportLogResult = await this.FileProcessorClient.GetFileImportLog(accessToken, fileImportLogId, estate.EstateDetails.EstateId, null, @@ -209,14 +209,13 @@ public async Task WhenIGetTheImportLogForEstateTheDateOnTheImportLogIs(String ac DateTime expectedDate = ReqnrollTableHelper.GetDateForDateString(expectedDateString, DateTime.Now); - var getFileImportLogsResult = await this.FileProcessorClient.GetFileImportLogs(accessToken, + PagingRequest pagingRequest = new(1, 50, Client.SortDirection.Descending); + Result getFileImportLogsResult = await this.FileProcessorClient.GetFileImportLogs(accessToken, estate.EstateDetails.EstateId, DateOnly.FromDateTime(expectedDate.AddDays(-1)), DateOnly.FromDateTime(DateTime.Now), null, - 1, - 50, - false, + pagingRequest, CancellationToken.None); getFileImportLogsResult.IsSuccess.ShouldBeTrue(); @@ -234,9 +233,9 @@ public async Task WhenIGetTheFileForEstateTheFollowingFileInformationIsReturned( await Retry.For(async () => { - var getFileResult = await this.FileProcessorClient.GetFile(accessToken, estate.EstateDetails.EstateId, fileId, CancellationToken.None); + Result getFileResult = await this.FileProcessorClient.GetFile(accessToken, estate.EstateDetails.EstateId, fileId, CancellationToken.None); getFileResult.IsSuccess.ShouldBeTrue(); - var fileDetails = getFileResult.Data; + FileDetails fileDetails = getFileResult.Data; fileDetails.ShouldNotBeNull(); fileDetails.ProcessingCompleted.ShouldBe(fileSummary.ProcessingCompleted); @@ -256,9 +255,9 @@ public async Task WhenIGetTheFileForEstateTheFollowingFileLinesAreReturned(Strin Guid fileId = estate.GetFileId(fileName); await Retry.For(async () => { - var getFileResult = await this.FileProcessorClient.GetFile(accessToken, estate.EstateDetails.EstateId, fileId, CancellationToken.None); + Result getFileResult = await this.FileProcessorClient.GetFile(accessToken, estate.EstateDetails.EstateId, fileId, CancellationToken.None); getFileResult.IsSuccess.ShouldBeTrue(); - var fileDetails = getFileResult.Data; + FileDetails fileDetails = getFileResult.Data; fileDetails.ShouldNotBeNull(); foreach (ReqnrollExtensions.FileLineDetails fileLineDetail in fileLineDetails){ @@ -285,9 +284,9 @@ public async Task WhenIGetTheFileForEstateItIsRejectedForExceedingTheDetailRowLi await Retry.For(async () => { - var getFileResult = await this.FileProcessorClient.GetFile(accessToken, estate.EstateDetails.EstateId, fileId, CancellationToken.None); + Result getFileResult = await this.FileProcessorClient.GetFile(accessToken, estate.EstateDetails.EstateId, fileId, CancellationToken.None); getFileResult.IsSuccess.ShouldBeTrue(); - var fileDetails = getFileResult.Data; + FileDetails fileDetails = getFileResult.Data; fileDetails.ShouldNotBeNull(); fileDetails.IsRejected.ShouldBeTrue(); fileDetails.RejectionCode.ShouldBe("MaximumDetailRecordsExceeded"); From 22990d16cdcf25d0b8233db68800386fec4d1148 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Wed, 28 Oct 2026 17:06:45 +0000 Subject: [PATCH 2/2] fix test failures --- .../Features/FileProfile.feature.cs | 9 +++++++++ .../Features/FileProfileSteps.cs | 5 ++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/FileProcessor.IntegrationTests/Features/FileProfile.feature.cs b/FileProcessor.IntegrationTests/Features/FileProfile.feature.cs index c1d7bc3..9463186 100644 --- a/FileProcessor.IntegrationTests/Features/FileProfile.feature.cs +++ b/FileProcessor.IntegrationTests/Features/FileProfile.feature.cs @@ -390,6 +390,15 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Operator", "1", "UnsupportedFileFormatHandler"}); + table10.AddRow(new string[] { + "Invalid Profile", + "55555555-5555-5555-5555-555555555555", + "Invalid Profile", + "/home/txnproc/bulkfiles/invalid", + "Profile A", + "Operator", + "1", + "UnsupportedFileFormatHandler"}); #line 48 await testRunner.WhenAsync("I try to create the following invalid file profiles", ((string)(null)), table10, "When "); #line hidden diff --git a/FileProcessor.IntegrationTests/Features/FileProfileSteps.cs b/FileProcessor.IntegrationTests/Features/FileProfileSteps.cs index 47a89ed..67762e3 100644 --- a/FileProcessor.IntegrationTests/Features/FileProfileSteps.cs +++ b/FileProcessor.IntegrationTests/Features/FileProfileSteps.cs @@ -109,8 +109,8 @@ public async Task WhenITryToCreateTheFollowingInvalidFileProfiles(DataTable tabl CancellationToken.None); result.IsFailed.ShouldBeTrue(result.Message); - result.Message.ShouldContain("Unsupported request type"); - result.Message.ShouldContain("Unsupported file format handler"); + result.Errors.Any(error => error.Contains("Unsupported request type")).ShouldBeTrue(); + result.Errors.Any(error => error.Contains("Unsupported file format handler")).ShouldBeTrue(); } } @@ -218,7 +218,6 @@ private async Task UpdateProfile(string alias, UpdateFileProfileRequest request) Result updateResult = await this.FileProcessorSteps.UpdateFileProfile(this.TestingContext.AccessToken, fileProfileId, request, CancellationToken.None); updateResult.IsSuccess.ShouldBeTrue(updateResult.Message); - updateResult.Data.ShouldNotBeNull(); } private async Task GetProfileByAlias(string alias)