diff --git a/src/Employer/Employer.Web/Controllers/VacancyPreviewController.cs b/src/Employer/Employer.Web/Controllers/VacancyPreviewController.cs index 0840b4bb23..0dd7f31111 100644 --- a/src/Employer/Employer.Web/Controllers/VacancyPreviewController.cs +++ b/src/Employer/Employer.Web/Controllers/VacancyPreviewController.cs @@ -30,14 +30,14 @@ public VacancyPreviewController(VacancyPreviewOrchestrator orchestrator) } [HttpGet("preview", Name = RouteNames.Vacancy_Preview_Get)] - public async Task VacancyPreview(VacancyRouteModel vrm, string selection = null) + public async Task VacancyPreview(VacancyRouteModel vrm, bool? submitToEfsa = null) { var viewModel = await _orchestrator.GetVacancyPreviewViewModelAsync(vrm); AddSoftValidationErrorsToModelState(viewModel); SetSectionStates(viewModel); viewModel.CanHideValidationSummary = true; - ViewBag.Selection = selection; + viewModel.SubmitToEsfa = submitToEfsa; if (TempData.ContainsKey(TempDataKeys.VacancyClonedInfoMessage)) viewModel.VacancyClonedInfoMessage = TempData[TempDataKeys.VacancyClonedInfoMessage].ToString(); @@ -50,6 +50,15 @@ public async Task Review(SubmitReviewModel m) { if (ModelState.IsValid) { + if(m.SubmitToEsfa.Value) + { + await _orchestrator.ClearRejectedVacancyReason(m, User.ToVacancyUser()); + } + else + { + await _orchestrator.UpdateRejectedVacancyReason(m, User.ToVacancyUser()); + } + return RedirectToRoute(m.SubmitToEsfa.GetValueOrDefault() ? RouteNames.ApproveJobAdvert_Get : RouteNames.RejectJobAdvert_Get); @@ -57,6 +66,8 @@ public async Task Review(SubmitReviewModel m) var viewModel = await _orchestrator.GetVacancyPreviewViewModelAsync(m); viewModel.SoftValidationErrors = null; + viewModel.SubmitToEsfa = m.SubmitToEsfa; + viewModel.RejectedReason = m.RejectedReason; SetSectionStates(viewModel); return View(ViewNames.VacancyPreview, viewModel); @@ -125,7 +136,7 @@ public async Task ApproveJobAdvert(ApproveJobAdvertViewModel vm) } else { - return RedirectToRoute(RouteNames.Vacancy_Preview_Get, new { VacancyId = vm.VacancyId, Selection = "Approve" }); + return RedirectToRoute(RouteNames.Vacancy_Preview_Get, new { VacancyId = vm.VacancyId, SubmitToEfsa = true }); } var viewModel = await _orchestrator.GetVacancyPreviewViewModelAsync(vm); @@ -167,7 +178,7 @@ public async Task RejectJobAdvert(RejectJobAdvertViewModel vm) } } - return RedirectToRoute(RouteNames.Vacancy_Preview_Get, new { VacancyId = vm.VacancyId, Selection = "Reject" }); + return RedirectToRoute(RouteNames.Vacancy_Preview_Get, new { VacancyId = vm.VacancyId, SubmitToEfsa = false }); } [HttpGet("confirmation-advert", Name = RouteNames.JobAdvertConfirmation_Get)] diff --git a/src/Employer/Employer.Web/Orchestrators/Part1/DatesOrchestrator.cs b/src/Employer/Employer.Web/Orchestrators/Part1/DatesOrchestrator.cs index c21c04985c..93d3caa47c 100644 --- a/src/Employer/Employer.Web/Orchestrators/Part1/DatesOrchestrator.cs +++ b/src/Employer/Employer.Web/Orchestrators/Part1/DatesOrchestrator.cs @@ -7,6 +7,7 @@ using Esfa.Recruit.Shared.Web.Orchestrators; using Esfa.Recruit.Shared.Web.Services; using Esfa.Recruit.Vacancies.Client.Application.Providers; +using Esfa.Recruit.Vacancies.Client.Application.Services; using Esfa.Recruit.Vacancies.Client.Application.Validation; using Esfa.Recruit.Vacancies.Client.Domain.Entities; using Esfa.Recruit.Vacancies.Client.Domain.Extensions; @@ -15,7 +16,7 @@ namespace Esfa.Recruit.Employer.Web.Orchestrators.Part1 { - public class DatesOrchestrator : EntityValidatingOrchestrator + public class DatesOrchestrator : VacancyValidatingOrchestrator { private const VacancyRuleSet ValidationRules = VacancyRuleSet.ClosingDate | VacancyRuleSet.StartDate | VacancyRuleSet.StartDateEndDate | VacancyRuleSet.TrainingExpiryDate; private readonly IEmployerVacancyClient _client; @@ -99,11 +100,33 @@ public async Task PostDatesEditModelAsync(DatesEditModel m { var vacancy = await Utility.GetAuthorisedVacancyForEditAsync(_client, _vacancyClient, m, RouteNames.Dates_Post); - vacancy.ClosingDate = m.ClosingDate.AsDateTimeUk()?.ToUniversalTime(); - vacancy.StartDate = m.StartDate.AsDateTimeUk()?.ToUniversalTime(); - - vacancy.DisabilityConfident = m.IsDisabilityConfident ? DisabilityConfident.Yes : DisabilityConfident.No; - + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.ClosingDate, + FieldIdResolver.ToFieldId(v => v.ClosingDate), + vacancy, + (v) => + { + return v.ClosingDate = m.ClosingDate.AsDateTimeUk()?.ToUniversalTime(); + }); + + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.StartDate, + FieldIdResolver.ToFieldId(v => v.StartDate), + vacancy, + (v) => + { + return v.StartDate = m.StartDate.AsDateTimeUk()?.ToUniversalTime(); + }); + + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.DisabilityConfident, + FieldIdResolver.ToFieldId(v => v.DisabilityConfident), + vacancy, + (v) => + { + return v.DisabilityConfident = m.IsDisabilityConfident ? DisabilityConfident.Yes : DisabilityConfident.No; + }); + return await ValidateAndExecute( vacancy, v => _vacancyClient.Validate(v, ValidationRules), diff --git a/src/Employer/Employer.Web/Orchestrators/Part1/DurationOrchestrator.cs b/src/Employer/Employer.Web/Orchestrators/Part1/DurationOrchestrator.cs index 9f0eb29a7d..123bfc6e3c 100644 --- a/src/Employer/Employer.Web/Orchestrators/Part1/DurationOrchestrator.cs +++ b/src/Employer/Employer.Web/Orchestrators/Part1/DurationOrchestrator.cs @@ -6,6 +6,7 @@ using Esfa.Recruit.Shared.Web.Extensions; using Esfa.Recruit.Shared.Web.Orchestrators; using Esfa.Recruit.Shared.Web.Services; +using Esfa.Recruit.Vacancies.Client.Application.Services; using Esfa.Recruit.Vacancies.Client.Application.Validation; using Esfa.Recruit.Vacancies.Client.Domain.Entities; using Esfa.Recruit.Vacancies.Client.Infrastructure.Client; @@ -13,7 +14,7 @@ namespace Esfa.Recruit.Employer.Web.Orchestrators.Part1 { - public class DurationOrchestrator : EntityValidatingOrchestrator + public class DurationOrchestrator : VacancyValidatingOrchestrator { private const VacancyRuleSet ValidationRules = VacancyRuleSet.Duration | VacancyRuleSet.WorkingWeekDescription | VacancyRuleSet.WeeklyHours; private readonly IEmployerVacancyClient _client; @@ -72,11 +73,42 @@ public async Task PostDurationEditModelAsync(DurationEditM if(vacancy.Wage == null) vacancy.Wage = new Wage(); - vacancy.Wage.Duration = int.TryParse(m.Duration, out int duration) ? duration : default(int?); - vacancy.Wage.DurationUnit = m.DurationUnit; - vacancy.Wage.WorkingWeekDescription = m.WorkingWeekDescription; - vacancy.Wage.WeeklyHours = m.WeeklyHours.AsDecimal(2); - + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.Wage.Duration, + FieldIdResolver.ToFieldId(v => v.Wage.Duration), + vacancy, + (v) => + { + return v.Wage.Duration = int.TryParse(m.Duration, out int duration) ? duration : default(int?); + }); + + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.Wage.DurationUnit, + FieldIdResolver.ToFieldId(v => v.Wage.DurationUnit), + vacancy, + (v) => + { + return v.Wage.DurationUnit = m.DurationUnit; + }); + + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.Wage.WorkingWeekDescription, + FieldIdResolver.ToFieldId(v => v.Wage.WorkingWeekDescription), + vacancy, + (v) => + { + return v.Wage.WorkingWeekDescription = m.WorkingWeekDescription; + }); + + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.Wage.WeeklyHours, + FieldIdResolver.ToFieldId(v => v.Wage.WeeklyHours), + vacancy, + (v) => + { + return v.Wage.WeeklyHours = m.WeeklyHours.AsDecimal(2); + }); + return await ValidateAndExecute( vacancy, v => _vacancyClient.Validate(v, ValidationRules), diff --git a/src/Employer/Employer.Web/Orchestrators/Part1/LocationOrchestrator.cs b/src/Employer/Employer.Web/Orchestrators/Part1/LocationOrchestrator.cs index 312511c11f..a2cee6bd02 100644 --- a/src/Employer/Employer.Web/Orchestrators/Part1/LocationOrchestrator.cs +++ b/src/Employer/Employer.Web/Orchestrators/Part1/LocationOrchestrator.cs @@ -16,10 +16,11 @@ using Esfa.Recruit.Shared.Web.Services; using Esfa.Recruit.Employer.Web.Mappings; using Esfa.Recruit.Shared.Web.Models; +using Esfa.Recruit.Vacancies.Client.Application.Services; namespace Esfa.Recruit.Employer.Web.Orchestrators.Part1 { - public class LocationOrchestrator : EntityValidatingOrchestrator + public class LocationOrchestrator : VacancyValidatingOrchestrator { private const VacancyRuleSet ValidationRules = VacancyRuleSet.EmployerAddress; private readonly IEmployerVacancyClient _employerVacancyClient; @@ -140,14 +141,91 @@ public async Task PostLocationEditModelAsync( var matchingAddress = GetMatchingAddress(newLocation, allLocations); - vacancy.EmployerLocation = matchingAddress != null ? matchingAddress : ConvertToDomainAddress(locationEditModel); + var employerLocation = matchingAddress != null ? matchingAddress : ConvertToDomainAddress(locationEditModel); - //if cookie is found then update legal entity and name option from cookie + // this has diverged from the usual pattern because the individual properties are review fields + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.EmployerLocation?.AddressLine1, + FieldIdResolver.ToFieldId(v => v.EmployerLocation.AddressLine1), + vacancy, + (v) => + { + return employerLocation.AddressLine1; + }); + + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.EmployerLocation?.AddressLine2, + FieldIdResolver.ToFieldId(v => v.EmployerLocation.AddressLine2), + vacancy, + (v) => + { + return employerLocation.AddressLine2; + }); + + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.EmployerLocation?.AddressLine3, + FieldIdResolver.ToFieldId(v => v.EmployerLocation.AddressLine3), + vacancy, + (v) => + { + return employerLocation.AddressLine3; + }); + + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.EmployerLocation?.AddressLine4, + FieldIdResolver.ToFieldId(v => v.EmployerLocation.AddressLine4), + vacancy, + (v) => + { + return employerLocation.AddressLine4; + }); + + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.EmployerLocation?.Postcode, + FieldIdResolver.ToFieldId(v => v.EmployerLocation.Postcode), + vacancy, + (v) => + { + return employerLocation.Postcode; + }); + + vacancy.EmployerLocation = employerLocation; + + // if cookie is found then update legal entity and name option from cookie if (employerInfoModel != null) { - vacancy.LegalEntityName = selectedOrganisation.Name; + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.LegalEntityName, + FieldIdResolver.ToFieldId(v => v.EmployerName), + vacancy, + (v) => + { + return v.LegalEntityName = selectedOrganisation.Name; + }); + + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.EmployerNameOption, + FieldIdResolver.ToFieldId(v => v.EmployerName), + vacancy, + (v) => + { + return v.EmployerNameOption = employerInfoModel.EmployerIdentityOption?.ConvertToDomainOption(); + }); + + if (employerInfoModel.EmployerIdentityOption == EmployerIdentityOption.NewTradingName) + { + SetVacancyWithEmployerReviewFieldIndicators( + employerProfile.TradingName, + FieldIdResolver.ToFieldId(v => v.EmployerName), + vacancy, + (e) => + { + // the indicator will be set for the vacancy when the employer profile will change to the new trading name + return employerInfoModel.NewTradingName; + }); + } + vacancy.AccountLegalEntityPublicHashedId = selectedOrganisation.AccountLegalEntityPublicHashedId; - vacancy.EmployerNameOption = employerInfoModel.EmployerIdentityOption?.ConvertToDomainOption(); vacancy.AnonymousReason = vacancy.IsAnonymous ? employerInfoModel.AnonymousReason : null; vacancy.EmployerName = vacancy.IsAnonymous ? employerInfoModel.AnonymousName : null; } @@ -161,6 +239,7 @@ public async Task PostLocationEditModelAsync( await UpdateEmployerProfile(employerInfoModel, employerProfile, matchingAddress == null ? vacancy.EmployerLocation : null, user); }); } + private Address GetMatchingAddress(string locationToMatch, IEnumerable
allLocations) { var matchingLocation = @@ -206,8 +285,6 @@ private async Task UpdateEmployerProfile(VacancyEmployerInfoModel employerInfoMo } } - - private async Task> GetAllAvailableLocationsAsync(EmployerProfile employerProfile) { var employerData = await _employerVacancyClient.GetEditVacancyInfoAsync(employerProfile.EmployerAccountId); diff --git a/src/Employer/Employer.Web/Orchestrators/Part1/NumberofPositionsOrchestrator.cs b/src/Employer/Employer.Web/Orchestrators/Part1/NumberofPositionsOrchestrator.cs index a7caee7c55..51b6d131ae 100644 --- a/src/Employer/Employer.Web/Orchestrators/Part1/NumberofPositionsOrchestrator.cs +++ b/src/Employer/Employer.Web/Orchestrators/Part1/NumberofPositionsOrchestrator.cs @@ -6,6 +6,7 @@ using Esfa.Recruit.Employer.Web.ViewModels.Part1.NumberOfPositions; using Esfa.Recruit.Shared.Web.Orchestrators; using Esfa.Recruit.Shared.Web.Services; +using Esfa.Recruit.Vacancies.Client.Application.Services; using Esfa.Recruit.Vacancies.Client.Application.Validation; using Esfa.Recruit.Vacancies.Client.Domain.Entities; using Esfa.Recruit.Vacancies.Client.Infrastructure.Client; @@ -13,7 +14,7 @@ namespace Esfa.Recruit.Employer.Web.Orchestrators.Part1 { - public class NumberOfPositionsOrchestrator : EntityValidatingOrchestrator + public class NumberOfPositionsOrchestrator : VacancyValidatingOrchestrator { private const VacancyRuleSet ValidationRules = VacancyRuleSet.NumberOfPositions; private readonly IEmployerVacancyClient _client; @@ -58,9 +59,17 @@ public async Task GetNumberOfPositionsViewModelAsync public async Task> PostNumberOfPositionsEditModelAsync(NumberOfPositionsEditModel m, VacancyUser user) { - var numberOfPositions = int.TryParse(m.NumberOfPositions, out var n)? n : default(int?); var vacancy = await Utility.GetAuthorisedVacancyForEditAsync(_client, _vacancyClient,m, RouteNames.NumberOfPositions_Post); - vacancy.NumberOfPositions = numberOfPositions; + + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.NumberOfPositions, + FieldIdResolver.ToFieldId(v => v.NumberOfPositions), + vacancy, + (v) => + { + return v.NumberOfPositions = int.TryParse(m.NumberOfPositions, out var n) ? n : default(int?); + }); + return await ValidateAndExecute( vacancy, v => _vacancyClient.Validate(v, ValidationRules), diff --git a/src/Employer/Employer.Web/Orchestrators/Part1/TitleOrchestrator.cs b/src/Employer/Employer.Web/Orchestrators/Part1/TitleOrchestrator.cs index b9dba9443d..661d5306c2 100644 --- a/src/Employer/Employer.Web/Orchestrators/Part1/TitleOrchestrator.cs +++ b/src/Employer/Employer.Web/Orchestrators/Part1/TitleOrchestrator.cs @@ -8,6 +8,7 @@ using Esfa.Recruit.Shared.Web.Orchestrators; using Esfa.Recruit.Shared.Web.Services; using Esfa.Recruit.Shared.Web.ViewModels; +using Esfa.Recruit.Vacancies.Client.Application.Services; using Esfa.Recruit.Vacancies.Client.Application.Validation; using Esfa.Recruit.Vacancies.Client.Domain.Entities; using Esfa.Recruit.Vacancies.Client.Infrastructure.Client; @@ -16,21 +17,19 @@ namespace Esfa.Recruit.Employer.Web.Orchestrators.Part1 { - public class TitleOrchestrator : EntityValidatingOrchestrator + public class TitleOrchestrator : VacancyValidatingOrchestrator { private const VacancyRuleSet ValidationRules = VacancyRuleSet.Title; private readonly IEmployerVacancyClient _client; private readonly IRecruitVacancyClient _vacancyClient; private readonly IReviewSummaryService _reviewSummaryService; - private readonly IEmployerVacancyClient _employerVacancyClient; private readonly ITrainingProviderService _trainingProviderService; - public TitleOrchestrator(IEmployerVacancyClient client, IRecruitVacancyClient vacancyClient, ILogger logger, IReviewSummaryService reviewSummaryService, IEmployerVacancyClient employerVacancyClient, ITrainingProviderService trainingProviderService) : base(logger) + public TitleOrchestrator(IEmployerVacancyClient client, IRecruitVacancyClient vacancyClient, ILogger logger, IReviewSummaryService reviewSummaryService, ITrainingProviderService trainingProviderService) : base(logger) { _client = client; _vacancyClient = vacancyClient; _reviewSummaryService = reviewSummaryService; - _employerVacancyClient = employerVacancyClient; _trainingProviderService = trainingProviderService; } @@ -45,7 +44,7 @@ public TitleViewModel GetTitleViewModel() public async Task GetTitleViewModelAsync(VacancyRouteModel vrm) { - var dashboard = await _employerVacancyClient.GetDashboardAsync(vrm.EmployerAccountId); + var dashboard = await _client.GetDashboardAsync(vrm.EmployerAccountId); var vacancy = await Utility.GetAuthorisedVacancyForEditAsync(_client, _vacancyClient, vrm, RouteNames.Title_Get); var vm = new TitleViewModel @@ -83,7 +82,6 @@ public async Task GetTitleViewModelAsync(TitleEditModel m) return vm; } - public async Task> PostTitleEditModelAsync(TitleEditModel m, VacancyUser user) { TrainingProvider provider = null; @@ -111,7 +109,14 @@ public async Task> PostTitleEditModelAsync(TitleEditM var vacancy = await Utility.GetAuthorisedVacancyForEditAsync(_client, _vacancyClient, new VacancyRouteModel{EmployerAccountId = m.EmployerAccountId, VacancyId = m.VacancyId.Value}, RouteNames.Title_Post); - vacancy.Title = m.Title; + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.Title, + FieldIdResolver.ToFieldId(v => v.Title), + vacancy, + (v) => + { + return v.Title = m.Title; + }); return await ValidateAndExecute( vacancy, diff --git a/src/Employer/Employer.Web/Orchestrators/Part1/TrainingOrchestrator.cs b/src/Employer/Employer.Web/Orchestrators/Part1/TrainingOrchestrator.cs index 1606d236df..ecbbfa3c92 100644 --- a/src/Employer/Employer.Web/Orchestrators/Part1/TrainingOrchestrator.cs +++ b/src/Employer/Employer.Web/Orchestrators/Part1/TrainingOrchestrator.cs @@ -8,6 +8,7 @@ using Esfa.Recruit.Shared.Web.Helpers; using Esfa.Recruit.Shared.Web.Orchestrators; using Esfa.Recruit.Shared.Web.Services; +using Esfa.Recruit.Vacancies.Client.Application.Services; using Esfa.Recruit.Vacancies.Client.Application.Validation; using Esfa.Recruit.Vacancies.Client.Domain.Entities; using Esfa.Recruit.Vacancies.Client.Domain.Extensions; @@ -16,7 +17,7 @@ namespace Esfa.Recruit.Employer.Web.Orchestrators.Part1 { - public class TrainingOrchestrator : EntityValidatingOrchestrator + public class TrainingOrchestrator : VacancyValidatingOrchestrator { private const VacancyRuleSet ValidationRules = VacancyRuleSet.TrainingProgramme; private readonly IEmployerVacancyClient _client; @@ -104,8 +105,15 @@ public async Task GetConfirmTrainingViewModelAsync(Vac public async Task PostConfirmTrainingEditModelAsync(ConfirmTrainingEditModel m, VacancyUser user) { var vacancy = await Utility.GetAuthorisedVacancyForEditAsync(_client, _vacancyClient, m, RouteNames.Training_Confirm_Post); - - vacancy.ProgrammeId = m.ProgrammeId; + + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.ProgrammeId, + FieldIdResolver.ToFieldId(v => v.ProgrammeId), + vacancy, + (v) => + { + return v.ProgrammeId = m.ProgrammeId; + }); return await ValidateAndExecute( vacancy, diff --git a/src/Employer/Employer.Web/Orchestrators/Part1/TrainingProviderOrchestrator.cs b/src/Employer/Employer.Web/Orchestrators/Part1/TrainingProviderOrchestrator.cs index 793269c0db..787b1589fc 100644 --- a/src/Employer/Employer.Web/Orchestrators/Part1/TrainingProviderOrchestrator.cs +++ b/src/Employer/Employer.Web/Orchestrators/Part1/TrainingProviderOrchestrator.cs @@ -11,6 +11,7 @@ using Esfa.Recruit.Shared.Web.Services; using Esfa.Recruit.Vacancies.Client.Application.Configuration; using Esfa.Recruit.Vacancies.Client.Application.Providers; +using Esfa.Recruit.Vacancies.Client.Application.Services; using Esfa.Recruit.Vacancies.Client.Application.Validation; using Esfa.Recruit.Vacancies.Client.Domain.Entities; using Esfa.Recruit.Vacancies.Client.Infrastructure.Client; @@ -19,7 +20,7 @@ namespace Esfa.Recruit.Employer.Web.Orchestrators.Part1 { - public class TrainingProviderOrchestrator : EntityValidatingOrchestrator + public class TrainingProviderOrchestrator : VacancyValidatingOrchestrator { private const VacancyRuleSet ValidationRules = VacancyRuleSet.TrainingProvider; private readonly IEmployerVacancyClient _client; @@ -148,6 +149,16 @@ public async Task PostConfirmEditModelAsync(ConfirmTrainin var vacancy = vacancyTask.Result; var provider = providerTask.Result; + // this has diverged from the usual pattern because only a single individual property is a review field + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.TrainingProvider?.Ukprn, + FieldIdResolver.ToFieldId(v => v.TrainingProvider.Ukprn), + vacancy, + (v) => + { + return provider.Ukprn; + }); + vacancy.TrainingProvider = provider; return await ValidateAndExecute( diff --git a/src/Employer/Employer.Web/Orchestrators/Part1/WageOrchestrator.cs b/src/Employer/Employer.Web/Orchestrators/Part1/WageOrchestrator.cs index 648609a3d7..9570297dd9 100644 --- a/src/Employer/Employer.Web/Orchestrators/Part1/WageOrchestrator.cs +++ b/src/Employer/Employer.Web/Orchestrators/Part1/WageOrchestrator.cs @@ -8,6 +8,7 @@ using Esfa.Recruit.Shared.Web.Orchestrators; using Esfa.Recruit.Shared.Web.Services; using Esfa.Recruit.Vacancies.Client.Application.Providers; +using Esfa.Recruit.Vacancies.Client.Application.Services; using Esfa.Recruit.Vacancies.Client.Application.Validation; using Esfa.Recruit.Vacancies.Client.Domain.Entities; using Esfa.Recruit.Vacancies.Client.Domain.Extensions; @@ -18,7 +19,7 @@ namespace Esfa.Recruit.Employer.Web.Orchestrators.Part1 { - public class WageOrchestrator : EntityValidatingOrchestrator + public class WageOrchestrator : VacancyValidatingOrchestrator { private const VacancyRuleSet ValidationRules = VacancyRuleSet.Wage | VacancyRuleSet.MinimumWage; private readonly IEmployerVacancyClient _client; @@ -82,10 +83,33 @@ public async Task PostWageEditModelAsync(WageEditModel m, if(vacancy.Wage == null) vacancy.Wage = new Wage(); - vacancy.Wage.WageType = m.WageType; - vacancy.Wage.FixedWageYearlyAmount = (m.WageType == WageType.FixedWage) ? m.FixedWageYearlyAmount?.AsMoney() : null; - vacancy.Wage.WageAdditionalInformation = m.WageAdditionalInformation; - + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.Wage.WageType, + FieldIdResolver.ToFieldId(v => v.Wage.WageType), + vacancy, + (v) => + { + return v.Wage.WageType = m.WageType; + }); + + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.Wage.FixedWageYearlyAmount, + FieldIdResolver.ToFieldId(v => v.Wage.FixedWageYearlyAmount), + vacancy, + (v) => + { + return v.Wage.FixedWageYearlyAmount = (m.WageType == WageType.FixedWage) ? m.FixedWageYearlyAmount?.AsMoney() : null; + }); + + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.Wage.WageAdditionalInformation, + FieldIdResolver.ToFieldId(v => v.Wage.WageAdditionalInformation), + vacancy, + (v) => + { + return v.Wage.WageAdditionalInformation = m.WageAdditionalInformation; + }); + return await ValidateAndExecute( vacancy, v => _vacancyClient.Validate(v, ValidationRules), diff --git a/src/Employer/Employer.Web/Orchestrators/Part2/AboutEmployerOrchestrator.cs b/src/Employer/Employer.Web/Orchestrators/Part2/AboutEmployerOrchestrator.cs index 2d9aa7cdb3..dfd86bb683 100644 --- a/src/Employer/Employer.Web/Orchestrators/Part2/AboutEmployerOrchestrator.cs +++ b/src/Employer/Employer.Web/Orchestrators/Part2/AboutEmployerOrchestrator.cs @@ -7,6 +7,7 @@ using Esfa.Recruit.Employer.Web.ViewModels.AboutEmployer; using Esfa.Recruit.Shared.Web.Orchestrators; using Esfa.Recruit.Shared.Web.Services; +using Esfa.Recruit.Vacancies.Client.Application.Services; using Esfa.Recruit.Vacancies.Client.Application.Validation; using Esfa.Recruit.Vacancies.Client.Domain.Entities; using Esfa.Recruit.Vacancies.Client.Infrastructure.Client; @@ -14,7 +15,7 @@ namespace Esfa.Recruit.Employer.Web.Orchestrators.Part2 { - public class AboutEmployerOrchestrator : EntityValidatingOrchestrator + public class AboutEmployerOrchestrator : VacancyValidatingOrchestrator { private const VacancyRuleSet ValidationRules = VacancyRuleSet.EmployerDescription | VacancyRuleSet.EmployerWebsiteUrl; private readonly IEmployerVacancyClient _client; @@ -64,8 +65,17 @@ public async Task PostAboutEmployerEditModelAsync(AboutEmp { var vacancy = await Utility.GetAuthorisedVacancyForEditAsync(_client, _vacancyClient, m, RouteNames.AboutEmployer_Post); - vacancy.EmployerDescription = m.EmployerDescription; - vacancy.EmployerWebsiteUrl = m.EmployerWebsiteUrl; + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.EmployerDescription, + FieldIdResolver.ToFieldId(v => v.EmployerDescription), + vacancy, + (v) => { return v.EmployerDescription = m.EmployerDescription; }); + + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.EmployerWebsiteUrl, + FieldIdResolver.ToFieldId(v => v.EmployerWebsiteUrl), + vacancy, + (v) => { return v.EmployerWebsiteUrl = m.EmployerWebsiteUrl; }); return await ValidateAndExecute( vacancy, diff --git a/src/Employer/Employer.Web/Orchestrators/Part2/ApplicationProcessOrchestrator.cs b/src/Employer/Employer.Web/Orchestrators/Part2/ApplicationProcessOrchestrator.cs index f3df55cf04..5077012da6 100644 --- a/src/Employer/Employer.Web/Orchestrators/Part2/ApplicationProcessOrchestrator.cs +++ b/src/Employer/Employer.Web/Orchestrators/Part2/ApplicationProcessOrchestrator.cs @@ -6,6 +6,7 @@ using Esfa.Recruit.Employer.Web.ViewModels; using Esfa.Recruit.Shared.Web.Orchestrators; using Esfa.Recruit.Shared.Web.Services; +using Esfa.Recruit.Vacancies.Client.Application.Services; using Esfa.Recruit.Vacancies.Client.Application.Validation; using Esfa.Recruit.Vacancies.Client.Domain.Entities; using Esfa.Recruit.Vacancies.Client.Infrastructure.Client; @@ -14,7 +15,7 @@ namespace Esfa.Recruit.Employer.Web.Orchestrators.Part2 { - public class ApplicationProcessOrchestrator : EntityValidatingOrchestrator + public class ApplicationProcessOrchestrator : VacancyValidatingOrchestrator { private const VacancyRuleSet ValidationRules = VacancyRuleSet.ApplicationMethod; private readonly IEmployerVacancyClient _client; @@ -70,11 +71,25 @@ public async Task PostApplicationProcessEditModelAsync(App { var vacancy = await Utility.GetAuthorisedVacancyForEditAsync(_client, _vacancyClient, m, RouteNames.ApplicationProcess_Post); + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.ApplicationMethod, + FieldIdResolver.ToFieldId(v => v.ApplicationMethod), + vacancy, + (v) => { return v.ApplicationMethod = m.ApplicationMethod; }); + var hasSelectedApplyThroughFaa = m.ApplicationMethod == ApplicationMethod.ThroughFindAnApprenticeship; - vacancy.ApplicationMethod = m.ApplicationMethod; - vacancy.ApplicationInstructions = hasSelectedApplyThroughFaa ? null : m.ApplicationInstructions; - vacancy.ApplicationUrl = hasSelectedApplyThroughFaa ? null : m.ApplicationUrl; + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.ApplicationInstructions, + FieldIdResolver.ToFieldId(v => v.ApplicationInstructions), + vacancy, + (v) => { return v.ApplicationInstructions = hasSelectedApplyThroughFaa ? null : m.ApplicationInstructions; }); + + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.ApplicationUrl, + FieldIdResolver.ToFieldId(v => v.ApplicationUrl), + vacancy, + (v) => { return v.ApplicationUrl = hasSelectedApplyThroughFaa ? null : m.ApplicationUrl; }); return await ValidateAndExecute( vacancy, diff --git a/src/Employer/Employer.Web/Orchestrators/Part2/ConsiderationsOrchestrator.cs b/src/Employer/Employer.Web/Orchestrators/Part2/ConsiderationsOrchestrator.cs index e4eeaa5a00..7ef06649a7 100644 --- a/src/Employer/Employer.Web/Orchestrators/Part2/ConsiderationsOrchestrator.cs +++ b/src/Employer/Employer.Web/Orchestrators/Part2/ConsiderationsOrchestrator.cs @@ -5,6 +5,7 @@ using Esfa.Recruit.Employer.Web.ViewModels; using Esfa.Recruit.Shared.Web.Orchestrators; using Esfa.Recruit.Shared.Web.Services; +using Esfa.Recruit.Vacancies.Client.Application.Services; using Esfa.Recruit.Vacancies.Client.Application.Validation; using Esfa.Recruit.Vacancies.Client.Domain.Entities; using Esfa.Recruit.Vacancies.Client.Infrastructure.Client; @@ -12,7 +13,7 @@ namespace Esfa.Recruit.Employer.Web.Orchestrators.Part2 { - public class ConsiderationsOrchestrator : EntityValidatingOrchestrator + public class ConsiderationsOrchestrator : VacancyValidatingOrchestrator { private const VacancyRuleSet ValidationRules = VacancyRuleSet.ThingsToConsider; private readonly IEmployerVacancyClient _client; @@ -57,8 +58,12 @@ public async Task GetConsiderationsViewModelAsync(Consi public async Task PostConsiderationsEditModelAsync(ConsiderationsEditModel m, VacancyUser user) { var vacancy = await Utility.GetAuthorisedVacancyForEditAsync(_client, _vacancyClient, m, RouteNames.Considerations_Post); - - vacancy.ThingsToConsider = m.ThingsToConsider; + + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.ThingsToConsider, + FieldIdResolver.ToFieldId(v => v.ThingsToConsider), + vacancy, + (v) => { return v.ThingsToConsider = m.ThingsToConsider; }); return await ValidateAndExecute( vacancy, diff --git a/src/Employer/Employer.Web/Orchestrators/Part2/EmployerContactDetailsOrchestrator.cs b/src/Employer/Employer.Web/Orchestrators/Part2/EmployerContactDetailsOrchestrator.cs index 57509cf30d..fd770f165f 100644 --- a/src/Employer/Employer.Web/Orchestrators/Part2/EmployerContactDetailsOrchestrator.cs +++ b/src/Employer/Employer.Web/Orchestrators/Part2/EmployerContactDetailsOrchestrator.cs @@ -5,6 +5,7 @@ using Esfa.Recruit.Employer.Web.ViewModels; using Esfa.Recruit.Shared.Web.Orchestrators; using Esfa.Recruit.Shared.Web.Services; +using Esfa.Recruit.Vacancies.Client.Application.Services; using Esfa.Recruit.Vacancies.Client.Application.Validation; using Esfa.Recruit.Vacancies.Client.Domain.Entities; using Esfa.Recruit.Vacancies.Client.Infrastructure.Client; @@ -12,7 +13,7 @@ namespace Esfa.Recruit.Employer.Web.Orchestrators.Part2 { - public class EmployerContactDetailsOrchestrator : EntityValidatingOrchestrator + public class EmployerContactDetailsOrchestrator : VacancyValidatingOrchestrator { private const VacancyRuleSet ValidationRules = VacancyRuleSet.EmployerContactDetails; private readonly IEmployerVacancyClient _client; @@ -63,12 +64,26 @@ public async Task PostEmployerContactDetailsEditModelAsync { var vacancy = await Utility.GetAuthorisedVacancyForEditAsync(_client, _vacancyClient, m, RouteNames.EmployerContactDetails_Post); - vacancy.EmployerContact = new ContactDetail - { - Name = m.EmployerContactName, - Email = m.EmployerContactEmail, - Phone = m.EmployerContactPhone - }; + if (vacancy.EmployerContact == null) + vacancy.EmployerContact = new ContactDetail(); + + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.EmployerContact.Name, + FieldIdResolver.ToFieldId(v => v.EmployerContact.Name), + vacancy, + (v) => { return v.EmployerContact.Name = m.EmployerContactName; }); + + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.EmployerContact.Email, + FieldIdResolver.ToFieldId(v => v.EmployerContact.Email), + vacancy, + (v) => { return v.EmployerContact.Email = m.EmployerContactEmail; }); + + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.EmployerContact.Phone, + FieldIdResolver.ToFieldId(v => v.EmployerContact.Phone), + vacancy, + (v) => { return v.EmployerContact.Phone = m.EmployerContactPhone; }); return await ValidateAndExecute( vacancy, diff --git a/src/Employer/Employer.Web/Orchestrators/Part2/QualificationsOrchestrator.cs b/src/Employer/Employer.Web/Orchestrators/Part2/QualificationsOrchestrator.cs index 9737f1eaf3..e5dea21242 100644 --- a/src/Employer/Employer.Web/Orchestrators/Part2/QualificationsOrchestrator.cs +++ b/src/Employer/Employer.Web/Orchestrators/Part2/QualificationsOrchestrator.cs @@ -10,6 +10,7 @@ using Esfa.Recruit.Shared.Web.Orchestrators; using Esfa.Recruit.Shared.Web.Services; using Esfa.Recruit.Shared.Web.ViewModels.Qualifications; +using Esfa.Recruit.Vacancies.Client.Application.Services; using Esfa.Recruit.Vacancies.Client.Application.Validation; using Esfa.Recruit.Vacancies.Client.Domain.Entities; using Esfa.Recruit.Vacancies.Client.Infrastructure.Client; @@ -17,7 +18,7 @@ namespace Esfa.Recruit.Employer.Web.Orchestrators.Part2 { - public class QualificationsOrchestrator : EntityValidatingOrchestrator + public class QualificationsOrchestrator : VacancyValidatingOrchestrator { private readonly IEmployerVacancyClient _client; private readonly IRecruitVacancyClient _vacancyClient; @@ -118,10 +119,10 @@ public async Task PostQualificationEditModelForAddAsync(Va if (vacancy.Qualifications == null) vacancy.Qualifications = new List(); - var qualification = new Qualification(); + var qualification = new Qualification(); vacancy.Qualifications.Add(qualification); - return await UpdateVacancyWithQualificationAsync(vacancy, qualification, m, user); + return await UpdateVacancyWithQualificationAsync(vacancy, null, qualification, m, user); } public async Task PostQualificationEditModelForEditAsync(VacancyRouteModel vrm, QualificationEditModel m, VacancyUser user, int index) @@ -129,8 +130,15 @@ public async Task PostQualificationEditModelForEditAsync(V var vacancy = await Utility.GetAuthorisedVacancyForEditAsync(_client, _vacancyClient, vrm, RouteNames.Qualification_Edit_Post); var qualification = vacancy.Qualifications[index]; + var currentQualification = new Qualification + { + QualificationType = qualification.QualificationType, + Grade = qualification.Grade, + Subject = qualification.Subject, + Weighting = qualification.Weighting, + }; - return await UpdateVacancyWithQualificationAsync(vacancy, qualification, m, user); + return await UpdateVacancyWithQualificationAsync(vacancy, currentQualification, qualification, m, user); } public async Task DeleteQualificationAsync(VacancyRouteModel vrm, int index, VacancyUser user) @@ -140,7 +148,15 @@ public async Task DeleteQualificationAsync(VacancyRouteModel vrm, int index, Vac if (vacancy.Qualifications == null) return; - vacancy.Qualifications.RemoveAt(index); + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.Qualifications[index], + FieldIdResolver.ToFieldId(v => v.Qualifications), + vacancy, + (v) => + { + vacancy.Qualifications.RemoveAt(index); + return null; + }); await _vacancyClient.UpdateDraftVacancyAsync(vacancy, user); } @@ -199,15 +215,22 @@ private void SetQualificationViewModelFromEditModel(QualificationViewModel vm, Q vm.Weighting = m.Weighting; } - private async Task UpdateVacancyWithQualificationAsync(Vacancy vacancy, Qualification qualification, QualificationEditModel m, VacancyUser user) + private async Task UpdateVacancyWithQualificationAsync(Vacancy vacancy, Qualification currentQualification, Qualification qualification, QualificationEditModel m, VacancyUser user) { - qualification.QualificationType = m.QualificationType; - qualification.Grade = m.Grade; - qualification.Subject = m.Subject; - qualification.Weighting = m.Weighting; - - var allQualifications = await _vacancyClient.GetCandidateQualificationsAsync(); - vacancy.Qualifications = vacancy.Qualifications.SortQualifications(allQualifications).ToList(); + SetVacancyWithEmployerReviewFieldIndicators( + currentQualification, + FieldIdResolver.ToFieldId(v => v.Qualifications), + vacancy, + (v) => { + qualification.QualificationType = m.QualificationType; + qualification.Grade = m.Grade; + qualification.Subject = m.Subject; + qualification.Weighting = m.Weighting; + return qualification; + }); + + var qualificationTypes = await _vacancyClient.GetCandidateQualificationsAsync(); + vacancy.Qualifications = vacancy.Qualifications.SortQualifications(qualificationTypes).ToList(); return await ValidateAndExecute(vacancy, v => diff --git a/src/Employer/Employer.Web/Orchestrators/Part2/ShortDescriptionOrchestrator.cs b/src/Employer/Employer.Web/Orchestrators/Part2/ShortDescriptionOrchestrator.cs index af42e2698a..002c203c3c 100644 --- a/src/Employer/Employer.Web/Orchestrators/Part2/ShortDescriptionOrchestrator.cs +++ b/src/Employer/Employer.Web/Orchestrators/Part2/ShortDescriptionOrchestrator.cs @@ -5,6 +5,7 @@ using Esfa.Recruit.Employer.Web.ViewModels.Part2.ShortDescription; using Esfa.Recruit.Shared.Web.Orchestrators; using Esfa.Recruit.Shared.Web.Services; +using Esfa.Recruit.Vacancies.Client.Application.Services; using Esfa.Recruit.Vacancies.Client.Application.Validation; using Esfa.Recruit.Vacancies.Client.Domain.Entities; using Esfa.Recruit.Vacancies.Client.Infrastructure.Client; @@ -12,7 +13,7 @@ namespace Esfa.Recruit.Employer.Web.Orchestrators.Part2 { - public class ShortDescriptionOrchestrator : EntityValidatingOrchestrator + public class ShortDescriptionOrchestrator : VacancyValidatingOrchestrator { private const VacancyRuleSet ValidationRules = VacancyRuleSet.ShortDescription; private readonly IEmployerVacancyClient _client; @@ -59,7 +60,14 @@ public async Task PostShortDescriptionEditModelAsync(Short { var vacancy = await Utility.GetAuthorisedVacancyForEditAsync(_client, _vacancyClient, m, RouteNames.ShortDescription_Post); - vacancy.ShortDescription = m.ShortDescription; + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.ShortDescription, + FieldIdResolver.ToFieldId(v => v.ShortDescription), + vacancy, + (v) => + { + return v.ShortDescription = m.ShortDescription; + }); return await ValidateAndExecute( vacancy, diff --git a/src/Employer/Employer.Web/Orchestrators/Part2/SkillsOrchestrator.cs b/src/Employer/Employer.Web/Orchestrators/Part2/SkillsOrchestrator.cs index ee53793ae3..c7e44a01ce 100644 --- a/src/Employer/Employer.Web/Orchestrators/Part2/SkillsOrchestrator.cs +++ b/src/Employer/Employer.Web/Orchestrators/Part2/SkillsOrchestrator.cs @@ -8,7 +8,7 @@ using Esfa.Recruit.Shared.Web.Extensions; using Esfa.Recruit.Shared.Web.Orchestrators; using Esfa.Recruit.Shared.Web.Services; -using Esfa.Recruit.Shared.Web.ViewModels.Skills; +using Esfa.Recruit.Vacancies.Client.Application.Services; using Esfa.Recruit.Vacancies.Client.Application.Validation; using Esfa.Recruit.Vacancies.Client.Domain.Entities; using Esfa.Recruit.Vacancies.Client.Infrastructure.Client; @@ -16,7 +16,7 @@ namespace Esfa.Recruit.Employer.Web.Orchestrators.Part2 { - public class SkillsOrchestrator : EntityValidatingOrchestrator + public class SkillsOrchestrator : VacancyValidatingOrchestrator { private const VacancyRuleSet ValidationRules = VacancyRuleSet.Skills; private readonly IEmployerVacancyClient _client; @@ -79,9 +79,22 @@ public async Task PostSkillsEditModelAsync(VacancyRouteMod m.Skills = new List(); } - _skillsHelper.SetVacancyFromEditModel(vacancy, m); + var currentSkills = new List(); + if (vacancy.Skills != null) + currentSkills.AddRange(vacancy.Skills); - //if we are adding/removing a skill then just validate and don't persist + SetVacancyWithEmployerReviewFieldIndicators( + currentSkills, + FieldIdResolver.ToFieldId(v => v.Skills), + vacancy, + (v) => + { + _skillsHelper.SetVacancyFromEditModel(v, m); + return v.Skills; + }); + + // when adding a custom skill the vacancy is not saved immediately, the new custom skill is + // validated but all the updates are saved later in a single operation var validateOnly = m.IsAddingCustomSkill; return await ValidateAndExecute(vacancy, diff --git a/src/Employer/Employer.Web/Orchestrators/Part2/VacancyDescriptionOrchestrator.cs b/src/Employer/Employer.Web/Orchestrators/Part2/VacancyDescriptionOrchestrator.cs index cc485af2d4..74e62b4035 100644 --- a/src/Employer/Employer.Web/Orchestrators/Part2/VacancyDescriptionOrchestrator.cs +++ b/src/Employer/Employer.Web/Orchestrators/Part2/VacancyDescriptionOrchestrator.cs @@ -5,6 +5,7 @@ using Esfa.Recruit.Employer.Web.ViewModels.Part2.VacancyDescription; using Esfa.Recruit.Shared.Web.Orchestrators; using Esfa.Recruit.Shared.Web.Services; +using Esfa.Recruit.Vacancies.Client.Application.Services; using Esfa.Recruit.Vacancies.Client.Application.Validation; using Esfa.Recruit.Vacancies.Client.Domain.Entities; using Esfa.Recruit.Vacancies.Client.Infrastructure.Client; @@ -12,9 +13,9 @@ namespace Esfa.Recruit.Employer.Web.Orchestrators.Part2 { - public class VacancyDescriptionOrchestrator : EntityValidatingOrchestrator + public class VacancyDescriptionOrchestrator : VacancyValidatingOrchestrator { - private const VacancyRuleSet ValdationRules = VacancyRuleSet.Description | VacancyRuleSet.TrainingDescription | VacancyRuleSet.OutcomeDescription; + private const VacancyRuleSet ValidationRules = VacancyRuleSet.Description | VacancyRuleSet.TrainingDescription | VacancyRuleSet.OutcomeDescription; private readonly IEmployerVacancyClient _client; private readonly IRecruitVacancyClient _vacancyClient; private readonly IReviewSummaryService _reviewSummaryService; @@ -65,14 +66,28 @@ public async Task GetVacancyDescriptionViewModelAsy public async Task PostVacancyDescriptionEditModelAsync(VacancyDescriptionEditModel m, VacancyUser user) { var vacancy = await Utility.GetAuthorisedVacancyForEditAsync(_client, _vacancyClient, m, RouteNames.VacancyDescription_Index_Post); - - vacancy.Description = m.VacancyDescription; - vacancy.TrainingDescription = m.TrainingDescription; - vacancy.OutcomeDescription = m.OutcomeDescription; - + + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.Description, + FieldIdResolver.ToFieldId(v => v.Description), + vacancy, + (v) => { return v.Description = m.VacancyDescription; }); + + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.TrainingDescription, + FieldIdResolver.ToFieldId(v => v.TrainingDescription), + vacancy, + (v) => { return v.TrainingDescription = m.TrainingDescription; }); + + SetVacancyWithEmployerReviewFieldIndicators( + vacancy.OutcomeDescription, + FieldIdResolver.ToFieldId(v => v.OutcomeDescription), + vacancy, + (v) => { return v.OutcomeDescription = m.OutcomeDescription; }); + return await ValidateAndExecute( vacancy, - v => _vacancyClient.Validate(v, ValdationRules), + v => _vacancyClient.Validate(v, ValidationRules), v => _vacancyClient.UpdateDraftVacancyAsync(vacancy, user) ); } diff --git a/src/Employer/Employer.Web/Orchestrators/VacancyPreviewOrchestrator.cs b/src/Employer/Employer.Web/Orchestrators/VacancyPreviewOrchestrator.cs index 0be15ac525..70eb29ec40 100644 --- a/src/Employer/Employer.Web/Orchestrators/VacancyPreviewOrchestrator.cs +++ b/src/Employer/Employer.Web/Orchestrators/VacancyPreviewOrchestrator.cs @@ -68,7 +68,8 @@ public async Task GetVacancyPreviewViewModelAsync(Vacan var vm = new VacancyPreviewViewModel(); await _vacancyDisplayMapper.MapFromVacancyAsync(vm, vacancy); - + + vm.RejectedReason = vacancy.EmployerRejectedReason; vm.HasProgramme = vacancy.ProgrammeId != null; vm.HasWage = vacancy.Wage != null; vm.CanShowReference = vacancy.Status != VacancyStatus.Draft; @@ -144,7 +145,25 @@ private async Task RejectActionAsync(Vacancy vacancy, Vac await _messaging.SendCommandAsync(command); - return new RejectVacancyResponse { IsRejected = true }; + return new RejectVacancyResponse { IsRejected = true }; + } + + public async Task ClearRejectedVacancyReason(SubmitReviewModel m, VacancyUser user) + { + var vacancy = await Utility.GetAuthorisedVacancyAsync(_vacancyClient, m, RouteNames.ApproveJobAdvert_Post); + + vacancy.EmployerRejectedReason = null; + + await _vacancyClient.UpdateDraftVacancyAsync(vacancy, user); + } + + public async Task UpdateRejectedVacancyReason(SubmitReviewModel m, VacancyUser user) + { + var vacancy = await Utility.GetAuthorisedVacancyAsync(_vacancyClient, m, RouteNames.ApproveJobAdvert_Post); + + vacancy.EmployerRejectedReason = m.RejectedReason; + + await _vacancyClient.UpdateDraftVacancyAsync(vacancy, user); } public async Task> ApproveJobAdvertAsync(ApproveJobAdvertViewModel m, VacancyUser user) @@ -205,7 +224,8 @@ public async Task GetVacancyRejectJobAdvertAsync(Vacan var vacancy = await _vacancyClient.GetVacancyAsync(vrm.VacancyId); var vm = new RejectJobAdvertViewModel - { + { + RejectionReason = vacancy.EmployerRejectedReason, TrainingProviderName = vacancy.TrainingProvider.Name }; diff --git a/src/Employer/Employer.Web/Orchestrators/VacancyValidatingOrchestrator.cs b/src/Employer/Employer.Web/Orchestrators/VacancyValidatingOrchestrator.cs new file mode 100644 index 0000000000..2beca6b849 --- /dev/null +++ b/src/Employer/Employer.Web/Orchestrators/VacancyValidatingOrchestrator.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Esfa.Recruit.Employer.Web.Mappings; +using Esfa.Recruit.Shared.Web.Orchestrators; +using Esfa.Recruit.Vacancies.Client.Domain.Entities; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; + +namespace Esfa.Recruit.Employer.Web.Orchestrators +{ + public abstract class VacancyValidatingOrchestrator : EntityValidatingOrchestrator + { + protected VacancyValidatingOrchestrator(ILogger logger) + : base(logger) + { + } + + protected void SetVacancyWithEmployerReviewFieldIndicators(T currentValue, + string fieldId, + Vacancy vacancy, + Func setFunc + ) + { + PopulateEmployerReviewFieldIndicators(vacancy); + + var newValue = setFunc(vacancy); + if (!JsonConvert.SerializeObject(currentValue).Equals(JsonConvert.SerializeObject(newValue))) + { + foreach (var fieldIdentifier in ReviewFieldMappingLookups.GetPreviewReviewFieldIndicators().VacancyPropertyMappingsLookup[fieldId]) + { + var changedIndicator = vacancy.EmployerReviewFieldIndicators.Where(p => p.FieldIdentifier == fieldIdentifier).FirstOrDefault(); + if (changedIndicator != null) + { + changedIndicator.IsChangeRequested = true; + } + } + } + } + + private void PopulateEmployerReviewFieldIndicators(Vacancy vacancy) + { + var employerReviewFieldIndicators = vacancy.EmployerReviewFieldIndicators ?? new List(); + + // add field indicators which are missing + var missingIndicators = + ReviewFieldMappingLookups + .GetPreviewReviewFieldIndicators().FieldIdentifiersForPage + .Select(p => new EmployerReviewFieldIndicator { FieldIdentifier = p.ReviewFieldIdentifier, IsChangeRequested = false }) + .Where(p => !employerReviewFieldIndicators.Any(v => v.FieldIdentifier == p.FieldIdentifier)); + + if (missingIndicators.Any()) + { + employerReviewFieldIndicators + .AddRange(missingIndicators); + + vacancy.EmployerReviewFieldIndicators = employerReviewFieldIndicators + .OrderBy(p => p.FieldIdentifier).ToList(); + } + } + } +} diff --git a/src/Employer/Employer.Web/ViewModels/VacancyPreview/RejectJobAdvertViewModel.cs b/src/Employer/Employer.Web/ViewModels/VacancyPreview/RejectJobAdvertViewModel.cs index c304694052..ce0600b970 100644 --- a/src/Employer/Employer.Web/ViewModels/VacancyPreview/RejectJobAdvertViewModel.cs +++ b/src/Employer/Employer.Web/ViewModels/VacancyPreview/RejectJobAdvertViewModel.cs @@ -7,6 +7,7 @@ public class RejectJobAdvertViewModel : VacancyRouteModel { [Required(ErrorMessage = "Select if you want to reject this job advert")] public bool? RejectJobAdvert { get; set; } + public string RejectionReason { get; set; } public string TrainingProviderName { get; set; } } diff --git a/src/Employer/Employer.Web/ViewModels/VacancyPreview/SubmitReviewModel.cs b/src/Employer/Employer.Web/ViewModels/VacancyPreview/SubmitReviewModel.cs index 48fe7ed7d2..af127ad960 100644 --- a/src/Employer/Employer.Web/ViewModels/VacancyPreview/SubmitReviewModel.cs +++ b/src/Employer/Employer.Web/ViewModels/VacancyPreview/SubmitReviewModel.cs @@ -1,11 +1,33 @@ -using System.ComponentModel.DataAnnotations; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using Esfa.Recruit.Employer.Web.RouteModel; namespace Esfa.Recruit.Employer.Web.ViewModels.Preview { - public class SubmitReviewModel : VacancyRouteModel + public class SubmitReviewModel : VacancyRouteModel, IValidatableObject { - [Required(ErrorMessage = "You need to submit or reject the advert")] + private const int RejectedReasonMaxCharacters = 200; + + [Required(ErrorMessage = "Confirm if you want to submit or reject this advert ")] public bool? SubmitToEsfa { get; set; } + + public string RejectedReason { get; set; } + + public IEnumerable Validate(ValidationContext validationContext) + { + var property = new[] { nameof(RejectedReason) }; + + if(SubmitToEsfa.HasValue && SubmitToEsfa.Value == false) + { + if (string.IsNullOrEmpty(RejectedReason)) + { + yield return new ValidationResult("Enter details of why you are rejecting this advert", property); + } + else if (RejectedReason.Length > RejectedReasonMaxCharacters) + { + yield return new ValidationResult($"You have {RejectedReason.Length- RejectedReasonMaxCharacters} character too many", property); + } + } + } } } diff --git a/src/Employer/Employer.Web/ViewModels/VacancyPreview/VacancyPreviewViewModel.cs b/src/Employer/Employer.Web/ViewModels/VacancyPreview/VacancyPreviewViewModel.cs index 1904afcd82..52a21e2dd8 100644 --- a/src/Employer/Employer.Web/ViewModels/VacancyPreview/VacancyPreviewViewModel.cs +++ b/src/Employer/Employer.Web/ViewModels/VacancyPreview/VacancyPreviewViewModel.cs @@ -66,6 +66,9 @@ public class VacancyPreviewViewModel : DisplayVacancyViewModel public bool HasSoftValidationErrors => SoftValidationErrors?.HasErrors == true; + public string RejectedReason { get; set; } + public bool? SubmitToEsfa { get; set; } + public bool ShowIncompleteSections => ((HasIncompleteMandatorySections || HasIncompleteOptionalSections) && !Review.HasBeenReviewed) || HasSoftValidationErrors; public ReviewSummaryViewModel Review { get; set; } = new ReviewSummaryViewModel(); public string SubmitButtonText => Review.HasBeenReviewed ? "Resubmit advert" : "Submit advert"; diff --git a/src/Employer/Employer.Web/Views/VacancyPreview/ConfirmationJobAdvert.cshtml b/src/Employer/Employer.Web/Views/VacancyPreview/ConfirmationJobAdvert.cshtml index b9ad6cb4a6..0b71b57030 100644 --- a/src/Employer/Employer.Web/Views/VacancyPreview/ConfirmationJobAdvert.cshtml +++ b/src/Employer/Employer.Web/Views/VacancyPreview/ConfirmationJobAdvert.cshtml @@ -29,7 +29,7 @@

When we approve your advert, it will appear on the Find an apprenticeship service (opens in a new tab or window).

-

This job advert has gone back to @Model.TrainingProviderName. You may want to tell them why you've rejected it.

+

This job advert has gone back to @Model.TrainingProviderName.

diff --git a/src/Employer/Employer.Web/Views/VacancyPreview/RejectJobAdvert.cshtml b/src/Employer/Employer.Web/Views/VacancyPreview/RejectJobAdvert.cshtml index b7ff56176d..e4289d2125 100644 --- a/src/Employer/Employer.Web/Views/VacancyPreview/RejectJobAdvert.cshtml +++ b/src/Employer/Employer.Web/Views/VacancyPreview/RejectJobAdvert.cshtml @@ -3,18 +3,22 @@ @{ ViewBag.Vpv = "/recruitment/employer/reject-job-advert"; ViewBag.Title = "Are you sure you want to reject this job advert?"; } -Back +Back

- Are you sure you want to reject this job advert? -

-
-

This job advert will go back to @Model.TrainingProviderName. You may want to tell them why you're rejecting it.

-
+ Are you sure you want to reject this job advert? + +
+

This job advert will go back to @Model.TrainingProviderName.

+
+

Rejection reason

+

@Model.RejectionReason

+
+
@@ -33,6 +37,6 @@ ViewBag.Title = "Are you sure you want to reject this job advert?"; }
- +
\ No newline at end of file diff --git a/src/Employer/Employer.Web/Views/VacancyPreview/VacancyPreview.cshtml b/src/Employer/Employer.Web/Views/VacancyPreview/VacancyPreview.cshtml index 3b781599c7..fcee3a2063 100644 --- a/src/Employer/Employer.Web/Views/VacancyPreview/VacancyPreview.cshtml +++ b/src/Employer/Employer.Web/Views/VacancyPreview/VacancyPreview.cshtml @@ -507,19 +507,29 @@ Error: @Html.ValidationMessage("SubmitToEsfa") -
+
- +
- +
+
+ +
+ + + You have + 200 + characters remaining +
+
diff --git a/src/Employer/Employer.Web/wwwroot/css/application.css b/src/Employer/Employer.Web/wwwroot/css/application.css index 87ad83fa0e..a205e1b138 100644 --- a/src/Employer/Employer.Web/wwwroot/css/application.css +++ b/src/Employer/Employer.Web/wwwroot/css/application.css @@ -1417,4 +1417,144 @@ a[rel="external"]:hover:after { /*** govuk confirmation panel ***/ .govuk-panel--confirmation { background: #00703c; +} + +/* govuk notification banner */ + +.govuk-notification-banner { + font-family: "nta", Arial, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-weight: 400; + font-size: 16px; + line-height: 1.25; + margin-bottom: 30px; + border: 5px solid #1d70b8; + background-color: #1d70b8; +} + +@media (min-width: 48.0625em) { + .govuk-notification-banner { + margin-bottom: 50px; + } +} + +.govuk-notification-banner__header { + padding: 2px 15px 5px; + border-bottom: 1px solid transparent; +} + +@media (min-width: 48.0625em) { + .govuk-notification-banner__header { + padding: 2px 20px 5px; + } +} + +.govuk-notification-banner__title { + font-family: "nta", Arial, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-weight: 700; + font-size: 16px; + line-height: 1.25; + margin: 0; + padding: 0; + color: #fff; +} + +@media (min-width: 48.0625em) { + .govuk-notification-banner__title { + font-size: 19px; + line-height: 1.35; + } +} + +.govuk-notification-banner__content { + color: #0b0c0c; + padding: 15px; + background-color: #fff; +} + +@media (min-width: 48.0625em) { + .govuk-notification-banner__content { + padding: 20px; + } +} + +.govuk-notification-banner__content > * { + max-width: 605px; +} + +.govuk-notification-banner__content>:last-child { + margin-bottom: 0; +} + +.govuk-notification-banner__heading { + font-family: "nta", Arial, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-weight: 700; + font-size: 18px; + line-height: 1.1; + margin: 0 0 15px 0; + padding: 0; +} + +@media (min-width: 48.0625em) { + .govuk-notification-banner__heading { + font-size: 24px; + line-height: 1.25; + } +} + +.govuk-notification-banner__link { + font-family: "nta", Arial, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-decoration: underline; + color: #1d70b8; +} + +.govuk-notification-banner__link:link { + color: #1d70b8; +} + +.govuk-notification-banner__link:hover { + color: #003078; +} + +.govuk-notification-banner__link:focus { + outline: 3px solid transparent; + color: #0b0c0c; + background-color: #fd0; + -webkit-box-shadow: 0 -2px #fd0, 0 4px #0b0c0c; + box-shadow: 0 -2px #fd0, 0 4px #0b0c0c; + text-decoration: none; +} + +/* Additional govuk tag colours */ + +.govuk-tag--purple { + color: #3d2375; + background: #dbd5e9; +} + +.govuk-tag--red { + color: #942514; + background: #f6d7d2; +} + +.govuk-tag--blue { + color: #144e81; + background: #d2e2f1; +} + +.govuk-tag--green { + color: #005a30; + background: #cce2d8; +} + +.govuk-tag--orange { + color: #6e3619; + background: #fcd6c3; } \ No newline at end of file diff --git a/src/Employer/UnitTests/Employer.Web/HardMocks/VacancyOrchestratorTestData.cs b/src/Employer/UnitTests/Employer.Web/HardMocks/VacancyOrchestratorTestData.cs index 227ad7ac94..0f41e6c359 100644 --- a/src/Employer/UnitTests/Employer.Web/HardMocks/VacancyOrchestratorTestData.cs +++ b/src/Employer/UnitTests/Employer.Web/HardMocks/VacancyOrchestratorTestData.cs @@ -1,15 +1,22 @@ -using Esfa.Recruit.Vacancies.Client.Domain.Entities; -using System; +using System; +using System.Collections.Generic; +using Esfa.Recruit.Vacancies.Client.Domain.Entities; +using Esfa.Recruit.Vacancies.Client.Infrastructure.QueryStore.Projections.EditVacancyInfo; +using Address = Esfa.Recruit.Vacancies.Client.Domain.Entities.Address; namespace Esfa.Recruit.Employer.UnitTests.Employer.Web.HardMocks { internal class VacancyOrchestratorTestData { + public const string AccountLegalEntityPublicHashedId123 = "ABC123"; + public const string AccountLegalEntityPublicHashedId456 = "ABC456"; + internal static Vacancy GetPart1CompleteVacancy() { return new Vacancy { EmployerAccountId = "EMPLOYER ACCOUNT ID", + AccountLegalEntityPublicHashedId = AccountLegalEntityPublicHashedId123, Id = Guid.Parse("84af954e-5baf-4942-897d-d00180a0839e"), Title = "has a value", NumberOfPositions = 1, @@ -17,7 +24,7 @@ internal static Vacancy GetPart1CompleteVacancy() ShortDescription = "has a value", ProgrammeId = "has a value", Wage = new Wage {Duration = 1, WageType = WageType.FixedWage }, - LegalEntityName = "legal name", + LegalEntityName = "LEGAL ENTITY NAME 123", EmployerNameOption = EmployerNameOption.RegisteredName, StartDate = DateTime.Now }; @@ -32,5 +39,50 @@ internal static VacancyUser GetVacancyUser() UserId = "scott" }; } + + internal static EmployerProfile GetEmployerProfile(string accountLegalEntityPublicHashedId123) + { + return new EmployerProfile + { + EmployerAccountId = "EMPLOYER ACCOUNT ID", + AccountLegalEntityPublicHashedId = accountLegalEntityPublicHashedId123 + }; + } + + internal static EmployerEditVacancyInfo GetEmployerEditVacancyInfo() + { + return new EmployerEditVacancyInfo + { + LegalEntities = new List + { + new LegalEntity + { + Name = "LEGAL ENTITY NAME 123", + AccountLegalEntityPublicHashedId = AccountLegalEntityPublicHashedId123, + Address = new Vacancies.Client.Infrastructure.QueryStore.Projections.EditVacancyInfo.Address + { + AddressLine1 = "this is a value", + AddressLine2 = "this is a value", + AddressLine3 = "this is a value", + AddressLine4 = "this is a value", + Postcode = "this is a value" + } + }, + new LegalEntity + { + Name = "LEGAL ENTITY NAME 456", + AccountLegalEntityPublicHashedId = AccountLegalEntityPublicHashedId456, + Address = new Vacancies.Client.Infrastructure.QueryStore.Projections.EditVacancyInfo.Address + { + AddressLine1 = "this is a value", + AddressLine2 = "this is a value", + AddressLine3 = "this is a value", + AddressLine4 = "this is a value", + Postcode = "this is a value" + } + } + } + }; + } } } diff --git a/src/Employer/UnitTests/Employer.Web/Orchestrators/Part1/DatesOrchestratorTests.cs b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part1/DatesOrchestratorTests.cs new file mode 100644 index 0000000000..39e9f7398a --- /dev/null +++ b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part1/DatesOrchestratorTests.cs @@ -0,0 +1,141 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using Esfa.Recruit.Employer.UnitTests.Employer.Web.HardMocks; +using Esfa.Recruit.Employer.Web.Orchestrators.Part1; +using Esfa.Recruit.Employer.Web.ViewModels.Part1.Dates; +using Esfa.Recruit.Shared.Web.Extensions; +using Esfa.Recruit.Shared.Web.Mappers; +using Esfa.Recruit.Shared.Web.Services; +using Esfa.Recruit.Vacancies.Client.Application.Providers; +using Esfa.Recruit.Vacancies.Client.Application.Validation; +using Esfa.Recruit.Vacancies.Client.Domain.Entities; +using Esfa.Recruit.Vacancies.Client.Infrastructure.Client; +using FluentAssertions; +using Microsoft.Extensions.Logging; +using Moq; +using Xunit; + +namespace Esfa.Recruit.Employer.UnitTests.Employer.Web.Orchestrators.Part1 +{ + public class DatesOrchestratorTests + { + private DatesOrchestratorTestsFixture _fixture; + + public DatesOrchestratorTests() + { + _fixture = new DatesOrchestratorTestsFixture(); + } + + [Theory] + [InlineData("31/12/2021", "01/01/2001", false, new string[] { FieldIdentifiers.ClosingDate }, new string[] { FieldIdentifiers.PossibleStartDate, FieldIdentifiers.DisabilityConfident})] + [InlineData("01/01/2001", "31/12/2021", false, new string[] { FieldIdentifiers.PossibleStartDate }, new string[] { FieldIdentifiers.ClosingDate, FieldIdentifiers.DisabilityConfident })] + [InlineData("01/01/2001", "01/01/2001", true, new string[] { FieldIdentifiers.DisabilityConfident }, new string[] { FieldIdentifiers.ClosingDate, FieldIdentifiers.PossibleStartDate })] + [InlineData("01/01/2001", "01/01/2001", false, new string[] { }, new string[] { FieldIdentifiers.ClosingDate, FieldIdentifiers.PossibleStartDate, FieldIdentifiers.DisabilityConfident })] + [InlineData("31/12/2021", "31/12/2021", true, new string[] { FieldIdentifiers.ClosingDate, FieldIdentifiers.PossibleStartDate, FieldIdentifiers.DisabilityConfident }, new string[] { })] + public async Task WhenUpdated_ShouldFlagFieldIndicators(string closingDate, string startDate, bool isDisablityConfident, string[] setFieldIdentifers, string [] unsetFieldIdentifiers) + { + _fixture + .WithClosingDate("01/01/2001") + .WithStartDate("01/01/2001") + .WithDisabilityConfident(false) + .Setup(); + + var closingDateTime = DateTime.ParseExact(closingDate, "dd/MM/yyyy", CultureInfo.InvariantCulture); + var startDateTime = DateTime.ParseExact(startDate, "dd/MM/yyyy", CultureInfo.InvariantCulture); + + var datesEditModel = new DatesEditModel + { + EmployerAccountId = _fixture.Vacancy.EmployerAccountId, + VacancyId = _fixture.Vacancy.Id, + ClosingDay = closingDateTime.Day.ToString(), + ClosingMonth = closingDateTime.Month.ToString(), + ClosingYear = closingDateTime.Year.ToString(), + StartDay = startDateTime.Day.ToString(), + StartMonth = startDateTime.Month.ToString(), + StartYear = startDateTime.Year.ToString(), + IsDisabilityConfident = isDisablityConfident + }; + + await _fixture.PostDatesEditModelAsync(datesEditModel); + + _fixture.VerifyEmployerReviewFieldIndicators(setFieldIdentifers, unsetFieldIdentifiers); + } + + public class DatesOrchestratorTestsFixture + { + private const VacancyRuleSet ValidationRules = VacancyRuleSet.ClosingDate | VacancyRuleSet.StartDate | VacancyRuleSet.StartDateEndDate | VacancyRuleSet.TrainingExpiryDate; + public VacancyUser User { get; } + public Vacancy Vacancy { get; } + public DatesOrchestrator Sut {get; private set;} + + public DatesOrchestratorTestsFixture() + { + MockClient = new Mock(); + MockRecruitVacancyClient = new Mock(); + + User = VacancyOrchestratorTestData.GetVacancyUser(); + Vacancy = VacancyOrchestratorTestData.GetPart1CompleteVacancy(); + } + + public DatesOrchestratorTestsFixture WithClosingDate(string closingDate) + { + Vacancy.ClosingDate = closingDate.AsDateTimeUk()?.ToUniversalTime(); + return this; + } + + public DatesOrchestratorTestsFixture WithStartDate(string startDate) + { + Vacancy.StartDate = startDate.AsDateTimeUk()?.ToUniversalTime(); + return this; + } + + public DatesOrchestratorTestsFixture WithDisabilityConfident(bool isDisablityConfident) + { + Vacancy.DisabilityConfident = isDisablityConfident ? DisabilityConfident.Yes : DisabilityConfident.No; + return this; + } + + public void Setup() + { + MockRecruitVacancyClient.Setup(x => x.GetVacancyAsync(Vacancy.Id)).ReturnsAsync(Vacancy); + MockRecruitVacancyClient.Setup(x => x.Validate(Vacancy, ValidationRules)).Returns(new EntityValidationResult()); + MockRecruitVacancyClient.Setup(x => x.UpdateDraftVacancyAsync(It.IsAny(), User)); + MockRecruitVacancyClient.Setup(x => x.UpdateEmployerProfileAsync(It.IsAny(), User)); + + Sut = new DatesOrchestrator(MockClient.Object, MockRecruitVacancyClient.Object, Mock.Of>(), + Mock.Of(),Mock.Of(), Mock.Of()); + } + + public async Task PostDatesEditModelAsync(DatesEditModel model) + { + await Sut.PostDatesEditModelAsync(model, User); + } + + public void VerifyEmployerReviewFieldIndicators(string[] setFieldIdentifiers, string[] unsetFieldIdentifiers) + { + foreach (var fieldIdentifier in setFieldIdentifiers) + { + VerifyEmployerReviewFieldIndicators(fieldIdentifier, true); + } + + foreach (var fieldIdentifier in unsetFieldIdentifiers) + { + VerifyEmployerReviewFieldIndicators(fieldIdentifier, false); + } + } + + public void VerifyEmployerReviewFieldIndicators(string fieldIdentifier, bool value) + { + Vacancy.EmployerReviewFieldIndicators + .Where(p => p.FieldIdentifier == fieldIdentifier).Single() + .Should().NotBeNull().And + .Match((x) => x.IsChangeRequested == value); + } + + public Mock MockClient { get; set; } + public Mock MockRecruitVacancyClient { get; set; } + } + } +} diff --git a/src/Employer/UnitTests/Employer.Web/Orchestrators/Part1/DurationOrchestratorTests.cs b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part1/DurationOrchestratorTests.cs new file mode 100644 index 0000000000..75bb42e784 --- /dev/null +++ b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part1/DurationOrchestratorTests.cs @@ -0,0 +1,143 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Esfa.Recruit.Employer.UnitTests.Employer.Web.HardMocks; +using Esfa.Recruit.Employer.Web.Orchestrators.Part1; +using Esfa.Recruit.Employer.Web.ViewModels.Part1.Dates; +using Esfa.Recruit.Employer.Web.ViewModels.Part1.Duration; +using Esfa.Recruit.Shared.Web.Extensions; +using Esfa.Recruit.Shared.Web.Mappers; +using Esfa.Recruit.Shared.Web.Services; +using Esfa.Recruit.Vacancies.Client.Application.Providers; +using Esfa.Recruit.Vacancies.Client.Application.Validation; +using Esfa.Recruit.Vacancies.Client.Domain.Entities; +using Esfa.Recruit.Vacancies.Client.Infrastructure.Client; +using FluentAssertions; +using Microsoft.Extensions.Logging; +using Moq; +using Xunit; + +namespace Esfa.Recruit.Employer.UnitTests.Employer.Web.Orchestrators.Part1 +{ + public class DurationOrchestratorTests + { + private DurationOrchestratorTestsFixture _fixture; + + public DurationOrchestratorTests() + { + _fixture = new DurationOrchestratorTestsFixture(); + } + + [Theory] + [InlineData(24, DurationUnit.Month, 37.0, "this is a value", new string[] { }, new string[] { FieldIdentifiers.ExpectedDuration, FieldIdentifiers.WorkingWeek })] + [InlineData(12, DurationUnit.Month, 37.0, "this is a value", new string[] { FieldIdentifiers.ExpectedDuration }, new string[] { FieldIdentifiers.WorkingWeek })] + [InlineData(24, DurationUnit.Year, 37.0, "this is a value", new string[] { FieldIdentifiers.ExpectedDuration }, new string[] { FieldIdentifiers.WorkingWeek })] + [InlineData(24, DurationUnit.Month, 35.0, "this is a value", new string[] { FieldIdentifiers.WorkingWeek }, new string[] { FieldIdentifiers.ExpectedDuration })] + [InlineData(24, DurationUnit.Month, 37.0, "this is a new value", new string[] { FieldIdentifiers.WorkingWeek }, new string[] { FieldIdentifiers.ExpectedDuration })] + [InlineData(1, DurationUnit.Year, 35.0, "this is a new value", new string[] { FieldIdentifiers.ExpectedDuration, FieldIdentifiers.WorkingWeek }, new string[] { })] + public async Task WhenUpdated_ShouldFlagFieldIndicators(int duration, DurationUnit durationUnit, decimal weeklyHours, string workingWeekDescription, string[] setFieldIdentifers, string [] unsetFieldIdentifiers) + { + _fixture + .WithDuration(24) + .WithDurationUnit(DurationUnit.Month) + .WithWeeklyHours(37) + .WithWorkingWeekDescription("this is a value") + .Setup(); + + var durationEditModel = new DurationEditModel + { + EmployerAccountId = _fixture.Vacancy.EmployerAccountId, + VacancyId = _fixture.Vacancy.Id, + Duration = duration.ToString(), + DurationUnit = durationUnit, + WeeklyHours = weeklyHours.ToString(), + WorkingWeekDescription = workingWeekDescription + }; + + await _fixture.PostDurationEditModelAsync(durationEditModel); + + _fixture.VerifyEmployerReviewFieldIndicators(setFieldIdentifers, unsetFieldIdentifiers); + } + + public class DurationOrchestratorTestsFixture + { + private const VacancyRuleSet ValidationRules = VacancyRuleSet.Duration | VacancyRuleSet.WorkingWeekDescription | VacancyRuleSet.WeeklyHours; + public VacancyUser User { get; } + public Vacancy Vacancy { get; } + public DurationOrchestrator Sut {get; private set;} + + public DurationOrchestratorTestsFixture() + { + MockClient = new Mock(); + MockRecruitVacancyClient = new Mock(); + + User = VacancyOrchestratorTestData.GetVacancyUser(); + Vacancy = VacancyOrchestratorTestData.GetPart1CompleteVacancy(); + } + + public DurationOrchestratorTestsFixture WithDuration(int duration) + { + Vacancy.Wage.Duration = duration; + return this; + } + + public DurationOrchestratorTestsFixture WithDurationUnit(DurationUnit durationUnit) + { + Vacancy.Wage.DurationUnit = durationUnit; + return this; + } + + public DurationOrchestratorTestsFixture WithWeeklyHours(decimal weeklyHours) + { + Vacancy.Wage.WeeklyHours = weeklyHours; + return this; + } + + public DurationOrchestratorTestsFixture WithWorkingWeekDescription(string workingWeekDescription) + { + Vacancy.Wage.WorkingWeekDescription = workingWeekDescription; + return this; + } + + public void Setup() + { + MockRecruitVacancyClient.Setup(x => x.GetVacancyAsync(Vacancy.Id)).ReturnsAsync(Vacancy); + MockRecruitVacancyClient.Setup(x => x.Validate(Vacancy, ValidationRules)).Returns(new EntityValidationResult()); + MockRecruitVacancyClient.Setup(x => x.UpdateDraftVacancyAsync(It.IsAny(), User)); + MockRecruitVacancyClient.Setup(x => x.UpdateEmployerProfileAsync(It.IsAny(), User)); + + Sut = new DurationOrchestrator(MockClient.Object, MockRecruitVacancyClient.Object, Mock.Of>(), + Mock.Of()); + } + + public async Task PostDurationEditModelAsync(DurationEditModel model) + { + await Sut.PostDurationEditModelAsync(model, User); + } + + public void VerifyEmployerReviewFieldIndicators(string[] setFieldIdentifiers, string[] unsetFieldIdentifiers) + { + foreach (var fieldIdentifier in setFieldIdentifiers) + { + VerifyEmployerReviewFieldIndicators(fieldIdentifier, true); + } + + foreach (var fieldIdentifier in unsetFieldIdentifiers) + { + VerifyEmployerReviewFieldIndicators(fieldIdentifier, false); + } + } + + public void VerifyEmployerReviewFieldIndicators(string fieldIdentifier, bool value) + { + Vacancy.EmployerReviewFieldIndicators + .Where(p => p.FieldIdentifier == fieldIdentifier).Single() + .Should().NotBeNull().And + .Match((x) => x.IsChangeRequested == value); + } + + public Mock MockClient { get; set; } + public Mock MockRecruitVacancyClient { get; set; } + } + } +} diff --git a/src/Employer/UnitTests/Employer.Web/Orchestrators/Part1/LocationOrchestratorTests.cs b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part1/LocationOrchestratorTests.cs new file mode 100644 index 0000000000..97ee8b8d5d --- /dev/null +++ b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part1/LocationOrchestratorTests.cs @@ -0,0 +1,241 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Esfa.Recruit.Employer.UnitTests.Employer.Web.HardMocks; +using Esfa.Recruit.Employer.Web.Models; +using Esfa.Recruit.Employer.Web.Orchestrators.Part1; +using Esfa.Recruit.Employer.Web.ViewModels.Part1.Duration; +using Esfa.Recruit.Employer.Web.ViewModels.Part1.Location; +using Esfa.Recruit.Employer.Web.ViewModels.Part1.Wage; +using Esfa.Recruit.Shared.Web.Mappers; +using Esfa.Recruit.Shared.Web.Models; +using Esfa.Recruit.Shared.Web.Services; +using Esfa.Recruit.Vacancies.Client.Application.Providers; +using Esfa.Recruit.Vacancies.Client.Application.Validation; +using Esfa.Recruit.Vacancies.Client.Domain.Entities; +using Esfa.Recruit.Vacancies.Client.Infrastructure.Client; +using Esfa.Recruit.Vacancies.Client.Infrastructure.QueryStore.Projections.EditVacancyInfo; +using FluentAssertions; +using Microsoft.Extensions.Logging; +using Moq; +using Xunit; +using Address = Esfa.Recruit.Vacancies.Client.Domain.Entities.Address; + +namespace Esfa.Recruit.Employer.UnitTests.Employer.Web.Orchestrators.Part1 +{ + public class LocationOrchestratorTests + { + private LocationOrchestratorTestsFixture _fixture; + + public LocationOrchestratorTests() + { + _fixture = new LocationOrchestratorTestsFixture(); + } + + [Theory] + [InlineData("this is a value", "this is a value", "this is a value", "this is a value", "this is a value", false)] + [InlineData("this is a new value", "this is a value", "this is a value", "this is a value", "this is a value", true)] + [InlineData("this is a value", "this is a new value", "this is a value", "this is a value", "this is a value", true)] + [InlineData("this is a value", "this is a value", "this is a new value", "this is a value", "this is a value", true)] + [InlineData("this is a value", "this is a value", "this is a value", "this is a new value", "this is a value", true)] + [InlineData("this is a value", "this is a value", "this is a value", "this is a value", "this is a new value", true)] + [InlineData("this is a new value", "this is a new value", "this is a new value", "this is a new value", "this is a new value", true)] + public async Task WhenAddressUpdated_ShouldFlagFieldIndicators(string addressLine1, string addressLine2, string addressLine3, string addressLine4, string postcode, bool fieldIndicatorSet) + { + _fixture + .WithAddressLine1("this is a value") + .WithAddressLine2("this is a value") + .WithAddresLine3("this is a value") + .WithAddresLine4("this is a value") + .WithPostcode("this is a value") + .Setup(); + + var locationEditModel = new LocationEditModel + { + EmployerAccountId = _fixture.Vacancy.EmployerAccountId, + VacancyId = _fixture.Vacancy.Id, + AddressLine1 = addressLine1, + AddressLine2 = addressLine2, + AddressLine3 = addressLine3, + AddressLine4 = addressLine4, + Postcode = postcode, + SelectedLocation = LocationViewModel.UseOtherLocationConst + }; + + await _fixture.PostLocationEditModelAsync(locationEditModel); + + _fixture.VerifyEmployerReviewFieldIndicators(FieldIdentifiers.EmployerAddress, fieldIndicatorSet); + } + + [Fact] + public async Task WhenEmployerLegalEntityUpdated_ShouldFlagEmployerNameFieldIndicator() + { + _fixture + .WithEmployerNameOption(EmployerNameOption.RegisteredName) + .Setup(); + + var locationEditModel = new LocationEditModel + { + EmployerAccountId = _fixture.Vacancy.EmployerAccountId, + VacancyId = _fixture.Vacancy.Id, + SelectedLocation = LocationViewModel.UseOtherLocationConst + }; + + await _fixture.PostLocationEditModelAsync(locationEditModel, new VacancyEmployerInfoModel + { + EmployerIdentityOption = EmployerIdentityOption.RegisteredName, + AccountLegalEntityPublicHashedId = VacancyOrchestratorTestData.AccountLegalEntityPublicHashedId456 + }); + + _fixture.VerifyEmployerReviewFieldIndicators(FieldIdentifiers.EmployerName, true); + } + + [Theory] + [InlineData(EmployerNameOption.RegisteredName, EmployerIdentityOption.ExistingTradingName, null, true)] + [InlineData(EmployerNameOption.RegisteredName, EmployerIdentityOption.NewTradingName, "this is a new value", true)] + [InlineData(EmployerNameOption.TradingName, EmployerIdentityOption.NewTradingName, "this is a new value", true)] + [InlineData(EmployerNameOption.TradingName, EmployerIdentityOption.ExistingTradingName, null, false)] + [InlineData(EmployerNameOption.RegisteredName, EmployerIdentityOption.RegisteredName, null, false)] + public async Task WhenEmployerNameOptionUpdated_ShouldFlagEmployerNameFieldIndicator(EmployerNameOption employerNameOption, EmployerIdentityOption employerIdentityOption, string newTradingName, bool shouldFlagIndicator) + { + _fixture + .WithEmployerNameOption(employerNameOption) + .WithTradingName(employerNameOption == EmployerNameOption.TradingName + ? "this is a value" + : null) + .Setup(); + + var locationEditModel = new LocationEditModel + { + EmployerAccountId = _fixture.Vacancy.EmployerAccountId, + VacancyId = _fixture.Vacancy.Id, + SelectedLocation = LocationViewModel.UseOtherLocationConst + }; + + await _fixture.PostLocationEditModelAsync(locationEditModel, new VacancyEmployerInfoModel + { + EmployerIdentityOption = employerIdentityOption, + NewTradingName = newTradingName, + AccountLegalEntityPublicHashedId = _fixture.Vacancy.AccountLegalEntityPublicHashedId + }); + + _fixture.VerifyEmployerReviewFieldIndicators(FieldIdentifiers.EmployerName, shouldFlagIndicator); + } + + public class LocationOrchestratorTestsFixture + { + private const VacancyRuleSet ValidationRules = VacancyRuleSet.EmployerAddress; + public VacancyUser User { get; } + public Vacancy Vacancy { get; } + public EmployerEditVacancyInfo EmployerEditVacancyInfo { get; } + public EmployerProfile VacancyEmployerProfile { get; } + public EmployerProfile AlternateEmployerProfile { get; } + public LocationOrchestrator Sut {get; private set;} + + public LocationOrchestratorTestsFixture() + { + MockClient = new Mock(); + MockRecruitVacancyClient = new Mock(); + + User = VacancyOrchestratorTestData.GetVacancyUser(); + Vacancy = VacancyOrchestratorTestData.GetPart1CompleteVacancy(); + VacancyEmployerProfile = VacancyOrchestratorTestData.GetEmployerProfile(Vacancy.AccountLegalEntityPublicHashedId); + AlternateEmployerProfile = VacancyOrchestratorTestData.GetEmployerProfile(VacancyOrchestratorTestData.AccountLegalEntityPublicHashedId456); + EmployerEditVacancyInfo = VacancyOrchestratorTestData.GetEmployerEditVacancyInfo(); + } + + public LocationOrchestratorTestsFixture WithAddressLine1(string addressLine1) + { + if (Vacancy.EmployerLocation == null) + Vacancy.EmployerLocation = new Address(); + + Vacancy.EmployerLocation.AddressLine1 = addressLine1; + return this; + } + + public LocationOrchestratorTestsFixture WithAddressLine2(string addressLine2) + { + if (Vacancy.EmployerLocation == null) + Vacancy.EmployerLocation = new Address(); + + Vacancy.EmployerLocation.AddressLine2 = addressLine2; + return this; + } + + public LocationOrchestratorTestsFixture WithAddresLine3(string addressLine3) + { + if (Vacancy.EmployerLocation == null) + Vacancy.EmployerLocation = new Address(); + + Vacancy.EmployerLocation.AddressLine3 = addressLine3; + return this; + } + + public LocationOrchestratorTestsFixture WithAddresLine4(string addressLine4) + { + if (Vacancy.EmployerLocation == null) + Vacancy.EmployerLocation = new Address(); + + Vacancy.EmployerLocation.AddressLine4 = addressLine4; + return this; + } + + public LocationOrchestratorTestsFixture WithPostcode(string postcode) + { + if (Vacancy.EmployerLocation == null) + Vacancy.EmployerLocation = new Address(); + + Vacancy.EmployerLocation.Postcode = postcode; + return this; + } + + public LocationOrchestratorTestsFixture WithEmployerNameOption(EmployerNameOption employerNameOption) + { + Vacancy.EmployerNameOption = employerNameOption; + return this; + } + + public LocationOrchestratorTestsFixture WithTradingName(string tradingName) + { + VacancyEmployerProfile.TradingName = tradingName; + return this; + } + + public void Setup() + { + MockClient.Setup(x => x.GetEditVacancyInfoAsync(Vacancy.EmployerAccountId)).ReturnsAsync(EmployerEditVacancyInfo); + + MockRecruitVacancyClient.Setup(x => x.GetVacancyAsync(Vacancy.Id)).ReturnsAsync(Vacancy); + MockRecruitVacancyClient.Setup(x => x.GetEmployerProfileAsync(Vacancy.EmployerAccountId, Vacancy.AccountLegalEntityPublicHashedId)).ReturnsAsync(VacancyEmployerProfile); + MockRecruitVacancyClient.Setup(x => x.GetEmployerProfileAsync(Vacancy.EmployerAccountId, AlternateEmployerProfile.AccountLegalEntityPublicHashedId)).ReturnsAsync(AlternateEmployerProfile); + MockRecruitVacancyClient.Setup(x => x.Validate(Vacancy, ValidationRules)).Returns(new EntityValidationResult()); + MockRecruitVacancyClient.Setup(x => x.UpdateDraftVacancyAsync(It.IsAny(), User)); + MockRecruitVacancyClient.Setup(x => x.UpdateEmployerProfileAsync(It.IsAny(), User)); + + Sut = new LocationOrchestrator(MockClient.Object, MockRecruitVacancyClient.Object, Mock.Of>(), + Mock.Of()); + } + + public async Task PostLocationEditModelAsync(LocationEditModel model, VacancyEmployerInfoModel vacancyEmployerInfoModel = null) + { + await Sut.PostLocationEditModelAsync(model, + vacancyEmployerInfoModel ?? new VacancyEmployerInfoModel + { + VacancyId = Vacancy.Id + }, + User); + } + + public void VerifyEmployerReviewFieldIndicators(string fieldIdentifier, bool value) + { + Vacancy.EmployerReviewFieldIndicators + .Where(p => p.FieldIdentifier == fieldIdentifier).Single() + .Should().NotBeNull().And + .Match((x) => x.IsChangeRequested == value); + } + + public Mock MockClient { get; set; } + public Mock MockRecruitVacancyClient { get; set; } + } + } +} diff --git a/src/Employer/UnitTests/Employer.Web/Orchestrators/Part1/NumberOfPositionsOrchestratorTests.cs b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part1/NumberOfPositionsOrchestratorTests.cs new file mode 100644 index 0000000000..038f7f6b06 --- /dev/null +++ b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part1/NumberOfPositionsOrchestratorTests.cs @@ -0,0 +1,97 @@ +using System.Linq; +using System.Threading.Tasks; +using Esfa.Recruit.Employer.UnitTests.Employer.Web.HardMocks; +using Esfa.Recruit.Employer.Web.Orchestrators.Part1; +using Esfa.Recruit.Employer.Web.ViewModels.Part1.NumberOfPositions; +using Esfa.Recruit.Shared.Web.Mappers; +using Esfa.Recruit.Shared.Web.Services; +using Esfa.Recruit.Vacancies.Client.Application.Validation; +using Esfa.Recruit.Vacancies.Client.Domain.Entities; +using Esfa.Recruit.Vacancies.Client.Infrastructure.Client; +using FluentAssertions; +using Microsoft.Extensions.Logging; +using Moq; +using Xunit; + +namespace Esfa.Recruit.Employer.UnitTests.Employer.Web.Orchestrators.Part1 +{ + public class NumberofPositionsOrchestratorTests + { + private NumberofPositionsOrchestratorTestsFixture _fixture; + + public NumberofPositionsOrchestratorTests() + { + _fixture = new NumberofPositionsOrchestratorTestsFixture(); + } + + [Theory] + [InlineData(1, false)] + [InlineData(2, true)] + public async Task WhenUpdated_ShouldFlagFieldIndicators(int numberOfPositions, bool fieldIndicatorSet) + { + _fixture + .WithNumberOfPositions(1) + .Setup(); + + var numberOfPositionsEditModel = new NumberOfPositionsEditModel + { + EmployerAccountId = _fixture.Vacancy.EmployerAccountId, + VacancyId = _fixture.Vacancy.Id, + NumberOfPositions = numberOfPositions.ToString() + }; + + await _fixture.PostNumberOfPositionsEditModelAsync(numberOfPositionsEditModel); + + _fixture.VerifyEmployerReviewFieldIndicators(FieldIdentifiers.NumberOfPositions, fieldIndicatorSet); + } + + public class NumberofPositionsOrchestratorTestsFixture + { + private const VacancyRuleSet ValidationRules = VacancyRuleSet.NumberOfPositions; + public VacancyUser User { get; } + public Vacancy Vacancy { get; } + public NumberOfPositionsOrchestrator Sut {get; private set;} + + public NumberofPositionsOrchestratorTestsFixture() + { + MockClient = new Mock(); + MockRecruitVacancyClient = new Mock(); + + User = VacancyOrchestratorTestData.GetVacancyUser(); + Vacancy = VacancyOrchestratorTestData.GetPart1CompleteVacancy(); + } + + public NumberofPositionsOrchestratorTestsFixture WithNumberOfPositions(int numberOfPositions) + { + Vacancy.NumberOfPositions = numberOfPositions; + return this; + } + + public void Setup() + { + MockRecruitVacancyClient.Setup(x => x.GetVacancyAsync(Vacancy.Id)).ReturnsAsync(Vacancy); + MockRecruitVacancyClient.Setup(x => x.Validate(Vacancy, ValidationRules)).Returns(new EntityValidationResult()); + MockRecruitVacancyClient.Setup(x => x.UpdateDraftVacancyAsync(It.IsAny(), User)); + MockRecruitVacancyClient.Setup(x => x.UpdateEmployerProfileAsync(It.IsAny(), User)); + + Sut = new NumberOfPositionsOrchestrator(MockClient.Object, MockRecruitVacancyClient.Object, Mock.Of>(), + Mock.Of()); + } + + public async Task PostNumberOfPositionsEditModelAsync(NumberOfPositionsEditModel model) + { + await Sut.PostNumberOfPositionsEditModelAsync(model, User); + } + public void VerifyEmployerReviewFieldIndicators(string fieldIdentifier, bool value) + { + Vacancy.EmployerReviewFieldIndicators + .Where(p => p.FieldIdentifier == fieldIdentifier).Single() + .Should().NotBeNull().And + .Match((x) => x.IsChangeRequested == value); + } + + public Mock MockClient { get; set; } + public Mock MockRecruitVacancyClient { get; set; } + } + } +} diff --git a/src/Employer/UnitTests/Employer.Web/Orchestrators/Part1/TitleOrchestratorTests.cs b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part1/TitleOrchestratorTests.cs new file mode 100644 index 0000000000..0c2a3d1cdd --- /dev/null +++ b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part1/TitleOrchestratorTests.cs @@ -0,0 +1,99 @@ +using System.Linq; +using System.Threading.Tasks; +using Esfa.Recruit.Employer.UnitTests.Employer.Web.HardMocks; +using Esfa.Recruit.Employer.Web.Orchestrators.Part1; +using Esfa.Recruit.Employer.Web.ViewModels.Part1.Title; +using Esfa.Recruit.Shared.Web.Mappers; +using Esfa.Recruit.Shared.Web.Services; +using Esfa.Recruit.Vacancies.Client.Application.Validation; +using Esfa.Recruit.Vacancies.Client.Domain.Entities; +using Esfa.Recruit.Vacancies.Client.Infrastructure.Client; +using Esfa.Recruit.Vacancies.Client.Infrastructure.Services.TrainingProvider; +using FluentAssertions; +using Microsoft.Extensions.Logging; +using Moq; +using Xunit; + +namespace Esfa.Recruit.Employer.UnitTests.Employer.Web.Orchestrators.Part1 +{ + public class TitleOrchestratorTests + { + private TitleOrchestratorTestsFixture _fixture; + + public TitleOrchestratorTests() + { + _fixture = new TitleOrchestratorTestsFixture(); + } + + [Theory] + [InlineData("this is a value", false)] + [InlineData("this is a new value", true)] + public async Task WhenUpdated_ShouldFlagFieldIndicators(string title, bool fieldIndicatorSet) + { + _fixture + .WithTitle("this is a value") + .Setup(); + + var titleEditModel = new TitleEditModel + { + EmployerAccountId = _fixture.Vacancy.EmployerAccountId, + VacancyId = _fixture.Vacancy.Id, + Title = title + }; + + await _fixture.PostTitleEditModelAsync(titleEditModel); + + _fixture.VerifyEmployerReviewFieldIndicators(FieldIdentifiers.Title, fieldIndicatorSet); + } + + public class TitleOrchestratorTestsFixture + { + private const VacancyRuleSet ValidationRules = VacancyRuleSet.Title; + public VacancyUser User { get; } + public Vacancy Vacancy { get; } + public TitleOrchestrator Sut {get; private set;} + + public TitleOrchestratorTestsFixture() + { + MockClient = new Mock(); + MockRecruitVacancyClient = new Mock(); + + User = VacancyOrchestratorTestData.GetVacancyUser(); + Vacancy = VacancyOrchestratorTestData.GetPart1CompleteVacancy(); + } + + public TitleOrchestratorTestsFixture WithTitle(string title) + { + Vacancy.Title = title; + return this; + } + + public void Setup() + { + MockRecruitVacancyClient.Setup(x => x.GetVacancyAsync(Vacancy.Id)).ReturnsAsync(Vacancy); + MockRecruitVacancyClient.Setup(x => x.Validate(Vacancy, ValidationRules)).Returns(new EntityValidationResult()); + MockRecruitVacancyClient.Setup(x => x.UpdateDraftVacancyAsync(It.IsAny(), User)); + MockRecruitVacancyClient.Setup(x => x.UpdateEmployerProfileAsync(It.IsAny(), User)); + + Sut = new TitleOrchestrator(MockClient.Object, MockRecruitVacancyClient.Object, Mock.Of>(), + Mock.Of(), Mock.Of()); + } + + public async Task PostTitleEditModelAsync(TitleEditModel model) + { + await Sut.PostTitleEditModelAsync(model, User); + } + + public void VerifyEmployerReviewFieldIndicators(string fieldIdentifier, bool value) + { + Vacancy.EmployerReviewFieldIndicators + .Where(p => p.FieldIdentifier == fieldIdentifier).Single() + .Should().NotBeNull().And + .Match((x) => x.IsChangeRequested == value); + } + + public Mock MockClient { get; set; } + public Mock MockRecruitVacancyClient { get; set; } + } + } +} diff --git a/src/Employer/UnitTests/Employer.Web/Orchestrators/Part1/TrainingOrchestratorTests.cs b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part1/TrainingOrchestratorTests.cs new file mode 100644 index 0000000000..6c60e54a78 --- /dev/null +++ b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part1/TrainingOrchestratorTests.cs @@ -0,0 +1,101 @@ +using System.Linq; +using System.Threading.Tasks; +using Esfa.Recruit.Employer.UnitTests.Employer.Web.HardMocks; +using Esfa.Recruit.Employer.Web.Orchestrators.Part1; +using Esfa.Recruit.Employer.Web.ViewModels.Part1.Title; +using Esfa.Recruit.Employer.Web.ViewModels.Part1.Training; +using Esfa.Recruit.Shared.Web.Mappers; +using Esfa.Recruit.Shared.Web.Services; +using Esfa.Recruit.Vacancies.Client.Application.Validation; +using Esfa.Recruit.Vacancies.Client.Domain.Entities; +using Esfa.Recruit.Vacancies.Client.Infrastructure.Client; +using Esfa.Recruit.Vacancies.Client.Infrastructure.Services.TrainingProvider; +using FluentAssertions; +using Microsoft.Extensions.Logging; +using Moq; +using Xunit; + +namespace Esfa.Recruit.Employer.UnitTests.Employer.Web.Orchestrators.Part1 +{ + public class TrainingOrchestratorTests + { + private TrainingOrchestratorTestsFixture _fixture; + + public TrainingOrchestratorTests() + { + _fixture = new TrainingOrchestratorTestsFixture(); + } + + [Theory] + [InlineData("this is a value", false)] + [InlineData("this is a new value", true)] + public async Task WhenUpdated_ShouldFlagFieldIndicators(string programmeId, bool fieldIndicatorSet) + { + _fixture + .WithProgrammeId("this is a value") + .Setup(); + + var confirmTrainingEditModel = new ConfirmTrainingEditModel + { + EmployerAccountId = _fixture.Vacancy.EmployerAccountId, + VacancyId = _fixture.Vacancy.Id, + ProgrammeId = programmeId + }; + + await _fixture.PostConfirmTrainingEditModelAsync(confirmTrainingEditModel); + + _fixture.VerifyEmployerReviewFieldIndicators(FieldIdentifiers.TrainingLevel, fieldIndicatorSet); + _fixture.VerifyEmployerReviewFieldIndicators(FieldIdentifiers.Training, fieldIndicatorSet); + } + + public class TrainingOrchestratorTestsFixture + { + private const VacancyRuleSet ValidationRules = VacancyRuleSet.TrainingProgramme; + public VacancyUser User { get; } + public Vacancy Vacancy { get; } + public TrainingOrchestrator Sut {get; private set;} + + public TrainingOrchestratorTestsFixture() + { + MockClient = new Mock(); + MockRecruitVacancyClient = new Mock(); + + User = VacancyOrchestratorTestData.GetVacancyUser(); + Vacancy = VacancyOrchestratorTestData.GetPart1CompleteVacancy(); + } + + public TrainingOrchestratorTestsFixture WithProgrammeId(string programmeId) + { + Vacancy.ProgrammeId = programmeId; + return this; + } + + public void Setup() + { + MockRecruitVacancyClient.Setup(x => x.GetVacancyAsync(Vacancy.Id)).ReturnsAsync(Vacancy); + MockRecruitVacancyClient.Setup(x => x.Validate(Vacancy, ValidationRules)).Returns(new EntityValidationResult()); + MockRecruitVacancyClient.Setup(x => x.UpdateDraftVacancyAsync(It.IsAny(), User)); + MockRecruitVacancyClient.Setup(x => x.UpdateEmployerProfileAsync(It.IsAny(), User)); + + Sut = new TrainingOrchestrator(MockClient.Object, MockRecruitVacancyClient.Object, Mock.Of>(), + Mock.Of()); + } + + public async Task PostConfirmTrainingEditModelAsync(ConfirmTrainingEditModel model) + { + await Sut.PostConfirmTrainingEditModelAsync(model, User); + } + + public void VerifyEmployerReviewFieldIndicators(string fieldIdentifier, bool value) + { + Vacancy.EmployerReviewFieldIndicators + .Where(p => p.FieldIdentifier == fieldIdentifier).Single() + .Should().NotBeNull().And + .Match((x) => x.IsChangeRequested == value); + } + + public Mock MockClient { get; set; } + public Mock MockRecruitVacancyClient { get; set; } + } + } +} diff --git a/src/Employer/UnitTests/Employer.Web/Orchestrators/Part1/TrainingProviderOrchestratorTests.cs b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part1/TrainingProviderOrchestratorTests.cs index ff888ff7c5..03db0c04cd 100644 --- a/src/Employer/UnitTests/Employer.Web/Orchestrators/Part1/TrainingProviderOrchestratorTests.cs +++ b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part1/TrainingProviderOrchestratorTests.cs @@ -1,8 +1,13 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; +using Esfa.Recruit.Employer.UnitTests.Employer.Web.HardMocks; +using Esfa.Recruit.Employer.Web.Models; using Esfa.Recruit.Employer.Web.Orchestrators.Part1; using Esfa.Recruit.Employer.Web.ViewModels.Part1.TrainingProvider; +using Esfa.Recruit.Shared.Web.Mappers; +using Esfa.Recruit.Shared.Web.Orchestrators; using Esfa.Recruit.Shared.Web.Services; using Esfa.Recruit.Vacancies.Client.Application.Providers; using Esfa.Recruit.Vacancies.Client.Application.Validation; @@ -17,158 +22,264 @@ namespace Esfa.Recruit.Employer.UnitTests.Employer.Web.Orchestrators.Part1 { public class TrainingProviderOrchestratorTests - { - private const string EmployerAccountId = "EMPLOYERACCOUNTID"; - private readonly Guid VacancyId = Guid.Parse("0a8e54c9-e284-4023-b688-14b9d841fcd7"); - + { [Fact] public async Task PostSelectTrainingProviderAsync_WhenNotChoosingThenRemoveExistingTrainingProvider() { - var vacancy = new Vacancy - { - Id = VacancyId, - EmployerAccountId = EmployerAccountId, - TrainingProvider = new TrainingProvider(), - Title = "specified for route validation", - ProgrammeId = "specified for route validation" - }; - - var orch = GetTrainingProviderOrchestrator(vacancy); + var fixture = new TrainingProviderOrchestratorTestsFixture(); + fixture + .WithVacacny( + new Vacancy + { + Id = fixture.VacancyId, + EmployerAccountId = TrainingProviderOrchestratorTestsFixture.EmployerAccountId, + TrainingProvider = new TrainingProvider(), + Title = "specified for route validation", + ProgrammeId = "specified for route validation" + }) + .Setup(); - var m = new SelectTrainingProviderEditModel + var selectTrainingProviderEditModel = new SelectTrainingProviderEditModel { - IsTrainingProviderSelected = false, - EmployerAccountId = EmployerAccountId, - VacancyId = VacancyId + EmployerAccountId = TrainingProviderOrchestratorTestsFixture.EmployerAccountId, + VacancyId = fixture.Vacancy.Id, + IsTrainingProviderSelected = false }; - var result = await orch.PostSelectTrainingProviderAsync(m, new VacancyUser()); + var result = await fixture.PostSelectTrainingProviderAsync(selectTrainingProviderEditModel); - vacancy.TrainingProvider.Should().BeNull(); - result.Data.FoundTrainingProviderUkprn.Should().BeNull(); + fixture.VerifyTrainingProviderNotSet(); + fixture.VerifyNotFoundTrainingProviderUkprn(result); } [Theory] [InlineData("This search won't match a single provider")] - [InlineData("88888")] //will match multiple providers + [InlineData("88888")] // will match multiple providers public async Task PostSelectTrainingProviderAsync_TrainingProviderSearch_WhenNoSingleProviderFoundShouldNotReturnFoundProvider(string trainingProviderSearch) { - var vacancy = new Vacancy - { - Id = VacancyId, - EmployerAccountId = EmployerAccountId, - TrainingProvider = new TrainingProvider(), - Title = "specified for route validation", - ProgrammeId = "specified for route validation" - }; + var fixture = new TrainingProviderOrchestratorTestsFixture(); + fixture + .WithVacacny( + new Vacancy + { + Id = fixture.VacancyId, + EmployerAccountId = TrainingProviderOrchestratorTestsFixture.EmployerAccountId, + TrainingProvider = new TrainingProvider(), + Title = "specified for route validation", + ProgrammeId = "specified for route validation" + }) + .Setup(); - var orch = GetTrainingProviderOrchestrator(vacancy); - - var m = new SelectTrainingProviderEditModel + var selectTrainingProviderEditModel = new SelectTrainingProviderEditModel { + EmployerAccountId = TrainingProviderOrchestratorTestsFixture.EmployerAccountId, + VacancyId = fixture.Vacancy.Id, IsTrainingProviderSelected = true, SelectionType = TrainingProviderSelectionType.TrainingProviderSearch, - TrainingProviderSearch = trainingProviderSearch, - EmployerAccountId = EmployerAccountId, - VacancyId = VacancyId + TrainingProviderSearch = trainingProviderSearch }; - - var result = await orch.PostSelectTrainingProviderAsync(m, new VacancyUser()); - result.Data.FoundTrainingProviderUkprn.Should().BeNull(); + var result = await fixture.PostSelectTrainingProviderAsync(selectTrainingProviderEditModel); + + fixture.VerifyNotFoundTrainingProviderUkprn(result); } [Fact] public async Task PostSelectTrainingProviderAsync_TrainingProviderSearch_WhenSingleProviderFoundShouldReturnFoundProvider() { - var vacancy = new Vacancy - { - Id = VacancyId, - EmployerAccountId = EmployerAccountId, - TrainingProvider = new TrainingProvider(), - Title = "specified for route validation", - ProgrammeId = "specified for route validation" - }; + var fixture = new TrainingProviderOrchestratorTestsFixture(); + fixture + .WithVacacny( + new Vacancy + { + Id = fixture.VacancyId, + EmployerAccountId = TrainingProviderOrchestratorTestsFixture.EmployerAccountId, + TrainingProvider = new TrainingProvider(), + Title = "specified for route validation", + ProgrammeId = "specified for route validation" + }) + .Setup(); - var orch = GetTrainingProviderOrchestrator(vacancy); - - var m = new SelectTrainingProviderEditModel + var selectTrainingProviderEditModel = new SelectTrainingProviderEditModel { + EmployerAccountId = TrainingProviderOrchestratorTestsFixture.EmployerAccountId, + VacancyId = fixture.Vacancy.Id, IsTrainingProviderSelected = true, SelectionType = TrainingProviderSelectionType.TrainingProviderSearch, - TrainingProviderSearch = "MR EGG 88888888", - EmployerAccountId = EmployerAccountId, - VacancyId = VacancyId + TrainingProviderSearch = "FIRST TRAINING PROVIDER 88888888", }; - var result = await orch.PostSelectTrainingProviderAsync(m, new VacancyUser()); + var result = await fixture.PostSelectTrainingProviderAsync(selectTrainingProviderEditModel); - result.Data.FoundTrainingProviderUkprn.Should().Be(88888888); + fixture.VerifyFoundTrainingProviderUkprn(result, fixture.TrainingProviderOne.Ukprn.Value); } [Fact] public async Task PostSelectTrainingProviderAsync_UKPRN_WhenSingleProviderFoundShouldReturnFoundProvider() { - var vacancy = new Vacancy + var fixture = new TrainingProviderOrchestratorTestsFixture(); + fixture + .WithVacacny( + new Vacancy + { + Id = fixture.VacancyId, + EmployerAccountId = TrainingProviderOrchestratorTestsFixture.EmployerAccountId, + TrainingProvider = new TrainingProvider(), + Title = "specified for route validation", + ProgrammeId = "specified for route validation" + }) + .Setup(); + + var selectTrainingProviderEditModel = new SelectTrainingProviderEditModel { - Id = VacancyId, - EmployerAccountId = EmployerAccountId, - TrainingProvider = new TrainingProvider(), - Title = "specified for route validation", - ProgrammeId = "specified for route validation" + EmployerAccountId = TrainingProviderOrchestratorTestsFixture.EmployerAccountId, + VacancyId = fixture.Vacancy.Id, + IsTrainingProviderSelected = true, + SelectionType = TrainingProviderSelectionType.Ukprn, + Ukprn = fixture.TrainingProviderOne.Ukprn.ToString() }; - var orch = GetTrainingProviderOrchestrator(vacancy); + var result = await fixture.PostSelectTrainingProviderAsync(selectTrainingProviderEditModel); + + fixture.VerifyFoundTrainingProviderUkprn(result, fixture.TrainingProviderOne.Ukprn.Value); + } + + [Theory] + [InlineData(88888888)] + [InlineData(88888889)] + public async Task PostConfirmEditModelAsync_ShouldFlagProviderFieldIndicator(long ukprn) + { + var fixture = new TrainingProviderOrchestratorTestsFixture(); + fixture + .WithVacacny( + new Vacancy + { + Id = fixture.VacancyId, + EmployerAccountId = TrainingProviderOrchestratorTestsFixture.EmployerAccountId, + TrainingProvider = new TrainingProvider(), + Title = "specified for route validation", + ProgrammeId = "specified for route validation" + }) + .Setup(); - var m = new SelectTrainingProviderEditModel + var confirmTrainingProviderEditModel = new ConfirmTrainingProviderEditModel { - IsTrainingProviderSelected = true, - SelectionType = TrainingProviderSelectionType.Ukprn, - Ukprn = "88888888", - EmployerAccountId = EmployerAccountId, - VacancyId = VacancyId + EmployerAccountId = TrainingProviderOrchestratorTestsFixture.EmployerAccountId, + VacancyId = fixture.Vacancy.Id, + Ukprn = ukprn.ToString() }; - var result = await orch.PostSelectTrainingProviderAsync(m, new VacancyUser()); + await fixture.PostConfirmEditModelAsync(confirmTrainingProviderEditModel); - result.Data.FoundTrainingProviderUkprn.Should().Be(88888888); + fixture.VerifyEmployerReviewFieldIndicators(FieldIdentifiers.Provider, true); } - private TrainingProviderOrchestrator GetTrainingProviderOrchestrator(Vacancy vacancy) + public class TrainingProviderOrchestratorTestsFixture { - var mockClient = new Mock(); + private const VacancyRuleSet ValidationRules = VacancyRuleSet.TrainingProvider; + public TrainingProvider TrainingProviderOne { get; set; } + public TrainingProvider TrainingProviderTwo { get; set; } + public TrainingProviderSummary TrainingProviderSummaryOne { get; set; } + public TrainingProviderSummary TrainingProviderSummaryTwo { get; set; } + public VacancyUser User { get; } + public Vacancy Vacancy { get; private set; } + public TrainingProviderOrchestrator Sut { get; private set; } + + public const string EmployerAccountId = "EmployerAccountId"; + public readonly Guid VacancyId = Guid.NewGuid(); + + public TrainingProviderOrchestratorTestsFixture() + { + MockClient = new Mock(); + MockRecruitVacancyClient = new Mock(); + MockTrainingProviderSummaryProvider = new Mock(); + MockTrainingProviderService = new Mock(); + + User = VacancyOrchestratorTestData.GetVacancyUser(); + + TrainingProviderOne = new TrainingProvider { Ukprn = 88888888 }; + TrainingProviderTwo = new TrainingProvider { Ukprn = 88888889 }; + TrainingProviderSummaryOne = new TrainingProviderSummary { ProviderName = "First Training Provider", Ukprn = TrainingProviderOne.Ukprn.Value }; + TrainingProviderSummaryTwo = new TrainingProviderSummary { ProviderName = "Second Training Provider", Ukprn = TrainingProviderTwo.Ukprn.Value }; + } + + public TrainingProviderOrchestratorTestsFixture WithVacacny(Vacancy vacancy) + { + Vacancy = vacancy; + return this; + } + + public void Setup() + { + MockRecruitVacancyClient.Setup(x => x.GetVacancyAsync(Vacancy.Id)).ReturnsAsync(Vacancy); + MockRecruitVacancyClient.Setup(x => x.Validate(Vacancy, ValidationRules)).Returns(new EntityValidationResult()); + MockRecruitVacancyClient.Setup(x => x.UpdateDraftVacancyAsync(It.IsAny(), User)); + MockRecruitVacancyClient.Setup(x => x.UpdateEmployerProfileAsync(It.IsAny(), User)); + + MockTrainingProviderSummaryProvider.Setup(p => p.FindAllAsync()).ReturnsAsync(new List + { + TrainingProviderSummaryOne, + TrainingProviderSummaryTwo + }); - var mockTrainingProviderService = new Mock(); - mockTrainingProviderService.Setup(t => t.GetProviderAsync(88888888)) - .ReturnsAsync(new TrainingProvider {Ukprn = 88888888}); + MockTrainingProviderSummaryProvider.Setup(p => p.GetAsync(TrainingProviderSummaryOne.Ukprn)) + .ReturnsAsync(TrainingProviderSummaryOne); - var mockRecruitClient = new Mock(); - mockRecruitClient.Setup(c => c.GetVacancyAsync(VacancyId)).ReturnsAsync(vacancy); + MockTrainingProviderSummaryProvider.Setup(p => p.GetAsync(TrainingProviderSummaryTwo.Ukprn)) + .ReturnsAsync(TrainingProviderSummaryTwo); - var mockTrainingProviderSummaryProvider = new Mock(); + MockTrainingProviderService.Setup(t => t.GetProviderAsync(TrainingProviderOne.Ukprn.Value)) + .ReturnsAsync(TrainingProviderOne); - var mrEggTrainingProvider = new TrainingProviderSummary { ProviderName = "MR EGG", Ukprn = 88888888 }; - var mrsEggTrainingProvider = new TrainingProviderSummary { ProviderName = "MRS EGG", Ukprn = 88888889 }; + MockTrainingProviderService.Setup(t => t.GetProviderAsync(TrainingProviderTwo.Ukprn.Value)) + .ReturnsAsync(TrainingProviderTwo); - mockTrainingProviderSummaryProvider.Setup(p => p.FindAllAsync()).ReturnsAsync(new List + Sut = new TrainingProviderOrchestrator(MockClient.Object, MockRecruitVacancyClient.Object, Mock.Of>(), + Mock.Of(), MockTrainingProviderSummaryProvider.Object, MockTrainingProviderService.Object); + } + + public async Task> PostSelectTrainingProviderAsync(SelectTrainingProviderEditModel model) { - mrEggTrainingProvider, - mrsEggTrainingProvider - }); + return await Sut.PostSelectTrainingProviderAsync(model, User); + } - mockTrainingProviderSummaryProvider.Setup(p => p.GetAsync(88888888)) - .ReturnsAsync(mrEggTrainingProvider); + public async Task PostConfirmEditModelAsync(ConfirmTrainingProviderEditModel model) + { + return await Sut.PostConfirmEditModelAsync(model, User); + } - mockTrainingProviderSummaryProvider.Setup(p => p.GetAsync(88888889)) - .ReturnsAsync(mrsEggTrainingProvider); + public void VerifyFoundTrainingProviderUkprn(OrchestratorResponse result, long value) + { + result.Data.FoundTrainingProviderUkprn.Should().Be(value); + } - mockRecruitClient.Setup(c => c.Validate(It.IsAny(), VacancyRuleSet.TrainingProvider)) - .Returns(new EntityValidationResult()); + public void VerifyNotFoundTrainingProviderUkprn(OrchestratorResponse result) + { + result.Data.FoundTrainingProviderUkprn.Should().BeNull(); + } - var mockLog = new Mock>(); - var mockReview = new Mock(); + public void VerifyTrainingProviderNotSet() + { + Vacancy.TrainingProvider.Should().BeNull(); + } + + public void VerifyEmployerReviewFieldIndicators(string fieldIdentifier, bool value) + { + Vacancy.EmployerReviewFieldIndicators + .Where(p => p.FieldIdentifier == fieldIdentifier).Single() + .Should().NotBeNull().And + .Match((x) => x.IsChangeRequested == value); + } + + public void VerifyUpdateDraftVacancyAsyncIsCalled() + { + MockRecruitVacancyClient.Verify(x => x.UpdateDraftVacancyAsync(Vacancy, User), Times.Once); + } - return new TrainingProviderOrchestrator(mockClient.Object, mockRecruitClient.Object, mockLog.Object, mockReview.Object, mockTrainingProviderSummaryProvider.Object, mockTrainingProviderService.Object); + public Mock MockClient { get; set; } + public Mock MockRecruitVacancyClient { get; set; } + public Mock MockTrainingProviderSummaryProvider { get; set; } + public Mock MockTrainingProviderService { get; set; } } } } diff --git a/src/Employer/UnitTests/Employer.Web/Orchestrators/Part1/WageOrchestratorTests.cs b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part1/WageOrchestratorTests.cs new file mode 100644 index 0000000000..e9e12c553f --- /dev/null +++ b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part1/WageOrchestratorTests.cs @@ -0,0 +1,131 @@ +using System.Linq; +using System.Threading.Tasks; +using Esfa.Recruit.Employer.UnitTests.Employer.Web.HardMocks; +using Esfa.Recruit.Employer.Web.Orchestrators.Part1; +using Esfa.Recruit.Employer.Web.ViewModels.Part1.Duration; +using Esfa.Recruit.Employer.Web.ViewModels.Part1.Wage; +using Esfa.Recruit.Shared.Web.Mappers; +using Esfa.Recruit.Shared.Web.Services; +using Esfa.Recruit.Vacancies.Client.Application.Providers; +using Esfa.Recruit.Vacancies.Client.Application.Validation; +using Esfa.Recruit.Vacancies.Client.Domain.Entities; +using Esfa.Recruit.Vacancies.Client.Infrastructure.Client; +using FluentAssertions; +using Microsoft.Extensions.Logging; +using Moq; +using Xunit; + +namespace Esfa.Recruit.Employer.UnitTests.Employer.Web.Orchestrators.Part1 +{ + public class WageOrchestratorTests + { + private WageOrchestratorTestsFixture _fixture; + + public WageOrchestratorTests() + { + _fixture = new WageOrchestratorTestsFixture(); + } + + [Theory] + [InlineData(WageType.FixedWage, 10000, "this is a value", false)] + [InlineData(WageType.NationalMinimumWage, 10000, "this is a value", true)] + [InlineData(WageType.FixedWage, 11000, "this is a value", true)] + [InlineData(WageType.FixedWage, 10000, "this is a new value", true)] + public async Task WhenUpdated_ShouldFlagFieldIndicators(WageType wageType, decimal fixedWageYearlyAmmount, string wageAddtionalInformation, bool fieldIndicatorSet) + { + _fixture + .WithWageType(WageType.FixedWage) + .WithFixedWageYearlyAmount(10000) + .WithWageAdditionalInformation("this is a value") + .Setup(); + + var wageEditModel = new WageEditModel + { + EmployerAccountId = _fixture.Vacancy.EmployerAccountId, + VacancyId = _fixture.Vacancy.Id, + WageType = wageType, + FixedWageYearlyAmount = fixedWageYearlyAmmount.ToString(), + WageAdditionalInformation = wageAddtionalInformation + }; + + await _fixture.PostWageEditModelAsync(wageEditModel); + + _fixture.VerifyEmployerReviewFieldIndicators(FieldIdentifiers.Wage, fieldIndicatorSet); + } + + public class WageOrchestratorTestsFixture + { + private const VacancyRuleSet ValidationRules = VacancyRuleSet.Wage | VacancyRuleSet.MinimumWage; + public VacancyUser User { get; } + public Vacancy Vacancy { get; } + public WageOrchestrator Sut {get; private set;} + + public WageOrchestratorTestsFixture() + { + MockClient = new Mock(); + MockRecruitVacancyClient = new Mock(); + + User = VacancyOrchestratorTestData.GetVacancyUser(); + Vacancy = VacancyOrchestratorTestData.GetPart1CompleteVacancy(); + } + + public WageOrchestratorTestsFixture WithWageType(WageType wageType) + { + Vacancy.Wage.WageType = wageType; + return this; + } + + public WageOrchestratorTestsFixture WithFixedWageYearlyAmount(decimal fixedWageYearlyAmmount) + { + Vacancy.Wage.FixedWageYearlyAmount = fixedWageYearlyAmmount; + return this; + } + + public WageOrchestratorTestsFixture WithWageAdditionalInformation(string wageAdditionalInformation) + { + Vacancy.Wage.WageAdditionalInformation = wageAdditionalInformation; + return this; + } + + public void Setup() + { + MockRecruitVacancyClient.Setup(x => x.GetVacancyAsync(Vacancy.Id)).ReturnsAsync(Vacancy); + MockRecruitVacancyClient.Setup(x => x.Validate(Vacancy, ValidationRules)).Returns(new EntityValidationResult()); + MockRecruitVacancyClient.Setup(x => x.UpdateDraftVacancyAsync(It.IsAny(), User)); + MockRecruitVacancyClient.Setup(x => x.UpdateEmployerProfileAsync(It.IsAny(), User)); + + Sut = new WageOrchestrator(MockClient.Object, MockRecruitVacancyClient.Object, Mock.Of>(), + Mock.Of(), Mock.Of()); + } + + public async Task PostWageEditModelAsync(WageEditModel model) + { + await Sut.PostWageEditModelAsync(model, User); + } + + public void VerifyEmployerReviewFieldIndicators(string[] setFieldIdentifiers, string[] unsetFieldIdentifiers) + { + foreach (var fieldIdentifier in setFieldIdentifiers) + { + VerifyEmployerReviewFieldIndicators(fieldIdentifier, true); + } + + foreach (var fieldIdentifier in unsetFieldIdentifiers) + { + VerifyEmployerReviewFieldIndicators(fieldIdentifier, false); + } + } + + public void VerifyEmployerReviewFieldIndicators(string fieldIdentifier, bool value) + { + Vacancy.EmployerReviewFieldIndicators + .Where(p => p.FieldIdentifier == fieldIdentifier).Single() + .Should().NotBeNull().And + .Match((x) => x.IsChangeRequested == value); + } + + public Mock MockClient { get; set; } + public Mock MockRecruitVacancyClient { get; set; } + } + } +} diff --git a/src/Employer/UnitTests/Employer.Web/Orchestrators/Part2/AboutEmployerOrchestratorTests.cs b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part2/AboutEmployerOrchestratorTests.cs new file mode 100644 index 0000000000..80a79ea1a7 --- /dev/null +++ b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part2/AboutEmployerOrchestratorTests.cs @@ -0,0 +1,122 @@ +using System.Linq; +using System.Threading.Tasks; +using Esfa.Recruit.Employer.UnitTests.Employer.Web.HardMocks; +using Esfa.Recruit.Employer.Web.Orchestrators.Part2; +using Esfa.Recruit.Employer.Web.ViewModels; +using Esfa.Recruit.Shared.Web.Mappers; +using Esfa.Recruit.Shared.Web.Services; +using Esfa.Recruit.Vacancies.Client.Application.Validation; +using Esfa.Recruit.Vacancies.Client.Domain.Entities; +using Esfa.Recruit.Vacancies.Client.Infrastructure.Client; +using FluentAssertions; +using Microsoft.Extensions.Logging; +using Moq; +using Xunit; + +namespace Esfa.Recruit.Employer.UnitTests.Employer.Web.Orchestrators.Part2 +{ + public class AboutEmployerOrchestratorTests + { + private AboutEmployerOrchestratorTestsFixture _fixture; + + public AboutEmployerOrchestratorTests() + { + _fixture = new AboutEmployerOrchestratorTestsFixture(); + } + + [Theory] + [InlineData("has a new value", "has a value", new string[] { FieldIdentifiers.EmployerDescription }, new string[] { FieldIdentifiers.EmployerWebsiteUrl})] + [InlineData("has a value", "has a new value", new string[] { FieldIdentifiers.EmployerWebsiteUrl }, new string[] { FieldIdentifiers.EmployerDescription })] + [InlineData("has a new value", "has a new value", new string[] { FieldIdentifiers.EmployerDescription, FieldIdentifiers.EmployerWebsiteUrl }, new string[] { })] + public async Task WhenUpdated_ShouldFlagFieldIndicators(string employerDescription, string employerWebSiteUrl, string[] setFieldIdentifers, string [] unsetFieldIdentifiers) + { + _fixture + .WithEmployerDescription("has a value") + .WithEmployerWebsiteUrl("has a value") + .Setup(); + + var aboutEmployerEditModel = new AboutEmployerEditModel + { + EmployerAccountId = _fixture.Vacancy.EmployerAccountId, + VacancyId = _fixture.Vacancy.Id, + EmployerDescription = employerDescription, + EmployerWebsiteUrl = employerWebSiteUrl + }; + + await _fixture.PostAboutEmployerEditModelAsync(aboutEmployerEditModel); + + _fixture.VerifyEmployerReviewFieldIndicators(setFieldIdentifers, unsetFieldIdentifiers); + } + + public class AboutEmployerOrchestratorTestsFixture + { + private const VacancyRuleSet ValidationRules = VacancyRuleSet.EmployerDescription | VacancyRuleSet.EmployerWebsiteUrl; + public VacancyUser User { get; } + public EmployerProfile EmployerProfile { get; } + public Vacancy Vacancy { get; } + public AboutEmployerOrchestrator Sut {get; private set;} + + public AboutEmployerOrchestratorTestsFixture() + { + MockClient = new Mock(); + MockRecruitVacancyClient = new Mock(); + + User = VacancyOrchestratorTestData.GetVacancyUser(); + Vacancy = VacancyOrchestratorTestData.GetPart1CompleteVacancy(); + EmployerProfile = VacancyOrchestratorTestData.GetEmployerProfile(Vacancy.AccountLegalEntityPublicHashedId); + } + + public AboutEmployerOrchestratorTestsFixture WithEmployerDescription(string employerDescription) + { + Vacancy.EmployerDescription = employerDescription; + return this; + } + + public AboutEmployerOrchestratorTestsFixture WithEmployerWebsiteUrl(string employerWebsiteUrl) + { + Vacancy.EmployerWebsiteUrl = employerWebsiteUrl; + return this; + } + + public void Setup() + { + MockRecruitVacancyClient.Setup(x => x.GetVacancyAsync(Vacancy.Id)).ReturnsAsync(Vacancy); + MockRecruitVacancyClient.Setup(x => x.GetEmployerProfileAsync(Vacancy.EmployerAccountId, Vacancy.AccountLegalEntityPublicHashedId)).ReturnsAsync(EmployerProfile); + MockRecruitVacancyClient.Setup(x => x.Validate(Vacancy, ValidationRules)).Returns(new EntityValidationResult()); + MockRecruitVacancyClient.Setup(x => x.UpdateDraftVacancyAsync(It.IsAny(), User)); + MockRecruitVacancyClient.Setup(x => x.UpdateEmployerProfileAsync(It.IsAny(), User)); + + Sut = new AboutEmployerOrchestrator(MockClient.Object, MockRecruitVacancyClient.Object, Mock.Of>(), Mock.Of()); + } + + public async Task PostAboutEmployerEditModelAsync(AboutEmployerEditModel model) + { + await Sut.PostAboutEmployerEditModelAsync(model, User); + } + + public void VerifyEmployerReviewFieldIndicators(string[] setFieldIdentifiers, string[] unsetFieldIdentifiers) + { + foreach (var fieldIdentifier in setFieldIdentifiers) + { + VerifyEmployerReviewFieldIndicators(fieldIdentifier, true); + } + + foreach (var fieldIdentifier in unsetFieldIdentifiers) + { + VerifyEmployerReviewFieldIndicators(fieldIdentifier, false); + } + } + + public void VerifyEmployerReviewFieldIndicators(string fieldIdentifier, bool value) + { + Vacancy.EmployerReviewFieldIndicators + .Where(p => p.FieldIdentifier == fieldIdentifier).Single() + .Should().NotBeNull().And + .Match((x) => x.IsChangeRequested == value); + } + + public Mock MockClient { get; set; } + public Mock MockRecruitVacancyClient { get; set; } + } + } +} diff --git a/src/Employer/UnitTests/Employer.Web/Orchestrators/Part2/ApplicationProcessOrchestratorTests.cs b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part2/ApplicationProcessOrchestratorTests.cs index f56934f819..6a9f661e1c 100644 --- a/src/Employer/UnitTests/Employer.Web/Orchestrators/Part2/ApplicationProcessOrchestratorTests.cs +++ b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part2/ApplicationProcessOrchestratorTests.cs @@ -1,7 +1,11 @@ -using Esfa.Recruit.Employer.UnitTests.Employer.Web.HardMocks; +using System; +using System.Linq; +using System.Threading.Tasks; +using Esfa.Recruit.Employer.UnitTests.Employer.Web.HardMocks; using Esfa.Recruit.Employer.Web.Configuration; using Esfa.Recruit.Employer.Web.Orchestrators.Part2; using Esfa.Recruit.Employer.Web.ViewModels; +using Esfa.Recruit.Shared.Web.Mappers; using Esfa.Recruit.Shared.Web.Services; using Esfa.Recruit.Vacancies.Client.Application.Validation; using Esfa.Recruit.Vacancies.Client.Domain.Entities; @@ -16,73 +20,235 @@ namespace Esfa.Recruit.Employer.UnitTests.Employer.Web.Orchestrators.Part2 { public class ApplicationProcessOrchestratorTests { - private readonly Mock _mockClient; - private readonly Mock _mockVacancyClient; + private ApplicationProcessOrchestratorTestsFixture _fixture; public ApplicationProcessOrchestratorTests() { - _mockClient = new Mock(); - _mockVacancyClient = new Mock(); + _fixture = new ApplicationProcessOrchestratorTestsFixture(); } [Fact] - public void WhenApplicationMethodIsThroughFaaVacancy_ShouldOverwriteApplicationUrlAsNull() + public async Task WhenApplicationMethodIsThroughFaaVacancy_ShouldCallUpdateDraftVacancy() { - var user = VacancyOrchestratorTestData.GetVacancyUser(); - var vacancy = VacancyOrchestratorTestData.GetPart1CompleteVacancy(); + _fixture + .WithApplicationMethod(ApplicationMethod.ThroughExternalApplicationSite) + .WithApplicationInstructions("has a value") + .WithApplicationUrl("has a value") + .Setup(); - _mockVacancyClient.Setup(x => x.GetVacancyAsync(vacancy.Id)) - .ReturnsAsync(vacancy); - _mockVacancyClient.Setup(x => x.Validate(vacancy, VacancyRuleSet.ApplicationMethod)) - .Returns(new EntityValidationResult()); - _mockVacancyClient.Setup(x => x.UpdateDraftVacancyAsync(It.IsAny(), user)); + var applicationProcessEditModel = new ApplicationProcessEditModel + { + EmployerAccountId = _fixture.Vacancy.EmployerAccountId, + VacancyId = _fixture.Vacancy.Id, + ApplicationMethod = ApplicationMethod.ThroughFindAnApprenticeship, + ApplicationInstructions = "has a value", + }; + + await _fixture.PostApplicationProcessEditModelAsync(applicationProcessEditModel); - var sut = new ApplicationProcessOrchestrator(_mockClient.Object, _mockVacancyClient.Object, Options.Create(new ExternalLinksConfiguration()), Mock.Of>(), Mock.Of()); + _fixture.VerifyUpdateDraftVacancyAsyncIsCalled(); + } + + [Fact] + public async Task WhenApplicationMethodIsThroughFaaVacancy_ShouldOverwriteApplicationUrlAsNull() + { + _fixture + .WithApplicationMethod(ApplicationMethod.ThroughExternalApplicationSite) + .WithApplicationInstructions("has a value") + .WithApplicationUrl("has a value") + .Setup(); var applicationProcessEditModel = new ApplicationProcessEditModel { - EmployerAccountId = vacancy.EmployerAccountId, - VacancyId = vacancy.Id, + EmployerAccountId = _fixture.Vacancy.EmployerAccountId, + VacancyId = _fixture.Vacancy.Id, ApplicationMethod = ApplicationMethod.ThroughFindAnApprenticeship, - ApplicationUrl = "www.google.com" + ApplicationUrl = "has a value" }; - var result = sut.PostApplicationProcessEditModelAsync(applicationProcessEditModel, user); + await _fixture.PostApplicationProcessEditModelAsync(applicationProcessEditModel); - vacancy.ApplicationMethod.HasValue.Should().BeTrue(); - vacancy.ApplicationMethod.Value.Should().Be(ApplicationMethod.ThroughFindAnApprenticeship); - vacancy.ApplicationUrl.Should().BeNull(); - _mockVacancyClient.Verify(x => x.UpdateDraftVacancyAsync(vacancy, user), Times.Once); + _fixture.VerifyApplicationMethod(ApplicationMethod.ThroughFindAnApprenticeship); + _fixture.VerifyOverwriteApplicationUrlAsNull(); } [Fact] - public void WhenApplicationMethodIsThroughFaaVacancy_ShouldOverwriteApplicationInstructionsAsNull() + public async Task WhenApplicationMethodIsThroughFaaVacancy_ShouldOverwriteApplicationInstructionsAsNull() { - var user = VacancyOrchestratorTestData.GetVacancyUser(); - var vacancy = VacancyOrchestratorTestData.GetPart1CompleteVacancy(); + _fixture + .WithApplicationMethod(ApplicationMethod.ThroughExternalApplicationSite) + .WithApplicationInstructions("has a value") + .WithApplicationUrl("has a value") + .Setup(); - _mockVacancyClient.Setup(x => x.GetVacancyAsync(vacancy.Id)) - .ReturnsAsync(vacancy); - _mockVacancyClient.Setup(x => x.Validate(vacancy, VacancyRuleSet.ApplicationMethod)) - .Returns(new EntityValidationResult()); - _mockVacancyClient.Setup(x => x.UpdateDraftVacancyAsync(It.IsAny(), user)); + var applicationProcessEditModel = new ApplicationProcessEditModel + { + EmployerAccountId = _fixture.Vacancy.EmployerAccountId, + VacancyId = _fixture.Vacancy.Id, + ApplicationMethod = ApplicationMethod.ThroughFindAnApprenticeship, + ApplicationInstructions = "has a value", + }; - var sut = new ApplicationProcessOrchestrator(_mockClient.Object, _mockVacancyClient.Object, Options.Create(new ExternalLinksConfiguration()), Mock.Of>(), Mock.Of()); + await _fixture.PostApplicationProcessEditModelAsync(applicationProcessEditModel); + + _fixture.VerifyApplicationMethod(ApplicationMethod.ThroughFindAnApprenticeship); + _fixture.VerifyOverwriteApplicationInstructionsAsNull(); + } + + [Fact] + public async Task WhenApplicationMethodIsThroughFaaVacancy_ShouldFlagAllApplicationFieldIndicators() + { + _fixture + .WithApplicationMethod(ApplicationMethod.ThroughExternalApplicationSite) + .WithApplicationInstructions("has a value") + .WithApplicationUrl("has a value") + .Setup(); var applicationProcessEditModel = new ApplicationProcessEditModel { - EmployerAccountId = vacancy.EmployerAccountId, - VacancyId = vacancy.Id, + EmployerAccountId = _fixture.Vacancy.EmployerAccountId, + VacancyId = _fixture.Vacancy.Id, ApplicationMethod = ApplicationMethod.ThroughFindAnApprenticeship, - ApplicationInstructions = "just do it" + ApplicationInstructions = "has a value", + ApplicationUrl = "has a value" + }; + + await _fixture.PostApplicationProcessEditModelAsync(applicationProcessEditModel); + + _fixture.VerifyEmployerReviewFieldIndicators(FieldIdentifiers.ApplicationMethod, true); + _fixture.VerifyEmployerReviewFieldIndicators(FieldIdentifiers.ApplicationInstructions, true); + _fixture.VerifyEmployerReviewFieldIndicators(FieldIdentifiers.ApplicationUrl, true); + } + + [Fact] + public async Task WhenApplicationInstructionsIsUpdated_ShouldFlagApplicationInstructionsFieldIndicator() + { + _fixture + .WithApplicationMethod(ApplicationMethod.ThroughExternalApplicationSite) + .WithApplicationInstructions("has a value") + .WithApplicationUrl("has a value") + .Setup(); + + var applicationProcessEditModel = new ApplicationProcessEditModel + { + EmployerAccountId = _fixture.Vacancy.EmployerAccountId, + VacancyId = _fixture.Vacancy.Id, + ApplicationMethod = ApplicationMethod.ThroughExternalApplicationSite, + ApplicationInstructions = "has a new value", + ApplicationUrl = "has a value" }; - var result = sut.PostApplicationProcessEditModelAsync(applicationProcessEditModel, user); + await _fixture.PostApplicationProcessEditModelAsync(applicationProcessEditModel); + + _fixture.VerifyEmployerReviewFieldIndicators(FieldIdentifiers.ApplicationMethod, false); + _fixture.VerifyEmployerReviewFieldIndicators(FieldIdentifiers.ApplicationInstructions, true); + _fixture.VerifyEmployerReviewFieldIndicators(FieldIdentifiers.ApplicationUrl, false); + } + + [Fact] + public async Task WhenApplicationUrlIsUpdated_ShouldFlagApplicationInstructionsFieldIndicator() + { + _fixture + .WithApplicationMethod(ApplicationMethod.ThroughExternalApplicationSite) + .WithApplicationInstructions("has a value") + .WithApplicationUrl("has a value") + .Setup(); + + var applicationProcessEditModel = new ApplicationProcessEditModel + { + EmployerAccountId = _fixture.Vacancy.EmployerAccountId, + VacancyId = _fixture.Vacancy.Id, + ApplicationMethod = ApplicationMethod.ThroughExternalApplicationSite, + ApplicationInstructions = "has a value", + ApplicationUrl = "has a new value" + }; + + await _fixture.PostApplicationProcessEditModelAsync(applicationProcessEditModel); + + _fixture.VerifyEmployerReviewFieldIndicators(FieldIdentifiers.ApplicationMethod, false); + _fixture.VerifyEmployerReviewFieldIndicators(FieldIdentifiers.ApplicationInstructions, false); + _fixture.VerifyEmployerReviewFieldIndicators(FieldIdentifiers.ApplicationUrl, true); + } + + public class ApplicationProcessOrchestratorTestsFixture + { + private const VacancyRuleSet ValidationRules = VacancyRuleSet.ApplicationMethod; + public VacancyUser User { get; } + public Vacancy Vacancy { get; } + public ApplicationProcessOrchestrator Sut { get; private set; } + + public ApplicationProcessOrchestratorTestsFixture() + { + MockClient = new Mock(); + MockRecruitVacancyClient = new Mock(); + User = VacancyOrchestratorTestData.GetVacancyUser(); + Vacancy = VacancyOrchestratorTestData.GetPart1CompleteVacancy(); + } + + public ApplicationProcessOrchestratorTestsFixture WithApplicationMethod(ApplicationMethod applicationMethod) + { + Vacancy.ApplicationMethod = applicationMethod; + return this; + } + + public ApplicationProcessOrchestratorTestsFixture WithApplicationInstructions(string applicationInstructions) + { + Vacancy.ApplicationInstructions = applicationInstructions; + return this; + } + + public ApplicationProcessOrchestratorTestsFixture WithApplicationUrl(string applicationUrl) + { + Vacancy.ApplicationUrl = applicationUrl; + return this; + } + + public void Setup() + { + MockRecruitVacancyClient.Setup(x => x.GetVacancyAsync(Vacancy.Id)).ReturnsAsync(Vacancy); + MockRecruitVacancyClient.Setup(x => x.Validate(Vacancy, ValidationRules)).Returns(new EntityValidationResult()); + MockRecruitVacancyClient.Setup(x => x.UpdateDraftVacancyAsync(It.IsAny(), User)); + MockRecruitVacancyClient.Setup(x => x.UpdateEmployerProfileAsync(It.IsAny(), User)); + + Sut = new ApplicationProcessOrchestrator(MockClient.Object, MockRecruitVacancyClient.Object, Options.Create(new ExternalLinksConfiguration()), Mock.Of>(), Mock.Of()); + } + + public async Task PostApplicationProcessEditModelAsync(ApplicationProcessEditModel model) + { + await Sut.PostApplicationProcessEditModelAsync(model, User); + } + + public void VerifyEmployerReviewFieldIndicators(string fieldIdentifier, bool value) + { + Vacancy.EmployerReviewFieldIndicators + .Where(p => p.FieldIdentifier == fieldIdentifier).Single() + .Should().NotBeNull().And + .Match((x) => x.IsChangeRequested == value); + } + + public void VerifyApplicationMethod(ApplicationMethod applicationMethod) + { + Vacancy.ApplicationMethod.HasValue.Should().BeTrue(); + Vacancy.ApplicationMethod.Value.Should().Be(applicationMethod); + } + + public void VerifyOverwriteApplicationInstructionsAsNull() + { + Vacancy.ApplicationInstructions.Should().BeNull(); + } + + public void VerifyOverwriteApplicationUrlAsNull() + { + Vacancy.ApplicationUrl.Should().BeNull(); + } + + public void VerifyUpdateDraftVacancyAsyncIsCalled() + { + MockRecruitVacancyClient.Verify(x => x.UpdateDraftVacancyAsync(Vacancy, User), Times.Once); + } - vacancy.ApplicationMethod.HasValue.Should().BeTrue(); - vacancy.ApplicationMethod.Value.Should().Be(ApplicationMethod.ThroughFindAnApprenticeship); - vacancy.ApplicationInstructions.Should().BeNull(); - _mockVacancyClient.Verify(x => x.UpdateDraftVacancyAsync(vacancy, user), Times.Once); + public Mock MockClient { get; set; } + public Mock MockRecruitVacancyClient { get; set; } } } } diff --git a/src/Employer/UnitTests/Employer.Web/Orchestrators/Part2/ConsiderationsOrchestratorTests.cs b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part2/ConsiderationsOrchestratorTests.cs new file mode 100644 index 0000000000..ca8b3d89f0 --- /dev/null +++ b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part2/ConsiderationsOrchestratorTests.cs @@ -0,0 +1,119 @@ +using System.Linq; +using System.Threading.Tasks; +using Esfa.Recruit.Employer.UnitTests.Employer.Web.HardMocks; +using Esfa.Recruit.Employer.Web.Orchestrators.Part2; +using Esfa.Recruit.Employer.Web.ViewModels; +using Esfa.Recruit.Shared.Web.Mappers; +using Esfa.Recruit.Shared.Web.Services; +using Esfa.Recruit.Vacancies.Client.Application.Validation; +using Esfa.Recruit.Vacancies.Client.Domain.Entities; +using Esfa.Recruit.Vacancies.Client.Infrastructure.Client; +using FluentAssertions; +using Microsoft.Extensions.Logging; +using Moq; +using Xunit; + +namespace Esfa.Recruit.Employer.UnitTests.Employer.Web.Orchestrators.Part2 +{ + public class ConsiderationsOrchestratorTests + { + private ConsiderationsOrchestratorTestsFixture _fixture; + + public ConsiderationsOrchestratorTests() + { + _fixture = new ConsiderationsOrchestratorTestsFixture(); + } + + [Fact] + public async Task WhenUpdated__ShouldCallUpdateDraftVacancy() + { + _fixture + .WithThingsToConsider("has a value") + .Setup(); + + var thingsToConsiderEditModel = new ConsiderationsEditModel + { + EmployerAccountId = _fixture.Vacancy.EmployerAccountId, + VacancyId = _fixture.Vacancy.Id, + ThingsToConsider = "has a new value" + }; + + await _fixture.PostConsiderationsEditModelAsync(thingsToConsiderEditModel); + + _fixture.VerifyUpdateDraftVacancyAsyncIsCalled(); + } + + [Fact] + public async Task WhenThingsToConsiderIsUpdated_ShouldFlagThingsToConsiderFieldIndicator() + { + _fixture + .WithThingsToConsider("has a value") + .Setup(); + + var thingsToConsiderEditModel = new ConsiderationsEditModel + { + EmployerAccountId = _fixture.Vacancy.EmployerAccountId, + VacancyId = _fixture.Vacancy.Id, + ThingsToConsider = "has a new value" + }; + + await _fixture.PostConsiderationsEditModelAsync(thingsToConsiderEditModel); + + _fixture.VerifyEmployerReviewFieldIndicators(FieldIdentifiers.ThingsToConsider, true); + } + + public class ConsiderationsOrchestratorTestsFixture + { + private const VacancyRuleSet ValidationRules = VacancyRuleSet.ThingsToConsider; + public VacancyUser User { get; } + public Vacancy Vacancy { get; } + public ConsiderationsOrchestrator Sut {get; private set;} + + public ConsiderationsOrchestratorTestsFixture() + { + MockClient = new Mock(); + MockRecruitVacancyClient = new Mock(); + + User = VacancyOrchestratorTestData.GetVacancyUser(); + Vacancy = VacancyOrchestratorTestData.GetPart1CompleteVacancy(); + } + + public ConsiderationsOrchestratorTestsFixture WithThingsToConsider(string thingsToConsider) + { + Vacancy.ThingsToConsider = thingsToConsider; + return this; + } + + public void Setup() + { + MockRecruitVacancyClient.Setup(x => x.GetVacancyAsync(Vacancy.Id)).ReturnsAsync(Vacancy); + MockRecruitVacancyClient.Setup(x => x.Validate(Vacancy, ValidationRules)).Returns(new EntityValidationResult()); + MockRecruitVacancyClient.Setup(x => x.UpdateDraftVacancyAsync(It.IsAny(), User)); + MockRecruitVacancyClient.Setup(x => x.UpdateEmployerProfileAsync(It.IsAny(), User)); + + Sut = new ConsiderationsOrchestrator(Mock.Of>(), MockClient.Object, MockRecruitVacancyClient.Object, Mock.Of()); + } + + public async Task PostConsiderationsEditModelAsync(ConsiderationsEditModel model) + { + await Sut.PostConsiderationsEditModelAsync(model, User); + } + + public void VerifyEmployerReviewFieldIndicators(string fieldIdentifier, bool value) + { + Vacancy.EmployerReviewFieldIndicators + .Where(p => p.FieldIdentifier == fieldIdentifier).FirstOrDefault() + .Should().NotBeNull().And + .Match((x) => x.IsChangeRequested == value); + } + + public void VerifyUpdateDraftVacancyAsyncIsCalled() + { + MockRecruitVacancyClient.Verify(x => x.UpdateDraftVacancyAsync(Vacancy, User), Times.Once); + } + + public Mock MockClient { get; set; } + public Mock MockRecruitVacancyClient { get; set; } + } + } +} diff --git a/src/Employer/UnitTests/Employer.Web/Orchestrators/Part2/EmployerContactDetailsOrchestratorTests.cs b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part2/EmployerContactDetailsOrchestratorTests.cs new file mode 100644 index 0000000000..4a0e38c96f --- /dev/null +++ b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part2/EmployerContactDetailsOrchestratorTests.cs @@ -0,0 +1,156 @@ +using System.Linq; +using System.Threading.Tasks; +using Esfa.Recruit.Employer.UnitTests.Employer.Web.HardMocks; +using Esfa.Recruit.Employer.Web.Orchestrators.Part2; +using Esfa.Recruit.Employer.Web.ViewModels; +using Esfa.Recruit.Shared.Web.Mappers; +using Esfa.Recruit.Shared.Web.Services; +using Esfa.Recruit.Vacancies.Client.Application.Validation; +using Esfa.Recruit.Vacancies.Client.Domain.Entities; +using Esfa.Recruit.Vacancies.Client.Infrastructure.Client; +using FluentAssertions; +using Microsoft.Extensions.Logging; +using Moq; +using Xunit; + +namespace Esfa.Recruit.Employer.UnitTests.Employer.Web.Orchestrators.Part2 +{ + public class EmployerContactDetailsOrchestratorTests + { + private EmployerContactDetailsOrchestratorTestsFixture _fixture; + + public EmployerContactDetailsOrchestratorTests() + { + _fixture = new EmployerContactDetailsOrchestratorTestsFixture(); + } + + [Theory] + [InlineData("has a new value", "has a value", "has a value")] + [InlineData("has a value", "has a new value", "has a value")] + [InlineData("has a value", "has a value", "has a new value")] + [InlineData("has a new value", "has a new value", "has a new value")] + public async Task WhenEmployerContactNameIsUpdated__ShouldCallUpdateDraftVacancy(string employerContactName, string employerContactEmail, string employerContactPhone) + { + _fixture + .WithEmployerContactName("has a value") + .WithEmployerContactEmail("has a value") + .WithEmployerContactPhone("has a value") + .Setup(); + + var employerContactDetailsEditModel = new EmployerContactDetailsEditModel + { + EmployerAccountId = _fixture.Vacancy.EmployerAccountId, + VacancyId = _fixture.Vacancy.Id, + EmployerContactName = employerContactName, + EmployerContactEmail = employerContactEmail, + EmployerContactPhone = employerContactPhone + }; + + await _fixture.PostEmployerContactDetailsEditModelAsync(employerContactDetailsEditModel); + + _fixture.VerifyUpdateDraftVacancyAsyncIsCalled(); + } + + [Theory] + [InlineData("has a new value", "has a value", "has a value")] + [InlineData("has a value", "has a new value", "has a value")] + [InlineData("has a value", "has a value", "has a new value")] + [InlineData("has a new value", "has a new value", "has a new value")] + public async Task WhenEmployerContactNameIsUpdated_ShouldFlagEmployerContactFieldIndicator(string employerContactName, string employerContactEmail, string employerContactPhone) + { + _fixture + .WithEmployerContactName("has a value") + .WithEmployerContactEmail("has a value") + .WithEmployerContactPhone("has a value") + .Setup(); + + var employerContactDetailsEditModel = new EmployerContactDetailsEditModel + { + EmployerAccountId = _fixture.Vacancy.EmployerAccountId, + VacancyId = _fixture.Vacancy.Id, + EmployerContactName = employerContactName, + EmployerContactEmail = employerContactEmail, + EmployerContactPhone = employerContactPhone + }; + + await _fixture.PostEmployerContactDetailsEditModelAsync(employerContactDetailsEditModel); + + _fixture.VerifyEmployerReviewFieldIndicators(FieldIdentifiers.EmployerContact, true); + } + + public class EmployerContactDetailsOrchestratorTestsFixture + { + private const VacancyRuleSet ValidationRules = VacancyRuleSet.EmployerContactDetails; + public VacancyUser User { get; } + public Vacancy Vacancy { get; } + public EmployerContactDetailsOrchestrator Sut {get; private set;} + + public EmployerContactDetailsOrchestratorTestsFixture() + { + MockClient = new Mock(); + MockRecruitVacancyClient = new Mock(); + + User = VacancyOrchestratorTestData.GetVacancyUser(); + Vacancy = VacancyOrchestratorTestData.GetPart1CompleteVacancy(); + } + + public EmployerContactDetailsOrchestratorTestsFixture WithEmployerContactName(string employerContactName) + { + if (Vacancy.EmployerContact == null) + Vacancy.EmployerContact = new ContactDetail(); + + Vacancy.EmployerContact.Name = employerContactName; + return this; + } + + public EmployerContactDetailsOrchestratorTestsFixture WithEmployerContactEmail(string employerContactEmail) + { + if (Vacancy.EmployerContact == null) + Vacancy.EmployerContact = new ContactDetail(); + + Vacancy.EmployerContact.Email = employerContactEmail; + return this; + } + + public EmployerContactDetailsOrchestratorTestsFixture WithEmployerContactPhone(string employerContactPhone) + { + if (Vacancy.EmployerContact == null) + Vacancy.EmployerContact = new ContactDetail(); + + Vacancy.EmployerContact.Phone = employerContactPhone; + return this; + } + + public void Setup() + { + MockRecruitVacancyClient.Setup(x => x.GetVacancyAsync(Vacancy.Id)).ReturnsAsync(Vacancy); + MockRecruitVacancyClient.Setup(x => x.Validate(Vacancy, ValidationRules)).Returns(new EntityValidationResult()); + MockRecruitVacancyClient.Setup(x => x.UpdateDraftVacancyAsync(It.IsAny(), User)); + MockRecruitVacancyClient.Setup(x => x.UpdateEmployerProfileAsync(It.IsAny(), User)); + + Sut = new EmployerContactDetailsOrchestrator(MockClient.Object, MockRecruitVacancyClient.Object, Mock.Of>(), Mock.Of()); + } + + public async Task PostEmployerContactDetailsEditModelAsync(EmployerContactDetailsEditModel model) + { + await Sut.PostEmployerContactDetailsEditModelAsync(model, User); + } + + public void VerifyEmployerReviewFieldIndicators(string fieldIdentifier, bool value) + { + Vacancy.EmployerReviewFieldIndicators + .Where(p => p.FieldIdentifier == fieldIdentifier).FirstOrDefault() + .Should().NotBeNull().And + .Match((x) => x.IsChangeRequested == value); + } + + public void VerifyUpdateDraftVacancyAsyncIsCalled() + { + MockRecruitVacancyClient.Verify(x => x.UpdateDraftVacancyAsync(Vacancy, User), Times.Once); + } + + public Mock MockClient { get; set; } + public Mock MockRecruitVacancyClient { get; set; } + } + } +} diff --git a/src/Employer/UnitTests/Employer.Web/Orchestrators/Part2/QualificationsOrchestratorTests.cs b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part2/QualificationsOrchestratorTests.cs new file mode 100644 index 0000000000..67da323bc5 --- /dev/null +++ b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part2/QualificationsOrchestratorTests.cs @@ -0,0 +1,381 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Esfa.Recruit.Employer.UnitTests.Employer.Web.HardMocks; +using Esfa.Recruit.Employer.Web.Orchestrators.Part2; +using Esfa.Recruit.Employer.Web.RouteModel; +using Esfa.Recruit.Employer.Web.ViewModels; +using Esfa.Recruit.Shared.Web.Mappers; +using Esfa.Recruit.Shared.Web.Services; +using Esfa.Recruit.Shared.Web.ViewModels.Qualifications; +using Esfa.Recruit.Vacancies.Client.Application.Validation; +using Esfa.Recruit.Vacancies.Client.Domain.Entities; +using Esfa.Recruit.Vacancies.Client.Infrastructure.Client; +using FluentAssertions; +using Microsoft.Extensions.Logging; +using Moq; +using Newtonsoft.Json; +using Xunit; + +namespace Esfa.Recruit.Employer.UnitTests.Employer.Web.Orchestrators.Part2 +{ + public class QualificationsOrchestratorTests + { + public QualificationsOrchestratorTests() + { + } + + [Fact] + public async Task WhenQualificationIsAdded_ShouldAddQualificationToDraftVacancy() + { + var fixture = new QualificationsOrchestratorTestsFixture(); + fixture + .WithQualfication("Mathematics", "A", "GCSE", QualificationWeighting.Desired) + .Setup(); + + var qualification = new Qualification + { + Subject = "English", + Grade = "A", + QualificationType = "GCSE", + Weighting = QualificationWeighting.Essential + }; + + await fixture.PostQualificationEditModelForAddAsync( + new VacancyRouteModel + { + EmployerAccountId = fixture.Vacancy.EmployerAccountId, + VacancyId = fixture.Vacancy.Id + }, + FromQualification(qualification)); + + fixture.VerifyAddQualificationToDraftVacancy(2, qualification); + } + + [Fact] + public async Task WhenQualificationIsAdded_ShouldCallUpdateDraftVacancyAsync() + { + var fixture = new QualificationsOrchestratorTestsFixture(); + fixture + .WithQualfication("Mathematics", "A", "GCSE", QualificationWeighting.Desired) + .Setup(); + + var qualification = new Qualification + { + Subject = "English", + Grade = "A", + QualificationType = "GCSE", + Weighting = QualificationWeighting.Essential + }; + + await fixture.PostQualificationEditModelForAddAsync( + new VacancyRouteModel + { + EmployerAccountId = fixture.Vacancy.EmployerAccountId, + VacancyId = fixture.Vacancy.Id + }, + FromQualification(qualification)); + + fixture.VerifyUpdateDraftVacancyAsyncIsCalled(); + } + + [Fact] + public async Task WhenQualificationIsAdded_ShouldFlagQualificationsFieldIndicator() + { + var fixture = new QualificationsOrchestratorTestsFixture(); + fixture + .WithQualfication("Mathematics", "A", "GCSE", QualificationWeighting.Desired) + .Setup(); + + var qualification = new Qualification + { + Subject = "English", + Grade = "A", + QualificationType = "GCSE", + Weighting = QualificationWeighting.Essential + }; + + await fixture.PostQualificationEditModelForAddAsync( + new VacancyRouteModel + { + EmployerAccountId = fixture.Vacancy.EmployerAccountId, + VacancyId = fixture.Vacancy.Id + }, + FromQualification(qualification)); + + fixture.VerifyEmployerReviewFieldIndicators(FieldIdentifiers.Qualifications, true); + } + + [Fact] + public async Task WhenQualificationIsUpdated_ShouldUpdateQualificationForDraftVacancy() + { + var fixture = new QualificationsOrchestratorTestsFixture(); + fixture + .WithQualfication("Mathematics", "A", "GCSE", QualificationWeighting.Desired) + .WithQualfication("English", "B", "GCSE", QualificationWeighting.Essential) + .WithQualfication("Science", "C", "GCSE", QualificationWeighting.Essential) + .Setup(); + + var qualification = new Qualification + { + Subject = "English", + Grade = "C", + QualificationType = "GCSE", + Weighting = QualificationWeighting.Desired + }; + + await fixture.PostQualificationEditModelForEditAsync( + new VacancyRouteModel + { + EmployerAccountId = fixture.Vacancy.EmployerAccountId, + VacancyId = fixture.Vacancy.Id + }, + FromQualification(qualification), + index: 1); + + fixture.VerifyUpdateQualificationForDraftVacancy(qualification); + } + + [Fact] + public async Task WhenQualificationIsUpdated_ShouldCallUpdateDraftVacancyAsync() + { + var fixture = new QualificationsOrchestratorTestsFixture(); + fixture + .WithQualfication("Mathematics", "A", "GCSE", QualificationWeighting.Desired) + .WithQualfication("English", "B", "GCSE", QualificationWeighting.Essential) + .WithQualfication("Science", "C", "GCSE", QualificationWeighting.Essential) + .Setup(); + + var qualification = new Qualification + { + Subject = "English", + Grade = "C", + QualificationType = "GCSE", + Weighting = QualificationWeighting.Desired + }; + + await fixture.PostQualificationEditModelForEditAsync( + new VacancyRouteModel + { + EmployerAccountId = fixture.Vacancy.EmployerAccountId, + VacancyId = fixture.Vacancy.Id + }, + FromQualification(qualification), + index: 1); + + fixture.VerifyUpdateDraftVacancyAsyncIsCalled(); + } + + [Fact] + public async Task WhenQualificationIsUpdated_ShouldFlagQualificationsFieldIndicator() + { + var fixture = new QualificationsOrchestratorTestsFixture(); + fixture + .WithQualfication("Mathematics", "A", "GCSE", QualificationWeighting.Desired) + .WithQualfication("English", "B", "GCSE", QualificationWeighting.Essential) + .WithQualfication("Science", "C", "GCSE", QualificationWeighting.Essential) + .Setup(); + + var qualification = new Qualification + { + Subject = "English", + Grade = "C", + QualificationType = "GCSE", + Weighting = QualificationWeighting.Desired + }; + + await fixture.PostQualificationEditModelForEditAsync( + new VacancyRouteModel + { + EmployerAccountId = fixture.Vacancy.EmployerAccountId, + VacancyId = fixture.Vacancy.Id + }, + FromQualification(qualification), + index: 1); + + fixture.VerifyEmployerReviewFieldIndicators(FieldIdentifiers.Qualifications, true); + } + + [Fact] + public async Task WhenQualificationIsDeleted_ShouldRemoveQualificationFromDraftVacancy() + { + var fixture = new QualificationsOrchestratorTestsFixture(); + fixture + .WithQualfication("Mathematics", "A", "GCSE", QualificationWeighting.Desired) + .WithQualfication("English", "B", "GCSE", QualificationWeighting.Essential) + .WithQualfication("Science", "C", "GCSE", QualificationWeighting.Essential) + .Setup(); + + var qualification = new Qualification + { + Subject = "English", + Grade = "C", + QualificationType = "GCSE", + Weighting = QualificationWeighting.Desired + }; + + await fixture.DeleteQualificationAsync( + new VacancyRouteModel + { + EmployerAccountId = fixture.Vacancy.EmployerAccountId, + VacancyId = fixture.Vacancy.Id + }, + index: 1); + + fixture.VerifyRemoveQualificationFromDraftVacancy(qualification); + } + + [Fact] + public async Task WhenQualificationIsDeleted_ShouldCallUpdateDraftVacancyAsync() + { + var fixture = new QualificationsOrchestratorTestsFixture(); + fixture + .WithQualfication("Mathematics", "A", "GCSE", QualificationWeighting.Desired) + .WithQualfication("English", "B", "GCSE", QualificationWeighting.Essential) + .WithQualfication("Science", "C", "GCSE", QualificationWeighting.Essential) + .Setup(); + + await fixture.DeleteQualificationAsync( + new VacancyRouteModel + { + EmployerAccountId = fixture.Vacancy.EmployerAccountId, + VacancyId = fixture.Vacancy.Id + }, + index: 1); + + fixture.VerifyUpdateDraftVacancyAsyncIsCalled(); + } + + [Fact] + public async Task WhenQualificationIsDeleted_ShouldFlagQualificationsFieldIndicator() + { + var fixture = new QualificationsOrchestratorTestsFixture(); + fixture + .WithQualfication("Mathematics", "A", "GCSE", QualificationWeighting.Desired) + .WithQualfication("English", "B", "GCSE", QualificationWeighting.Essential) + .WithQualfication("Science", "C", "GCSE", QualificationWeighting.Essential) + .Setup(); + + await fixture.DeleteQualificationAsync( + new VacancyRouteModel + { + EmployerAccountId = fixture.Vacancy.EmployerAccountId, + VacancyId = fixture.Vacancy.Id + }, + index: 1); + + fixture.VerifyEmployerReviewFieldIndicators(FieldIdentifiers.Qualifications, true); + } + + private QualificationEditModel FromQualification(Qualification qualification) + { + return new QualificationEditModel + { + Subject = qualification.Subject, + Grade = qualification.Grade, + QualificationType = qualification.QualificationType, + Weighting = qualification.Weighting + }; + } + + public class QualificationsOrchestratorTestsFixture + { + public VacancyUser User { get; } + public Vacancy Vacancy { get; private set; } + public QualificationsOrchestrator Sut {get; private set;} + + public QualificationsOrchestratorTestsFixture() + { + MockClient = new Mock(); + MockRecruitVacancyClient = new Mock(); + + User = VacancyOrchestratorTestData.GetVacancyUser(); + Vacancy = VacancyOrchestratorTestData.GetPart1CompleteVacancy(); + } + + public QualificationsOrchestratorTestsFixture WithQualfication(string subject, string grade, string qualificationType, QualificationWeighting weighting) + { + if ((Vacancy.Qualifications == null)) + Vacancy.Qualifications = new List(); + + Vacancy.Qualifications.Add(new Qualification + { + Subject = subject, + Grade = grade, + QualificationType = qualificationType, + Weighting = weighting + }); + + return this; + } + + public void Setup() + { + MockRecruitVacancyClient.Setup(x => x.GetVacancyAsync(Vacancy.Id)).ReturnsAsync(Vacancy); + MockRecruitVacancyClient.Setup(x => x.GetCandidateQualificationsAsync()).ReturnsAsync(new List() { "GCSE", "A-LEVEL" }); + MockRecruitVacancyClient.Setup(x => x.ValidateQualification(It.IsAny())).Returns(new EntityValidationResult()); + + MockRecruitVacancyClient.Setup(x => x.UpdateDraftVacancyAsync(It.IsAny(), User)) + .Callback((vacancy, user) => { Vacancy = vacancy; }) + .Returns(Task.FromResult(0)); + + MockRecruitVacancyClient.Setup(x => x.UpdateEmployerProfileAsync(It.IsAny(), User)); + + Sut = new QualificationsOrchestrator(MockClient.Object, MockRecruitVacancyClient.Object, Mock.Of>(), Mock.Of()); + } + + public async Task PostQualificationEditModelForAddAsync(VacancyRouteModel vacancyRouteModel, QualificationEditModel model) + { + await Sut.PostQualificationEditModelForAddAsync(vacancyRouteModel, model, User); + } + + public async Task PostQualificationEditModelForEditAsync(VacancyRouteModel vacancyRouteModel, QualificationEditModel model, int index) + { + await Sut.PostQualificationEditModelForEditAsync(vacancyRouteModel, model, User, index); + } + + public async Task DeleteQualificationAsync(VacancyRouteModel vacancyRouteModel, int index) + { + await Sut.DeleteQualificationAsync(vacancyRouteModel, index, User); + } + + public void VerifyAddQualificationToDraftVacancy(int count, Qualification qualification) + { + Vacancy.Qualifications.Should().HaveCount(count); + ContainEquivalentOf(Vacancy.Qualifications, qualification).Should().BeTrue(); + } + + public void VerifyUpdateQualificationForDraftVacancy(Qualification qualification) + { + ContainEquivalentOf(Vacancy.Qualifications, qualification).Should().BeTrue(); + } + + public void VerifyRemoveQualificationFromDraftVacancy(Qualification qualification) + { + ContainEquivalentOf(Vacancy.Qualifications, qualification).Should().BeFalse(); + } + + public void VerifyEmployerReviewFieldIndicators(string fieldIdentifier, bool value) + { + Vacancy.EmployerReviewFieldIndicators + .Where(p => p.FieldIdentifier == fieldIdentifier).FirstOrDefault() + .Should().NotBeNull().And + .Match((x) => x.IsChangeRequested == value); + } + + public void VerifyUpdateDraftVacancyAsyncIsCalled() + { + MockRecruitVacancyClient.Verify(x => x.UpdateDraftVacancyAsync(Vacancy, User), Times.Once); + } + + private bool ContainEquivalentOf(List collection, T expected) + { + // the FluentAssertions ContainEquivalentOf does not have a negated equivalent + return collection.Any(p => JsonConvert.SerializeObject(p).Equals(JsonConvert.SerializeObject(expected))); + } + + public Mock MockClient { get; set; } + public Mock MockRecruitVacancyClient { get; set; } + } + } +} diff --git a/src/Employer/UnitTests/Employer.Web/Orchestrators/Part2/ShortDescriptionOrchestratorTests.cs b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part2/ShortDescriptionOrchestratorTests.cs new file mode 100644 index 0000000000..06a6cd99ed --- /dev/null +++ b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part2/ShortDescriptionOrchestratorTests.cs @@ -0,0 +1,120 @@ +using System.Linq; +using System.Threading.Tasks; +using Esfa.Recruit.Employer.UnitTests.Employer.Web.HardMocks; +using Esfa.Recruit.Employer.Web.Orchestrators.Part2; +using Esfa.Recruit.Employer.Web.ViewModels; +using Esfa.Recruit.Employer.Web.ViewModels.Part2.ShortDescription; +using Esfa.Recruit.Shared.Web.Mappers; +using Esfa.Recruit.Shared.Web.Services; +using Esfa.Recruit.Vacancies.Client.Application.Validation; +using Esfa.Recruit.Vacancies.Client.Domain.Entities; +using Esfa.Recruit.Vacancies.Client.Infrastructure.Client; +using FluentAssertions; +using Microsoft.Extensions.Logging; +using Moq; +using Xunit; + +namespace Esfa.Recruit.Employer.UnitTests.Employer.Web.Orchestrators.Part2 +{ + public class ShortDescriptionOrchestratorTests + { + private ShortDescriptionOrchestratorTestsFixture _fixture; + + public ShortDescriptionOrchestratorTests() + { + _fixture = new ShortDescriptionOrchestratorTestsFixture(); + } + + [Fact] + public async Task WhenUpdated__ShouldCallUpdateDraftVacancy() + { + _fixture + .WithShortDescription("has a value") + .Setup(); + + var shortDescriptionEditModel = new ShortDescriptionEditModel + { + EmployerAccountId = _fixture.Vacancy.EmployerAccountId, + VacancyId = _fixture.Vacancy.Id, + ShortDescription = "has a new value" + }; + + await _fixture.PostShortDescriptionEditModelAsync(shortDescriptionEditModel); + + _fixture.VerifyUpdateDraftVacancyAsyncIsCalled(); + } + + [Fact] + public async Task WhenShortDescriptionIsUpdated_ShouldFlagThingsToConsiderFieldIndicator() + { + _fixture + .WithShortDescription("has a value") + .Setup(); + + var shortDescriptionEditModel = new ShortDescriptionEditModel + { + EmployerAccountId = _fixture.Vacancy.EmployerAccountId, + VacancyId = _fixture.Vacancy.Id, + ShortDescription = "has a new value" + }; + + await _fixture.PostShortDescriptionEditModelAsync(shortDescriptionEditModel); + + _fixture.VerifyEmployerReviewFieldIndicators(FieldIdentifiers.ShortDescription, true); + } + + public class ShortDescriptionOrchestratorTestsFixture + { + private const VacancyRuleSet ValidationRules = VacancyRuleSet.ShortDescription; + public VacancyUser User { get; } + public Vacancy Vacancy { get; } + public ShortDescriptionOrchestrator Sut {get; private set;} + + public ShortDescriptionOrchestratorTestsFixture() + { + MockClient = new Mock(); + MockRecruitVacancyClient = new Mock(); + + User = VacancyOrchestratorTestData.GetVacancyUser(); + Vacancy = VacancyOrchestratorTestData.GetPart1CompleteVacancy(); + } + + public ShortDescriptionOrchestratorTestsFixture WithShortDescription(string shortDescription) + { + Vacancy.ShortDescription = shortDescription; + return this; + } + + public void Setup() + { + MockRecruitVacancyClient.Setup(x => x.GetVacancyAsync(Vacancy.Id)).ReturnsAsync(Vacancy); + MockRecruitVacancyClient.Setup(x => x.Validate(Vacancy, ValidationRules)).Returns(new EntityValidationResult()); + MockRecruitVacancyClient.Setup(x => x.UpdateDraftVacancyAsync(It.IsAny(), User)); + MockRecruitVacancyClient.Setup(x => x.UpdateEmployerProfileAsync(It.IsAny(), User)); + + Sut = new ShortDescriptionOrchestrator(MockClient.Object, MockRecruitVacancyClient.Object, Mock.Of>(), Mock.Of()); + } + + public async Task PostShortDescriptionEditModelAsync(ShortDescriptionEditModel model) + { + await Sut.PostShortDescriptionEditModelAsync(model, User); + } + + public void VerifyEmployerReviewFieldIndicators(string fieldIdentifier, bool value) + { + Vacancy.EmployerReviewFieldIndicators + .Where(p => p.FieldIdentifier == fieldIdentifier).FirstOrDefault() + .Should().NotBeNull().And + .Match((x) => x.IsChangeRequested == value); + } + + public void VerifyUpdateDraftVacancyAsyncIsCalled() + { + MockRecruitVacancyClient.Verify(x => x.UpdateDraftVacancyAsync(Vacancy, User), Times.Once); + } + + public Mock MockClient { get; set; } + public Mock MockRecruitVacancyClient { get; set; } + } + } +} diff --git a/src/Employer/UnitTests/Employer.Web/Orchestrators/Part2/SkillOrchestratorTests.cs b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part2/SkillOrchestratorTests.cs index 217eaeeaac..c2c4df1e87 100644 --- a/src/Employer/UnitTests/Employer.Web/Orchestrators/Part2/SkillOrchestratorTests.cs +++ b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part2/SkillOrchestratorTests.cs @@ -1,14 +1,15 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Esfa.Recruit.Employer.UnitTests.Employer.Web.HardMocks; using Esfa.Recruit.Employer.Web.Orchestrators.Part2; using Esfa.Recruit.Employer.Web.RouteModel; -using Esfa.Recruit.Employer.Web.Services; +using Esfa.Recruit.Employer.Web.ViewModels.Part2.Skills; +using Esfa.Recruit.Shared.Web.Mappers; using Esfa.Recruit.Shared.Web.Services; +using Esfa.Recruit.Vacancies.Client.Application.Validation; using Esfa.Recruit.Vacancies.Client.Domain.Entities; using Esfa.Recruit.Vacancies.Client.Infrastructure.Client; -using Esfa.Recruit.Vacancies.Client.Infrastructure.ReferenceData.Skills; using FluentAssertions; using Microsoft.Extensions.Logging; using Moq; @@ -18,216 +19,360 @@ namespace Esfa.Recruit.Employer.UnitTests.Employer.Web.Orchestrators.Part2 { public class SkillOrchestratorTests { - private const string TestEmployerAccountId = "ABC"; - private readonly Mock _mockClient; - private readonly Mock _mockVacancyClient; - - private readonly SkillsOrchestrator _orchestrator; - private readonly Vacancy _testVacancy; - private readonly VacancyRouteModel _testRouteModel = new VacancyRouteModel { EmployerAccountId = TestEmployerAccountId, VacancyId = Guid.NewGuid() }; - - public SkillOrchestratorTests() - { - var mockLogger = new Mock>(); - var candidateSkills = GetBaseSkills(); - _mockClient = new Mock(); - _mockVacancyClient = new Mock(); - _orchestrator = new SkillsOrchestrator(_mockClient.Object, _mockVacancyClient.Object, mockLogger.Object, Mock.Of()); - _testVacancy = GetTestVacancy(); - - _mockVacancyClient.Setup(x => x.GetCandidateSkillsAsync()).ReturnsAsync(candidateSkills); - } - [Fact] public async Task WhenNoSkillsSaved_ShouldReturnListOfAllBasicSkillsUnchecked() { - _mockVacancyClient.Setup(x => x.GetVacancyAsync(It.IsAny())).ReturnsAsync(_testVacancy); - - var result = await _orchestrator.GetSkillsViewModelAsync(_testRouteModel); - - // Count that all 18 base skills are there - result.Column1Checkboxes.Count(x => x.Selected == false).Should().Be(9); - result.Column2Checkboxes.Count(x => x.Selected == false).Should().Be(8); - } - - [Fact] - public async Task WhenSingleBaseSkillSaved_ShouldReturnListOfAllBasicSkillsWithSkillSelected() - { - _testVacancy.Skills = new List { "Logical" }; // Set the saved skill + var fixture = new SkillsOrchestratorTestsFixture(); + fixture + .Setup(); - _mockVacancyClient.Setup(x => x.GetVacancyAsync(It.IsAny())).ReturnsAsync(_testVacancy); + var vacancyRouteModel = new VacancyRouteModel + { + EmployerAccountId = fixture.Vacancy.EmployerAccountId, + VacancyId = fixture.Vacancy.Id + }; - var result = await _orchestrator.GetSkillsViewModelAsync(_testRouteModel); + var skillsViewModel = await fixture.GetSkillsViewModelAsync(vacancyRouteModel); - var allCheckboxes = result.Column1Checkboxes.Union(result.Column2Checkboxes); - allCheckboxes.Where(x => x.Selected).Should().HaveCount(1); + fixture.VerifyAllBasicSkillsAreUnchecked(skillsViewModel); } - [Fact] - public async Task WhenMultipleBaseSkillsSaved_ShouldReturnListOfAllBasicSkillsWithSkillsSelected() + [Theory] + [InlineData(new string[] { "Logical" }, 1)] + [InlineData(new string[] { "Logical", "Patience" }, 2)] + public async Task WhenBaseSkillSaved_ShouldReturnListOfAllBasicSkillsWithSkillsSelected(string[] selectedSkills, int selectedCount) { - _testVacancy.Skills = new List { "Logical", "Patience" }; // Set the saved skill + var fixture = new SkillsOrchestratorTestsFixture(); + fixture + .WithSelectedSkills(selectedSkills) + .Setup(); - _mockVacancyClient.Setup(x => x.GetVacancyAsync(It.IsAny())).ReturnsAsync(_testVacancy); + var vacancyRouteModel = new VacancyRouteModel + { + EmployerAccountId = fixture.Vacancy.EmployerAccountId, + VacancyId = fixture.Vacancy.Id + }; - var result = await _orchestrator.GetSkillsViewModelAsync(_testRouteModel); + var skillsViewModel = await fixture.GetSkillsViewModelAsync(vacancyRouteModel); - var allCheckboxes = result.Column1Checkboxes.Union(result.Column2Checkboxes); - allCheckboxes.Where(x => x.Selected).Should().HaveCount(2); + fixture.VerifySelectedSkillsCount(skillsViewModel, selectedCount); } [Fact] public async Task WhenCustomSkillHasBeenSaved_ShouldReturnCustomSkillsSelectedInLastItemInSecondColumn() { - _testVacancy.Skills = new List { "Custom1" }; // Set the saved skill - - _mockVacancyClient.Setup(x => x.GetVacancyAsync(It.IsAny())).ReturnsAsync(_testVacancy); + var fixture = new SkillsOrchestratorTestsFixture(); + + var customSkill = "Custom1"; + fixture + .WithSelectedSkills(new string[] { customSkill }) + .Setup(); + + var vacancyRouteModel = new VacancyRouteModel + { + EmployerAccountId = fixture.Vacancy.EmployerAccountId, + VacancyId = fixture.Vacancy.Id + }; - var result = await _orchestrator.GetSkillsViewModelAsync(_testRouteModel); + var skillsViewModel = await fixture.GetSkillsViewModelAsync(vacancyRouteModel); - var lastCol2Item = result.Column2Checkboxes.Last(); - lastCol2Item.Name.Should().Be("Custom1"); - lastCol2Item.Selected.Should().BeTrue(); + fixture.VerifyColumn2CheckboxesItemSelected(skillsViewModel, customSkill, 8); } [Fact] public async Task WhenMultipleCustomSkillsSaved_ShouldReturnTheCustomSkillsInAlternateColumnsStaringWithColumn2() { - _testVacancy.Skills = new List { "Custom1", "Custom2", "Custom3" }; // Set the saved skill + var fixture = new SkillsOrchestratorTestsFixture(); + + var customSkill1 = "Custom1"; + var customSkill2 = "Custom2"; + var customSkill3 = "Custom3"; - _mockVacancyClient.Setup(x => x.GetVacancyAsync(It.IsAny())).ReturnsAsync(_testVacancy); + fixture + .WithSelectedSkills(new string[] { customSkill1, customSkill2, customSkill3 }) + .Setup(); - var result = await _orchestrator.GetSkillsViewModelAsync(_testRouteModel); + var vacancyRouteModel = new VacancyRouteModel + { + EmployerAccountId = fixture.Vacancy.EmployerAccountId, + VacancyId = fixture.Vacancy.Id + }; + + var skillsViewModel = await fixture.GetSkillsViewModelAsync(vacancyRouteModel); - result.Column2Checkboxes.FindIndex(x => x.Name == "Custom1").Should().Be(8); // 9th item in list - result.Column1Checkboxes.FindIndex(x => x.Name == "Custom2").Should().Be(9); // 10th item in list - result.Column2Checkboxes.FindIndex(x => x.Name == "Custom3").Should().Be(9); // 10th item in list + fixture.VerifyColumn2CheckboxesItemSelected(skillsViewModel, customSkill1, 8); + fixture.VerifyColumn1CheckboxesItemSelected(skillsViewModel, customSkill2, 9); + fixture.VerifyColumn2CheckboxesItemSelected(skillsViewModel, customSkill3, 9); } [Fact] public async Task WhenCustomDraftSkillHasBeenAdded_ShouldBeAddedToColumn2() { - _testVacancy.Skills = new List(); // No selected skills already persisted + var fixture = new SkillsOrchestratorTestsFixture(); - var draftSkills = new [] { "1-Draft1" }; + var draftSkill = "Draft1"; + fixture + .WithSelectedSkills(new string[] { }) + .Setup(); - _mockVacancyClient.Setup(x => x.GetVacancyAsync(It.IsAny())).ReturnsAsync(_testVacancy); + var vacancyRouteModel = new VacancyRouteModel + { + EmployerAccountId = fixture.Vacancy.EmployerAccountId, + VacancyId = fixture.Vacancy.Id + }; - var result = await _orchestrator.GetSkillsViewModelAsync(_testRouteModel, draftSkills); + var skillsViewModel = await fixture.GetSkillsViewModelAsync(vacancyRouteModel, new string[] { "1-" + draftSkill}); - result.Column2Checkboxes.FindIndex(x => x.Name == "Draft1").Should().Be(8); // 9th item in list + fixture.VerifyColumn2CheckboxesItemSelected(skillsViewModel, draftSkill, 8); } [Fact] public async Task WhenCustomeDraftSkillsAdded_ShouldBeAddedToAlternateColumnsStartingWithColumn2() { - _testVacancy.Skills = new List(); // No selected skills already persisted + var fixture = new SkillsOrchestratorTestsFixture(); - var draftSkills = new [] { "1-Draft1", "2-Draft2", "3-Draft3" }; + var draftSkill1 = "Draft1"; + var draftSkill2 = "Draft2"; + var draftSkill3 = "Draft3"; - _mockVacancyClient.Setup(x => x.GetVacancyAsync(It.IsAny())).ReturnsAsync(_testVacancy); + fixture + .WithSelectedSkills(new string[] { }) + .Setup(); - var result = await _orchestrator.GetSkillsViewModelAsync(_testRouteModel, draftSkills); + var vacancyRouteModel = new VacancyRouteModel + { + EmployerAccountId = fixture.Vacancy.EmployerAccountId, + VacancyId = fixture.Vacancy.Id + }; + + var skillsViewModel = await fixture.GetSkillsViewModelAsync(vacancyRouteModel, + new string[] { "1-" + draftSkill1, "2-" + draftSkill2, "3-" + draftSkill3 }); - result.Column2Checkboxes.FindIndex(x => x.Name == "Draft1").Should().Be(8); // 9th item in list - result.Column1Checkboxes.FindIndex(x => x.Name == "Draft2").Should().Be(9); // 10th item in list - result.Column2Checkboxes.FindIndex(x => x.Name == "Draft3").Should().Be(9); // 10th item in list + fixture.VerifyColumn2CheckboxesItemSelected(skillsViewModel, draftSkill1, 8); + fixture.VerifyColumn1CheckboxesItemSelected(skillsViewModel, draftSkill2, 9); + fixture.VerifyColumn2CheckboxesItemSelected(skillsViewModel, draftSkill3, 9); } [Fact] public async Task WhenCustomDraftSkillsAddedAndBaseSkillSelected_ShouldBeAddedToAlternateColumnsStartingWithColumn2() { - _testVacancy.Skills = new List(); // No selected skills already persisted + var fixture = new SkillsOrchestratorTestsFixture(); - var draftSkills = new [] { "1-Draft1", "Patience", "2-Draft2", "3-Draft3" }; + var draftSkill1 = "Draft1"; + var draftSkill2 = "Draft2"; + var draftSkill3 = "Draft3"; - _mockVacancyClient.Setup(x => x.GetVacancyAsync(It.IsAny())).ReturnsAsync(_testVacancy); + fixture + .WithSelectedSkills(new string[] { }) + .Setup(); + + var vacancyRouteModel = new VacancyRouteModel + { + EmployerAccountId = fixture.Vacancy.EmployerAccountId, + VacancyId = fixture.Vacancy.Id + }; - var result = await _orchestrator.GetSkillsViewModelAsync(_testRouteModel, draftSkills); + var skillsViewModel = await fixture.GetSkillsViewModelAsync(vacancyRouteModel, + new string[] { "1-" + draftSkill1, "Patience", "2-" + draftSkill2, "3-" + draftSkill3 }); - result.Column2Checkboxes.FindIndex(x => x.Name == "Draft1").Should().Be(8); // 9th item in list - result.Column1Checkboxes.FindIndex(x => x.Name == "Draft2").Should().Be(9); // 10th item in list - result.Column2Checkboxes.FindIndex(x => x.Name == "Draft3").Should().Be(9); // 10th item in list + fixture.VerifyColumn2CheckboxesItemSelected(skillsViewModel, draftSkill1, 8); + fixture.VerifyColumn1CheckboxesItemSelected(skillsViewModel, draftSkill2, 9); + fixture.VerifyColumn2CheckboxesItemSelected(skillsViewModel, draftSkill3, 9); } - + [Fact] - public async Task WhenCustomDraftSkillsAdded_ShouldIncludeIndicatorOfItsOrder() + public async Task WhenCustomDraftSkillsHaveBeenAdded_ShouldBeOrderedByTheirPrefix() { - _testVacancy.Skills = new List(); // No selected skills already persisted + var fixture = new SkillsOrchestratorTestsFixture(); + + var draftSkill1 = "Draft1"; + var draftSkill2 = "Draft2"; + var draftSkill3 = "Draft3"; - var draftSkills = new [] { "2-Draft2", "3-Draft3", "1-Draft1" }; + fixture + .WithSelectedSkills(new string[] { }) + .Setup(); - _mockVacancyClient.Setup(x => x.GetVacancyAsync(It.IsAny())).ReturnsAsync(_testVacancy); + var vacancyRouteModel = new VacancyRouteModel + { + EmployerAccountId = fixture.Vacancy.EmployerAccountId, + VacancyId = fixture.Vacancy.Id + }; - var result = await _orchestrator.GetSkillsViewModelAsync(_testRouteModel, draftSkills); + var skillsViewModel = await fixture.GetSkillsViewModelAsync(vacancyRouteModel, + new string[] { "2-" + draftSkill2, "3-" + draftSkill3, "1-" + draftSkill1 }); - result.Column2Checkboxes.Single(x => x.Name == "Draft1").Value.Should().Be("1-Draft1"); - result.Column1Checkboxes.Single(x => x.Name == "Draft2").Value.Should().Be("2-Draft2"); - result.Column2Checkboxes.Single(x => x.Name == "Draft3").Value.Should().Be("3-Draft3"); + fixture.VerifyColumn2CheckboxesItemSelected(skillsViewModel, draftSkill1, 8); + fixture.VerifyColumn1CheckboxesItemSelected(skillsViewModel, draftSkill2, 9); + fixture.VerifyColumn2CheckboxesItemSelected(skillsViewModel, draftSkill3, 9); } - - [Fact] - public async Task WhenCustomDraftSkillsHaveBeenAdded_ShouldBeOrderedByTheirPrefix() + + [Theory] + [InlineData(new string[] { }, new string[] { }, false)] + [InlineData(new string[] { }, new string[] { "Organisation skills" }, true)] + [InlineData(new string[] { "Organisation skills" }, new string[] { "Organisation skills" }, false)] + [InlineData(new string[] { "Organisation skills" }, new string[] { }, true)] + public async Task WhenSkillsAreUpdated_ShouldFlagSkillsFieldIndicator(string[] currentlySelectedSkills, string[] newSelectedSkills, bool fieldIndicatorSet) { - _testVacancy.Skills = new List(); // No selected skills already persisted + var fixture = new SkillsOrchestratorTestsFixture(); + fixture + .WithSelectedSkills(currentlySelectedSkills) + .Setup(); - var draftSkills = new [] { "2-Draft2", "3-Draft3", "1-Draft1" }; + var vacancyRouteModel = new VacancyRouteModel + { + EmployerAccountId = fixture.Vacancy.EmployerAccountId, + VacancyId = fixture.Vacancy.Id + }; - _mockVacancyClient.Setup(x => x.GetVacancyAsync(It.IsAny())).ReturnsAsync(_testVacancy); + var skillsEditModel = new SkillsEditModel + { + Skills = newSelectedSkills.ToList(), + AddCustomSkillAction = null, + AddCustomSkillName = null + }; - var result = await _orchestrator.GetSkillsViewModelAsync(_testRouteModel, draftSkills); + await fixture.PostSkillsEditModelAsync(vacancyRouteModel, skillsEditModel); - result.Column2Checkboxes.FindIndex(x => x.Name == "Draft1").Should().Be(8); // 9th item in list - result.Column1Checkboxes.FindIndex(x => x.Name == "Draft2").Should().Be(9); // 10th item in list - result.Column2Checkboxes.FindIndex(x => x.Name == "Draft3").Should().Be(9); // 10th item in list + fixture.VerifyEmployerReviewFieldIndicators(FieldIdentifiers.Skills, fieldIndicatorSet); } - private static Vacancy GetTestVacancy() + [Theory] + [InlineData(new string[] { }, new string[] { })] + [InlineData(new string[] { }, new string[] { "Organisation skills" })] + [InlineData(new string[] { "Organisation skills" }, new string[] { "Organisation skills" })] + [InlineData(new string[] { "Organisation skills" }, new string[] { })] + public async Task WhenSkillsAreUpdated_ShouldCallUpdateDraftVacancyAsync(string[] currentlySelectedSkills, string[] newSelectedSkills) { - return new Vacancy + var fixture = new SkillsOrchestratorTestsFixture(); + fixture + .WithSelectedSkills(currentlySelectedSkills) + .Setup(); + + var vacancyRouteModel = new VacancyRouteModel { - EmployerAccountId = TestEmployerAccountId, - Title = "Test Title", - NumberOfPositions = 1, - ShortDescription = "Test Short Description", - EmployerLocation = new Address - { - Postcode = "AB1 2XZ" - }, - ProgrammeId = "2", - Wage = new Wage - { - Duration = 1, - WageType = WageType.NationalMinimumWage - }, - LegalEntityName = "legal name", - EmployerNameOption = EmployerNameOption.RegisteredName, - StartDate = DateTime.Now + EmployerAccountId = fixture.Vacancy.EmployerAccountId, + VacancyId = fixture.Vacancy.Id + }; + + var skillsEditModel = new SkillsEditModel + { + Skills = newSelectedSkills.ToList(), + AddCustomSkillAction = null, + AddCustomSkillName = null }; + + await fixture.PostSkillsEditModelAsync(vacancyRouteModel, skillsEditModel); + + fixture.VerifyUpdateDraftVacancyAsyncIsCalled(); } - private static List GetBaseSkills() + public class SkillsOrchestratorTestsFixture { - return new List - { - "Communication skills", - "IT skills", - "Attention to detail", - "Organisation skills", - "Customer care skills", - "Problem solving skills", - "Presentation skills", - "Administrative skills", - "Number skills", - "Analytical skills", - "Logical", - "Team working", - "Creative", - "Initiative", - "Non judgemental", - "Patience", - "Physical fitness" - }; + private const VacancyRuleSet ValidationRules = VacancyRuleSet.Skills; + public VacancyUser User { get; } + public Vacancy Vacancy { get; } + public SkillsOrchestrator Sut { get; private set; } + + public SkillsOrchestratorTestsFixture() + { + MockClient = new Mock(); + MockRecruitVacancyClient = new Mock(); + + User = VacancyOrchestratorTestData.GetVacancyUser(); + Vacancy = VacancyOrchestratorTestData.GetPart1CompleteVacancy(); + } + + public SkillsOrchestratorTestsFixture WithSelectedSkills(string[] selectedSkills) + { + Vacancy.Skills = selectedSkills.ToList(); + return this; + } + + public void Setup() + { + MockRecruitVacancyClient.Setup(x => x.GetVacancyAsync(Vacancy.Id)).ReturnsAsync(Vacancy); + MockRecruitVacancyClient.Setup(x => x.GetCandidateSkillsAsync()).ReturnsAsync(GetBasicSkills()); + MockRecruitVacancyClient.Setup(x => x.Validate(Vacancy, ValidationRules)).Returns(new EntityValidationResult()); + MockRecruitVacancyClient.Setup(x => x.UpdateDraftVacancyAsync(It.IsAny(), User)); + MockRecruitVacancyClient.Setup(x => x.UpdateEmployerProfileAsync(It.IsAny(), User)); + + Sut = new SkillsOrchestrator(MockClient.Object, MockRecruitVacancyClient.Object, Mock.Of>(), Mock.Of()); + } + + public async Task GetSkillsViewModelAsync(VacancyRouteModel vacancyRouteModel, string[] draftSkills = null) + { + return await Sut.GetSkillsViewModelAsync(vacancyRouteModel, draftSkills); + } + + public async Task PostSkillsEditModelAsync(VacancyRouteModel vacancyRouteModel, SkillsEditModel model) + { + await Sut.PostSkillsEditModelAsync(vacancyRouteModel, model, User); + } + + public void VerifyAllBasicSkillsAreUnchecked(SkillsViewModel skillsViewModel) + { + var allCheckboxes = skillsViewModel.Column1Checkboxes.Union(skillsViewModel.Column2Checkboxes); + allCheckboxes.Where(x => x.Selected == false).Should().HaveCount(GetBasicSkills().Count); + } + + internal void VerifySelectedSkillsCount(SkillsViewModel skillsViewModel, int count) + { + var allCheckboxes = skillsViewModel.Column1Checkboxes.Union(skillsViewModel.Column2Checkboxes); + allCheckboxes.Where(x => x.Selected).Should().HaveCount(count); + } + + public SkillsOrchestratorTestsFixture VerifyColumn2CheckboxesItemSelected(SkillsViewModel skillsViewModel, string customSkill, int index) + { + skillsViewModel.Column2Checkboxes.FindIndex(x => x.Name == customSkill && x.Selected).Should().Be(index); + return this; + } + + public SkillsOrchestratorTestsFixture VerifyColumn1CheckboxesItemSelected(SkillsViewModel skillsViewModel, string customSkill, int index) + { + skillsViewModel.Column1Checkboxes.FindIndex(x => x.Name == customSkill && x.Selected).Should().Be(index); + return this; + } + + public void VerifyEmployerReviewFieldIndicators(string fieldIdentifier, bool value) + { + Vacancy.EmployerReviewFieldIndicators + .Where(p => p.FieldIdentifier == fieldIdentifier).Single() + .Should().NotBeNull().And + .Match((x) => x.IsChangeRequested == value); + } + + public void VerifyUpdateDraftVacancyAsyncIsCalled() + { + MockRecruitVacancyClient.Verify(x => x.UpdateDraftVacancyAsync(Vacancy, User), Times.Once); + } + + private static List GetBasicSkills() + { + return new List + { + "Communication skills", + "IT skills", + "Attention to detail", + "Organisation skills", + "Customer care skills", + "Problem solving skills", + "Presentation skills", + "Administrative skills", + "Number skills", + "Analytical skills", + "Logical", + "Team working", + "Creative", + "Initiative", + "Non judgemental", + "Patience", + "Physical fitness" + }; + } + + public Mock MockClient { get; set; } + public Mock MockRecruitVacancyClient { get; set; } } } } diff --git a/src/Employer/UnitTests/Employer.Web/Orchestrators/Part2/VacancyDescriptionOrchestratorTests.cs b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part2/VacancyDescriptionOrchestratorTests.cs new file mode 100644 index 0000000000..dea2af123e --- /dev/null +++ b/src/Employer/UnitTests/Employer.Web/Orchestrators/Part2/VacancyDescriptionOrchestratorTests.cs @@ -0,0 +1,163 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Esfa.Recruit.Employer.UnitTests.Employer.Web.HardMocks; +using Esfa.Recruit.Employer.Web.Orchestrators.Part2; +using Esfa.Recruit.Employer.Web.ViewModels; +using Esfa.Recruit.Employer.Web.ViewModels.Part2.ShortDescription; +using Esfa.Recruit.Employer.Web.ViewModels.Part2.VacancyDescription; +using Esfa.Recruit.Shared.Web.Mappers; +using Esfa.Recruit.Shared.Web.Services; +using Esfa.Recruit.Vacancies.Client.Application.Validation; +using Esfa.Recruit.Vacancies.Client.Domain.Entities; +using Esfa.Recruit.Vacancies.Client.Infrastructure.Client; +using FluentAssertions; +using Microsoft.Extensions.Logging; +using Moq; +using Xunit; + +namespace Esfa.Recruit.Employer.UnitTests.Employer.Web.Orchestrators.Part2 +{ + public class VacancyDescriptionOrchestratorTests + { + private VacancyDescriptionOrchestratorTestsFixture _fixture; + + public VacancyDescriptionOrchestratorTests() + { + _fixture = new VacancyDescriptionOrchestratorTestsFixture(); + } + + [Theory] + [InlineData("has a new value", "has a value", "has a value")] + [InlineData("has a value", "has a new value", "has a value")] + [InlineData("has a value", "has a value", "has a new value")] + [InlineData("has a new value", "has a new value", "has a new value")] + public async Task WhenUpdated__ShouldCallUpdateDraftVacancy(string description, string trainingDescription, string outcomeDescription) + { + _fixture + .WithDescription("has a value") + .WithTrainingDescription("has a value") + .WithOutcomeDescription("has a value") + .Setup(); + + var vacancyDescriptionEditModel = new VacancyDescriptionEditModel + { + EmployerAccountId = _fixture.Vacancy.EmployerAccountId, + VacancyId = _fixture.Vacancy.Id, + VacancyDescription = description, + TrainingDescription = trainingDescription, + OutcomeDescription = outcomeDescription + }; + + await _fixture.PostVacancyDescriptionEditModelAsync(vacancyDescriptionEditModel); + + _fixture.VerifyUpdateDraftVacancyAsyncIsCalled(); + } + + [Theory] + [InlineData("has a new value", "has a value", "has a value", new string[] { FieldIdentifiers.VacancyDescription }, new string[] { FieldIdentifiers.TrainingDescription, FieldIdentifiers.OutcomeDescription })] + [InlineData("has a value", "has a new value", "has a value", new string[] { FieldIdentifiers.TrainingDescription }, new string[] { FieldIdentifiers.VacancyDescription, FieldIdentifiers.OutcomeDescription })] + [InlineData("has a value", "has a value", "has a new value", new string[] { FieldIdentifiers.OutcomeDescription }, new string[] { FieldIdentifiers.VacancyDescription, FieldIdentifiers.Training})] + [InlineData("has a new value", "has a new value", "has a new value", new string[] { FieldIdentifiers.VacancyDescription, FieldIdentifiers.TrainingDescription , FieldIdentifiers.OutcomeDescription }, new string[] { })] + public async Task WhenShortDescriptionIsUpdated_ShouldFlagFieldIndicators(string description, string trainingDescription, string outcomeDescription, string[] setFieldIndicators, string[] unsetFieldIndicators) + { + _fixture + .WithDescription("has a value") + .WithTrainingDescription("has a value") + .WithOutcomeDescription("has a value") + .Setup(); + + var vacancyDescriptionEditModel = new VacancyDescriptionEditModel + { + EmployerAccountId = _fixture.Vacancy.EmployerAccountId, + VacancyId = _fixture.Vacancy.Id, + VacancyDescription = description, + TrainingDescription = trainingDescription, + OutcomeDescription = outcomeDescription + }; + + await _fixture.PostVacancyDescriptionEditModelAsync(vacancyDescriptionEditModel); + + _fixture.VerifyEmployerReviewFieldIndicators(setFieldIndicators, unsetFieldIndicators); + } + + public class VacancyDescriptionOrchestratorTestsFixture + { + private const VacancyRuleSet ValidationRules = VacancyRuleSet.Description | VacancyRuleSet.TrainingDescription | VacancyRuleSet.OutcomeDescription; + public VacancyUser User { get; } + public Vacancy Vacancy { get; } + public VacancyDescriptionOrchestrator Sut {get; private set;} + + public VacancyDescriptionOrchestratorTestsFixture() + { + MockClient = new Mock(); + MockRecruitVacancyClient = new Mock(); + + User = VacancyOrchestratorTestData.GetVacancyUser(); + Vacancy = VacancyOrchestratorTestData.GetPart1CompleteVacancy(); + } + + public VacancyDescriptionOrchestratorTestsFixture WithDescription(string description) + { + Vacancy.Description = description; + return this; + } + + public VacancyDescriptionOrchestratorTestsFixture WithTrainingDescription(string trainingDescription) + { + Vacancy.TrainingDescription = trainingDescription; + return this; + } + + public VacancyDescriptionOrchestratorTestsFixture WithOutcomeDescription(string outcomeDescription) + { + Vacancy.OutcomeDescription = outcomeDescription; + return this; + } + + public void Setup() + { + MockRecruitVacancyClient.Setup(x => x.GetVacancyAsync(Vacancy.Id)).ReturnsAsync(Vacancy); + MockRecruitVacancyClient.Setup(x => x.Validate(Vacancy, ValidationRules)).Returns(new EntityValidationResult()); + MockRecruitVacancyClient.Setup(x => x.UpdateDraftVacancyAsync(It.IsAny(), User)); + MockRecruitVacancyClient.Setup(x => x.UpdateEmployerProfileAsync(It.IsAny(), User)); + + Sut = new VacancyDescriptionOrchestrator(MockClient.Object, MockRecruitVacancyClient.Object, Mock.Of>(), Mock.Of()); + } + + public async Task PostVacancyDescriptionEditModelAsync(VacancyDescriptionEditModel model) + { + await Sut.PostVacancyDescriptionEditModelAsync(model, User); + } + + public void VerifyEmployerReviewFieldIndicators(string[] setFieldIdentifiers, string[] unsetFieldIdentifiers) + { + foreach (var fieldIdentifier in setFieldIdentifiers) + { + VerifyEmployerReviewFieldIndicators(fieldIdentifier, true); + } + + foreach (var fieldIdentifier in unsetFieldIdentifiers) + { + VerifyEmployerReviewFieldIndicators(fieldIdentifier, false); + } + } + + public void VerifyEmployerReviewFieldIndicators(string fieldIdentifier, bool value) + { + Vacancy.EmployerReviewFieldIndicators + .Where(p => p.FieldIdentifier == fieldIdentifier).Single() + .Should().NotBeNull().And + .Match((x) => x.IsChangeRequested == value); + } + + public void VerifyUpdateDraftVacancyAsyncIsCalled() + { + MockRecruitVacancyClient.Verify(x => x.UpdateDraftVacancyAsync(Vacancy, User), Times.Once); + } + + public Mock MockClient { get; set; } + public Mock MockRecruitVacancyClient { get; set; } + } + } +} diff --git a/src/Provider/Provider.Web/wwwroot/css/application.css b/src/Provider/Provider.Web/wwwroot/css/application.css index 99595cf653..ab6fb494c9 100644 --- a/src/Provider/Provider.Web/wwwroot/css/application.css +++ b/src/Provider/Provider.Web/wwwroot/css/application.css @@ -1227,4 +1227,145 @@ a[rel="external"]:hover:after { display: none; } -/*** End of cookie banner ***/ \ No newline at end of file +/*** End of cookie banner ***/ + + +/* govuk notification banner */ + +.govuk-notification-banner { + font-family: "nta", Arial, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-weight: 400; + font-size: 16px; + line-height: 1.25; + margin-bottom: 30px; + border: 5px solid #1d70b8; + background-color: #1d70b8; +} + +@media (min-width: 48.0625em) { + .govuk-notification-banner { + margin-bottom: 50px; + } +} + +.govuk-notification-banner__header { + padding: 2px 15px 5px; + border-bottom: 1px solid transparent; +} + +@media (min-width: 48.0625em) { + .govuk-notification-banner__header { + padding: 2px 20px 5px; + } +} + +.govuk-notification-banner__title { + font-family: "nta", Arial, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-weight: 700; + font-size: 16px; + line-height: 1.25; + margin: 0; + padding: 0; + color: #fff; +} + +@media (min-width: 48.0625em) { + .govuk-notification-banner__title { + font-size: 19px; + line-height: 1.35; + } +} + +.govuk-notification-banner__content { + color: #0b0c0c; + padding: 15px; + background-color: #fff; +} + +@media (min-width: 48.0625em) { + .govuk-notification-banner__content { + padding: 20px; + } +} + +.govuk-notification-banner__content > * { + max-width: 605px; +} + +.govuk-notification-banner__content>:last-child { + margin-bottom: 0; +} + +.govuk-notification-banner__heading { + font-family: "nta", Arial, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-weight: 700; + font-size: 18px; + line-height: 1.1; + margin: 0 0 15px 0; + padding: 0; +} + +@media (min-width: 48.0625em) { + .govuk-notification-banner__heading { + font-size: 24px; + line-height: 1.25; + } +} + +.govuk-notification-banner__link { + font-family: "nta", Arial, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-decoration: underline; + color: #1d70b8; +} + +.govuk-notification-banner__link:link { + color: #1d70b8; +} + +.govuk-notification-banner__link:hover { + color: #003078; +} + +.govuk-notification-banner__link:focus { + outline: 3px solid transparent; + color: #0b0c0c; + background-color: #fd0; + -webkit-box-shadow: 0 -2px #fd0, 0 4px #0b0c0c; + box-shadow: 0 -2px #fd0, 0 4px #0b0c0c; + text-decoration: none; +} + +/* Additional govuk tag colours */ + +.govuk-tag--purple { + color: #3d2375; + background: #dbd5e9; +} + +.govuk-tag--red { + color: #942514; + background: #f6d7d2; +} + +.govuk-tag--blue { + color: #144e81; + background: #d2e2f1; +} + +.govuk-tag--green { + color: #005a30; + background: #cce2d8; +} + +.govuk-tag--orange { + color: #6e3619; + background: #fcd6c3; +} \ No newline at end of file diff --git a/src/Shared/Recruit.Vacancies.Client/Application/CommandHandlers/CloneVacancyCommandHandler.cs b/src/Shared/Recruit.Vacancies.Client/Application/CommandHandlers/CloneVacancyCommandHandler.cs index 667843d291..fc6836cf06 100644 --- a/src/Shared/Recruit.Vacancies.Client/Application/CommandHandlers/CloneVacancyCommandHandler.cs +++ b/src/Shared/Recruit.Vacancies.Client/Application/CommandHandlers/CloneVacancyCommandHandler.cs @@ -90,6 +90,8 @@ private Vacancy CreateClone(CloneVacancyCommand message, Vacancy vacancy) clone.TransferInfo = null; clone.ReviewByUser = null; clone.ReviewDate = null; + clone.EmployerReviewFieldIndicators = null; + clone.EmployerRejectedReason = null; return clone; } diff --git a/src/Shared/Recruit.Vacancies.Client/Domain/Entities/EmployerReviewFieldIndicator.cs b/src/Shared/Recruit.Vacancies.Client/Domain/Entities/EmployerReviewFieldIndicator.cs new file mode 100644 index 0000000000..151d549850 --- /dev/null +++ b/src/Shared/Recruit.Vacancies.Client/Domain/Entities/EmployerReviewFieldIndicator.cs @@ -0,0 +1,8 @@ +namespace Esfa.Recruit.Vacancies.Client.Domain.Entities +{ + public class EmployerReviewFieldIndicator + { + public string FieldIdentifier { get; set; } + public bool IsChangeRequested { get; set; } + } +} diff --git a/src/Shared/Recruit.Vacancies.Client/Domain/Entities/Vacancy.cs b/src/Shared/Recruit.Vacancies.Client/Domain/Entities/Vacancy.cs index ee7f9495ac..200c029774 100644 --- a/src/Shared/Recruit.Vacancies.Client/Domain/Entities/Vacancy.cs +++ b/src/Shared/Recruit.Vacancies.Client/Domain/Entities/Vacancy.cs @@ -41,6 +41,8 @@ public class Vacancy public Address EmployerLocation { get; set; } public string EmployerName { get; set; } public EmployerNameOption? EmployerNameOption { get; set; } + public List EmployerReviewFieldIndicators { get; set; } + public string EmployerRejectedReason { get; set; } public string LegalEntityName { get; set; } public string EmployerWebsiteUrl { get; set; } public GeoCodeMethod? GeoCodeMethod { get; set; } diff --git a/src/Shared/UnitTests/Vacancies.Client/Application/CommandHandlers/CloneVacancyCommandHandlerTests.cs b/src/Shared/UnitTests/Vacancies.Client/Application/CommandHandlers/CloneVacancyCommandHandlerTests.cs index f7745aced0..26472f85ac 100644 --- a/src/Shared/UnitTests/Vacancies.Client/Application/CommandHandlers/CloneVacancyCommandHandlerTests.cs +++ b/src/Shared/UnitTests/Vacancies.Client/Application/CommandHandlers/CloneVacancyCommandHandlerTests.cs @@ -150,6 +150,8 @@ private static void AssertKnownProperties(Vacancy original, Vacancy clone) {nameof(Vacancy.EmployerLocation), (o, c, s) => AssertProperty(o, c, s, CloneAssertType.Cloned)}, {nameof(Vacancy.EmployerName), (o, c, s) => AssertProperty(o, c, s, CloneAssertType.Cloned)}, {nameof(Vacancy.EmployerNameOption), (o, c, s) => AssertProperty(o, c, s, CloneAssertType.Cloned)}, + {nameof(Vacancy.EmployerReviewFieldIndicators), (o, c, s) => AssertProperty(o, c, s, CloneAssertType.IsNull)}, + {nameof(Vacancy.EmployerRejectedReason), (o, c, s) => AssertProperty(o, c, s, CloneAssertType.IsNull)}, {nameof(Vacancy.LegalEntityName), (o, c, s) => AssertProperty(o, c, s, CloneAssertType.Cloned)}, {nameof(Vacancy.EmployerWebsiteUrl), (o, c, s) => AssertProperty(o, c, s, CloneAssertType.Cloned)}, {nameof(Vacancy.GeoCodeMethod), (o, c, s) => AssertProperty(o, c, s, CloneAssertType.Cloned)},