diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/DataStore/Postgres/Models/Person.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/DataStore/Postgres/Models/Person.cs index 2faf9183c..f4883334d 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Core/DataStore/Postgres/Models/Person.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/DataStore/Postgres/Models/Person.cs @@ -322,6 +322,13 @@ public static bool ValidateInductionData( return true; } + public bool InductionStatusManagedByCpd(DateOnly now) + { + var sevenYearsAgo = now.AddYears(-7); + return InductionCompletedDate is not null + && InductionCompletedDate < sevenYearsAgo; + } + private static void AssertInductionChangeIsValid( InductionStatus status, DateOnly? startDate, diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Models/InductionStatus.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Models/InductionStatus.cs index 40d3eb519..c2f1c7bd4 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Models/InductionStatus.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Models/InductionStatus.cs @@ -27,6 +27,12 @@ public static class InductionStatusRegistry public static IReadOnlyCollection All => _info.Values.ToArray(); + public static IReadOnlyCollection ValidStatusChangesWhenManagedByCpd => + _info + .Where(s => s.Key is InductionStatus.RequiredToComplete or InductionStatus.Exempt or InductionStatus.FailedInWales) + .Select(s => s.Value) + .ToArray(); + public static string GetName(this InductionStatus status) => _info[status].Name; public static string GetTitle(this InductionStatus status) => _info[status].Title; diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/EditInduction/EditInductionState.cs b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/EditInduction/EditInductionState.cs index a170d5e5e..c939f2591 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/EditInduction/EditInductionState.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/EditInduction/EditInductionState.cs @@ -11,11 +11,13 @@ public class EditInductionState : IRegisterJourney appendUniqueKey: true); public InductionStatus InductionStatus { get; set; } + public InductionStatus CurrentInductionStatus { get; set; } public DateOnly? StartDate { get; set; } public DateOnly? CompletedDate { get; set; } public InductionExemptionReasons? ExemptionReasons { get; set; } public string? ChangeReason { get; set; } public InductionJourneyPage? JourneyStartPage { get; set; } + public bool RecordManagedInCpd { get; set; } public bool Initialized { get; set; } @@ -27,7 +29,8 @@ public async Task EnsureInitializedAsync(TrsDbContext dbContext, Guid personId, } var person = await dbContext.Persons .SingleAsync(q => q.PersonId == personId); - InductionStatus = person!.InductionStatus; + + CurrentInductionStatus = person!.InductionStatus; if (JourneyStartPage == null) { JourneyStartPage = startPage; diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/EditInduction/Status.cshtml b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/EditInduction/Status.cshtml index ecd6a2881..f5b80cba2 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/EditInduction/Status.cshtml +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/EditInduction/Status.cshtml @@ -1,7 +1,7 @@ @page "/persons/{PersonId}/edit-induction/status" @model TeachingRecordSystem.SupportUi.Pages.Persons.PersonDetail.EditInduction.StatusModel @{ - ViewBag.Title = "Edit status: " + Model.InductionStatus.GetName(); + ViewBag.Title = "What is their induction status?"; } @section BeforeContent { @@ -9,10 +9,23 @@ }

@ViewBag.Title

- +@if (!String.IsNullOrEmpty(Model.StatusWarningMessage)) +{ + @Model.StatusWarningMessage +}
+ Induction - @Model.PersonName
+ + + + @foreach (var inductionStatus in Model.StatusChoices) + { + @inductionStatus.Title + } + +
Continue diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/EditInduction/Status.cshtml.cs b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/EditInduction/Status.cshtml.cs index 9901837d0..c525ce7eb 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/EditInduction/Status.cshtml.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/EditInduction/Status.cshtml.cs @@ -1,16 +1,49 @@ +using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using TeachingRecordSystem.Core.DataStore.Postgres; +using TeachingRecordSystem.SupportUi.ValidationAttributes; namespace TeachingRecordSystem.SupportUi.Pages.Persons.PersonDetail.EditInduction; [Journey(JourneyNames.EditInduction), ActivatesJourney, RequireJourneyInstance] public class StatusModel : CommonJourneyPage { + private const string InductionIsManagedByCpdWarning = "To change this teacher’s induction status to passed, failed, or in progress, use the Record inductions as an appropriate body service."; + protected TrsDbContext _dbContext; + protected IClock _clock; + protected bool InductionStatusManagedByCpd; [BindProperty] + [Display(Name = "Select a status")] + [NotEqual(InductionStatus.None, ErrorMessage = "Select a status")] public InductionStatus InductionStatus { get; set; } + public InductionStatus CurrentInductionStatus { get; set; } + public string? PersonName { get; set; } + public IEnumerable StatusChoices + { + get + { + return InductionStatusManagedByCpd ? + InductionStatusRegistry.ValidStatusChangesWhenManagedByCpd.Where(i => i.Value != CurrentInductionStatus) + : InductionStatusRegistry.All.ToArray()[1..].Where(i => i.Value != CurrentInductionStatus); + } + } + public string? StatusWarningMessage + { + get + { + if (InductionStatusManagedByCpd) + { + return InductionIsManagedByCpdWarning; + } + else + { + return null; + } + } + } public InductionJourneyPage NextPage { @@ -24,20 +57,39 @@ _ when InductionStatus.RequiresStartDate() => InductionJourneyPage.StartDate, }; } } + public string BackLink => LinkGenerator.PersonInduction(PersonId); - public StatusModel(TrsLinkGenerator linkGenerator, TrsDbContext dbContext) : base(linkGenerator) + public StatusModel(TrsLinkGenerator linkGenerator, TrsDbContext dbContext, IClock clock) : base(linkGenerator) { _dbContext = dbContext; + _clock = clock; } - public void OnGet() + public async Task OnGetAsync() { + var person = await _dbContext.Persons.SingleAsync(q => q.PersonId == PersonId); + InductionStatusManagedByCpd = person.InductionStatusManagedByCpd(_clock.Today); InductionStatus = JourneyInstance!.State.InductionStatus; + CurrentInductionStatus = JourneyInstance!.State.CurrentInductionStatus; + await JourneyInstance!.UpdateStateAsync(state => + { + if (state.CurrentInductionStatus == InductionStatus.None) + { + state.CurrentInductionStatus = CurrentInductionStatus; + } + }); } public async Task OnPostAsync() { + if (!ModelState.IsValid) + { + var person = await _dbContext.Persons.SingleAsync(q => q.PersonId == PersonId); + InductionStatusManagedByCpd = person.InductionStatusManagedByCpd(_clock.Today); + CurrentInductionStatus = JourneyInstance!.State.CurrentInductionStatus; + return this.PageWithErrors(); + } await JourneyInstance!.UpdateStateAsync(state => { state.InductionStatus = InductionStatus; @@ -54,6 +106,9 @@ public override async Task OnPageHandlerExecutionAsync(PageHandlerExecutingConte { await JourneyInstance!.State.EnsureInitializedAsync(_dbContext, PersonId, InductionJourneyPage.Status); + var personInfo = context.HttpContext.GetCurrentPersonFeature(); + PersonId = personInfo.PersonId; + PersonName = personInfo.Name; await next(); } } diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/Induction.cshtml.cs b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/Induction.cshtml.cs index 62335778a..4972c4512 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/Induction.cshtml.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/Induction.cshtml.cs @@ -9,7 +9,7 @@ namespace TeachingRecordSystem.SupportUi.Pages.Persons.PersonDetail; public class InductionModel(TrsDbContext dbContext, ICrmQueryDispatcher crmQueryDispatcher, IClock clock) : PageModel { private const string NoQualifiedTeacherStatusWarning = "This teacher has not been awarded QTS and is therefore ineligible for induction."; - private const string InductionIsManagedByCpdWarning = "To change a teacher\u2019s induction status to passed, failed, or in progress, use the Record inductions as an appropriate body service."; + private const string InductionIsManagedByCpdWarning = "To change this teacher’s induction status to passed, failed, or in progress, use the Record inductions as an appropriate body service."; private bool _statusIsManagedByCpd; private bool _teacherHoldsQualifiedTeacherStatus; @@ -68,7 +68,7 @@ public async Task OnGetAsync() StartDate = person!.InductionStartDate; CompletionDate = person!.InductionCompletedDate; ExemptionReasons = person!.InductionExemptionReasons; - _statusIsManagedByCpd = StatusManagedByCpdRule(person!.CpdInductionStatus, person.CpdInductionCompletedDate); + _statusIsManagedByCpd = person.InductionStatusManagedByCpd(clock.Today); _teacherHoldsQualifiedTeacherStatus = TeacherHoldsQualifiedTeacherStatusRule(result?.Contact.dfeta_QTSDate); } @@ -76,12 +76,4 @@ private bool TeacherHoldsQualifiedTeacherStatusRule(DateTime? qtsDate) { return qtsDate is null; } - - private bool StatusManagedByCpdRule(InductionStatus? status, DateOnly? inductionCompletedDate) - { - var sevenYearsAgo = clock.Today.AddYears(-7); - return status is not null - && inductionCompletedDate is not null - && inductionCompletedDate < sevenYearsAgo; - } } diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/ValidationAttributes/NotEqualAttribute.cs b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/ValidationAttributes/NotEqualAttribute.cs new file mode 100644 index 000000000..c4b2879c9 --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/ValidationAttributes/NotEqualAttribute.cs @@ -0,0 +1,23 @@ +namespace TeachingRecordSystem.SupportUi.ValidationAttributes; + +using System.ComponentModel.DataAnnotations; + +public class NotEqualAttribute : ValidationAttribute +{ + private readonly object _notAllowedValue; + + public NotEqualAttribute(object notAllowedValue) + { + _notAllowedValue = notAllowedValue; + } + + protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) + { + if (value != null && value.Equals(_notAllowedValue)) + { + return new ValidationResult(ErrorMessage ?? $"The value cannot be {_notAllowedValue}."); + } + + return ValidationResult.Success; + } +} diff --git a/TeachingRecordSystem/tests/TeachingRecordSystem.Core.Tests/DataStore/Postgres/Models/PersonTests.cs b/TeachingRecordSystem/tests/TeachingRecordSystem.Core.Tests/DataStore/Postgres/Models/PersonTests.cs index 3ac8b9c31..30fe4374d 100644 --- a/TeachingRecordSystem/tests/TeachingRecordSystem.Core.Tests/DataStore/Postgres/Models/PersonTests.cs +++ b/TeachingRecordSystem/tests/TeachingRecordSystem.Core.Tests/DataStore/Postgres/Models/PersonTests.cs @@ -228,4 +228,32 @@ public void TrySetWelshInductionStatus_StatusIsAtLowerPriorityStatus_UpdatesStat Assert.Equal(expectedStatus, person.InductionStatus); Assert.Equal(expectedExemptionReasons, person.InductionExemptionReasons); } + + [Theory] + [InlineData(-3, false)] + [InlineData(-7, true)] + public void InductionManagedByCpd_ReturnsTrue(int yearsSinceCompleted, bool expected) + { + // Arrange + var dateTimeCompleted = Clock.UtcNow.AddYears(yearsSinceCompleted).AddDays(-1); + var dateCompleted = Clock.Today.AddYears(yearsSinceCompleted).AddDays(-1); + var person = new Person + { + PersonId = Guid.NewGuid(), + CreatedOn = dateTimeCompleted, + UpdatedOn = dateTimeCompleted, + Trn = "1234567", + FirstName = "Joe", + MiddleName = "", + LastName = "Bloggs", + DateOfBirth = new(1990, 1, 1), + }; + person.SetInductionStatus(InductionStatus.Passed, dateCompleted, dateCompleted, InductionExemptionReasons.None, SystemUser.SystemUserId, Clock.UtcNow, out _); + + // Act + var result = person.InductionStatusManagedByCpd(Clock.Today); + + // Assert + Assert.Equal(expected, result); + } } diff --git a/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Persons/PersonDetail/EditInduction/EditInductionStateBuilder.cs b/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Persons/PersonDetail/EditInduction/EditInductionStateBuilder.cs new file mode 100644 index 000000000..f07829318 --- /dev/null +++ b/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Persons/PersonDetail/EditInduction/EditInductionStateBuilder.cs @@ -0,0 +1,50 @@ +using TeachingRecordSystem.SupportUi.Pages.Persons.PersonDetail.EditInduction; + +namespace TeachingRecordSystem.SupportUi.Tests.PageTests.Persons.PersonDetail.EditInduction; + +public class EditInductionStateBuilder +{ + private InductionStatus InductionStatus { get; set; } + private InductionStatus CurrentInductionStatus { get; set; } + private DateOnly? StartDate { get; set; } + private DateOnly? CompletedDate { get; set; } + private InductionExemptionReasons? ExemptionReasons { get; set; } + private string? ChangeReason { get; set; } + private InductionJourneyPage? JourneyStartPage { get; set; } + private bool RecordManagedInCpd { get; set; } + private bool Initialized { get; set; } + + public EditInductionStateBuilder WithInitialisedState(InductionStatus? currentInductionStatus, InductionJourneyPage startPage) + { + this.Initialized = true; + JourneyStartPage = startPage; + CurrentInductionStatus = currentInductionStatus ?? InductionStatus.None; + return this; + } + + public EditInductionStateBuilder WithUpdatedState(InductionStatus inductionStatus) + { + if (CurrentInductionStatus == InductionStatus.None) + { + throw new NotSupportedException("Initialised state must be set using WithInitialisedState"); + } + InductionStatus = inductionStatus; + return this; + } + + public EditInductionState Create() + { + return new EditInductionState() + { + InductionStatus = InductionStatus, + CurrentInductionStatus = CurrentInductionStatus, + StartDate = StartDate, + CompletedDate = CompletedDate, + ExemptionReasons = ExemptionReasons, + ChangeReason = ChangeReason, + JourneyStartPage = JourneyStartPage, + RecordManagedInCpd = RecordManagedInCpd, + Initialized = Initialized + }; + } +} diff --git a/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Persons/PersonDetail/EditInduction/EditInductionStatusTests.cs b/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Persons/PersonDetail/EditInduction/EditInductionStatusTests.cs index 2ee303e71..0e37d5b53 100644 --- a/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Persons/PersonDetail/EditInduction/EditInductionStatusTests.cs +++ b/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Persons/PersonDetail/EditInduction/EditInductionStatusTests.cs @@ -1,4 +1,6 @@ +using AngleSharp.Dom; using AngleSharp.Html.Dom; +using TeachingRecordSystem.Core.DataStore.Postgres.Models; using TeachingRecordSystem.SupportUi.Pages.Persons.PersonDetail.EditInduction; namespace TeachingRecordSystem.SupportUi.Tests.PageTests.Persons.PersonDetail.EditInduction; @@ -6,7 +8,36 @@ namespace TeachingRecordSystem.SupportUi.Tests.PageTests.Persons.PersonDetail.Ed public class EditInductionStatusTests(HostFixture hostFixture) : TestBase(hostFixture) { [Fact] - public async Task ContinueAndCancelButtons_ExistOnPage() + public async Task Get_PageLegend_Expected() + { + // Arrange + InductionStatus inductionStatus = InductionStatus.Passed; + var person = await TestData.CreatePersonAsync(p => p + .WithQts() + .WithFirstName("Alfred") + .WithMiddleName("The") + .WithLastName("Great")); + var expectedCaption = "Induction - Alfred The Great"; + + var journeyInstance = await CreateJourneyInstanceAsync( + person.PersonId, + new EditInductionStateBuilder() + .WithInitialisedState(inductionStatus, InductionJourneyPage.Status) + .Create()); + + var request = new HttpRequestMessage(HttpMethod.Get, $"/persons/{person.PersonId}/edit-induction/status?{journeyInstance.GetUniqueIdQueryParameter()}"); + + // Act + var response = await HttpClient.SendAsync(request); + + // Assert + var doc = await AssertEx.HtmlResponseAsync(response); + var caption = doc.GetElementByTestId("induction-status-caption"); + Assert.Equal(expectedCaption, caption!.TextContent); + } + + [Fact] + public async Task Get_ContinueAndCancelButtons_ExistOnPage() { // Arrange InductionStatus inductionStatus = InductionStatus.Passed; @@ -14,11 +45,10 @@ public async Task ContinueAndCancelButtons_ExistOnPage() var journeyInstance = await CreateJourneyInstanceAsync( person.PersonId, - new EditInductionState() - { - Initialized = true, - InductionStatus = inductionStatus - }); + new EditInductionStateBuilder() + .WithInitialisedState(inductionStatus, InductionJourneyPage.Status) + .Create()); + var request = new HttpRequestMessage(HttpMethod.Get, $"/persons/{person.PersonId}/edit-induction/status?{journeyInstance.GetUniqueIdQueryParameter()}"); // Act @@ -35,6 +65,258 @@ public async Task ContinueAndCancelButtons_ExistOnPage() Assert.Equal("Cancel and return to record", buttons.ElementAt(1)!.TextContent); } + [Theory] + [InlineData(InductionStatus.RequiredToComplete, new InductionStatus[] { InductionStatus.Exempt, InductionStatus.InProgress, InductionStatus.Passed, InductionStatus.Failed, InductionStatus.FailedInWales })] + [InlineData(InductionStatus.Exempt, new InductionStatus[] { InductionStatus.RequiredToComplete, InductionStatus.InProgress, InductionStatus.Passed, InductionStatus.Failed, InductionStatus.FailedInWales })] + [InlineData(InductionStatus.InProgress, new InductionStatus[] { InductionStatus.RequiredToComplete, InductionStatus.Exempt, InductionStatus.Passed, InductionStatus.Failed, InductionStatus.FailedInWales })] + [InlineData(InductionStatus.Passed, new InductionStatus[] { InductionStatus.RequiredToComplete, InductionStatus.Exempt, InductionStatus.InProgress, InductionStatus.Failed, InductionStatus.FailedInWales })] + [InlineData(InductionStatus.Failed, new InductionStatus[] { InductionStatus.RequiredToComplete, InductionStatus.Exempt, InductionStatus.InProgress, InductionStatus.Passed, InductionStatus.FailedInWales })] + [InlineData(InductionStatus.FailedInWales, new InductionStatus[] { InductionStatus.RequiredToComplete, InductionStatus.Exempt, InductionStatus.InProgress, InductionStatus.Passed, InductionStatus.Failed })] + public async Task Get_InductionNotManagedByCpd_ExpectedRadioButtonsExistOnPage(InductionStatus currentInductionStatus, InductionStatus[] expectedStatuses) + { + // Arrange + var expectedChoices = expectedStatuses.Select(s => s.ToString()); + + var person = await TestData.CreatePersonAsync(p => p.WithQts()); + + var journeyInstance = await CreateJourneyInstanceAsync( + person.PersonId, + new EditInductionStateBuilder() + .WithInitialisedState(currentInductionStatus, InductionJourneyPage.Status) + .Create()); + + var request = new HttpRequestMessage(HttpMethod.Get, $"/persons/{person.PersonId}/edit-induction/status?{journeyInstance.GetUniqueIdQueryParameter()}"); + + // Act + var response = await HttpClient.SendAsync(request); + + // Assert + var doc = await AssertEx.HtmlResponseAsync(response); + var statusChoices = doc.QuerySelectorAll("[type=radio]").Select(r => r.Value); + var statusChoicesLegend = doc.GetElementByTestId("status-choices-legend"); + Assert.Equal("Select a status", statusChoicesLegend!.TextContent); + Assert.Equal(expectedChoices, statusChoices); + } + + [Theory] + [InlineData(InductionStatus.Passed, new InductionStatus[] { InductionStatus.RequiredToComplete, InductionStatus.Exempt, InductionStatus.FailedInWales })] + [InlineData(InductionStatus.Failed, new InductionStatus[] { InductionStatus.RequiredToComplete, InductionStatus.Exempt, InductionStatus.FailedInWales })] + [InlineData(InductionStatus.FailedInWales, new InductionStatus[] { InductionStatus.RequiredToComplete, InductionStatus.Exempt })] + public async Task Get_InductionManagedByCpd_ExpectedRadioButtonsExistOnPage(InductionStatus currentInductionStatus, InductionStatus[] expectedStatuses) + { + // Arrange + var expectedChoices = expectedStatuses.Select(s => s.ToString()); + var overSevenYearsAgo = Clock.Today.AddYears(-7).AddDays(-1); + var person = await TestData.CreatePersonAsync(p => p.WithQts()); + await WithDbContext(async dbContext => + { + dbContext.Attach(person.Person); + person.Person.SetCpdInductionStatus( + InductionStatus.Passed, + startDate: Clock.Today.AddYears(-7).AddMonths(-6), + completedDate: overSevenYearsAgo, + cpdModifiedOn: Clock.UtcNow, + updatedBy: SystemUser.SystemUserId, + now: Clock.UtcNow, + out _); + await dbContext.SaveChangesAsync(); + }); + + var journeyInstance = await CreateJourneyInstanceAsync( + person.PersonId, + new EditInductionStateBuilder() + .WithInitialisedState(currentInductionStatus, InductionJourneyPage.Status) + .Create()); + + var request = new HttpRequestMessage(HttpMethod.Get, $"/persons/{person.PersonId}/edit-induction/status?{journeyInstance.GetUniqueIdQueryParameter()}"); + + // Act + var response = await HttpClient.SendAsync(request); + + // Assert + var doc = await AssertEx.HtmlResponseAsync(response); + var statusChoices = doc.QuerySelectorAll("[type=radio]").Select(r => r.Value); + var statusChoicesLegend = doc.GetElementByTestId("status-choices-legend"); + Assert.Equal("Select a status", statusChoicesLegend!.TextContent); + Assert.Equal(expectedChoices, statusChoices); + } + + [Theory] + [InlineData(InductionStatus.RequiredToComplete, InductionStatus.Exempt)] + [InlineData(InductionStatus.Exempt, InductionStatus.RequiredToComplete)] + [InlineData(InductionStatus.InProgress, InductionStatus.Passed)] + [InlineData(InductionStatus.Passed, InductionStatus.InProgress)] + [InlineData(InductionStatus.Failed, InductionStatus.FailedInWales)] + [InlineData(InductionStatus.FailedInWales, InductionStatus.Failed)] + public async Task Get_InductionstatusHasBeenSet_ShowsSelectedRadioButton(InductionStatus currentInductionStatus, InductionStatus selectedInductionStatus) + { + // Arrange + var person = await TestData.CreatePersonAsync(p => p.WithQts()); + + var journeyInstance = await CreateJourneyInstanceAsync( + person.PersonId, + new EditInductionStateBuilder() + .WithInitialisedState(currentInductionStatus, InductionJourneyPage.Status) + .WithUpdatedState(selectedInductionStatus) + .Create()); + + var request = new HttpRequestMessage(HttpMethod.Get, $"/persons/{person.PersonId}/edit-induction/status?{journeyInstance.GetUniqueIdQueryParameter()}"); + + // Act + var response = await HttpClient.SendAsync(request); + + // Assert + var doc = await AssertEx.HtmlResponseAsync(response); + var selectedStatus = doc.QuerySelectorAll("[type=radio]").Single(r => r.IsChecked == true); + Assert.Equal(selectedInductionStatus.ToString(), selectedStatus.Value); + } + + [Fact] + public async Task Post_SelectedStatus_PersistsStatus() + { + // Arrange + var person = await TestData.CreatePersonAsync(p => p.WithQts()); + + var journeyInstance = await CreateJourneyInstanceAsync( + person.PersonId, + new EditInductionStateBuilder() + .WithInitialisedState(InductionStatus.Passed, InductionJourneyPage.Status) + .Create()); + + var postRequest = new HttpRequestMessage(HttpMethod.Post, $"/persons/{person.PersonId}/edit-induction/status?{journeyInstance.GetUniqueIdQueryParameter()}") + { + Content = new FormUrlEncodedContent(new Dictionary + { + ["InductionStatus"] = "Exempt" + }) + }; + + // Act + var response = await HttpClient.SendAsync(postRequest); + + // Assert + journeyInstance = await ReloadJourneyInstance(journeyInstance); + Assert.Equal("Exempt", journeyInstance.State.InductionStatus.GetTitle()); + } + + [Theory] + [InlineData(InductionStatus.RequiredToComplete, new InductionStatus[] { InductionStatus.Exempt, InductionStatus.InProgress, InductionStatus.Passed, InductionStatus.Failed, InductionStatus.FailedInWales })] + [InlineData(InductionStatus.Exempt, new InductionStatus[] { InductionStatus.RequiredToComplete, InductionStatus.InProgress, InductionStatus.Passed, InductionStatus.Failed, InductionStatus.FailedInWales })] + [InlineData(InductionStatus.InProgress, new InductionStatus[] { InductionStatus.RequiredToComplete, InductionStatus.Exempt, InductionStatus.Passed, InductionStatus.Failed, InductionStatus.FailedInWales })] + [InlineData(InductionStatus.Passed, new InductionStatus[] { InductionStatus.RequiredToComplete, InductionStatus.Exempt, InductionStatus.InProgress, InductionStatus.Failed, InductionStatus.FailedInWales })] + [InlineData(InductionStatus.Failed, new InductionStatus[] { InductionStatus.RequiredToComplete, InductionStatus.Exempt, InductionStatus.InProgress, InductionStatus.Passed, InductionStatus.FailedInWales })] + [InlineData(InductionStatus.FailedInWales, new InductionStatus[] { InductionStatus.RequiredToComplete, InductionStatus.Exempt, InductionStatus.InProgress, InductionStatus.Passed, InductionStatus.Failed })] + + public async Task Post_NoSelectedStatus_ShowsPageError(InductionStatus currentInductionStatus, InductionStatus[] expectedStatusChoices) + { + // Arrange + var person = await TestData.CreatePersonAsync(p => p.WithQts()); + + var journeyInstance = await CreateJourneyInstanceAsync( + person.PersonId, + new EditInductionStateBuilder() + .WithInitialisedState(currentInductionStatus, InductionJourneyPage.Status) + .Create()); + + var postRequest = new HttpRequestMessage(HttpMethod.Post, $"/persons/{person.PersonId}/edit-induction/status?{journeyInstance.GetUniqueIdQueryParameter()}") + { + Content = new FormUrlEncodedContent(new Dictionary + { + ["InductionStatus"] = InductionStatus.None.ToString() + }) + }; + + // Act + var response = await HttpClient.SendAsync(postRequest); + + // Assert + await AssertEx.HtmlResponseHasErrorAsync(response, nameof(StatusModel.InductionStatus), "Select a status"); + var doc = await response.GetDocumentAsync(); + var statusChoices = doc.QuerySelectorAll("[type=radio]").Select(r => r.Value); + var statusChoicesLegend = doc.GetElementByTestId("status-choices-legend"); + Assert.Equal("Select a status", statusChoicesLegend!.TextContent); + Assert.Equal(expectedStatusChoices.Select(c => c.ToString()), statusChoices); + } + + [Theory] + [InlineData(InductionStatus.Passed, new InductionStatus[] { InductionStatus.RequiredToComplete, InductionStatus.Exempt, InductionStatus.FailedInWales })] + [InlineData(InductionStatus.Failed, new InductionStatus[] { InductionStatus.RequiredToComplete, InductionStatus.Exempt, InductionStatus.FailedInWales })] + [InlineData(InductionStatus.FailedInWales, new InductionStatus[] { InductionStatus.RequiredToComplete, InductionStatus.Exempt })] + public async Task Post_PersonManagedByCpd_NoSelectedStatus_ShowsPageError(InductionStatus currentInductionStatus, InductionStatus[] expectedChoices) + { + var overSevenYearsAgo = Clock.Today.AddYears(-7).AddDays(-1); + var person = await TestData.CreatePersonAsync(p => p.WithQts()); + await WithDbContext(async dbContext => + { + dbContext.Attach(person.Person); + person.Person.SetCpdInductionStatus( + InductionStatus.Passed, + startDate: Clock.Today.AddYears(-7).AddMonths(-6), + completedDate: overSevenYearsAgo, + cpdModifiedOn: Clock.UtcNow, + updatedBy: SystemUser.SystemUserId, + now: Clock.UtcNow, + out _); + await dbContext.SaveChangesAsync(); + }); + var journeyInstance = await CreateJourneyInstanceAsync( + person.PersonId, + new EditInductionStateBuilder() + .WithInitialisedState(currentInductionStatus, InductionJourneyPage.Status) + .Create()); + + var postRequest = new HttpRequestMessage(HttpMethod.Post, $"/persons/{person.PersonId}/edit-induction/status?{journeyInstance.GetUniqueIdQueryParameter()}") + { + Content = new FormUrlEncodedContent(new Dictionary + { + ["InductionStatus"] = InductionStatus.None.ToString() + }) + }; + + // Act + var response = await HttpClient.SendAsync(postRequest); + + // Assert + await AssertEx.HtmlResponseHasErrorAsync(response, nameof(StatusModel.InductionStatus), "Select a status"); + var doc = await response.GetDocumentAsync(); + var statusChoices = doc.QuerySelectorAll("[type=radio]").Select(r => r.Value); + var statusChoicesLegend = doc.GetElementByTestId("status-choices-legend"); + Assert.Equal("Select a status", statusChoicesLegend!.TextContent); + Assert.Equal(expectedChoices.Select(c => c.ToString()), statusChoices); + } + + [Fact] + public async Task Get_ForPersonWithInductionStatusManagedByCPD_ShowsWarning() + { + //Arrange + var expectedWarning = "To change this teacher’s induction status "; + var overSevenYearsAgo = Clock.Today.AddYears(-7).AddDays(-1); + + var person = await TestData.CreatePersonAsync(); + await WithDbContext(async dbContext => + { + dbContext.Attach(person.Person); + person.Person.SetCpdInductionStatus( + InductionStatus.Passed, + startDate: Clock.Today.AddYears(-7).AddMonths(-6), + completedDate: overSevenYearsAgo, + cpdModifiedOn: Clock.UtcNow, + updatedBy: SystemUser.SystemUserId, + now: Clock.UtcNow, + out _); + await dbContext.SaveChangesAsync(); + }); + + var request = new HttpRequestMessage(HttpMethod.Get, $"/persons/{person.ContactId}/induction"); + + // Act + var response = await HttpClient.SendAsync(request); + + // Assert + var doc = await AssertEx.HtmlResponseAsync(response); + Assert.Contains(expectedWarning, doc!.GetElementByTestId("induction-status-warning")!.Children[1].TextContent); + } + private Task> CreateJourneyInstanceAsync(Guid personId, EditInductionState? state = null) => CreateJourneyInstance( JourneyNames.EditInduction, diff --git a/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Persons/PersonDetail/InductionTests.cs b/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Persons/PersonDetail/InductionTests.cs index 171050df4..976b7d505 100644 --- a/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Persons/PersonDetail/InductionTests.cs +++ b/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Persons/PersonDetail/InductionTests.cs @@ -226,7 +226,7 @@ public async Task Get_WithPersonIdForPersonWithInductionStatusRequiringCompletio public async Task Get_WithPersonIdForPersonWithInductionStatusManagedByCPD_ShowsWarning() { //Arrange - var expectedWarning = "To change a teacher’s induction status "; + var expectedWarning = "To change this teacher’s induction status "; var overSevenYearsAgo = Clock.Today.AddYears(-7).AddDays(-1); var person = await TestData.CreatePersonAsync();