Skip to content

Commit

Permalink
Add TRN page for teaching record lookup (#1150)
Browse files Browse the repository at this point in the history
  • Loading branch information
gunndabad authored Feb 8, 2024
1 parent fc0bcd4 commit a808557
Show file tree
Hide file tree
Showing 12 changed files with 616 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ public string NationalInsuranceNumberContinueWithout(JourneyInstanceId journeyIn
public string Trn(JourneyInstanceId journeyInstanceId) =>
GetRequiredPathByPage("/Trn", journeyInstanceId: journeyInstanceId);

public string NotFound(JourneyInstanceId journeyInstanceId) =>
GetRequiredPathByPage("/NotFound", journeyInstanceId: journeyInstanceId);

protected virtual string GetRequiredPathByPage(string page, string? handler = null, object? routeValues = null, JourneyInstanceId? journeyInstanceId = null)
{
var url = GetRequiredPathByPage(page, handler, routeValues);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@

<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<form action="@LinkGenerator.NationalInsuranceNumber(Model.JourneyInstance!.InstanceId)" method="post" asp-antiforgery="true">
<form action="@LinkGenerator.NationalInsuranceNumber(Model.JourneyInstance!.InstanceId)" method="post">
<govuk-input asp-for="NationalInsuranceNumber" input-class="govuk-input--width-10 govuk-input--extra-letter-spacing">
<govuk-input-label is-page-heading="true" class="govuk-label--l" />
</govuk-input>

@* Pressing the Enter key will submit the first submit button - make sure it's the default action *@
<button type="submit" class="govuk-!-display-none" tabindex="-1"></button>

<govuk-details>
<govuk-details open="@Model.PreviouslyAnsweredCannotProvide">
<govuk-details-summary>I cannot provide my National Insurance number</govuk-details-summary>
<govuk-details-text>
<p class="govuk-body">You can <a href="https://www.gov.uk/lost-national-insurance-number" rel="noreferrer noopener" target="_blank" class="govuk-link">find a lost National Insurance number (opens in new tab)</a>.</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public class NationalInsuranceNumberModel(SignInJourneyHelper helper, AuthorizeA
[Required(ErrorMessage = "Enter a National Insurance number")]
public string? NationalInsuranceNumber { get; set; }

public bool PreviouslyAnsweredCannotProvide { get; set; }

public void OnGet()
{
NationalInsuranceNumber = JourneyInstance!.State.NationalInsuranceNumber;
Expand Down Expand Up @@ -46,8 +48,10 @@ public async Task<IActionResult> OnPost()
{
return helper.GetNextPage(JourneyInstance).ToActionResult();
}

return RedirectToNextPage();
else
{
return RedirectToNextPage();
}
}

public async Task<IActionResult> OnPostContinueWithout()
Expand Down Expand Up @@ -75,6 +79,8 @@ public override void OnPageHandlerExecuting(PageHandlerExecutingContext context)
// Already matched to a Teaching Record
context.Result = Redirect(helper.GetSafeRedirectUri(JourneyInstance));
}

PreviouslyAnsweredCannotProvide = state.NationalInsuranceNumberSpecified && state.NationalInsuranceNumber is null;
}

private IActionResult RedirectToNextPage() => Redirect(linkGenerator.Trn(JourneyInstance!.InstanceId));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@page "/not-found"
@model TeachingRecordSystem.AuthorizeAccess.Pages.NotFoundModel
@{
ViewBag.Title = "[PLACEHOLDER] Not found";
}

@section BeforeContent {
<govuk-back-link href="@LinkGenerator.Trn(Model.JourneyInstance!.InstanceId)" />
}

<h1>@ViewBag.Title</h1>
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.RazorPages;
using TeachingRecordSystem.FormFlow;

namespace TeachingRecordSystem.AuthorizeAccess.Pages;

[Journey(SignInJourneyState.JourneyName), RequireJourneyInstance]
public class NotFoundModel(SignInJourneyHelper helper) : PageModel
{
public JourneyInstance<SignInJourneyState>? JourneyInstance { get; set; }

public void OnGet()
{
}

public override void OnPageHandlerExecuting(PageHandlerExecutingContext context)
{
var state = JourneyInstance!.State;

if (state.OneLoginAuthenticationTicket is null || !state.IdentityVerified)
{
// Not authenticated/verified with One Login
context.Result = BadRequest();
}
else if (!state.NationalInsuranceNumberSpecified || !state.TrnSpecified)
{
// Not specified NINO or TRN
context.Result = helper.GetNextPage(JourneyInstance).ToActionResult();
}
else if (state.AuthenticationTicket is not null)
{
// Already matched to a Teaching Record
context.Result = Redirect(helper.GetSafeRedirectUri(JourneyInstance));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@
Layout = "_GovUkPageTemplate";
}

@section BeforeContent {
@RenderSection("BeforeContent", required: false)
}

@RenderBody()
Original file line number Diff line number Diff line change
@@ -1,4 +1,29 @@
@page "/trn"
@model TeachingRecordSystem.AuthorizeAccess.Pages.TrnModel
@{
ViewBag.Title = "What is your teacher reference number (TRN)?";
}

@section BeforeContent {
<govuk-back-link href="@LinkGenerator.NationalInsuranceNumber(Model.JourneyInstance!.InstanceId)" />
}

<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<form action="@LinkGenerator.Trn(Model.JourneyInstance!.InstanceId)" method="post">
<h1 class="govuk-heading-l">@ViewBag.Title</h1>

<p class="govuk-body">A TRN is 7 digits long, for example 4567814</p>
<p class="govuk-body">It might include the letters RP and a slash, for example RP99/12345</p>
<p class="govuk-body">It’s previously been known as a QTS, GTC, DfE, DfES and DCSF number</p>

<govuk-input
asp-for="Trn"
input-class="govuk-!-width-one-quarter govuk-input--extra-letter-spacing"
spellcheck="false"
label-class="govuk-label--s" />

<govuk-button type="submit">Continue</govuk-button>
</form>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,12 +1,63 @@
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.RazorPages;
using TeachingRecordSystem.FormFlow;

namespace TeachingRecordSystem.AuthorizeAccess.Pages;

[Journey(SignInJourneyState.JourneyName), RequireJourneyInstance]
public class TrnModel : PageModel
public class TrnModel(SignInJourneyHelper helper, AuthorizeAccessLinkGenerator linkGenerator) : PageModel
{
public JourneyInstance<SignInJourneyState>? JourneyInstance { get; set; }

[BindProperty]
[Display(Name = "Teacher reference number (TRN)")]
[Required(ErrorMessage = "Enter your TRN")]
[RegularExpression(@"\A\D*(\d{1}\D*){7}\D*\Z", ErrorMessage = "Your TRN should contain 7 digits")]
public string? Trn { get; set; }

public void OnGet()
{
Trn = JourneyInstance!.State.Trn;
}

public async Task<IActionResult> OnPost()
{
if (!ModelState.IsValid)
{
return this.PageWithErrors();
}

await JourneyInstance!.UpdateStateAsync(state =>
{
state.TrnSpecified = true;
state.Trn = Trn;
});

if (await helper.TryMatchToTeachingRecord(JourneyInstance!))
{
return helper.GetNextPage(JourneyInstance).ToActionResult();
}
else
{
return Redirect(linkGenerator.NotFound(JourneyInstance.InstanceId));
}
}

public override void OnPageHandlerExecuting(PageHandlerExecutingContext context)
{
var state = JourneyInstance!.State;

if (state.OneLoginAuthenticationTicket is null || !state.IdentityVerified)
{
// Not authenticated/verified with One Login
context.Result = BadRequest();
}
else if (state.AuthenticationTicket is not null)
{
// Already matched to a Teaching Record
context.Result = Redirect(helper.GetSafeRedirectUri(JourneyInstance));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,8 @@ public async Task<NextPageInfo> CreateOrUpdateOneLoginUser(JourneyInstance<SignI
// Authentication is complete
{ AuthenticationTicket: not null } => new NextPageInfo(GetSafeRedirectUri(journeyInstance)),

// Authenticated with OneLogin, identity verification succeeded, person lookup failed
// TODO

// Authenticated with OneLogin, identity verification succeeded, TRN not yet specified
// TODO

// Authenticated with OneLogin, identity verification succeeded, NINO not yet specified
{ OneLoginAuthenticationTicket: not null } => new NextPageInfo(linkGenerator.NationalInsuranceNumber(journeyInstance.InstanceId)),
// Authenticated with OneLogin, identity verification succeeded, not yet matched to teaching record
{ OneLoginAuthenticationTicket: not null, AuthenticationTicket: null } => new NextPageInfo(linkGenerator.NationalInsuranceNumber(journeyInstance.InstanceId)),

// Authenticated with OneLogin, identity verification failed
// TODO
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,9 @@ public async Task Post_NotAuthenticatedWithOneLogin_ReturnsBadRequest()
var request = new HttpRequestMessage(HttpMethod.Post, $"/national-insurance-number?{journeyInstance.GetUniqueIdQueryParameter()}")
{
Content = new FormUrlEncodedContentBuilder
{
{ "NationalInsuranceNumber", nationalInsuranceNumber }
}
{
{ "NationalInsuranceNumber", nationalInsuranceNumber }
}
};

// Act
Expand Down Expand Up @@ -158,7 +158,7 @@ public async Task Post_AlreadyAuthenticated_RedirectsToStateRedirectUri()
var journeyInstance = await CreateJourneyInstance(state);

var person = await TestData.CreatePerson(b => b.WithTrn().WithNationalInsuranceNumber());
var nationalInsuranceNumber = Faker.Identification.UkNationalInsuranceNumber();
var nationalInsuranceNumber = person.NationalInsuranceNumber!;
var oneLoginUser = await TestData.CreateOneLoginUser(personId: person.PersonId, firstName: person.FirstName, lastName: person.LastName, dateOfBirth: person.DateOfBirth);

var ticket = CreateOneLoginAuthenticationTicket(oneLoginUser);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
namespace TeachingRecordSystem.AuthorizeAccess.Tests.PageTests;

public class NotFoundTests(HostFixture hostFixture) : TestBase(hostFixture)
{
[Fact]
public async Task Get_NotAuthenticatedWithOneLogin_ReturnsBadRequest()
{
// Arrange
var state = new SignInJourneyState(redirectUri: "/", authenticationProperties: null);
var journeyInstance = await CreateJourneyInstance(state);

var request = new HttpRequestMessage(HttpMethod.Get, $"/not-found?{journeyInstance.GetUniqueIdQueryParameter()}");

// Act
var response = await HttpClient.SendAsync(request);

// Assert
Assert.Equal(StatusCodes.Status400BadRequest, (int)response.StatusCode);
}

[Fact]
public async Task Get_NotVerifiedWithOneLogin_ReturnsBadRequest()
{
// Arrange
var state = new SignInJourneyState(redirectUri: "/", authenticationProperties: null);
var journeyInstance = await CreateJourneyInstance(state);

var ticket = CreateOneLoginAuthenticationTicket(createCoreIdentityVc: false);
await GetSignInJourneyHelper().OnSignedInWithOneLogin(journeyInstance, ticket);

var request = new HttpRequestMessage(HttpMethod.Get, $"/not-found?{journeyInstance.GetUniqueIdQueryParameter()}");

// Act
var response = await HttpClient.SendAsync(request);

// Assert
Assert.Equal(StatusCodes.Status400BadRequest, (int)response.StatusCode);
}

[Fact]
public async Task Get_AlreadyAuthenticated_RedirectsToStateRedirectUri()
{
// Arrange
var redirectUri = "/";
var state = new SignInJourneyState(redirectUri, authenticationProperties: null);
var journeyInstance = await CreateJourneyInstance(state);

var person = await TestData.CreatePerson(b => b.WithTrn());
var oneLoginUser = await TestData.CreateOneLoginUser(person.PersonId);

var ticket = CreateOneLoginAuthenticationTicket(oneLoginUser);
await GetSignInJourneyHelper().OnSignedInWithOneLogin(journeyInstance, ticket);

var request = new HttpRequestMessage(HttpMethod.Get, $"/not-found?{journeyInstance.GetUniqueIdQueryParameter()}");

// Act
var response = await HttpClient.SendAsync(request);

// Assert
Assert.Equal(StatusCodes.Status302Found, (int)response.StatusCode);
Assert.Equal($"{redirectUri}?{journeyInstance.GetUniqueIdQueryParameter()}", response.Headers.Location?.OriginalString);
}

[Fact]
public async Task Get_NationalInsuranceNumberNotSpecified_RedirectsToStartOfMatchingQuestions()
{
// Arrange
var state = new SignInJourneyState(redirectUri: "/", authenticationProperties: null);
var journeyInstance = await CreateJourneyInstance(state);

var ticket = CreateOneLoginAuthenticationTicket();
await GetSignInJourneyHelper().OnSignedInWithOneLogin(journeyInstance, ticket);

await journeyInstance.UpdateStateAsync(state =>
{
state.NationalInsuranceNumberSpecified = false;
});

var request = new HttpRequestMessage(HttpMethod.Get, $"/not-found?{journeyInstance.GetUniqueIdQueryParameter()}");

// Act
var response = await HttpClient.SendAsync(request);

// Assert
Assert.Equal(StatusCodes.Status302Found, (int)response.StatusCode);
Assert.Equal($"/national-insurance-number?{journeyInstance.GetUniqueIdQueryParameter()}", response.Headers.Location?.OriginalString);
}

[Fact]
public async Task Get_TrnNotSpecified_RedirectsToStartOfMatchingQuestions()
{
// Arrange
var state = new SignInJourneyState(redirectUri: "/", authenticationProperties: null);
var journeyInstance = await CreateJourneyInstance(state);

var ticket = CreateOneLoginAuthenticationTicket();
await GetSignInJourneyHelper().OnSignedInWithOneLogin(journeyInstance, ticket);

await journeyInstance.UpdateStateAsync(state =>
{
state.NationalInsuranceNumberSpecified = true;
state.TrnSpecified = false;
});

var request = new HttpRequestMessage(HttpMethod.Get, $"/not-found?{journeyInstance.GetUniqueIdQueryParameter()}");

// Act
var response = await HttpClient.SendAsync(request);

// Assert
Assert.Equal(StatusCodes.Status302Found, (int)response.StatusCode);
Assert.Equal($"/national-insurance-number?{journeyInstance.GetUniqueIdQueryParameter()}", response.Headers.Location?.OriginalString);
}

[Fact]
public async Task Get_ValidRequest_RendersExpectedContent()
{
// Arrange
var state = new SignInJourneyState(redirectUri: "/", authenticationProperties: null);
var journeyInstance = await CreateJourneyInstance(state);

var ticket = CreateOneLoginAuthenticationTicket();
await GetSignInJourneyHelper().OnSignedInWithOneLogin(journeyInstance, ticket);

await journeyInstance.UpdateStateAsync(async state =>
{
state.NationalInsuranceNumberSpecified = true;
state.Trn = await TestData.GenerateTrn();
state.TrnSpecified = true;
});

var request = new HttpRequestMessage(HttpMethod.Get, $"/not-found?{journeyInstance.GetUniqueIdQueryParameter()}");

// Act
var response = await HttpClient.SendAsync(request);

// Assert
Assert.Equal(StatusCodes.Status200OK, (int)response.StatusCode);

var doc = await response.GetDocument();
}
}
Loading

0 comments on commit a808557

Please sign in to comment.