diff --git a/wolverine/a-frame-architecture/FinancialApi.Application.UnitTests/ApplyFeeHandlerTests.cs b/wolverine/a-frame-architecture/FinancialApi.Application.UnitTests/ApplyFeeHandlerTests.cs index 1b865ab..6a91949 100644 --- a/wolverine/a-frame-architecture/FinancialApi.Application.UnitTests/ApplyFeeHandlerTests.cs +++ b/wolverine/a-frame-architecture/FinancialApi.Application.UnitTests/ApplyFeeHandlerTests.cs @@ -118,6 +118,12 @@ public class ApplyFeeHandlerTests using var _ = new AssertionScope(); customerWrite.Entity.Balance.Should() .Be(expectedBalance); + revenueWrite.Entity.Balance.Should() + .Be(feeAmount); + journalWrite.Should() + .NotBeNull(); + @event.Should() + .NotBeNull(); } } } \ No newline at end of file diff --git a/wolverine/a-frame-architecture/FinancialApi.Application.UnitTests/ReverseJournalHandlerTests.cs b/wolverine/a-frame-architecture/FinancialApi.Application.UnitTests/ReverseJournalHandlerTests.cs index 731eb52..9f14ae3 100644 --- a/wolverine/a-frame-architecture/FinancialApi.Application.UnitTests/ReverseJournalHandlerTests.cs +++ b/wolverine/a-frame-architecture/FinancialApi.Application.UnitTests/ReverseJournalHandlerTests.cs @@ -1,8 +1,8 @@ using FinancialApi.Application.Commands; -using FinancialApi.Application.Events; using FinancialApi.Application.Handlers; using FinancialApi.Application.Models; using FinancialApi.Domain.Entities; +using FinancialApi.Domain.Events; using FluentAssertions; using FluentAssertions.Execution; using Microsoft.Extensions.Time.Testing; diff --git a/wolverine/a-frame-architecture/FinancialApi.Application/Handlers/ApplyFeeHandler.cs b/wolverine/a-frame-architecture/FinancialApi.Application/Handlers/ApplyFeeHandler.cs index c5f160e..c31a65a 100644 --- a/wolverine/a-frame-architecture/FinancialApi.Application/Handlers/ApplyFeeHandler.cs +++ b/wolverine/a-frame-architecture/FinancialApi.Application/Handlers/ApplyFeeHandler.cs @@ -1,9 +1,9 @@ using FinancialApi.Application.Commands; -using FinancialApi.Application.Events; using FinancialApi.Application.Interfaces; using FinancialApi.Application.Models; using FinancialApi.Domain.Aggregates; using FinancialApi.Domain.Entities; +using FinancialApi.Domain.Events; using Wolverine.Persistence; namespace FinancialApi.Application.Handlers; diff --git a/wolverine/a-frame-architecture/FinancialApi.Application/Handlers/FinancialEventHandler.cs b/wolverine/a-frame-architecture/FinancialApi.Application/Handlers/FinancialEventHandler.cs index c68ebcb..ede771f 100644 --- a/wolverine/a-frame-architecture/FinancialApi.Application/Handlers/FinancialEventHandler.cs +++ b/wolverine/a-frame-architecture/FinancialApi.Application/Handlers/FinancialEventHandler.cs @@ -1,4 +1,4 @@ -using FinancialApi.Application.Events; +using FinancialApi.Domain.Events; using Microsoft.Extensions.Logging; namespace FinancialApi.Application.Handlers; diff --git a/wolverine/a-frame-architecture/FinancialApi.Application/Handlers/ReverseJournalHandler.cs b/wolverine/a-frame-architecture/FinancialApi.Application/Handlers/ReverseJournalHandler.cs index 786bd26..df8260e 100644 --- a/wolverine/a-frame-architecture/FinancialApi.Application/Handlers/ReverseJournalHandler.cs +++ b/wolverine/a-frame-architecture/FinancialApi.Application/Handlers/ReverseJournalHandler.cs @@ -1,9 +1,9 @@ using FinancialApi.Application.Commands; -using FinancialApi.Application.Events; using FinancialApi.Application.Interfaces; using FinancialApi.Application.Models; using FinancialApi.Domain.Aggregates; using FinancialApi.Domain.Entities; +using FinancialApi.Domain.Events; using Wolverine.Persistence; namespace FinancialApi.Application.Handlers; diff --git a/wolverine/a-frame-architecture/FinancialApi.Application/Handlers/TransferFundsHandler.cs b/wolverine/a-frame-architecture/FinancialApi.Application/Handlers/TransferFundsHandler.cs index 8db07f3..4fc117f 100644 --- a/wolverine/a-frame-architecture/FinancialApi.Application/Handlers/TransferFundsHandler.cs +++ b/wolverine/a-frame-architecture/FinancialApi.Application/Handlers/TransferFundsHandler.cs @@ -1,9 +1,9 @@ using FinancialApi.Application.Commands; -using FinancialApi.Application.Events; using FinancialApi.Application.Interfaces; using FinancialApi.Application.Models; using FinancialApi.Domain.Aggregates; using FinancialApi.Domain.Entities; +using FinancialApi.Domain.Events; using Wolverine.Persistence; namespace FinancialApi.Application.Handlers; diff --git a/wolverine/a-frame-architecture/FinancialApi.Application/Events/FeeAppliedEvent.cs b/wolverine/a-frame-architecture/FinancialApi.Domain/Events/FeeAppliedEvent.cs similarity index 59% rename from wolverine/a-frame-architecture/FinancialApi.Application/Events/FeeAppliedEvent.cs rename to wolverine/a-frame-architecture/FinancialApi.Domain/Events/FeeAppliedEvent.cs index d1619b7..5fdaa23 100644 --- a/wolverine/a-frame-architecture/FinancialApi.Application/Events/FeeAppliedEvent.cs +++ b/wolverine/a-frame-architecture/FinancialApi.Domain/Events/FeeAppliedEvent.cs @@ -1,3 +1,3 @@ -namespace FinancialApi.Application.Events; +namespace FinancialApi.Domain.Events; public record FeeAppliedEvent(int AccountId, decimal Amount); \ No newline at end of file diff --git a/wolverine/a-frame-architecture/FinancialApi.Application/Events/FundsTransferredEvent.cs b/wolverine/a-frame-architecture/FinancialApi.Domain/Events/FundsTransferredEvent.cs similarity index 69% rename from wolverine/a-frame-architecture/FinancialApi.Application/Events/FundsTransferredEvent.cs rename to wolverine/a-frame-architecture/FinancialApi.Domain/Events/FundsTransferredEvent.cs index 7913ad2..8e28286 100644 --- a/wolverine/a-frame-architecture/FinancialApi.Application/Events/FundsTransferredEvent.cs +++ b/wolverine/a-frame-architecture/FinancialApi.Domain/Events/FundsTransferredEvent.cs @@ -1,3 +1,3 @@ -namespace FinancialApi.Application.Events; +namespace FinancialApi.Domain.Events; public record FundsTransferredEvent(int SourceAccountId, int DestinationAccountId, decimal Amount); \ No newline at end of file diff --git a/wolverine/a-frame-architecture/FinancialApi.Application/Events/IEventTracker.cs b/wolverine/a-frame-architecture/FinancialApi.Domain/Events/IEventTracker.cs similarity index 61% rename from wolverine/a-frame-architecture/FinancialApi.Application/Events/IEventTracker.cs rename to wolverine/a-frame-architecture/FinancialApi.Domain/Events/IEventTracker.cs index 3ae0a84..264d02f 100644 --- a/wolverine/a-frame-architecture/FinancialApi.Application/Events/IEventTracker.cs +++ b/wolverine/a-frame-architecture/FinancialApi.Domain/Events/IEventTracker.cs @@ -1,4 +1,4 @@ -namespace FinancialApi.Application.Events; +namespace FinancialApi.Domain.Events; public interface IEventTracker { diff --git a/wolverine/a-frame-architecture/FinancialApi.Application/Events/JournalReversedEvent.cs b/wolverine/a-frame-architecture/FinancialApi.Domain/Events/JournalReversedEvent.cs similarity index 66% rename from wolverine/a-frame-architecture/FinancialApi.Application/Events/JournalReversedEvent.cs rename to wolverine/a-frame-architecture/FinancialApi.Domain/Events/JournalReversedEvent.cs index 160433a..cf33e38 100644 --- a/wolverine/a-frame-architecture/FinancialApi.Application/Events/JournalReversedEvent.cs +++ b/wolverine/a-frame-architecture/FinancialApi.Domain/Events/JournalReversedEvent.cs @@ -1,3 +1,3 @@ -namespace FinancialApi.Application.Events; +namespace FinancialApi.Domain.Events; public record JournalReversedEvent(Guid OriginalJournalId, Guid ReversalJournalId); \ No newline at end of file diff --git a/wolverine/a-frame-architecture/FinancialApi.Infrastructure.Tests/ApplyFeeIntegrationTests.cs b/wolverine/a-frame-architecture/FinancialApi.Infrastructure.Tests/ApplyFeeIntegrationTests.cs new file mode 100644 index 0000000..1af6217 --- /dev/null +++ b/wolverine/a-frame-architecture/FinancialApi.Infrastructure.Tests/ApplyFeeIntegrationTests.cs @@ -0,0 +1,35 @@ +using FinancialApi.Application.Commands; +using FinancialApi.Application.Interfaces; +using FinancialApi.Domain.Entities; +using FinancialApi.Domain.Events; +using FluentAssertions; +using Wolverine; +using Wolverine.Tracking; + +namespace FinancialApi.Infrastructure.Tests; + +internal class FakeAccountQuery : IAccountQuery +{ + public Task FindByIdAsync(int id) => Task.FromResult(new Account(id, 100.00m, AccountType.Debit))!; +} + +public class ApplyFeeIntegrationTests(WolverineTestFixture fixture) : IClassFixture +{ + [Fact] + public async Task GivenValidCommand_WhenApplyingFee_ShouldPublishFeeAppliedEvent() + { + // Arrange + var command = new ApplyFeeCommand(1, 100); + + // Act + var session = await fixture.Host.InvokeMessageAndWaitAsync(command); + + // Assert + session.Sent.AllMessages() + .ShouldHaveMessageOfType(); + session.Sent.MessagesOf() + .Count() + .Should() + .Be(1); + } +} \ No newline at end of file diff --git a/wolverine/a-frame-architecture/FinancialApi.Infrastructure.Tests/FinancialApi.Infrastructure.Tests.csproj b/wolverine/a-frame-architecture/FinancialApi.Infrastructure.Tests/FinancialApi.Infrastructure.Tests.csproj new file mode 100644 index 0000000..4a800f9 --- /dev/null +++ b/wolverine/a-frame-architecture/FinancialApi.Infrastructure.Tests/FinancialApi.Infrastructure.Tests.csproj @@ -0,0 +1,41 @@ + + + + net10.0 + enable + enable + false + + + + + + + + + + + + + + + + + + + + + ..\..\..\..\..\..\.nuget\packages\microsoft.extensions.hosting\10.0.0\lib\net10.0\Microsoft.Extensions.Hosting.dll + + + + ..\..\..\..\..\..\.nuget\packages\microsoft.extensions.timeprovider.testing\10.6.0\lib\net10.0\Microsoft.Extensions.TimeProvider.Testing.dll + + + + + + + + + \ No newline at end of file diff --git a/wolverine/a-frame-architecture/FinancialApi.Infrastructure.Tests/WolverineTestFixture.cs b/wolverine/a-frame-architecture/FinancialApi.Infrastructure.Tests/WolverineTestFixture.cs new file mode 100644 index 0000000..91e3b3e --- /dev/null +++ b/wolverine/a-frame-architecture/FinancialApi.Infrastructure.Tests/WolverineTestFixture.cs @@ -0,0 +1,34 @@ +using FinancialApi.Application.Handlers; +using FinancialApi.Application.Interfaces; +using FinancialApi.Domain.Events; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Time.Testing; +using Wolverine; + +namespace FinancialApi.Infrastructure.Tests; + +public class WolverineTestFixture : IDisposable +{ + public IHost Host { get; } + + public WolverineTestFixture() + { + // We build and start the host ONCE here + Host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder() + .ConfigureServices((context, services) => + { + services.AddSingleton(_ => new FakeAccountQuery()); + services.AddSingleton(_ => new FakeTimeProvider()); + services.AddSingleton(_ => new DemoEventStore()); + } + ) + .UseWolverine(opts => { opts.Discovery.IncludeAssembly(typeof(ApplyFeeHandler).Assembly); }) + .Start(); + } + + public void Dispose() + { + Host.Dispose(); + } +} \ No newline at end of file diff --git a/wolverine/a-frame-architecture/FinancialApi.Infrastructure/AccountQuery.cs b/wolverine/a-frame-architecture/FinancialApi.Infrastructure/AccountQuery.cs index 74a390f..13901db 100644 --- a/wolverine/a-frame-architecture/FinancialApi.Infrastructure/AccountQuery.cs +++ b/wolverine/a-frame-architecture/FinancialApi.Infrastructure/AccountQuery.cs @@ -6,9 +6,9 @@ namespace FinancialApi.Infrastructure; public class AccountQuery(AccountDbContext db) : IAccountQuery { - public Task FindByIdAsync(int id) + public async Task FindByIdAsync(int id) { - return db.Accounts.AsNoTracking() + return await db.Accounts.AsNoTracking() .FirstOrDefaultAsync(x => x.Id == id); } } \ No newline at end of file diff --git a/wolverine/a-frame-architecture/FinancialApi/DemoEventStore.cs b/wolverine/a-frame-architecture/FinancialApi.Infrastructure/DemoEventStore.cs similarity index 76% rename from wolverine/a-frame-architecture/FinancialApi/DemoEventStore.cs rename to wolverine/a-frame-architecture/FinancialApi.Infrastructure/DemoEventStore.cs index 7a174f8..c00b0be 100644 --- a/wolverine/a-frame-architecture/FinancialApi/DemoEventStore.cs +++ b/wolverine/a-frame-architecture/FinancialApi.Infrastructure/DemoEventStore.cs @@ -1,6 +1,6 @@ -using FinancialApi.Application.Events; +using FinancialApi.Domain.Events; -namespace FinancialApi; +namespace FinancialApi.Infrastructure; public class DemoEventStore : IEventTracker { diff --git a/wolverine/a-frame-architecture/FinancialApi/DomainExceptionHandler.cs b/wolverine/a-frame-architecture/FinancialApi/DomainExceptionHandler.cs index 181d59c..35cd90e 100644 --- a/wolverine/a-frame-architecture/FinancialApi/DomainExceptionHandler.cs +++ b/wolverine/a-frame-architecture/FinancialApi/DomainExceptionHandler.cs @@ -1,3 +1,4 @@ +using FinancialApi.Domain.Exceptions; using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Mvc; @@ -11,7 +12,7 @@ public class DomainExceptionHandler : IExceptionHandler CancellationToken cancellationToken ) { - if (exception is not (InvalidOperationException or ArgumentException)) + if (exception is not (InvalidOperationException or ArgumentException or GaapViolationException)) { return false; } diff --git a/wolverine/a-frame-architecture/FinancialApi/FinancialApi.http b/wolverine/a-frame-architecture/FinancialApi/FinancialApi.http index 3a5d97a..e6ba523 100644 --- a/wolverine/a-frame-architecture/FinancialApi/FinancialApi.http +++ b/wolverine/a-frame-architecture/FinancialApi/FinancialApi.http @@ -7,8 +7,8 @@ Accept: application/json Content-Type: application/json { - "accountId": 1, - "amount": 50.0 + "accountId": 2, + "amount": 100 } ### Transfer Between Accounts @@ -28,7 +28,7 @@ POST {{FinancialApi_HostAddress}}/journal/reverse Content-Type: application/json { - "originalJournalId": "B65EB28C-7EC1-4B70-9069-06A61A4FE857", + "originalJournalId": "E7FB25DB-406F-4063-A4B0-DE6BB8292437", "reason": "Incorrect Account Fee" } diff --git a/wolverine/a-frame-architecture/FinancialApi/Program.cs b/wolverine/a-frame-architecture/FinancialApi/Program.cs index 5b3193c..d08c147 100644 --- a/wolverine/a-frame-architecture/FinancialApi/Program.cs +++ b/wolverine/a-frame-architecture/FinancialApi/Program.cs @@ -1,8 +1,8 @@ using FinancialApi; using FinancialApi.Application.Commands; -using FinancialApi.Application.Events; using FinancialApi.Application.Handlers; using FinancialApi.Application.Interfaces; +using FinancialApi.Domain.Events; using FinancialApi.Infrastructure; using FinancialApi.ServiceDefaults; using JasperFx.Resources; diff --git a/wolverine/a-frame-architecture/a-frame-architecture.sln b/wolverine/a-frame-architecture/a-frame-architecture.sln index d825d30..03833ff 100644 --- a/wolverine/a-frame-architecture/a-frame-architecture.sln +++ b/wolverine/a-frame-architecture/a-frame-architecture.sln @@ -14,6 +14,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinancialApi.Application.Un EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinancialApi.Domain.Tests", "FinancialApi.Domain.Tests\FinancialApi.Domain.Tests.csproj", "{C15E3FE0-6E3D-4FFC-892E-D469536ADC13}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinancialApi.Infrastructure.Tests", "FinancialApi.Infrastructure.Tests\FinancialApi.Infrastructure.Tests.csproj", "{8C1BA932-16F2-42B3-A07C-C589C31044B3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -48,5 +50,9 @@ Global {C15E3FE0-6E3D-4FFC-892E-D469536ADC13}.Debug|Any CPU.Build.0 = Debug|Any CPU {C15E3FE0-6E3D-4FFC-892E-D469536ADC13}.Release|Any CPU.ActiveCfg = Release|Any CPU {C15E3FE0-6E3D-4FFC-892E-D469536ADC13}.Release|Any CPU.Build.0 = Release|Any CPU + {8C1BA932-16F2-42B3-A07C-C589C31044B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8C1BA932-16F2-42B3-A07C-C589C31044B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8C1BA932-16F2-42B3-A07C-C589C31044B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8C1BA932-16F2-42B3-A07C-C589C31044B3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal