diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/RequestTrn/Index.cshtml.cs b/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/RequestTrn/Index.cshtml.cs index ce54b678f..f3a09a1b5 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/RequestTrn/Index.cshtml.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/RequestTrn/Index.cshtml.cs @@ -1,10 +1,24 @@ +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using TeachingRecordSystem.UiCommon.FormFlow; namespace TeachingRecordSystem.AuthorizeAccess.Pages.RequestTrn; [Journey(RequestTrnJourneyState.JourneyName), ActivatesJourney, RequireJourneyInstance] -public class IndexModel : PageModel +public class IndexModel(IConfiguration configuration) : PageModel { public JourneyInstance? JourneyInstance { get; set; } + + [FromQuery] + public string? AccessToken { get; set; } + + public ActionResult OnGet() + { + var whitelistedAccessToken = configuration.GetRequiredValue("RequestTrnAccessToken"); + if (!whitelistedAccessToken.Equals(AccessToken, StringComparison.Ordinal)) + { + return NotFound(); + } + return Page(); + } } diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/appsettings.Testing.json b/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/appsettings.Testing.json index a1507c044..03f632c95 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/appsettings.Testing.json +++ b/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/appsettings.Testing.json @@ -7,5 +7,6 @@ "Microsoft.AspNetCore": "Fatal" } } - } + }, + "RequestTrnAccessToken": "n8hhN5MSrNXxCzRo" } diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/DataStore/Postgres/Models/Person.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/DataStore/Postgres/Models/Person.cs index 39d217397..bfabf04a8 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Core/DataStore/Postgres/Models/Person.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/DataStore/Postgres/Models/Person.cs @@ -140,8 +140,8 @@ public void SetInductionStatus( DateTime now, out PersonInductionUpdatedEvent? @event) { - // FUTURE When we have QTS in TRS - assert person has QTS - AssertInductionChangeIsValid(status, startDate, completedDate, exemptionReasons); + // N.B. We allow missing data fields as some migrated data has missing fields + // and we want to be able to test such scenarios. var changes = PersonInductionUpdatedEventChanges.None | (InductionStatus != status ? PersonInductionUpdatedEventChanges.InductionStatus : 0) | diff --git a/TeachingRecordSystem/tests/TeachingRecordSystem.AuthorizeAccess.Tests/PageTests/RequestTrn/IndexTests.cs b/TeachingRecordSystem/tests/TeachingRecordSystem.AuthorizeAccess.Tests/PageTests/RequestTrn/IndexTests.cs index abfbd4c7c..edc67fe1b 100644 --- a/TeachingRecordSystem/tests/TeachingRecordSystem.AuthorizeAccess.Tests/PageTests/RequestTrn/IndexTests.cs +++ b/TeachingRecordSystem/tests/TeachingRecordSystem.AuthorizeAccess.Tests/PageTests/RequestTrn/IndexTests.cs @@ -6,10 +6,11 @@ public class IndexTests(HostFixture hostFixture) : TestBase(hostFixture) public async Task Get_ValidRequest_RendersExpectedContent() { // Arrange + var accessToken = HostFixture.Services.GetRequiredService().GetValue("RequestTrnAccessToken"); var state = CreateNewState(); var journeyInstance = await CreateJourneyInstance(state); - var request = new HttpRequestMessage(HttpMethod.Get, $"/request-trn?{journeyInstance.GetUniqueIdQueryParameter()}"); + var request = new HttpRequestMessage(HttpMethod.Get, $"/request-trn?{journeyInstance.GetUniqueIdQueryParameter()}&AccessToken={accessToken}"); // Act var response = await HttpClient.SendAsync(request); @@ -17,4 +18,20 @@ public async Task Get_ValidRequest_RendersExpectedContent() // Assert await AssertEx.HtmlResponseAsync(response); } + + [Fact] + public async Task Get_MissingAccessToken_ReturnsBadRequest() + { + // Arrange + var state = CreateNewState(); + var journeyInstance = await CreateJourneyInstance(state); + + var request = new HttpRequestMessage(HttpMethod.Get, $"/request-trn?{journeyInstance.GetUniqueIdQueryParameter()}&AccessToken="); + + // Act + var response = await HttpClient.SendAsync(request); + + // Assert + Assert.Equal(StatusCodes.Status404NotFound, (int)response.StatusCode); + } } diff --git a/TeachingRecordSystem/tests/TeachingRecordSystem.TestCommon/TestData.CreatePerson.cs b/TeachingRecordSystem/tests/TeachingRecordSystem.TestCommon/TestData.CreatePerson.cs index 732a3f83a..ee2b12885 100644 --- a/TeachingRecordSystem/tests/TeachingRecordSystem.TestCommon/TestData.CreatePerson.cs +++ b/TeachingRecordSystem/tests/TeachingRecordSystem.TestCommon/TestData.CreatePerson.cs @@ -50,7 +50,7 @@ public class CreatePersonBuilder private string? _trnToken; private string? _slugId; private int? _loginFailedCounter; - private CreatePersonMandatoryQualificationBuilder.CreatePersonInductionBuilder? _inductionBuilder; + private CreatePersonInductionBuilder? _inductionBuilder; public Guid PersonId { get; } = Guid.NewGuid(); @@ -291,13 +291,30 @@ public CreatePersonBuilder WithLoginFailedCounter(int? loginFailedCounter) } public CreatePersonBuilder WithInductionStatus(InductionStatus status) => - WithInductionStatus(i => i.WithStatus(status)); + WithInductionStatus(i => + { + var qtsDate = GetQtsDate(); + var startDate = CreatePersonInductionBuilder.GetDefaultStartDate(status, qtsDate); + var completedDate = CreatePersonInductionBuilder.GetDefaultCompletedDate(status, startDate); + var exemptionReasons = CreatePersonInductionBuilder.GetDefaultExemptionReasons(status); + + if (!Person.ValidateInductionData(status, startDate, completedDate, exemptionReasons, out var error)) + { + throw new InvalidOperationException(error); + } + + i + .WithStatus(status) + .WithStartDate(startDate) + .WithCompletedDate(completedDate) + .WithExemptionReasons(exemptionReasons); + }); - public CreatePersonBuilder WithInductionStatus(Action configure) + public CreatePersonBuilder WithInductionStatus(Action configure) { EnsureTrn(); - _inductionBuilder ??= new(this); + _inductionBuilder ??= new(); configure(_inductionBuilder); return this; @@ -1042,178 +1059,100 @@ await dbContext.MandatoryQualificationProviders.SingleAsync(p => p.MandatoryQual return (QualificationId, events); } + } - public class CreatePersonInductionBuilder(CreatePersonBuilder createPersonBuilder) - { - private Option _status; - private Option _startDate; - private Option _completedDate; - private Option _exemptionReasons; + public class CreatePersonInductionBuilder + { + private Option _status; + private Option _startDate; + private Option _completedDate; + private Option _exemptionReasons; - public bool HasStatusRequiringQts => _status.HasValue && _status.ValueOrFailure() != InductionStatus.None; + public bool HasStatusRequiringQts => _status.HasValue && _status.ValueOrFailure() != InductionStatus.None; - public CreatePersonInductionBuilder WithStatus(InductionStatus status) + public CreatePersonInductionBuilder WithStatus(InductionStatus status) + { + if (_status.HasValue && _status.ValueOrFailure() != status) { - if (_status.HasValue && _status.ValueOrFailure() != status) - { - throw new InvalidOperationException("Status has already been set."); - } - - var qtsDate = createPersonBuilder.GetQtsDate(); - - if (status != InductionStatus.None && !qtsDate.HasValue) - { - throw new InvalidOperationException("Person requires QTS."); - } - else if (status == InductionStatus.None && qtsDate.HasValue) - { - throw new InvalidOperationException($"Status cannot be '{status}' when person has QTS."); - } - - _status = Option.Some(status); - return this; + throw new InvalidOperationException("Status has already been set."); } - public CreatePersonInductionBuilder WithStartDate(DateOnly? startDate) - { - if (_startDate.HasValue) - { - throw new InvalidOperationException("Start date has already been set."); - } - - if (!_status.HasValue) - { - throw new InvalidOperationException("Status must be specified before the start date."); - } - - var status = _status.ValueOrFailure(); - - if (!Person.ValidateInductionData( - status, - startDate, - GetDefaultCompletedDate(status, startDate), - GetDefaultExemptionReasons(status), - out var error)) - { - throw new InvalidOperationException(error); - } - - _startDate = Option.Some(startDate); - return this; - } + _status = Option.Some(status); + return this; + } - public CreatePersonInductionBuilder WithCompletedDate(DateOnly? completedDate) + public CreatePersonInductionBuilder WithStartDate(DateOnly? startDate) + { + if (_startDate.HasValue) { - if (_completedDate.HasValue) - { - throw new InvalidOperationException("Completed date has already been set."); - } - - if (!_status.HasValue) - { - throw new InvalidOperationException("Status must be specified before the start date."); - } - - if (!_startDate.HasValue) - { - throw new InvalidOperationException("Start date must be specified before the completed date."); - } - - var status = _status.ValueOrFailure(); - var startDate = _startDate.ValueOrFailure(); + throw new InvalidOperationException("Start date has already been set."); + } - if (!Person.ValidateInductionData( - status, - startDate, - completedDate, - GetDefaultExemptionReasons(status), - out var error)) - { - throw new InvalidOperationException(error); - } + _startDate = Option.Some(startDate); + return this; + } - _completedDate = Option.Some(completedDate); - return this; + public CreatePersonInductionBuilder WithCompletedDate(DateOnly? completedDate) + { + if (_completedDate.HasValue) + { + throw new InvalidOperationException("Completed date has already been set."); } - public CreatePersonInductionBuilder WithExemptionReasons(InductionExemptionReasons exemptionReasons) - { - if (_exemptionReasons.HasValue) - { - throw new InvalidOperationException("Exemption reasons have already been set."); - } + _completedDate = Option.Some(completedDate); + return this; + } - if (!_status.HasValue) - { - throw new InvalidOperationException("Status must be specified before the exemption reasons."); - } + public CreatePersonInductionBuilder WithExemptionReasons(InductionExemptionReasons exemptionReasons) + { + if (_exemptionReasons.HasValue) + { + throw new InvalidOperationException("Exemption reasons have already been set."); + } - var status = _status.ValueOrFailure(); + _exemptionReasons = Option.Some(exemptionReasons); + return this; + } - if (status is not InductionStatus.Exempt && exemptionReasons != InductionExemptionReasons.None) - { - throw new InvalidOperationException($"Exemption reasons cannot be specified unless the status is {InductionStatus.Exempt}."); - } + internal IReadOnlyCollection Execute( + Person person, + CreatePersonBuilder createPersonBuilder, + TestData testData, + TrsDbContext dbContext) + { + var qtsDate = createPersonBuilder.GetQtsDate(); - if (status is InductionStatus.Exempt && exemptionReasons == InductionExemptionReasons.None) - { - throw new InvalidOperationException($"Exemption reasons cannot be {InductionExemptionReasons.None} when the status is {InductionStatus.Exempt}."); - } + var status = _status.ValueOr(qtsDate.HasValue ? InductionStatus.RequiredToComplete : InductionStatus.None); + var startDate = _startDate.ValueOrDefault(); + var completedDate = _completedDate.ValueOrDefault(); + var exemptionReasons = _exemptionReasons.ValueOr(InductionExemptionReasons.None); - _exemptionReasons = Option.Some(exemptionReasons); - return this; - } + person.SetInductionStatus( + status, + startDate, + completedDate, + exemptionReasons, + updatedBy: SystemUser.SystemUserId, + testData.Clock.UtcNow, + out var @event); - internal IReadOnlyCollection Execute( - Person person, - CreatePersonBuilder createPersonBuilder, - TestData testData, - TrsDbContext dbContext) + if (@event is not null) { - var qtsDate = createPersonBuilder.GetQtsDate(); - - var status = _status.ValueOr(qtsDate.HasValue ? InductionStatus.RequiredToComplete : InductionStatus.None); - var startDate = _startDate.ValueOr(GetDefaultStartDate(status, qtsDate)); - var completedDate = _completedDate.ValueOr(GetDefaultCompletedDate(status, startDate)); - var exemptionReasons = _exemptionReasons.ValueOr(GetDefaultExemptionReasons(status)); - - if (!Person.ValidateInductionData( - status, - startDate, - completedDate, - exemptionReasons, - out var error)) - { - throw new InvalidOperationException(error); - } - - person.SetInductionStatus( - status, - startDate, - completedDate, - exemptionReasons, - updatedBy: SystemUser.SystemUserId, - testData.Clock.UtcNow, - out var @event); - - if (@event is not null) - { - dbContext.AddEvent(@event); - return [@event]; - } - - return []; + dbContext.AddEvent(@event); + return [@event]; } - private static DateOnly? GetDefaultStartDate(InductionStatus status, DateOnly? qtsDate) => - status.RequiresStartDate() ? qtsDate!.Value.AddMonths(6) : null; + return []; + } - private static DateOnly? GetDefaultCompletedDate(InductionStatus status, DateOnly? startDate) => - status.RequiresCompletedDate() ? startDate!.Value.AddMonths(12) : null; + internal static DateOnly? GetDefaultStartDate(InductionStatus status, DateOnly? qtsDate) => + status.RequiresStartDate() ? qtsDate!.Value.AddMonths(6) : null; - private static InductionExemptionReasons GetDefaultExemptionReasons(InductionStatus status) => - status is InductionStatus.Exempt ? (InductionExemptionReasons)1 : InductionExemptionReasons.None; - } + internal static DateOnly? GetDefaultCompletedDate(InductionStatus status, DateOnly? startDate) => + status.RequiresCompletedDate() ? startDate!.Value.AddMonths(12) : null; + + internal static InductionExemptionReasons GetDefaultExemptionReasons(InductionStatus status) => + status is InductionStatus.Exempt ? (InductionExemptionReasons)1 : InductionExemptionReasons.None; } public record CreatePersonResult