Skip to content
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
39 changes: 17 additions & 22 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@
<PackageVersion Include="AspNetCore.HealthChecks.UI.Client" Version="9.0.0" />
<PackageVersion Include="AspNetCore.HealthChecks.UI.InMemory.Storage" Version="9.0.0" />
<PackageVersion Include="AspNetCore.HealthChecks.Uris" Version="9.0.0" />
<PackageVersion Include="ClientProxyBase" Version="2026.10.3" />
<PackageVersion Include="HealthMonitoring.Client" Version="2026.9.4.53" />
<PackageVersion Include="ClientProxyBase" Version="2026.10.4" />
<PackageVersion Include="HealthMonitoring.Client" Version="2026.10.1" />
<PackageVersion Include="OpenIddict.Validation.AspNetCore" Version="7.7.2" />
<PackageVersion Include="OpenIddict.Validation.SystemNetHttp" Version="7.7.2" />
<PackageVersion Include="coverlet.collector" Version="10.1.0" />
<PackageVersion Include="coverlet.msbuild" Version="10.1.0" />
<PackageVersion Include="Ductus.FluentDocker" Version="2.85.0" />
<PackageVersion Include="EventStoreProjections" Version="2023.12.3" />
<PackageVersion Include="Grpc.Net.Client" Version="2.84.0" />
<PackageVersion Include="Imposter" Version="1.0.1" />
<PackageVersion Include="Imposter" Version="1.0.3" />
<PackageVersion Include="Lamar" Version="16.0.0" />
<PackageVersion Include="Lamar.Microsoft.DependencyInjection" Version="16.0.0" />
<PackageVersion Include="MessagingService.IntegrationTesting.Helpers" Version="2026.9.3.155" />
<PackageVersion Include="MessagingService.IntegrationTesting.Helpers" Version="2026.10.2.156" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.13" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="10.0.13" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.13" />
Expand All @@ -37,27 +37,22 @@
<PackageVersion Include="Reqnroll.NUnit" Version="3.3.4" />
<PackageVersion Include="Reqnroll.Tools.MsBuild.Generation" Version="3.3.4" />
<PackageVersion Include="Sentry.AspNetCore" Version="6.12.1" />
<PackageVersion Include="SharpCompress" Version="0.50.5" />
<PackageVersion Include="SharpCompress" Version="0.50.7" />
<PackageVersion Include="Shouldly" Version="4.3.0" />
<PackageVersion Include="Shared" Version="2026.10.3" />
<PackageVersion Include="Shared.DomainDrivenDesign" Version="2026.10.3" />
<PackageVersion Include="Shared.EventStore" Version="2026.10.3" />
<PackageVersion Include="Shared.IntegrationTesting" Version="2026.10.3" />
<PackageVersion Include="Shared.Results" Version="2026.10.3" />
<PackageVersion Include="Shared.Results.Web" Version="2026.10.3" />
<PackageVersion Include="SecurityService.Client" Version="2026.9.3.199" />
<PackageVersion Include="SecurityService.IntegrationTesting.Helpers" Version="2026.9.3.199" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="10.3.0" />
<PackageVersion Include="Swashbuckle.AspNetCore.Annotations" Version="10.3.0" />
<PackageVersion Include="Swashbuckle.AspNetCore.Filters" Version="10.0.1" />
<PackageVersion Include="Swashbuckle.AspNetCore.Swagger" Version="10.3.0" />
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerGen" Version="10.3.0" />
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.3.0" />
<PackageVersion Include="Shared" Version="2026.10.4" />
<PackageVersion Include="Shared.DomainDrivenDesign" Version="2026.10.4" />
<PackageVersion Include="Shared.EventStore" Version="2026.10.4" />
<PackageVersion Include="Shared.IntegrationTesting" Version="2026.10.4" />
<PackageVersion Include="Shared.Results" Version="2026.10.4" />
<PackageVersion Include="Shared.Results.Web" Version="2026.10.4" />
<PackageVersion Include="SecurityService.Client" Version="2026.10.2.200" />
<PackageVersion Include="SecurityService.IntegrationTesting.Helpers" Version="2026.10.2.200" />
<PackageVersion Include="System.IO.Abstractions" Version="22.3.0" />
<PackageVersion Include="System.IO.Abstractions.TestingHelpers" Version="22.3.0" />
<PackageVersion Include="TransactionProcessor.Client" Version="2026.9.3.409" />
<PackageVersion Include="TransactionProcessor.Database" Version="2026.9.3.409" />
<PackageVersion Include="TransactionProcessor.IntegrationTesting.Helpers" Version="2026.9.3.409" />
<PackageVersion Include="TransactionProcessing.ApiDefaults" Version="2026.10.4" />
<PackageVersion Include="TransactionProcessor.Client" Version="2026.10.2.411" />
<PackageVersion Include="TransactionProcessor.Database" Version="2026.10.2.411" />
<PackageVersion Include="TransactionProcessor.IntegrationTesting.Helpers" Version="2026.10.2.411" />
<PackageVersion Include="xunit.v3" Version="4.0.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="4.0.1" />
</ItemGroup>
Expand Down
34 changes: 20 additions & 14 deletions FileProcessor.BusinessLogic.Tests/FileHandlersTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,17 +66,12 @@ public async Task UploadFile_WhenEventStoreAcceptanceFails_DeletesTemporaryFile(
FormFileCollection files = new() { formFile };
FormCollection formCollection = new(new Dictionary<string, Microsoft.Extensions.Primitives.StringValues>(), files);

DefaultHttpContext httpContext = new();
httpContext.Request.Form = formCollection;
await FileHandlers.UploadFile(
mediator.Instance(),
new FileProcessor.DataTransferObjects.UploadFileRequest
{
FileId = TestData.FileId,
EstateId = TestData.EstateId,
MerchantId = TestData.MerchantId,
UserId = TestData.UserId,
FileProfileId = TestData.FileProfileId
},
formCollection,
CreateUploadRequest(formFile),
httpContext.Request,
CancellationToken.None);

Directory.GetFiles(temporaryDirectory).ShouldBeEmpty();
Expand Down Expand Up @@ -107,8 +102,15 @@ public async Task UploadFile_UsesUniqueTemporaryPathForRepeatedClientFilename()
IMediatorImposter mediator = new();
mediator.Send(Arg<IRequest<Result>>.Any(), Arg<CancellationToken>.Any()).ReturnsAsync(Result.Success());

await FileHandlers.UploadFile(mediator.Instance(), CreateUploadRequest(), CreateFormCollection(), CancellationToken.None);
await FileHandlers.UploadFile(mediator.Instance(), CreateUploadRequest(), CreateFormCollection(), CancellationToken.None);
FormCollection firstForm = CreateFormCollection();
DefaultHttpContext firstContext = new();
firstContext.Request.Form = firstForm;
await FileHandlers.UploadFile(mediator.Instance(), CreateUploadRequest(firstForm.Files[0]), firstContext.Request, CancellationToken.None);

FormCollection secondForm = CreateFormCollection();
DefaultHttpContext secondContext = new();
secondContext.Request.Form = secondForm;
await FileHandlers.UploadFile(mediator.Instance(), CreateUploadRequest(secondForm.Files[0]), secondContext.Request, CancellationToken.None);

Directory.GetFiles(temporaryDirectory).Length.ShouldBe(2);
}
Expand Down Expand Up @@ -138,7 +140,10 @@ public async Task UploadFile_DoesNotUseClientFilenameCharactersInTemporaryPath()
IMediatorImposter mediator = new();
mediator.Send(Arg<IRequest<Result>>.Any(), Arg<CancellationToken>.Any()).ReturnsAsync(Result.Success());

