Skip to content
Draft
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
8 changes: 8 additions & 0 deletions TeachingRecordSystem/src/TeachingRecordSystem.Api/ApiError.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public static class ErrorCodes
public static int UnableToChangeFailProfessionalStatusStatus => 10055;
public static int UnableToChangeWithdrawnProfessionalStatusStatus => 10056;
public static int PiiUpdatesForbiddenPersonHasEyts => 10057;
public static int PersonInactive => 10058;
}

public static ApiError PersonNotFound(string trn, DateOnly? dateOfBirth = null, string? nationalInsuranceNumber = null)
Expand All @@ -55,6 +56,13 @@ public static ApiError PersonNotFound(string trn, DateOnly? dateOfBirth = null,
return new ApiError(ErrorCodes.PersonNotFound, title, detail);
}

public static ApiError PersonInactive(string trn)
{
var title = $"Person is inactive.";

return new ApiError(ErrorCodes.PersonInactive, title);
}

public static ApiError SpecifiedResourceUrlDoesNotExist(string url) =>
new(ErrorCodes.SpecifiedResourceUrlDoesNotExist, "The specified resource does not exist.", $"URL: '{url}'");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ public async Task<ApiResult<GetPersonResult>> HandleAsync(GetPersonCommand comma
}

var contactDetail = await crmQueryDispatcher.ExecuteQueryAsync(
new GetActiveContactDetailByTrnQuery(
new GetContactDetailByTrnQuery(
command.Trn,
new ColumnSet(
Contact.Fields.FirstName,
Expand All @@ -203,6 +203,11 @@ public async Task<ApiResult<GetPersonResult>> HandleAsync(GetPersonCommand comma
return ApiError.PersonNotFound(command.Trn);
}

if (contactDetail.Contact.StateCode is ContactState.Inactive)
{
return ApiError.PersonInactive(command.Trn);
}

// If a DateOfBirth or NationalInsuranceNumber was provided, ensure the record we've retrieved with the TRN matches
if (command.DateOfBirth is DateOnly dateOfBirth &&
contactDetail.Contact.BirthDate.ToDateOnlyWithDqtBstFix(isLocalTime: false) != dateOfBirth)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public async Task<IActionResult> GetAsync(
var result = await handler.HandleAsync(command);

return result.ToActionResult(r => Ok(mapper.Map<GetTeacherResponse>(r)))
.MapErrorCode(ApiError.ErrorCodes.PersonNotFound, StatusCodes.Status403Forbidden)
.MapErrorCode(ApiError.ErrorCodes.PersonNotFound, StatusCodes.Status403Forbidden);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ public async Task<IActionResult> GetAsync(
var result = await handler.HandleAsync(command);

return result.ToActionResult(r => Ok(mapper.Map<GetTeacherResponse>(r)))
.MapErrorCode(ApiError.ErrorCodes.PersonNotFound, StatusCodes.Status404NotFound);
.MapErrorCode(ApiError.ErrorCodes.PersonNotFound, StatusCodes.Status404NotFound)
.MapErrorCode(ApiError.ErrorCodes.PersonInactive, StatusCodes.Status404NotFound);
}

[HttpPost("name-changes")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public async Task<IActionResult> GetAsync(
var result = await handler.HandleAsync(command);

return result.ToActionResult(r => Ok(mapper.Map<GetTeacherResponse>(r)))
.MapErrorCode(ApiError.ErrorCodes.PersonNotFound, StatusCodes.Status403Forbidden);
.MapErrorCode(ApiError.ErrorCodes.PersonNotFound, StatusCodes.Status403Forbidden)
.MapErrorCode(ApiError.ErrorCodes.PersonInactive, StatusCodes.Status403Forbidden);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public async Task<IActionResult> GetAsync(
var result = await handler.HandleAsync(command);

return result.ToActionResult(r => Ok(mapper.Map<GetTeacherResponse>(r)))
.MapErrorCode(ApiError.ErrorCodes.PersonNotFound, StatusCodes.Status404NotFound);
.MapErrorCode(ApiError.ErrorCodes.PersonNotFound, StatusCodes.Status404NotFound)
.MapErrorCode(ApiError.ErrorCodes.PersonInactive, StatusCodes.Status404NotFound);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ public async Task<IActionResult> GetAsync(
var result = await handler.HandleAsync(command);

return result.ToActionResult(r => Ok(mapper.Map<GetPersonResponse>(r)))
.MapErrorCode(ApiError.ErrorCodes.PersonNotFound, StatusCodes.Status403Forbidden);
.MapErrorCode(ApiError.ErrorCodes.PersonNotFound, StatusCodes.Status403Forbidden)
.MapErrorCode(ApiError.ErrorCodes.PersonInactive, StatusCodes.Status403Forbidden);
}

[HttpPost("name-changes")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ public async Task<IActionResult> GetAsync(
var result = await handler.HandleAsync(command);

return result.ToActionResult(r => Ok(mapper.Map<GetPersonResponse>(r)))
.MapErrorCode(ApiError.ErrorCodes.PersonNotFound, StatusCodes.Status404NotFound);
.MapErrorCode(ApiError.ErrorCodes.PersonNotFound, StatusCodes.Status404NotFound)
.MapErrorCode(ApiError.ErrorCodes.PersonInactive, StatusCodes.Status404NotFound);
}

[HttpGet("")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public async Task<IActionResult> GetAsync(
var result = await handler.HandleAsync(command);

return result.ToActionResult(r => Ok(mapper.Map<GetPersonResponse>(r)))
.MapErrorCode(ApiError.ErrorCodes.PersonNotFound, StatusCodes.Status403Forbidden);
.MapErrorCode(ApiError.ErrorCodes.PersonNotFound, StatusCodes.Status403Forbidden)
.MapErrorCode(ApiError.ErrorCodes.PersonInactive, StatusCodes.Status403Forbidden);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public async Task<IActionResult> GetAsync(

return result.ToActionResult(r => Ok(mapper.Map<GetPersonResponse>(r)))
.MapErrorCode(ApiError.ErrorCodes.PersonNotFound, StatusCodes.Status404NotFound)
.MapErrorCode(ApiError.ErrorCodes.PersonInactive, StatusCodes.Status404NotFound)
.MapErrorCode(ApiError.ErrorCodes.ForbiddenForAppropriateBody, StatusCodes.Status403Forbidden);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public async Task<IActionResult> GetAsync(
var result = await handler.HandleAsync(command);

return result.ToActionResult(r => Ok(mapper.Map<GetPersonResponse>(r)))
.MapErrorCode(ApiError.ErrorCodes.PersonNotFound, StatusCodes.Status403Forbidden);
.MapErrorCode(ApiError.ErrorCodes.PersonNotFound, StatusCodes.Status403Forbidden)
.MapErrorCode(ApiError.ErrorCodes.PersonInactive, StatusCodes.Status403Forbidden);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public async Task<IActionResult> GetAsync(
return result
.ToActionResult(r => Ok(mapper.Map<GetPersonResponse>(r)))
.MapErrorCode(ApiError.ErrorCodes.PersonNotFound, StatusCodes.Status404NotFound)
.MapErrorCode(ApiError.ErrorCodes.PersonInactive, StatusCodes.Status404NotFound)
.MapErrorCode(ApiError.ErrorCodes.ForbiddenForAppropriateBody, StatusCodes.Status403Forbidden);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public async Task<IActionResult> GetAsync(
var result = await handler.HandleAsync(command);

return result.ToActionResult(r => Ok(mapper.Map<GetPersonResponse>(r)))
.MapErrorCode(ApiError.ErrorCodes.PersonNotFound, StatusCodes.Status403Forbidden);
.MapErrorCode(ApiError.ErrorCodes.PersonNotFound, StatusCodes.Status403Forbidden)
.MapErrorCode(ApiError.ErrorCodes.PersonInactive, StatusCodes.Status403Forbidden);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public async Task<IActionResult> GetAsync(
return result
.ToActionResult(r => Ok(mapper.Map<GetPersonResponse>(r)))
.MapErrorCode(ApiError.ErrorCodes.PersonNotFound, StatusCodes.Status404NotFound)
.MapErrorCode(ApiError.ErrorCodes.PersonInactive, StatusCodes.Status404NotFound)
.MapErrorCode(ApiError.ErrorCodes.ForbiddenForAppropriateBody, StatusCodes.Status403Forbidden);
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using Microsoft.Xrm.Sdk.Query;

namespace TeachingRecordSystem.Core.Dqt.Queries;

public record GetContactDetailByTrnQuery(string Trn, ColumnSet ColumnSet) : ICrmQuery<ContactDetail?>;
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@

namespace TeachingRecordSystem.Core.Dqt.QueryHandlers;

public class GetActiveContactDetailByTrnHandler : ICrmQueryHandler<GetActiveContactDetailByTrnQuery, ContactDetail?>
public class GetContactDetailByTrnHandler : ICrmQueryHandler<GetContactDetailByTrnQuery, ContactDetail?>
{
public async Task<ContactDetail?> ExecuteAsync(GetActiveContactDetailByTrnQuery query, IOrganizationServiceAsync organizationService)
public async Task<ContactDetail?> ExecuteAsync(GetContactDetailByTrnQuery query, IOrganizationServiceAsync organizationService)
{
var columns = query.ColumnSet.AllColumns ? query.ColumnSet : new ColumnSet(query.ColumnSet.Columns.Append(Contact.Fields.StateCode).ToArray());

var contactFilter = new FilterExpression();
contactFilter.AddCondition(Contact.Fields.dfeta_TRN, ConditionOperator.Equal, query.Trn);
contactFilter.AddCondition(Contact.Fields.StateCode, ConditionOperator.Equal, (int)ContactState.Active);
var contactQueryExpression = new QueryExpression(Contact.EntityLogicalName)
{
ColumnSet = query.ColumnSet,
ColumnSet = columns,
Criteria = contactFilter
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,4 +311,19 @@ public async Task Get_ValidRequestWithPreviousNames_ReturnsExpectedPreviousNames

await ValidRequestWithPreviousNames_ReturnsExpectedPreviousNamesContent(GetHttpClientWithApiKey(), baseUrl, contact);
}

[Fact]
public async Task ValidRequest_ForInactiveContact_ReturnsNotFound()
{
// Arrange
var person = await TestData.CreatePersonAsync(p => p.WithTrn().WithContactState(ContactState.Inactive));

var request = new HttpRequestMessage(HttpMethod.Get, $"/v3/teachers/{person.Trn}");

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

// Assert
await AssertEx.JsonResponseIsErrorAsync(response, ApiError.ErrorCodes.PersonInactive, StatusCodes.Status404NotFound);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,19 @@ public async Task Get_DateOfBirthNotProvided_ReturnsOk()
// Assert
Assert.Equal(StatusCodes.Status200OK, (int)response.StatusCode);
}

[Fact]
public async Task ValidRequest_ForInactiveContact_ReturnsNotFound()
{
// Arrange
var person = await TestData.CreatePersonAsync(p => p.WithTrn().WithContactState(ContactState.Inactive));

var request = new HttpRequestMessage(HttpMethod.Get, $"/v3/teachers/{person.Trn}");

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

// Assert
await AssertEx.JsonResponseIsErrorAsync(response, ApiError.ErrorCodes.PersonInactive, StatusCodes.Status404NotFound);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -359,4 +359,19 @@ public async Task Get_DateOfBirthNotProvided_ReturnsOk()
// Assert
Assert.Equal(StatusCodes.Status200OK, (int)response.StatusCode);
}

[Fact]
public async Task ValidRequest_ForInactiveContact_ReturnsNotFound()
{
// Arrange
var person = await TestData.CreatePersonAsync(p => p.WithTrn().WithContactState(ContactState.Inactive));

var request = new HttpRequestMessage(HttpMethod.Get, $"/v3/persons/{person.Trn}");

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

// Assert
await AssertEx.JsonResponseIsErrorAsync(response, ApiError.ErrorCodes.PersonInactive, StatusCodes.Status404NotFound);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,21 @@ public async Task Get_WithItt_ReturnsExpectedItt()
responseItt);
}

[Fact]
public async Task ValidRequest_ForInactiveContact_ReturnsNotFound()
{
// Arrange
var person = await TestData.CreatePersonAsync(p => p.WithTrn().WithContactState(ContactState.Inactive));

var request = new HttpRequestMessage(HttpMethod.Get, $"/v3/persons/{person.Trn}");

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

// Assert
await AssertEx.JsonResponseIsErrorAsync(response, ApiError.ErrorCodes.PersonInactive, StatusCodes.Status404NotFound);
}

private static dfeta_initialteachertraining CreateIttEntity(Guid contactId, string ittProviderUkprn, string ittProviderName)
{
var ittStartDate = new DateOnly(2021, 9, 7);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,4 +235,19 @@ public async Task Get_WithNullDqtInductionStatus_ReturnsNoneInductionStatus()
},
responseInduction);
}

[Fact]
public async Task ValidRequest_ForInactiveContact_ReturnsNotFound()
{
// Arrange
var person = await TestData.CreatePersonAsync(p => p.WithTrn().WithContactState(ContactState.Inactive));

var request = new HttpRequestMessage(HttpMethod.Get, $"/v3/persons/{person.Trn}");

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

// Assert
await AssertEx.JsonResponseIsErrorAsync(response, ApiError.ErrorCodes.PersonInactive, StatusCodes.Status404NotFound);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ public GetPersonByTrnTests(HostFixture hostFixture)
public async Task Get_PersonWithQtlsAndQtsViaAnotherRoute_ReturnsExpectedAwardedOrApprovedCount()
{
// Arrange


var person = await TestData.CreatePersonAsync(p => p
.WithTrn()
.WithQts()
Expand All @@ -31,4 +29,19 @@ public async Task Get_PersonWithQtlsAndQtsViaAnotherRoute_ReturnsExpectedAwarded
var awardedOrApprovedCount = responseJson.RootElement.GetProperty("qts").GetProperty("awardedOrApprovedCount").GetInt32();
Assert.Equal(2, awardedOrApprovedCount);
}

[Fact]
public async Task ValidRequest_ForInactiveContact_ReturnsNotFound()
{
// Arrange
var person = await TestData.CreatePersonAsync(p => p.WithTrn().WithContactState(ContactState.Inactive));

var request = new HttpRequestMessage(HttpMethod.Get, $"/v3/persons/{person.Trn}");

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

// Assert
await AssertEx.JsonResponseIsErrorAsync(response, ApiError.ErrorCodes.PersonInactive, StatusCodes.Status404NotFound);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public async Task WhenCalled_WithTrnForNonExistentContact_ReturnsNull()
var trn = "DodgyTrn";

// Act
var result = await _crmQueryDispatcher.ExecuteQueryAsync(new GetActiveContactDetailByTrnQuery(trn, new ColumnSet()));
var result = await _crmQueryDispatcher.ExecuteQueryAsync(new GetContactDetailByTrnQuery(trn, new ColumnSet()));

// Assert
Assert.Null(result);
Expand All @@ -37,7 +37,7 @@ public async Task WhenCalled_WithTrnForExistingContact_ReturnsContactDetail()
var person = await _dataScope.TestData.CreatePersonAsync(p => p.WithTrn());

// Act
var result = await _crmQueryDispatcher.ExecuteQueryAsync(new GetActiveContactDetailByTrnQuery(person.Trn!, new ColumnSet()));
var result = await _crmQueryDispatcher.ExecuteQueryAsync(new GetContactDetailByTrnQuery(person.Trn!, new ColumnSet()));

// Assert
Assert.NotNull(result);
Expand All @@ -56,7 +56,7 @@ public async Task WhenCalled_WithTrnForExistingContactWithPreviousName_ReturnsCo
await _dataScope.TestData.UpdatePersonAsync(b => b.WithPersonId(person.ContactId).WithUpdatedName(updatedFirstName, updatedMiddleName, updatedLastName));

// Act
var result = await _crmQueryDispatcher.ExecuteQueryAsync(new GetActiveContactDetailByTrnQuery(person.Trn!, new ColumnSet()));
var result = await _crmQueryDispatcher.ExecuteQueryAsync(new GetContactDetailByTrnQuery(person.Trn!, new ColumnSet()));

// Assert
Assert.NotNull(result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public class CreatePersonBuilder
private string? _slugId;
private int? _loginFailedCounter;
private CreatePersonInductionBuilder? _inductionBuilder;
private ContactState? _contactState;

public Guid PersonId { get; } = Guid.NewGuid();

Expand Down Expand Up @@ -299,6 +300,12 @@ public CreatePersonBuilder WithInductionStatus(Action<CreatePersonInductionBuild
return this;
}

public CreatePersonBuilder WithContactState(ContactState contactState)
{
_contactState = contactState;
return this;
}

internal async Task<CreatePersonResult> ExecuteAsync(TestData testData)
{
var trn = _hasTrn == true ? await testData.GenerateTrnAsync() : null;
Expand Down Expand Up @@ -511,6 +518,16 @@ internal async Task<CreatePersonResult> ExecuteAsync(TestData testData)
}
}

if (_contactState is ContactState.Inactive)
{
txnRequestBuilder.AddRequest<SetStateResponse>(new SetStateRequest()
{
EntityMoniker = PersonId.ToEntityReference(Contact.EntityLogicalName),
State = new OptionSetValue((int)TaskState.Completed),
Status = new OptionSetValue((int)Task_StatusCode.Completed)
});
}

var retrieveContactHandle = txnRequestBuilder.AddRequest<RetrieveResponse>(new RetrieveRequest()
{
ColumnSet = new(allColumns: true),
Expand Down
Loading