diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.TestHarness/TestHarness.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.TestHarness/TestHarness.cs index 90b051fab..cba427f79 100644 --- a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.TestHarness/TestHarness.cs +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers.TestHarness/TestHarness.cs @@ -8,6 +8,7 @@ using SFA.DAS.CommitmentsV2.Types; using CommonEmployerType = SFA.DAS.Common.Domain.Types.ApprenticeshipEmployerType; 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; @@ -65,6 +66,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; @@ -172,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(); @@ -242,7 +234,17 @@ await _publisher.Publish(new ApprenticeshipStopDateChangedEvent() 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; + } } 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 new file mode 100644 index 000000000..72ae6d5d9 --- /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 ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfersTestsFixture() + .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 ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfersTestsFixture() + .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 ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfersTestsFixture() + .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 ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfersTestsFixture() + .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 ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfersTestsFixture : 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 ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfersTestsFixture() + { + _db = new ProviderCommitmentsDbContext(new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString(), b => b.EnableNullChecks(false)) + .Options); + + _handler = new ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfers( + new Lazy(() => _db), + Mock.Of>()); + } + + public ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfersTestsFixture WithEmployerType(ApprenticeshipEmployerType employerType) + { + Event.ApprenticeshipEmployerType = employerType; + return this; + } + + public ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfersTestsFixture WithPendingTransferRequestForSender(long senderAccountId, long transferRequestId) + { + return WithTransferRequestForSender(senderAccountId, transferRequestId, TransferApprovalStatus.Pending); + } + + public ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfersTestsFixture 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/ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfers.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfers.cs new file mode 100644 index 000000000..d9cafbdff --- /dev/null +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.MessageHandlers/EventHandlers/ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfers.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 ApprenticeshipEmployerTypeChangeEventHandlerToAutoRejectTransfers( + 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); + } +}