await FileHandlers.UploadFile(mediator.Instance(), CreateUploadRequest(), CreateFormCollection("invoice:payload.csv"), CancellationToken.None);
FormCollection formCollection = CreateFormCollection("invoice:payload.csv");
DefaultHttpContext httpContext = new();
httpContext.Request.Form = formCollection;
await FileHandlers.UploadFile(mediator.Instance(), CreateUploadRequest(formCollection.Files[0]), httpContext.Request, CancellationToken.None);

string storedFileName = Path.GetFileName(Directory.GetFiles(temporaryDirectory).Single());
storedFileName.ShouldNotContain(":");
Expand All @@ -151,13 +156,14 @@ public async Task UploadFile_DoesNotUseClientFilenameCharactersInTemporaryPath()
}
}

private static FileProcessor.DataTransferObjects.UploadFileRequest CreateUploadRequest() => new()
private static UploadFileForm CreateUploadRequest(IFormFile file) => new()
{
FileId = TestData.FileId,
EstateId = TestData.EstateId,
MerchantId = TestData.MerchantId,
UserId = TestData.UserId,
FileProfileId = TestData.FileProfileId
FileProfileId = TestData.FileProfileId,
File = file
};

private static FormCollection CreateFormCollection(string fileName = "source.csv")
Expand Down
30 changes: 22 additions & 8 deletions FileProcessor.BusinessLogic.Tests/MediatorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
using System.Text;
using System.Threading.Tasks;
using FileProcessor.BusinessLogic.Managers;
using FileProcessor.Bootstrapper;
using FileProcessor.Common;
using Xunit;

namespace FileProcessor.BusinessLogic.Tests
Expand All @@ -20,6 +22,7 @@ namespace FileProcessor.BusinessLogic.Tests
using Services;
using Shared.DomainDrivenDesign.EventSourcing;
using Shared.EventStore.Aggregate;
using Shared.General;

public class MediatorTests
{
Expand All @@ -44,14 +47,24 @@ public async Task Mediator_Send_RequestHandled()
hostingEnvironment.ApplicationName.Getter().Returns("Test Application");

ServiceRegistry services = new ServiceRegistry();
Startup s = new Startup(hostingEnvironment.Instance());
Startup.Configuration = this.SetupMemoryConfiguration();

this.AddTestRegistrations(services, hostingEnvironment.Instance());
s.ConfigureContainer(services);
IConfigurationRoot configuration = this.SetupMemoryConfiguration();
ConfigurationReader.Initialise(configuration);

this.AddTestRegistrations(services, hostingEnvironment.Instance(), configuration);
TypeProvider.LoadDomainEventsTypeDynamically();
services.IncludeRegistry<MediatorRegistry>();
services.IncludeRegistry(new DomainEventHandlerRegistry(configuration));
services.IncludeRegistry(new RepositoryRegistry(configuration, hostingEnvironment.Instance()));
services.IncludeRegistry(new MiddlewareRegistry(configuration));
services.IncludeRegistry<FileRegistry>();
services.IncludeRegistry<MiscRegistry>();
services.IncludeRegistry<ClientRegistry>();
services.IncludeRegistry<SerialiserRegistry>();

using Container container = new Container(services);

List<String> errors = new List<String>();
IMediator mediator = Startup.Container.GetService<IMediator>();
IMediator mediator = container.GetService<IMediator>();
foreach (IBaseRequest baseRequest in this.Requests)
{
try
Expand Down Expand Up @@ -95,15 +108,16 @@ private IConfigurationRoot SetupMemoryConfiguration()
}

private void AddTestRegistrations(ServiceRegistry services,
IWebHostEnvironment hostingEnvironment)
IWebHostEnvironment hostingEnvironment,
IConfiguration configuration)
{
services.AddLogging();
DiagnosticListener diagnosticSource = new DiagnosticListener(hostingEnvironment.ApplicationName);
services.AddSingleton<DiagnosticSource>(diagnosticSource);
services.AddSingleton<DiagnosticListener>(diagnosticSource);
services.AddSingleton<IWebHostEnvironment>(hostingEnvironment);
services.AddSingleton<IHostEnvironment>(hostingEnvironment);
services.AddSingleton<IConfiguration>(Startup.Configuration);
services.AddSingleton<IConfiguration>(configuration);

services.OverrideServices(s => {
s.AddSingleton<IFileProcessorDomainService, DummyFileProcessorDomainService>();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
Expand Down Expand Up @@ -125,17 +126,31 @@ private Dictionary<Guid, Guid> GetInitialVersions(List<FileProfileModel> profile
private sealed record ExistingFile(Guid FileId, Guid FileProfileId);
}

/// <summary>Summary of the file-profile version migration.</summary>
[Description("Summary of the file-profile version migration.")]
public sealed class FileProfileVersionMigrationReport
{
/// <summary>Gets the number of profiles checked.</summary>
[Description("Number of profiles checked.")]
public Int32 ProfilesChecked { get; set; }

/// <summary>Gets the number of profiles that received an initial version.</summary>
[Description("Number of profiles that received an initial version.")]
public Int32 ProfilesWithInitialVersion { get; set; }

/// <summary>Gets the number of files checked.</summary>
[Description("Number of files checked.")]
public Int32 FilesChecked { get; set; }

/// <summary>Gets the number of files assigned to a profile version.</summary>
[Description("Number of files assigned to a profile version.")]
public Int32 FilesAssigned { get; set; }

/// <summary>Gets the number of files skipped by the migration.</summary>
[Description("Number of files skipped by the migration.")]
public Int32 FilesSkipped { get; set; }

/// <summary>Gets the migration failure messages.</summary>
[Description("Migration failure messages, if any.")]
public List<String> Failures { get; } = new();
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFrameworks>net10.0</TargetFrameworks>
<DebugType>None</DebugType>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,37 @@
using System;
using System;
using System.ComponentModel;

namespace FileProcessor.DataTransferObjects.Requests;

/// <summary>Configuration for creating a file profile.</summary>
[Description("Configuration for creating a file profile.")]
public class CreateFileProfileRequest
{
/// <summary>Gets or sets the identifier to assign to the new profile.</summary>
[Description("The identifier to assign to the new profile.")]
public Guid FileProfileId { get; set; }

/// <summary>Gets or sets the profile's display name.</summary>
[Description("The profile's display name.")]
public string Name { get; set; }

/// <summary>Gets or sets the directory from which files for this profile are collected.</summary>
[Description("The directory from which files for this profile are collected.")]
public string ListeningDirectory { get; set; }

/// <summary>Gets or sets the request or transaction type associated with files using this profile.</summary>
[Description("The request or transaction type associated with files using this profile.")]
public string RequestType { get; set; }

/// <summary>Gets or sets the operator name recorded for the profile.</summary>
[Description("The operator name recorded for the profile.")]
public string OperatorName { get; set; }

/// <summary>Gets or sets the required line ending used to separate records.</summary>
[Description("The required line ending used to separate records.")]
public LineTerminatorType? LineTerminator { get; set; }

/// <summary>Gets or sets the registered file format handler used to parse files for this profile.</summary>
[Description("The registered file format handler used to parse files for this profile.")]
public string FileFormatHandler { get; set; }
}
11 changes: 10 additions & 1 deletion FileProcessor.DataTransferObjects/Requests/LineTerminatorType.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
using System;
using System;
using System.ComponentModel;
using System.Text.Json.Serialization;

namespace FileProcessor.DataTransferObjects.Requests;

/// <summary>Line ending values accepted by file profile requests.</summary>
[Description("Line ending values accepted by file profile requests.")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum LineTerminatorType
{
/// <summary>Line feed (LF, `\n`).</summary>
[Description("Line feed (LF, `\\n`).")]
LineFeed = 1,

/// <summary>Carriage return followed by line feed (CRLF, `\r\n`).</summary>
[Description("Carriage return followed by line feed (CRLF, `\\r\\n`).")]
CarriageReturnLineFeed = 2,

/// <summary>Carriage return (CR, `\r`).</summary>
[Description("Carriage return (CR, `\\r`).")]
CarriageReturn = 3
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
using System.ComponentModel;
namespace FileProcessor.DataTransferObjects.Requests;

/// <summary>Configuration values to apply when updating a file profile.</summary>
[Description("Configuration values to apply when updating a file profile.")]
public class UpdateFileProfileRequest
{
/// <summary>Gets or sets the profile's display name.</summary>
[Description("The profile's display name.")]
public string Name { get; set; }

/// <summary>Gets or sets the directory from which files for this profile are collected.</summary>
[Description("The directory from which files for this profile are collected.")]
public string ListeningDirectory { get; set; }

/// <summary>Gets or sets the request or transaction type associated with files using this profile.</summary>
[Description("The request or transaction type associated with files using this profile.")]
public string RequestType { get; set; }

/// <summary>Gets or sets the operator name recorded for the profile.</summary>
[Description("The operator name recorded for the profile.")]
public string OperatorName { get; set; }

/// <summary>Gets or sets the line ending used to separate records. When omitted, the profile's current value is retained.</summary>
[Description("The line ending used to separate records. When omitted, the profile's current value is retained.")]
public LineTerminatorType? LineTerminator { get; set; }

/// <summary>Gets or sets the registered file format handler used to parse files for this profile.</summary>
[Description("The registered file format handler used to parse files for this profile.")]
public string FileFormatHandler { get; set; }
}
18 changes: 15 additions & 3 deletions FileProcessor.DataTransferObjects/Requests/UploadFileRequest.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,34 @@
using System;
using System.ComponentModel;

namespace FileProcessor.DataTransferObjects
{
/// <summary>
///
/// </summary>
/// <summary>Metadata submitted with a file upload.</summary>
[Description("Metadata submitted with a file upload.")]
public class UploadFileRequest
{
/// <summary>Gets or sets the caller-supplied identifier for the file being uploaded.</summary>
[Description("The caller-supplied identifier for the file being uploaded.")]
public Guid FileId { get; set; }

/// <summary>Gets or sets the identifier of the estate that owns the file.</summary>
[Description("The identifier of the estate that owns the file.")]
public Guid EstateId { get; set; }

/// <summary>Gets or sets the identifier of the merchant associated with the file.</summary>
[Description("The identifier of the merchant associated with the file.")]
public Guid MerchantId { get; set; }

/// <summary>Gets or sets the identifier of the user submitting the file.</summary>
[Description("The identifier of the user submitting the file.")]
public Guid UserId { get; set; }

/// <summary>Gets or sets the identifier of the file profile used to process the file.</summary>
[Description("The identifier of the file profile used to process the file.")]
public Guid FileProfileId { get; set; }

/// <summary>Gets or sets the time the file was uploaded. If omitted or set to the minimum date value, the server uses the current local time.</summary>
[Description("The time the file was uploaded. If omitted or set to the minimum date value, the server uses the current local time.")]
public DateTime UploadDateTime { get; set; }
}
}
Loading
Loading