From e6bc6b243271fccc911da4cd9266bcc2a570aca3 Mon Sep 17 00:00:00 2001 From: Corey Faulconbridge Date: Mon, 13 Jul 2026 10:58:42 +0100 Subject: [PATCH 1/5] auto reject transfer requests --- .../TestHarness.cs | 12 ++ ...shipEmployerTypeChangeEventHandlerTests.cs | 176 ++++++++++++++++++ ...nticeshipEmployerTypeChangeEventHandler.cs | 47 +++++ ...A.DAS.CommitmentsV2.MessageHandlers.csproj | 2 +- 4 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.UnitTests/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandlerTests.cs create mode 100644 src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandler.cs diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.TestHarness/TestHarness.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.TestHarness/TestHarness.cs index bce7cba0f..7caad74df 100644 --- a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.TestHarness/TestHarness.cs +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.TestHarness/TestHarness.cs @@ -7,6 +7,7 @@ using SFA.DAS.CommitmentsV2.Messages.Events; using SFA.DAS.CommitmentsV2.Types; using SFA.DAS.EmployerAccounts.Messages.Events; +using ApprenticeshipEmployerType = SFA.DAS.Common.Domain.Types.ApprenticeshipEmployerType; using SFA.DAS.EmployerAccounts.Types.Models; using SFA.DAS.EmployerFinance.Messages.Events; using SFA.DAS.Payments.ProviderPayments.Messages; @@ -63,6 +64,7 @@ public async Task Run() Console.WriteLine("Z - ApprenticeshipStopDateChangedEvent Event"); Console.WriteLine("1 - ChangeOfPartyRequestCreatedEvent Event"); Console.WriteLine("2 - ApprenticeshipStartDateChangedEvent Event"); + Console.WriteLine("3 - ApprenticeshipEmployerTypeChangeEvent (NonLevy)"); Console.WriteLine("X - Exit"); Console.WriteLine("Press [Key] for Test Option"); key = Console.ReadKey().Key; @@ -229,6 +231,16 @@ await _publisher.Publish(new ApprenticeshipStopDateChangedEvent() await _publisher.Publish(new ChangeOfPartyRequestCreatedEvent(12345, new UserInfo(), false)); Console.WriteLine(); Console.WriteLine($"Sent {nameof(ChangeOfPartyRequestCreatedEvent)}"); + break; + case ConsoleKey.D3: + await _publisher.Publish(new ApprenticeshipEmployerTypeChangeEvent + { + AccountId = accountId, + ApprenticeshipEmployerType = ApprenticeshipEmployerType.NonLevy, + Created = DateTime.UtcNow + }); + Console.WriteLine(); + Console.WriteLine($"Published {nameof(ApprenticeshipEmployerTypeChangeEvent)} (NonLevy)"); break; } } diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.UnitTests/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandlerTests.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.UnitTests/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandlerTests.cs new file mode 100644 index 000000000..ad8491801 --- /dev/null +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.UnitTests/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandlerTests.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Moq; +using NServiceBus; +using NUnit.Framework; +using SFA.DAS.CommitmentsV2.Data; +using SFA.DAS.CommitmentsV2.MessageHandlers.EventHandlers; +using SFA.DAS.CommitmentsV2.Messages.Commands; +using SFA.DAS.CommitmentsV2.Models; +using SFA.DAS.CommitmentsV2.Types; +using SFA.DAS.EmployerAccounts.Messages.Events; +using ApprenticeshipEmployerType = SFA.DAS.Common.Domain.Types.ApprenticeshipEmployerType; + +namespace SFA.DAS.CommitmentsV2.MessageHandlers.UnitTests.EventHandlers; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class ApprenticeshipEmployerTypeChangeEventHandlerTests +{ + [Test] + public async Task Handle_WhenEmployerBecomesNonLevy_ThenRejectTransferRequestCommandsAreSentForPendingSenderRequests() + { + // Arrange + var fixture = new ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture() + .WithPendingTransferRequestForSender(1001, 501) + .WithPendingTransferRequestForSender(1001, 502) + .WithPendingTransferRequestForSender(9999, 503); + + // Act + await fixture.Handle(); + + // Assert + fixture.MessageHandlerContext.Verify(m => m.Send( + It.Is(c => + c.TransferRequestId == 501 && + c.UserInfo.IsSystem()), + It.IsAny()), + Times.Once); + + fixture.MessageHandlerContext.Verify(m => m.Send( + It.Is(c => c.TransferRequestId == 502), + It.IsAny()), + Times.Once); + + fixture.MessageHandlerContext.Verify(m => m.Send( + It.IsAny(), + It.IsAny()), + Times.Exactly(2)); + } + + [Test] + public async Task Handle_WhenEmployerBecomesLevy_ThenNoRejectTransferRequestCommandsAreSent() + { + // Arrange + var fixture = new ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture() + .WithPendingTransferRequestForSender(1001, 501) + .WithEmployerType(ApprenticeshipEmployerType.Levy); + + // Act + await fixture.Handle(); + + // Assert + fixture.MessageHandlerContext.Verify(m => m.Send( + It.IsAny(), + It.IsAny()), + Times.Never); + } + + [Test] + public async Task Handle_WhenEmployerTypeIsUnknown_ThenNoRejectTransferRequestCommandsAreSent() + { + // Arrange + var fixture = new ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture() + .WithPendingTransferRequestForSender(1001, 501) + .WithEmployerType(ApprenticeshipEmployerType.Unknown); + + // Act + await fixture.Handle(); + + // Assert + fixture.MessageHandlerContext.Verify(m => m.Send( + It.IsAny(), + It.IsAny()), + Times.Never); + } + + [Test] + public async Task Handle_WhenTransferRequestIsApprovedOrRejected_ThenNoRejectTransferRequestCommandIsSent() + { + // Arrange + var fixture = new ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture() + .WithTransferRequestForSender(1001, 601, TransferApprovalStatus.Approved) + .WithTransferRequestForSender(1001, 602, TransferApprovalStatus.Rejected); + + // Act + await fixture.Handle(); + + // Assert + fixture.MessageHandlerContext.Verify(m => m.Send( + It.IsAny(), + It.IsAny()), + Times.Never); + } +} + +public class ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture : IDisposable +{ + private readonly ProviderCommitmentsDbContext _db; + private readonly ApprenticeshipEmployerTypeChangeEventHandler _handler; + + public Mock MessageHandlerContext { get; } = new(); + public ApprenticeshipEmployerTypeChangeEvent Event { get; private set; } = new() + { + AccountId = 1001, + ApprenticeshipEmployerType = ApprenticeshipEmployerType.NonLevy, + Created = DateTime.UtcNow + }; + + public ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture() + { + _db = new ProviderCommitmentsDbContext(new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString(), b => b.EnableNullChecks(false)) + .Options); + + _handler = new ApprenticeshipEmployerTypeChangeEventHandler( + new Lazy(() => _db), + Mock.Of>()); + } + + public ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture WithEmployerType(ApprenticeshipEmployerType employerType) + { + Event.ApprenticeshipEmployerType = employerType; + return this; + } + + public ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture WithPendingTransferRequestForSender(long senderAccountId, long transferRequestId) + { + return WithTransferRequestForSender(senderAccountId, transferRequestId, TransferApprovalStatus.Pending); + } + + public ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture WithTransferRequestForSender( + long senderAccountId, + long transferRequestId, + TransferApprovalStatus status) + { + var cohort = new Cohort + { + Id = transferRequestId + 1000, + TransferSenderId = senderAccountId, + EmployerAccountId = senderAccountId + 1 + }; + + var transferRequest = new TransferRequest + { + Id = transferRequestId, + Status = status, + Cohort = cohort, + CommitmentId = cohort.Id + }; + + _db.Cohorts.Add(cohort); + _db.TransferRequests.Add(transferRequest); + _db.SaveChanges(); + + return this; + } + + public Task Handle() => _handler.Handle(Event, MessageHandlerContext.Object); + + public void Dispose() => _db.Dispose(); +} diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandler.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandler.cs new file mode 100644 index 000000000..6cee16d7e --- /dev/null +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandler.cs @@ -0,0 +1,47 @@ +using SFA.DAS.CommitmentsV2.Data; +using SFA.DAS.CommitmentsV2.Messages.Commands; +using SFA.DAS.CommitmentsV2.Types; +using SFA.DAS.EmployerAccounts.Messages.Events; +using ApprenticeshipEmployerType = SFA.DAS.Common.Domain.Types.ApprenticeshipEmployerType; + +namespace SFA.DAS.CommitmentsV2.MessageHandlers.EventHandlers; + +public class ApprenticeshipEmployerTypeChangeEventHandler( + Lazy dbContext, + ILogger logger) + : IHandleMessages +{ + public async Task Handle(ApprenticeshipEmployerTypeChangeEvent message, IMessageHandlerContext context) + { + if (message.ApprenticeshipEmployerType != ApprenticeshipEmployerType.NonLevy) + { + logger.LogInformation( + "Ignoring ApprenticeshipEmployerTypeChangeEvent for account {AccountId} because employer type is {EmployerType}", + message.AccountId, + message.ApprenticeshipEmployerType); + return; + } + + logger.LogInformation("Handling ApprenticeshipEmployerTypeChangeEvent for account {AccountId}", message.AccountId); + + var transferRequestIds = await dbContext.Value.TransferRequests + .Where(tr => tr.Status == TransferApprovalStatus.Pending + && tr.Cohort.TransferSenderId == message.AccountId) + .Select(tr => tr.Id) + .ToListAsync(); + + var rejectedOn = DateTime.UtcNow; + + foreach (var transferRequestId in transferRequestIds) + { + await context.Send( + new RejectTransferRequestCommand(transferRequestId, rejectedOn, UserInfo.System), + new SendOptions()); + } + + logger.LogInformation( + "Queued {TransferRequestCount} transfer request rejections for account {AccountId}", + transferRequestIds.Count, + message.AccountId); + } +} diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers/SFA.DAS.CommitmentsV2.MessageHandlers.csproj b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers/SFA.DAS.CommitmentsV2.MessageHandlers.csproj index fe4c26ab9..49e107423 100644 --- a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers/SFA.DAS.CommitmentsV2.MessageHandlers.csproj +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers/SFA.DAS.CommitmentsV2.MessageHandlers.csproj @@ -22,7 +22,7 @@ - + From 5a2a9c119e6d498ef1f8377b43759b4e04e77304 Mon Sep 17 00:00:00 2001 From: Corey Faulconbridge Date: Mon, 13 Jul 2026 15:40:36 +0100 Subject: [PATCH 2/5] fix tests --- ...shipEmployerTypeChangeEventHandlerTests.cs | 64 +++++++++++-------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.UnitTests/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandlerTests.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.UnitTests/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandlerTests.cs index ad8491801..19337ab0e 100644 --- a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.UnitTests/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandlerTests.cs +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.UnitTests/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandlerTests.cs @@ -22,35 +22,38 @@ namespace SFA.DAS.CommitmentsV2.MessageHandlers.UnitTests.EventHandlers; [Parallelizable(ParallelScope.All)] public class ApprenticeshipEmployerTypeChangeEventHandlerTests { + private const long SenderAccountId = 1001; + private const long FirstTransferRequestId = 501; + private const long SecondTransferRequestId = 502; + + private static bool IsRejectCommand(object message, long transferRequestId) => + message is RejectTransferRequestCommand command + && command.TransferRequestId == transferRequestId + && command.UserInfo.IsSystem(); + [Test] public async Task Handle_WhenEmployerBecomesNonLevy_ThenRejectTransferRequestCommandsAreSentForPendingSenderRequests() { // Arrange var fixture = new ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture() - .WithPendingTransferRequestForSender(1001, 501) - .WithPendingTransferRequestForSender(1001, 502) + .WithPendingTransferRequestForSender(SenderAccountId, FirstTransferRequestId) + .WithPendingTransferRequestForSender(SenderAccountId, SecondTransferRequestId) .WithPendingTransferRequestForSender(9999, 503); + var sentCommands = new List(); + fixture.MessageHandlerContext + .Setup(m => m.Send( + It.Is(o => o is RejectTransferRequestCommand), + It.Is(_ => true))) + .Callback((command, _) => sentCommands.Add((RejectTransferRequestCommand)command)) + .Returns(Task.CompletedTask); + // Act await fixture.Handle(); // Assert - fixture.MessageHandlerContext.Verify(m => m.Send( - It.Is(c => - c.TransferRequestId == 501 && - c.UserInfo.IsSystem()), - It.IsAny()), - Times.Once); - - fixture.MessageHandlerContext.Verify(m => m.Send( - It.Is(c => c.TransferRequestId == 502), - It.IsAny()), - Times.Once); - - fixture.MessageHandlerContext.Verify(m => m.Send( - It.IsAny(), - It.IsAny()), - Times.Exactly(2)); + Assert.That(sentCommands.Select(c => c.TransferRequestId), Is.EquivalentTo(new[] { FirstTransferRequestId, SecondTransferRequestId })); + Assert.That(sentCommands, Has.All.Matches(c => c.UserInfo.IsSystem())); } [Test] @@ -58,7 +61,7 @@ public async Task Handle_WhenEmployerBecomesLevy_ThenNoRejectTransferRequestComm { // Arrange var fixture = new ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture() - .WithPendingTransferRequestForSender(1001, 501) + .WithPendingTransferRequestForSender(SenderAccountId, FirstTransferRequestId) .WithEmployerType(ApprenticeshipEmployerType.Levy); // Act @@ -66,8 +69,8 @@ public async Task Handle_WhenEmployerBecomesLevy_ThenNoRejectTransferRequestComm // Assert fixture.MessageHandlerContext.Verify(m => m.Send( - It.IsAny(), - It.IsAny()), + It.Is(o => IsRejectCommand(o, FirstTransferRequestId)), + It.Is(_ => true)), Times.Never); } @@ -76,7 +79,7 @@ public async Task Handle_WhenEmployerTypeIsUnknown_ThenNoRejectTransferRequestCo { // Arrange var fixture = new ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture() - .WithPendingTransferRequestForSender(1001, 501) + .WithPendingTransferRequestForSender(SenderAccountId, FirstTransferRequestId) .WithEmployerType(ApprenticeshipEmployerType.Unknown); // Act @@ -84,8 +87,8 @@ public async Task Handle_WhenEmployerTypeIsUnknown_ThenNoRejectTransferRequestCo // Assert fixture.MessageHandlerContext.Verify(m => m.Send( - It.IsAny(), - It.IsAny()), + It.Is(o => IsRejectCommand(o, FirstTransferRequestId)), + It.Is(_ => true)), Times.Never); } @@ -94,16 +97,21 @@ public async Task Handle_WhenTransferRequestIsApprovedOrRejected_ThenNoRejectTra { // Arrange var fixture = new ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture() - .WithTransferRequestForSender(1001, 601, TransferApprovalStatus.Approved) - .WithTransferRequestForSender(1001, 602, TransferApprovalStatus.Rejected); + .WithTransferRequestForSender(SenderAccountId, 601, TransferApprovalStatus.Approved) + .WithTransferRequestForSender(SenderAccountId, 602, TransferApprovalStatus.Rejected); // Act await fixture.Handle(); // Assert fixture.MessageHandlerContext.Verify(m => m.Send( - It.IsAny(), - It.IsAny()), + It.Is(o => IsRejectCommand(o, 601)), + It.Is(_ => true)), + Times.Never); + + fixture.MessageHandlerContext.Verify(m => m.Send( + It.Is(o => IsRejectCommand(o, 602)), + It.Is(_ => true)), Times.Never); } } From 32c60306ebfffef21a2584ea2bb55801f6df0b96 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Mon, 20 Jul 2026 13:13:46 +0100 Subject: [PATCH 3/5] renamed file to remove clash with existing handler --- ...eEventHandlerToAutoRejectTransfersTests.cs | 184 ++++++++++++++++++ ...hangeEventHandlerToAutoRejectTransfers.cs} | 4 +- 2 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.UnitTests/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfersTests.cs rename src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers/EventHandlers/{ApprenticeshipEmployerTypeChangeEventHandler.cs => ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfers.cs} (91%) diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.UnitTests/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfersTests.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.UnitTests/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfersTests.cs new file mode 100644 index 000000000..d43a5e7e6 --- /dev/null +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.UnitTests/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfersTests.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Moq; +using NServiceBus; +using NUnit.Framework; +using SFA.DAS.CommitmentsV2.Data; +using SFA.DAS.CommitmentsV2.MessageHandlers.EventHandlers; +using SFA.DAS.CommitmentsV2.Messages.Commands; +using SFA.DAS.CommitmentsV2.Models; +using SFA.DAS.CommitmentsV2.Types; +using SFA.DAS.EmployerAccounts.Messages.Events; +using ApprenticeshipEmployerType = SFA.DAS.Common.Domain.Types.ApprenticeshipEmployerType; + +namespace SFA.DAS.CommitmentsV2.MessageHandlers.UnitTests.EventHandlers; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfersTests +{ + private const long SenderAccountId = 1001; + private const long FirstTransferRequestId = 501; + private const long SecondTransferRequestId = 502; + + private static bool IsRejectCommand(object message, long transferRequestId) => + message is RejectTransferRequestCommand command + && command.TransferRequestId == transferRequestId + && command.UserInfo.IsSystem(); + + [Test] + public async Task Handle_WhenEmployerBecomesNonLevy_ThenRejectTransferRequestCommandsAreSentForPendingSenderRequests() + { + // Arrange + var fixture = new ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture() + .WithPendingTransferRequestForSender(SenderAccountId, FirstTransferRequestId) + .WithPendingTransferRequestForSender(SenderAccountId, SecondTransferRequestId) + .WithPendingTransferRequestForSender(9999, 503); + + var sentCommands = new List(); + fixture.MessageHandlerContext + .Setup(m => m.Send( + It.Is(o => o is RejectTransferRequestCommand), + It.Is(_ => true))) + .Callback((command, _) => sentCommands.Add((RejectTransferRequestCommand)command)) + .Returns(Task.CompletedTask); + + // Act + await fixture.Handle(); + + // Assert + Assert.That(sentCommands.Select(c => c.TransferRequestId), Is.EquivalentTo(new[] { FirstTransferRequestId, SecondTransferRequestId })); + Assert.That(sentCommands, Has.All.Matches(c => c.UserInfo.IsSystem())); + } + + [Test] + public async Task Handle_WhenEmployerBecomesLevy_ThenNoRejectTransferRequestCommandsAreSent() + { + // Arrange + var fixture = new ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture() + .WithPendingTransferRequestForSender(SenderAccountId, FirstTransferRequestId) + .WithEmployerType(ApprenticeshipEmployerType.Levy); + + // Act + await fixture.Handle(); + + // Assert + fixture.MessageHandlerContext.Verify(m => m.Send( + It.Is(o => IsRejectCommand(o, FirstTransferRequestId)), + It.Is(_ => true)), + Times.Never); + } + + [Test] + public async Task Handle_WhenEmployerTypeIsUnknown_ThenNoRejectTransferRequestCommandsAreSent() + { + // Arrange + var fixture = new ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture() + .WithPendingTransferRequestForSender(SenderAccountId, FirstTransferRequestId) + .WithEmployerType(ApprenticeshipEmployerType.Unknown); + + // Act + await fixture.Handle(); + + // Assert + fixture.MessageHandlerContext.Verify(m => m.Send( + It.Is(o => IsRejectCommand(o, FirstTransferRequestId)), + It.Is(_ => true)), + Times.Never); + } + + [Test] + public async Task Handle_WhenTransferRequestIsApprovedOrRejected_ThenNoRejectTransferRequestCommandIsSent() + { + // Arrange + var fixture = new ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture() + .WithTransferRequestForSender(SenderAccountId, 601, TransferApprovalStatus.Approved) + .WithTransferRequestForSender(SenderAccountId, 602, TransferApprovalStatus.Rejected); + + // Act + await fixture.Handle(); + + // Assert + fixture.MessageHandlerContext.Verify(m => m.Send( + It.Is(o => IsRejectCommand(o, 601)), + It.Is(_ => true)), + Times.Never); + + fixture.MessageHandlerContext.Verify(m => m.Send( + It.Is(o => IsRejectCommand(o, 602)), + It.Is(_ => true)), + Times.Never); + } +} + +public class ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture : IDisposable +{ + private readonly ProviderCommitmentsDbContext _db; + private readonly ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfers _handler; + + public Mock MessageHandlerContext { get; } = new(); + public ApprenticeshipEmployerTypeChangeEvent Event { get; private set; } = new() + { + AccountId = 1001, + ApprenticeshipEmployerType = ApprenticeshipEmployerType.NonLevy, + Created = DateTime.UtcNow + }; + + public ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture() + { + _db = new ProviderCommitmentsDbContext(new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString(), b => b.EnableNullChecks(false)) + .Options); + + _handler = new ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfers( + new Lazy(() => _db), + Mock.Of>()); + } + + public ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture WithEmployerType(ApprenticeshipEmployerType employerType) + { + Event.ApprenticeshipEmployerType = employerType; + return this; + } + + public ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture WithPendingTransferRequestForSender(long senderAccountId, long transferRequestId) + { + return WithTransferRequestForSender(senderAccountId, transferRequestId, TransferApprovalStatus.Pending); + } + + public ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture WithTransferRequestForSender( + long senderAccountId, + long transferRequestId, + TransferApprovalStatus status) + { + var cohort = new Cohort + { + Id = transferRequestId + 1000, + TransferSenderId = senderAccountId, + EmployerAccountId = senderAccountId + 1 + }; + + var transferRequest = new TransferRequest + { + Id = transferRequestId, + Status = status, + Cohort = cohort, + CommitmentId = cohort.Id + }; + + _db.Cohorts.Add(cohort); + _db.TransferRequests.Add(transferRequest); + _db.SaveChanges(); + + return this; + } + + public Task Handle() => _handler.Handle(Event, MessageHandlerContext.Object); + + public void Dispose() => _db.Dispose(); +} diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandler.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfers.cs similarity index 91% rename from src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandler.cs rename to src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfers.cs index 6cee16d7e..d9cafbdff 100644 --- a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandler.cs +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfers.cs @@ -6,9 +6,9 @@ namespace SFA.DAS.CommitmentsV2.MessageHandlers.EventHandlers; -public class ApprenticeshipEmployerTypeChangeEventHandler( +public class ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfers( Lazy dbContext, - ILogger logger) + ILogger logger) : IHandleMessages { public async Task Handle(ApprenticeshipEmployerTypeChangeEvent message, IMessageHandlerContext context) From b50712716b1c9db0ab845b9062284326d90b53c4 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Mon, 20 Jul 2026 13:19:21 +0100 Subject: [PATCH 4/5] delete file --- ...shipEmployerTypeChangeEventHandlerTests.cs | 184 ------------------ 1 file changed, 184 deletions(-) delete mode 100644 src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.UnitTests/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandlerTests.cs diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.UnitTests/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandlerTests.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.UnitTests/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandlerTests.cs deleted file mode 100644 index 19337ab0e..000000000 --- a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.UnitTests/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandlerTests.cs +++ /dev/null @@ -1,184 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using MediatR; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using Moq; -using NServiceBus; -using NUnit.Framework; -using SFA.DAS.CommitmentsV2.Data; -using SFA.DAS.CommitmentsV2.MessageHandlers.EventHandlers; -using SFA.DAS.CommitmentsV2.Messages.Commands; -using SFA.DAS.CommitmentsV2.Models; -using SFA.DAS.CommitmentsV2.Types; -using SFA.DAS.EmployerAccounts.Messages.Events; -using ApprenticeshipEmployerType = SFA.DAS.Common.Domain.Types.ApprenticeshipEmployerType; - -namespace SFA.DAS.CommitmentsV2.MessageHandlers.UnitTests.EventHandlers; - -[TestFixture] -[Parallelizable(ParallelScope.All)] -public class ApprenticeshipEmployerTypeChangeEventHandlerTests -{ - private const long SenderAccountId = 1001; - private const long FirstTransferRequestId = 501; - private const long SecondTransferRequestId = 502; - - private static bool IsRejectCommand(object message, long transferRequestId) => - message is RejectTransferRequestCommand command - && command.TransferRequestId == transferRequestId - && command.UserInfo.IsSystem(); - - [Test] - public async Task Handle_WhenEmployerBecomesNonLevy_ThenRejectTransferRequestCommandsAreSentForPendingSenderRequests() - { - // Arrange - var fixture = new ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture() - .WithPendingTransferRequestForSender(SenderAccountId, FirstTransferRequestId) - .WithPendingTransferRequestForSender(SenderAccountId, SecondTransferRequestId) - .WithPendingTransferRequestForSender(9999, 503); - - var sentCommands = new List(); - fixture.MessageHandlerContext - .Setup(m => m.Send( - It.Is(o => o is RejectTransferRequestCommand), - It.Is(_ => true))) - .Callback((command, _) => sentCommands.Add((RejectTransferRequestCommand)command)) - .Returns(Task.CompletedTask); - - // Act - await fixture.Handle(); - - // Assert - Assert.That(sentCommands.Select(c => c.TransferRequestId), Is.EquivalentTo(new[] { FirstTransferRequestId, SecondTransferRequestId })); - Assert.That(sentCommands, Has.All.Matches(c => c.UserInfo.IsSystem())); - } - - [Test] - public async Task Handle_WhenEmployerBecomesLevy_ThenNoRejectTransferRequestCommandsAreSent() - { - // Arrange - var fixture = new ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture() - .WithPendingTransferRequestForSender(SenderAccountId, FirstTransferRequestId) - .WithEmployerType(ApprenticeshipEmployerType.Levy); - - // Act - await fixture.Handle(); - - // Assert - fixture.MessageHandlerContext.Verify(m => m.Send( - It.Is(o => IsRejectCommand(o, FirstTransferRequestId)), - It.Is(_ => true)), - Times.Never); - } - - [Test] - public async Task Handle_WhenEmployerTypeIsUnknown_ThenNoRejectTransferRequestCommandsAreSent() - { - // Arrange - var fixture = new ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture() - .WithPendingTransferRequestForSender(SenderAccountId, FirstTransferRequestId) - .WithEmployerType(ApprenticeshipEmployerType.Unknown); - - // Act - await fixture.Handle(); - - // Assert - fixture.MessageHandlerContext.Verify(m => m.Send( - It.Is(o => IsRejectCommand(o, FirstTransferRequestId)), - It.Is(_ => true)), - Times.Never); - } - - [Test] - public async Task Handle_WhenTransferRequestIsApprovedOrRejected_ThenNoRejectTransferRequestCommandIsSent() - { - // Arrange - var fixture = new ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture() - .WithTransferRequestForSender(SenderAccountId, 601, TransferApprovalStatus.Approved) - .WithTransferRequestForSender(SenderAccountId, 602, TransferApprovalStatus.Rejected); - - // Act - await fixture.Handle(); - - // Assert - fixture.MessageHandlerContext.Verify(m => m.Send( - It.Is(o => IsRejectCommand(o, 601)), - It.Is(_ => true)), - Times.Never); - - fixture.MessageHandlerContext.Verify(m => m.Send( - It.Is(o => IsRejectCommand(o, 602)), - It.Is(_ => true)), - Times.Never); - } -} - -public class ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture : IDisposable -{ - private readonly ProviderCommitmentsDbContext _db; - private readonly ApprenticeshipEmployerTypeChangeEventHandler _handler; - - public Mock MessageHandlerContext { get; } = new(); - public ApprenticeshipEmployerTypeChangeEvent Event { get; private set; } = new() - { - AccountId = 1001, - ApprenticeshipEmployerType = ApprenticeshipEmployerType.NonLevy, - Created = DateTime.UtcNow - }; - - public ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture() - { - _db = new ProviderCommitmentsDbContext(new DbContextOptionsBuilder() - .UseInMemoryDatabase(Guid.NewGuid().ToString(), b => b.EnableNullChecks(false)) - .Options); - - _handler = new ApprenticeshipEmployerTypeChangeEventHandler( - new Lazy(() => _db), - Mock.Of>()); - } - - public ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture WithEmployerType(ApprenticeshipEmployerType employerType) - { - Event.ApprenticeshipEmployerType = employerType; - return this; - } - - public ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture WithPendingTransferRequestForSender(long senderAccountId, long transferRequestId) - { - return WithTransferRequestForSender(senderAccountId, transferRequestId, TransferApprovalStatus.Pending); - } - - public ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture WithTransferRequestForSender( - long senderAccountId, - long transferRequestId, - TransferApprovalStatus status) - { - var cohort = new Cohort - { - Id = transferRequestId + 1000, - TransferSenderId = senderAccountId, - EmployerAccountId = senderAccountId + 1 - }; - - var transferRequest = new TransferRequest - { - Id = transferRequestId, - Status = status, - Cohort = cohort, - CommitmentId = cohort.Id - }; - - _db.Cohorts.Add(cohort); - _db.TransferRequests.Add(transferRequest); - _db.SaveChanges(); - - return this; - } - - public Task Handle() => _handler.Handle(Event, MessageHandlerContext.Object); - - public void Dispose() => _db.Dispose(); -} From cccb32bcd506dfacef01decf22f0847d87395415 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Mon, 20 Jul 2026 16:06:43 +0100 Subject: [PATCH 5/5] fix conflicts --- .../TestHarness.cs | 12 +----------- ...geEventHandlerToAutoRejectTransfersTests.cs | 18 +++++++++--------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.TestHarness/TestHarness.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.TestHarness/TestHarness.cs index 725ae0a5b..cba427f79 100644 --- a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.TestHarness/TestHarness.cs +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.TestHarness/TestHarness.cs @@ -174,16 +174,6 @@ await _publisher.Publish(new ApprenticeshipEmailAddressConfirmedEvent() Console.WriteLine($"Sent {nameof(ApprenticeshipEmailAddressConfirmedEvent)}"); break; - case ConsoleKey.D3: - await _publisher.Publish(new ApprenticeshipEmployerTypeChangeEvent - { - AccountId = accountId, - ApprenticeshipEmployerType = CommonEmployerType.NonLevy, - Created = DateTime.UtcNow - }); - Console.WriteLine(); - Console.WriteLine($"Published {nameof(ApprenticeshipEmployerTypeChangeEvent)}"); - break; case ConsoleKey.S: await _publisher.Publish(new LevyAddedToAccount { AccountId = accountId, Amount = 10, Created = DateTime.UtcNow }); Console.WriteLine(); @@ -254,7 +244,7 @@ await _publisher.Publish(new ApprenticeshipEmployerTypeChangeEvent Console.WriteLine(); Console.WriteLine($"Published {nameof(ApprenticeshipEmployerTypeChangeEvent)} (NonLevy)"); break; - } + } } catch (Exception e) { diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.UnitTests/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfersTests.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.UnitTests/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfersTests.cs index d43a5e7e6..72ae6d5d9 100644 --- a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.UnitTests/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfersTests.cs +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.UnitTests/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfersTests.cs @@ -35,7 +35,7 @@ message is RejectTransferRequestCommand command public async Task Handle_WhenEmployerBecomesNonLevy_ThenRejectTransferRequestCommandsAreSentForPendingSenderRequests() { // Arrange - var fixture = new ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture() + var fixture = new ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfersTestsFixture() .WithPendingTransferRequestForSender(SenderAccountId, FirstTransferRequestId) .WithPendingTransferRequestForSender(SenderAccountId, SecondTransferRequestId) .WithPendingTransferRequestForSender(9999, 503); @@ -60,7 +60,7 @@ public async Task Handle_WhenEmployerBecomesNonLevy_ThenRejectTransferRequestCom public async Task Handle_WhenEmployerBecomesLevy_ThenNoRejectTransferRequestCommandsAreSent() { // Arrange - var fixture = new ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture() + var fixture = new ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfersTestsFixture() .WithPendingTransferRequestForSender(SenderAccountId, FirstTransferRequestId) .WithEmployerType(ApprenticeshipEmployerType.Levy); @@ -78,7 +78,7 @@ public async Task Handle_WhenEmployerBecomesLevy_ThenNoRejectTransferRequestComm public async Task Handle_WhenEmployerTypeIsUnknown_ThenNoRejectTransferRequestCommandsAreSent() { // Arrange - var fixture = new ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture() + var fixture = new ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfersTestsFixture() .WithPendingTransferRequestForSender(SenderAccountId, FirstTransferRequestId) .WithEmployerType(ApprenticeshipEmployerType.Unknown); @@ -96,7 +96,7 @@ public async Task Handle_WhenEmployerTypeIsUnknown_ThenNoRejectTransferRequestCo public async Task Handle_WhenTransferRequestIsApprovedOrRejected_ThenNoRejectTransferRequestCommandIsSent() { // Arrange - var fixture = new ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture() + var fixture = new ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfersTestsFixture() .WithTransferRequestForSender(SenderAccountId, 601, TransferApprovalStatus.Approved) .WithTransferRequestForSender(SenderAccountId, 602, TransferApprovalStatus.Rejected); @@ -116,7 +116,7 @@ public async Task Handle_WhenTransferRequestIsApprovedOrRejected_ThenNoRejectTra } } -public class ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture : IDisposable +public class ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfersTestsFixture : IDisposable { private readonly ProviderCommitmentsDbContext _db; private readonly ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfers _handler; @@ -129,7 +129,7 @@ public class ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture : IDisposa Created = DateTime.UtcNow }; - public ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture() + public ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfersTestsFixture() { _db = new ProviderCommitmentsDbContext(new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString(), b => b.EnableNullChecks(false)) @@ -140,18 +140,18 @@ public ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture() Mock.Of>()); } - public ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture WithEmployerType(ApprenticeshipEmployerType employerType) + public ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfersTestsFixture WithEmployerType(ApprenticeshipEmployerType employerType) { Event.ApprenticeshipEmployerType = employerType; return this; } - public ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture WithPendingTransferRequestForSender(long senderAccountId, long transferRequestId) + public ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfersTestsFixture WithPendingTransferRequestForSender(long senderAccountId, long transferRequestId) { return WithTransferRequestForSender(senderAccountId, transferRequestId, TransferApprovalStatus.Pending); } - public ApprenticeshipEmployerTypeChangeEventHandlerTestsFixture WithTransferRequestForSender( + public ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfersTestsFixture WithTransferRequestForSender( long senderAccountId, long transferRequestId, TransferApprovalStatus status)