Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,19 @@ public async Task<IActionResult> OnPostAsync()
var processContext = await ProcessContext.FromDbAsync(dbContext, state.SigningInProcessId, timeProvider.UtcNow);

SupportTask supportTask;
string? trnRequestId = null;

if (IdentityVerified)
{
if (state.RecordMatchingPolicy == RecordMatchingPolicy.Deferred)
{
await coordinator.UpdateStateAsync(async state =>
{
trnRequestId = await coordinator.CompleteWithDeferredMatchingAsync(state);
return state;
});
}

supportTask = await oneLoginUserMatchingSupportTaskService.CreateRecordMatchingSupportTaskAsync(
new CreateOneLoginUserRecordMatchingSupportTaskOptions
{
Expand All @@ -56,7 +66,8 @@ public async Task<IActionResult> OnPostAsync()
StatedNationalInsuranceNumber = state.NationalInsuranceNumber,
StatedTrn = state.Trn,
ClientApplicationUserId = state.ClientApplicationUserId,
TrnTokenTrn = state.TrnTokenTrn
TrnTokenTrn = state.TrnTokenTrn,
TrnRequestId = trnRequestId
},
processContext);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,25 @@
5 working days
</div>
<div>
Your reference number is:<br/>
Your reference number:<br/>
<strong>@Model.SupportTaskReference</strong>
</div>
</govuk-panel-body>
</govuk-panel>

<h2 class="govuk-heading-m">What happens next</h2>
<p class="govuk-body">
We’ll contact you when we have an update.
We’ll contact you when we have an update. Please do not submit another support request before then.
</p>
<p class="govuk-body">
You’ll need your reference number if you need to contact support about your request.
</p>
<p class="govuk-body">
We’ll email you once your request has been completed.
Please do not submit another support request before then.
</p>

@if (Model.CanReturnToService)
{
<form method="post">
<govuk-button type="submit">You can return to the @Model.ServiceName service.</govuk-button>
</form>
}
</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace TeachingRecordSystem.AuthorizeAccess.Pages;
Expand All @@ -6,4 +7,13 @@ namespace TeachingRecordSystem.AuthorizeAccess.Pages;
public class SupportRequestSubmittedModel(SignInJourneyCoordinator coordinator) : PageModel
{
public string SupportTaskReference => coordinator.State.CreatedSupportTaskReference!;

public bool CanReturnToService => coordinator.State.AuthenticationTicket is not null;

public string ServiceName => coordinator.State.ServiceName;

public IActionResult OnPost()
{
return coordinator.GetNextPage().ToActionResult();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ public record ConnectedOutcomeOptions
{
public required SupportTask SupportTask { get; init; }
public required Guid MatchedPersonId { get; init; }
public required string Trn { get; init; }
public required IEnumerable<KeyValuePair<PersonMatchedAttribute, string>> MatchedAttributes { get; init; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ public record CreateOneLoginUserRecordMatchingSupportTaskOptions
public required string? StatedTrn { get; init; }
public required Guid ClientApplicationUserId { get; init; }
public required string? TrnTokenTrn { get; init; }
public string? TrnRequestId { get; init; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ public async Task<SupportTask> CreateRecordMatchingSupportTaskAsync(
CreateOneLoginUserRecordMatchingSupportTaskOptions options,
ProcessContext processContext)
{
var trnRequest = options.TrnRequestId is not null
? (options.ClientApplicationUserId, options.TrnRequestId)
: ((Guid ApplicationUserId, string RequestId)?)null;

var supportTask = await supportTaskService.CreateSupportTaskAsync(
new CreateSupportTaskOptions
{
Expand All @@ -27,7 +31,7 @@ public async Task<SupportTask> CreateRecordMatchingSupportTaskAsync(
},
PersonId = null,
OneLoginUserSubject = options.OneLoginUserSubject,
TrnRequest = null
TrnRequest = trnRequest
},
processContext);

Expand Down Expand Up @@ -95,10 +99,6 @@ await oneLoginService.SetUserMatchedAsync(
},
processContext);

var firstVerifiedOrStatedName = data.VerifiedOrStatedNames!.First();
var name = $"{firstVerifiedOrStatedName.First()} {firstVerifiedOrStatedName.LastOrDefault()}";
await oneLoginService.EnqueueRecordMatchedEmailAsync(supportTask.OneLoginUser!.EmailAddress!, name, processContext);

await supportTaskService.UpdateSupportTaskAsync(
new UpdateSupportTaskOptions<OneLoginUserRecordMatchingData>
{
Expand All @@ -111,5 +111,21 @@ await supportTaskService.UpdateSupportTaskAsync(
Status = SupportTaskStatus.Closed
},
processContext);

if (supportTask.TrnRequestId is not null)
{
await trnRequestService.ResolveTrnRequestWithMatchedPersonAsync(
supportTask.TrnRequestApplicationUserId!.Value,
supportTask.TrnRequestId,
(options.MatchedPersonId, options.Trn),
options.MatchedAttributes.Select(kvp => kvp.Key).ToArray(),
processContext);
}
else
{
var firstVerifiedOrStatedName = data.VerifiedOrStatedNames!.First();
var name = $"{firstVerifiedOrStatedName.First()} {firstVerifiedOrStatedName.LastOrDefault()}";
await oneLoginService.EnqueueRecordMatchedEmailAsync(supportTask.OneLoginUser!.EmailAddress!, name, processContext);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using TeachingRecordSystem.Core.Services.OneLogin;
using TeachingRecordSystem.Core.Services.TrnRequests;

namespace TeachingRecordSystem.Core.Services.SupportTasks.OneLoginUserMatching;

public partial class OneLoginUserMatchingSupportTaskService(
SupportTaskService supportTaskService,
OneLoginService oneLoginService);
OneLoginService oneLoginService,
TrnRequestService trnRequestService);
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,38 @@ public Task ResolveTrnRequestWithMatchedPersonAsync(
publishTrnRequestUpdatedEvent: true,
processContext);

public async Task ResolveTrnRequestWithMatchedPersonAsync(
Guid applicationUserId,
string requestId,
(Guid PersonId, string Trn) person,
IReadOnlyCollection<PersonMatchedAttribute> attributesToUpdate,
ProcessContext processContext)
{
var trnRequest = await dbContext.TrnRequestMetadata
.SingleAsync(tr => tr.ApplicationUserId == applicationUserId && tr.RequestId == requestId);

await ResolveTrnRequestWithMatchedPersonAsync(
trnRequest,
person,
publishTrnRequestUpdatedEvent: true,
processContext);

await personService.UpdatePersonDetailsAsync(
new UpdatePersonDetailsOptions
{
PersonId = person.PersonId,
CreatePreviousName = false,
FirstName = attributesToUpdate.Contains(PersonMatchedAttribute.FirstName) ? Option.Some(trnRequest.FirstName!) : default,
MiddleName = attributesToUpdate.Contains(PersonMatchedAttribute.MiddleName) ? Option.Some(trnRequest.MiddleName ?? string.Empty) : default,
LastName = attributesToUpdate.Contains(PersonMatchedAttribute.LastName) ? Option.Some(trnRequest.LastName!) : default,
DateOfBirth = attributesToUpdate.Contains(PersonMatchedAttribute.DateOfBirth) ? Option.Some<DateOnly?>(trnRequest.DateOfBirth) : default,
EmailAddress = attributesToUpdate.Contains(PersonMatchedAttribute.EmailAddress) && !string.IsNullOrEmpty(trnRequest.EmailAddress) ? Option.Some<EmailAddress?>(EmailAddress.Parse(trnRequest.EmailAddress)) : default,
NationalInsuranceNumber = attributesToUpdate.Contains(PersonMatchedAttribute.NationalInsuranceNumber) && !string.IsNullOrEmpty(trnRequest.NationalInsuranceNumber) ? Option.Some<NationalInsuranceNumber?>(NationalInsuranceNumber.Parse(trnRequest.NationalInsuranceNumber)) : default,
Gender = attributesToUpdate.Contains(PersonMatchedAttribute.Gender) ? Option.Some(trnRequest.Gender) : default
},
processContext);
}

public async Task ResolveTrnRequestWithMatchedPersonAsync(
TrnRequestMetadata trnRequest,
Person person,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ public async Task OnResourceExecutionAsync(ResourceExecutingContext context, Res
SupportTaskType.ApiTrnRequest,
SupportTaskType.TrnRequestManualChecksNeeded,
SupportTaskType.NpqTrnRequest,
SupportTaskType.TeacherPensionsPotentialDuplicate
SupportTaskType.TeacherPensionsPotentialDuplicate,
SupportTaskType.OneLoginUserRecordMatching
])
.Any())
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ await supportTaskService.ResolveRecordMatchingSupportTaskAsync(
{
SupportTask = _supportTask!,
MatchedPersonId = MatchedPersonId,
Trn = matchedPerson.Trn,
MatchedAttributes = matchedPerson.MatchedAttributes
},
processContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -514,4 +514,70 @@ public async Task SignIn_VerifiedUserWithDeferredRecordMatchingPolicyAndNoTrn_Cr

await page.AssertSignedInWithDormantTrnRequestAsync(trnRequestId);
}

[Fact]
public async Task SignIn_VerifiedUserWithDeferredRecordMatchingPolicyAndUnmatchedTrn_CreatesSupportTaskWithDormantTrnRequestAndSignsIn()
{
var subject = TestData.CreateOneLoginUserSubject();
var email = Faker.Internet.Email();
var coreIdentityVc = TestData.CreateOneLoginCoreIdentityVc(
TestData.GenerateFirstName(),
TestData.GenerateLastName(),
TestData.GenerateDateOfBirth());
SetCurrentOneLoginUser(OneLoginUserInfo.Create(subject, email, coreIdentityVc));

await using var context = await HostFixture.CreateBrowserContext();
var page = await context.NewPageAsync();

await page.GoToTestStartPageAsync(deferred: true);

await page.WaitForUrlPathAsync("/connect");
await page.ClickGovUkButtonAsync("Find your teaching record");

await page.WaitForUrlPathAsync("/national-insurance-number");
await page.CheckAsync("text=Yes");
await page.FillAsync("label:text-is('National Insurance number')", TestData.GenerateNationalInsuranceNumber());
await page.ClickGovUkButtonAsync("Continue");

await page.WaitForUrlPathAsync("/trn");
await page.CheckAsync("text=Yes");
await page.FillAsync("label:text-is('Teacher reference number')", await TestData.GenerateTrnAsync());
await page.ClickGovUkButtonAsync("Continue");

await page.WaitForUrlPathAsync("/not-found");
await page.ClickGovUkButtonAsync("Check your answers");

await page.WaitForUrlPathAsync("/check-answers");
await page.ClickGovUkButtonAsync("Submit support request");

await page.WaitForUrlPathAsync("/request-submitted");

var trnRequestId = await WithDbContextAsync(async dbContext =>
{
var trnRequest = await dbContext.TrnRequestMetadata
.Where(r => r.OneLoginUserSubject == subject)
.OrderByDescending(r => r.CreatedOn)
.FirstOrDefaultAsync();

Assert.NotNull(trnRequest);
Assert.Equal(TrnRequestStatus.Pending, trnRequest.Status);

var supportTask = await dbContext.SupportTasks
.Where(st => st.OneLoginUserSubject == subject)
.OrderByDescending(st => st.CreatedOn)
.FirstOrDefaultAsync();

Assert.NotNull(supportTask);
Assert.Equal(SupportTaskType.OneLoginUserRecordMatching, supportTask.SupportTaskType);
Assert.Equal(SupportTaskStatus.Open, supportTask.Status);
Assert.Equal(trnRequest.RequestId, supportTask.TrnRequestId);
Assert.Equal(trnRequest.ApplicationUserId, supportTask.TrnRequestApplicationUserId);

return trnRequest.RequestId;
});

await page.ClickGovUkButtonAsync("You can return to the Test service service.");

await page.AssertSignedInWithDormantTrnRequestAsync(trnRequestId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,100 @@ await SetupInstanceStateForUnverifiedUserAsync(
});
}

[Fact]
public async Task Post_ValidRequestForVerifiedUserWithDeferredRecordMatchingPolicy_CreatesDormantTrnRequestAndSupportTicket()
{
// Arrange
var person = await TestData.CreatePersonAsync();
var trnToken = await CreateTrnTokenAsync(person.Trn);
var applicationUser = await TestData.CreateApplicationUserAsync(isOidcClient: true, recordMatchingPolicy: RecordMatchingPolicy.Deferred);

await WithJourneyCoordinatorAsync(
(instanceId, processId) => CreateSignInJourneyState(instanceId, processId, "/", applicationUser.UserId, trnToken.TrnToken, trnToken.Trn, RecordMatchingPolicy.Deferred),
async coordinator =>
{
var oneLoginUser = await TestData.CreateOneLoginUserAsync(verified: true);

var nationalInsuranceNumber = TestData.GenerateNationalInsuranceNumber();
var trn = await TestData.GenerateTrnAsync();

await SetupInstanceStateForVerifiedUserAsync(coordinator, oneLoginUser, nationalInsuranceNumber, trn);

LegacyEventPublisher.Clear();

var request = new HttpRequestMessage(HttpMethod.Post, JourneyUrls.CheckAnswers(coordinator.InstanceId));

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

// Assert
Assert.Equal(StatusCodes.Status302Found, (int)response.StatusCode);
Assert.Equal(JourneyUrls.RequestSubmitted(coordinator.InstanceId), response.Headers.Location?.OriginalString);

Assert.NotNull(coordinator.State.AuthenticationTicket);
var trnRequestIdClaim = coordinator.State.AuthenticationTicket.Principal.FindFirst(AuthorizeAccess.ClaimTypes.TrnRequestId);
Assert.NotNull(trnRequestIdClaim);

var trnRequest = await WithDbContextAsync(dbContext =>
dbContext.TrnRequestMetadata.SingleAsync(r => r.RequestId == trnRequestIdClaim.Value));
Assert.NotNull(trnRequest);
Assert.Equal(applicationUser.UserId, trnRequest.ApplicationUserId);
Assert.Equal(oneLoginUser.Subject, trnRequest.OneLoginUserSubject);
Assert.True(trnRequest.IdentityVerified);

var supportTask = await WithDbContextAsync(dbContext =>
dbContext.SupportTasks.SingleAsync(t => t.OneLoginUserSubject == oneLoginUser.Subject));
Assert.NotNull(supportTask);
Assert.Equal(SupportTaskType.OneLoginUserRecordMatching, supportTask.SupportTaskType);
Assert.Equal(applicationUser.UserId, supportTask.TrnRequestApplicationUserId);
Assert.Equal(trnRequestIdClaim.Value, supportTask.TrnRequestId);

var data = Assert.IsType<OneLoginUserRecordMatchingData>(supportTask.Data);
Assert.Equal(nationalInsuranceNumber, data.StatedNationalInsuranceNumber);
Assert.Equal(trn, data.StatedTrn);
});
}

[Fact]
public async Task Post_ValidRequestForVerifiedUserWithRequiredRecordMatchingPolicy_DoesNotCreateDormantTrnRequest()
{
// Arrange
var person = await TestData.CreatePersonAsync();
var trnToken = await CreateTrnTokenAsync(person.Trn);
var applicationUser = await TestData.CreateApplicationUserAsync(isOidcClient: true, recordMatchingPolicy: RecordMatchingPolicy.Required);

await WithJourneyCoordinatorAsync(
(instanceId, processId) => CreateSignInJourneyState(instanceId, processId, "/", applicationUser.UserId, trnToken.TrnToken, trnToken.Trn, RecordMatchingPolicy.Required),
async coordinator =>
{
var oneLoginUser = await TestData.CreateOneLoginUserAsync(verified: true);

var nationalInsuranceNumber = TestData.GenerateNationalInsuranceNumber();
var trn = await TestData.GenerateTrnAsync();

await SetupInstanceStateForVerifiedUserAsync(coordinator, oneLoginUser, nationalInsuranceNumber, trn);

var request = new HttpRequestMessage(HttpMethod.Post, JourneyUrls.CheckAnswers(coordinator.InstanceId));

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

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

Assert.Null(coordinator.State.AuthenticationTicket);

var trnRequestCount = await WithDbContextAsync(dbContext =>
dbContext.TrnRequestMetadata.CountAsync(r => r.ApplicationUserId == applicationUser.UserId));
Assert.Equal(0, trnRequestCount);

var supportTask = await WithDbContextAsync(dbContext =>
dbContext.SupportTasks.SingleAsync(t => t.OneLoginUserSubject == oneLoginUser.Subject));
Assert.Null(supportTask.TrnRequestApplicationUserId);
Assert.Null(supportTask.TrnRequestId);
});
}

private async Task SetupInstanceStateForVerifiedUserAsync(
SignInJourneyCoordinator coordinator,
OneLoginUser oneLoginUser,
Expand Down
Loading
Loading