From 981127a07ef09403ac20baf3f58c0ecb034fb187 Mon Sep 17 00:00:00 2001 From: Corey Date: Fri, 18 Feb 2022 16:08:34 +0000 Subject: [PATCH] When invalid apprenticeship training model is posted, a bug existed where global rule would not be found meaning that the dates would no longer be filtered when the view is returned. EmployerAccountId needed to be successfully resolved to enable successful obtain of AccountFundingRules. --- .../WhenCallingPostApprenticeshipTraining.cs | 231 +++++++++++++++++- .../Controllers/ReservationsController.cs | 15 +- 2 files changed, 239 insertions(+), 7 deletions(-) diff --git a/src/SFA.DAS.Reservations.Web.UnitTests/Reservations/WhenCallingPostApprenticeshipTraining.cs b/src/SFA.DAS.Reservations.Web.UnitTests/Reservations/WhenCallingPostApprenticeshipTraining.cs index 5b68f74d6..2390342bf 100644 --- a/src/SFA.DAS.Reservations.Web.UnitTests/Reservations/WhenCallingPostApprenticeshipTraining.cs +++ b/src/SFA.DAS.Reservations.Web.UnitTests/Reservations/WhenCallingPostApprenticeshipTraining.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Threading; using System.Threading.Tasks; @@ -14,6 +15,7 @@ using Newtonsoft.Json; using NUnit.Framework; using SFA.DAS.Encoding; +using SFA.DAS.Reservations.Application.FundingRules.Queries.GetAccountFundingRules; using SFA.DAS.Reservations.Application.Reservations.Commands.CacheReservationCourse; using SFA.DAS.Reservations.Application.Reservations.Commands.CacheReservationStartDate; using SFA.DAS.Reservations.Application.Reservations.Queries.GetCachedReservation; @@ -83,6 +85,214 @@ public void Arrange() }); } + [Test, MoqAutoData] + public async Task When_Model_Error_Then_It_Calls_Start_Date_Service_To_Get_Start_Dates( + ReservationsRouteModel routeModel, + ApprenticeshipTrainingFormModel apprenticeshipTrainingFormModel, + TrainingDateModel trainingDateModel, + long accountLegalEntityId, + [Frozen] Mock mockEncodingService, + [Frozen] Mock mockStartDateService, + [Frozen] Mock mockMediator, + ReservationsController controller) + { + apprenticeshipTrainingFormModel.StartDate = JsonConvert.SerializeObject(trainingDateModel); + controller.ModelState.AddModelError("StartDate", "StartDate"); + + mockEncodingService + .Setup(service => service.Decode( + apprenticeshipTrainingFormModel.AccountLegalEntityPublicHashedId, + EncodingType.PublicAccountLegalEntityId)) + .Returns(accountLegalEntityId); + + await controller.PostApprenticeshipTraining(routeModel, apprenticeshipTrainingFormModel); + + mockStartDateService.Verify(provider => provider.GetTrainingDates(accountLegalEntityId), Times.Once); + } + + [Test, MoqAutoData] + public async Task When_Model_Error_And_Is_Employer_Then_It_Calls_To_Get_Account_Global_Rules( + ReservationsRouteModel routeModel, + ApprenticeshipTrainingFormModel apprenticeshipTrainingFormModel, + TrainingDateModel trainingDateModel, + long accountLegalEntityId, + long accountId, + [Frozen] Mock mockEncodingService, + [Frozen] Mock mockStartDateService, + [Frozen] Mock mockMediator, + ReservationsController controller) + { + apprenticeshipTrainingFormModel.StartDate = JsonConvert.SerializeObject(trainingDateModel); + controller.ModelState.AddModelError("StartDate", "StartDate"); + + mockEncodingService + .Setup(service => service.Decode( + apprenticeshipTrainingFormModel.AccountLegalEntityPublicHashedId, + EncodingType.PublicAccountLegalEntityId)) + .Returns(accountLegalEntityId); + + mockEncodingService + .Setup(service => service.Decode( + routeModel.EmployerAccountId, + EncodingType.AccountId)) + .Returns(accountId); + + await controller.PostApprenticeshipTraining(routeModel, apprenticeshipTrainingFormModel); + + mockMediator.Verify(provider => provider.Send(It.Is(x => x.AccountId == accountId), It.IsAny()), Times.Once); + } + + [Test, MoqAutoData] + public async Task When_Model_Error_If_Provider_Then_It_Calls_To_Get_Account_Global_Rules_With_Cached_Reservation_Account_Id( + ReservationsRouteModel routeModel, + ApprenticeshipTrainingFormModel apprenticeshipTrainingFormModel, + TrainingDateModel trainingDateModel, + GetCachedReservationResult cachedReservationResult, + long accountLegalEntityId, + long accountId, + [Frozen] Mock mockMediator, + [Frozen] Mock mockEncodingService, + ReservationsController controller, + string hashedAccountId) + { + apprenticeshipTrainingFormModel.StartDate = JsonConvert.SerializeObject(trainingDateModel); + controller.ModelState.AddModelError("StartDate", "StartDate"); + routeModel.EmployerAccountId = string.Empty; + + mockMediator + .Setup(mediator => mediator.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(cachedReservationResult); + mockEncodingService + .Setup(service => service.Decode( + apprenticeshipTrainingFormModel.AccountLegalEntityPublicHashedId, + EncodingType.PublicAccountLegalEntityId)) + .Returns(accountLegalEntityId); + + mockEncodingService + .Setup(service => service.Encode( + cachedReservationResult.AccountId, + EncodingType.AccountId)) + .Returns(hashedAccountId); + + mockEncodingService + .Setup(service => service.Decode( + hashedAccountId, + EncodingType.AccountId)) + .Returns(cachedReservationResult.AccountId); + + await controller.PostApprenticeshipTraining(routeModel, apprenticeshipTrainingFormModel); + + mockMediator.Verify(provider => provider.Send(It.Is(x => x.AccountId == cachedReservationResult.AccountId), It.IsAny()), Times.Once); + } + + [Test, MoqAutoData] + public async Task When_Model_Error_If_Rule_Ends_After_Available_Dates_Show_None( + ReservationsRouteModel routeModel, + ApprenticeshipTrainingFormModel apprenticeshipTrainingFormModel, + TrainingDateModel trainingDateModel, + GetCachedReservationResult cachedReservationResult, + long accountLegalEntityId, + long accountId, + [Frozen] Mock mockMediator, + [Frozen] Mock mockEncodingService, + [Frozen] Mock mockStartDateService, + ReservationsController controller) + { + apprenticeshipTrainingFormModel.StartDate = JsonConvert.SerializeObject(trainingDateModel); + controller.ModelState.AddModelError("StartDate", "StartDate"); + + mockMediator + .Setup(mediator => mediator.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(cachedReservationResult); + mockEncodingService + .Setup(service => service.Decode( + apprenticeshipTrainingFormModel.AccountLegalEntityPublicHashedId, + EncodingType.PublicAccountLegalEntityId)) + .Returns(accountLegalEntityId); + + mockEncodingService + .Setup(service => service.Decode( + routeModel.EmployerAccountId, + EncodingType.AccountId)) + .Returns(accountId); + + mockMediator.Setup(mediator => mediator.Send(It.Is(x => x.AccountId == accountId), It.IsAny())).ReturnsAsync(new GetAccountFundingRulesResult + { + ActiveRule = new GlobalRule + { + RuleType = GlobalRuleType.DynamicPause, + ActiveFrom = DateTime.Now.AddMonths(-1), + ActiveTo = DateTime.Now.AddMonths(3) + } + }); + + mockStartDateService + .Setup(d => d.GetTrainingDates(It.Is(x => x == accountLegalEntityId))) + .ReturnsAsync(GetMockTrainingDates()); + + var result = await controller.PostApprenticeshipTraining(routeModel, apprenticeshipTrainingFormModel); + + var viewModel = result.Should().BeOfType() + .Which.Model.Should().BeOfType() + .Subject; + + viewModel.PossibleStartDates.Should().BeEmpty(); + } + + [Test, MoqAutoData] + public async Task When_Model_Error_If_Rule_Ends_Before_Available_Dates_Show( + ReservationsRouteModel routeModel, + ApprenticeshipTrainingFormModel apprenticeshipTrainingFormModel, + TrainingDateModel trainingDateModel, + GetCachedReservationResult cachedReservationResult, + long accountLegalEntityId, + long accountId, + [Frozen] Mock mockMediator, + [Frozen] Mock mockEncodingService, + [Frozen] Mock mockStartDateService, + ReservationsController controller) + { + apprenticeshipTrainingFormModel.StartDate = JsonConvert.SerializeObject(trainingDateModel); + controller.ModelState.AddModelError("StartDate", "StartDate"); + + mockMediator + .Setup(mediator => mediator.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(cachedReservationResult); + mockEncodingService + .Setup(service => service.Decode( + apprenticeshipTrainingFormModel.AccountLegalEntityPublicHashedId, + EncodingType.PublicAccountLegalEntityId)) + .Returns(accountLegalEntityId); + + mockEncodingService + .Setup(service => service.Decode( + routeModel.EmployerAccountId, + EncodingType.AccountId)) + .Returns(accountId); + + mockMediator.Setup(mediator => mediator.Send(It.Is(x => x.AccountId == accountId), It.IsAny())).ReturnsAsync(new GetAccountFundingRulesResult + { + ActiveRule = new GlobalRule + { + RuleType = GlobalRuleType.DynamicPause, + ActiveFrom = DateTime.Now.AddMonths(-1), + ActiveTo = DateTime.Now.AddMonths(1) + } + }); + + mockStartDateService + .Setup(d => d.GetTrainingDates(It.Is(x => x == accountLegalEntityId))) + .ReturnsAsync(GetMockTrainingDates()); + + var result = await controller.PostApprenticeshipTraining(routeModel, apprenticeshipTrainingFormModel); + + var viewModel = result.Should().BeOfType() + .Which.Model.Should().BeOfType() + .Subject; + + viewModel.PossibleStartDates.Should().HaveCount(2); + } + [Test, AutoData] public async Task Then_The_Model_Is_Validated_And_Confirmation_Returned( ApprenticeshipTrainingFormModel model, @@ -286,7 +496,6 @@ public async Task Then_If_There_Is_An_Error_The_Model_Is_Correctly_Built_And_Ret Assert.AreEqual(cohortDetailsUrl, actualModel.BackLink); } - [Test, AutoData] public async Task Then_If_There_Is_An_Error_The_Model_Is_Correctly_Built_And_Returned_To_The_View_And_The_Back_Link_Is_Correct( string cohortDetailsUrl, @@ -350,5 +559,23 @@ public async Task And_CachedReservationNotFoundException_And_No_Ukprn_Then_Redir actual.Should().NotBeNull(); actual.RouteName.Should().Be(RouteNames.EmployerIndex); } + + private IEnumerable GetMockTrainingDates() + { + var trainingDates = new List(); + + for (int i = 0; i < 3; i++) + { + var startDate = DateTime.Now.AddMonths(i); + var endDate = startDate.AddMonths(2); + trainingDates.Add(new TrainingDateModel + { + StartDate = startDate, + EndDate = endDate + }); + } + + return trainingDates; + } } } diff --git a/src/SFA.DAS.Reservations.Web/Controllers/ReservationsController.cs b/src/SFA.DAS.Reservations.Web/Controllers/ReservationsController.cs index eecd616db..416a7869c 100644 --- a/src/SFA.DAS.Reservations.Web/Controllers/ReservationsController.cs +++ b/src/SFA.DAS.Reservations.Web/Controllers/ReservationsController.cs @@ -154,12 +154,19 @@ public async Task PostApprenticeshipTraining(ReservationsRouteMod { var isProvider = routeModel.UkPrn != null; TrainingDateModel trainingDateModel = null; + string hashedEmployerAccountId = routeModel.EmployerAccountId; try { if (!string.IsNullOrWhiteSpace(formModel.StartDate)) trainingDateModel = JsonConvert.DeserializeObject(formModel.StartDate); + var cachedReservation = await _mediator.Send(new GetCachedReservationQuery { Id = routeModel.Id.GetValueOrDefault() }); + + hashedEmployerAccountId = !string.IsNullOrEmpty(routeModel.EmployerAccountId) + ? routeModel.EmployerAccountId + : (cachedReservation != null ? _encodingService.Encode(cachedReservation.AccountId, EncodingType.AccountId) : null); + if (!ModelState.IsValid) { var model = await BuildApprenticeshipTrainingViewModel( @@ -170,12 +177,10 @@ public async Task PostApprenticeshipTraining(ReservationsRouteMod formModel.FromReview, formModel.CohortRef, routeModel.UkPrn, - routeModel.EmployerAccountId); + hashedEmployerAccountId); return View("ApprenticeshipTraining", model); - } - - var cachedReservation = await _mediator.Send(new GetCachedReservationQuery { Id = routeModel.Id.GetValueOrDefault() }); + } if (isProvider) { @@ -213,7 +218,7 @@ public async Task PostApprenticeshipTraining(ReservationsRouteMod formModel.FromReview, formModel.CohortRef, routeModel.UkPrn, - routeModel.EmployerAccountId); + hashedEmployerAccountId); return View("ApprenticeshipTraining", model); } catch (CachedReservationNotFoundException ex)