Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FAI-1991 Add endpoint for get candidate by email #80

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
@@ -0,0 +1,72 @@
using System.Net;
using AutoFixture.NUnit3;
using FluentAssertions;
using MediatR;
using Microsoft.AspNetCore.Mvc;
using Moq;
using SFA.DAS.CandidateAccount.Api.Controllers;
using SFA.DAS.CandidateAccount.Application.Candidate.Queries.GetCandidateByEmail;
using SFA.DAS.Testing.AutoFixture;

namespace SFA.DAS.CandidateAccount.Api.UnitTests.Controllers.Candidate;

public class WhenCallingGetCandidateByEmail
{
[Test, MoqAutoData]
public async Task Then_Mediator_Query_Is_Called_And_Candidate_Returned(
string email,
GetCandidateByEmailQueryResult queryResult,
[Frozen] Mock<IMediator> mediator,
[Greedy] CandidateController controller)
{
//Arrange
mediator.Setup(x => x.Send(It.Is<GetCandidateByEmailQuery>(c => c.Email.Equals(email)), CancellationToken.None))
.ReturnsAsync(queryResult);

//Act
var actual = await controller.GetCandidateByEmail(email) as OkObjectResult;

//Assert
Assert.That(actual, Is.Not.Null);
actual.StatusCode.Should().Be((int) HttpStatusCode.OK);
actual.Value.Should().BeEquivalentTo(queryResult.Candidate);
}

[Test, MoqAutoData]
public async Task Then_If_Null_From_Mediator_Response_Not_Found_Returned(
string email,
GetCandidateByEmailQueryResult queryResult,
[Frozen] Mock<IMediator> mediator,
[Greedy] CandidateController controller)
{
//Arrange
queryResult.Candidate = null;
mediator.Setup(x => x.Send(It.Is<GetCandidateByEmailQuery>(c => c.Email.Equals(email)), CancellationToken.None))
.ReturnsAsync(queryResult);

//Act
var actual = await controller.GetCandidateByEmail(email) as NotFoundResult;

//Assert
Assert.That(actual, Is.Not.Null);
actual.StatusCode.Should().Be((int) HttpStatusCode.NotFound);
}

[Test, MoqAutoData]
public async Task Then_If_Exception_Internal_Server_Error_Returned(
string email,
[Frozen] Mock<IMediator> mediator,
[Greedy] CandidateController controller)
{
//Arrange
mediator.Setup(x => x.Send(It.Is<GetCandidateByEmailQuery>(c => c.Email.Equals(email)), CancellationToken.None))
.ThrowsAsync(new Exception());

//Act
var actual = await controller.GetCandidateByEmail(email) as StatusCodeResult;

//Assert
Assert.That(actual, Is.Not.Null);
actual.StatusCode.Should().Be((int) HttpStatusCode.InternalServerError);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ public async Task Then_If_MediatorCall_Returns_Created_Then_Created_Result_Retur
&& c.Candidate.PhoneNumber.Equals(postCandidateRequest.PhoneNumber)
&& c.Candidate.DateOfBirth.Equals(postCandidateRequest.DateOfBirth)
&& c.Candidate.TermsOfUseAcceptedOn.Equals(postCandidateRequest.TermsOfUseAcceptedOn)
&& c.Candidate.GovUkIdentifier.Equals(postCandidateRequest.GovUkIdentifier)
&& c.Candidate.MigratedEmail.Equals(postCandidateRequest.MigratedEmail)
&& c.Candidate.MigratedCandidateId.Equals(postCandidateRequest.MigratedCandidateId)
), CancellationToken.None))
.ReturnsAsync(upsertCandidateCommandResult);

Expand Down Expand Up @@ -63,6 +66,9 @@ public async Task Then_If_MediatorCall_Returns_NotCreated_Then_Ok_Result_Returne
&& c.Candidate.PhoneNumber.Equals(postCandidateRequest.PhoneNumber)
&& c.Candidate.DateOfBirth.Equals(postCandidateRequest.DateOfBirth)
&& c.Candidate.TermsOfUseAcceptedOn.Equals(postCandidateRequest.TermsOfUseAcceptedOn)
&& c.Candidate.GovUkIdentifier.Equals(postCandidateRequest.GovUkIdentifier)
&& c.Candidate.MigratedEmail.Equals(postCandidateRequest.MigratedEmail)
&& c.Candidate.MigratedCandidateId.Equals(postCandidateRequest.MigratedCandidateId)
), CancellationToken.None))
.ReturnsAsync(upsertCandidateCommandResult);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ public abstract class CandidateRequest
public CandidateStatus? Status { get; set; }
public string? MigratedEmail { get; set; }
public Guid? MigratedCandidateId { get; set; }
public string? GovUkIdentifier { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using SFA.DAS.CandidateAccount.Application.Candidate.Commands.DeleteCandidate;
using SFA.DAS.CandidateAccount.Application.Candidate.Commands.UpsertCandidate;
using SFA.DAS.CandidateAccount.Application.Candidate.Queries.GetCandidate;
using SFA.DAS.CandidateAccount.Application.Candidate.Queries.GetCandidateByEmail;
using SFA.DAS.CandidateAccount.Application.Candidate.Queries.GetCandidateByMigratedEmail;
using SFA.DAS.CandidateAccount.Application.Candidate.Queries.GetCandidateByMigratedId;
using SFA.DAS.CandidateAccount.Domain.Candidate;
Expand Down Expand Up @@ -112,6 +113,30 @@ public async Task<IActionResult> GetCandidateByMigratedEmail(string email)
return new StatusCodeResult((int)HttpStatusCode.InternalServerError);
}
}


[HttpGet]
[Route("email/{email}")]
public async Task<IActionResult> GetCandidateByEmail(string email)
{
try
{
var result = await mediator.Send(new GetCandidateByEmailQuery
{
Email = email
});
if (result.Candidate == null)
{
return NotFound();
}
return Ok(result.Candidate);
}
catch (Exception e)
{
logger.LogError(e, "Get candidate by email : An error occurred");
return new StatusCodeResult((int)HttpStatusCode.InternalServerError);
}
}

[HttpPut]
[Route("{candidateId}")]
Expand All @@ -133,7 +158,8 @@ public async Task<IActionResult> PutCandidate([FromRoute] Guid candidateId, PutC
TermsOfUseAcceptedOn = postCandidateRequest.TermsOfUseAcceptedOn,
Status = postCandidateRequest.Status,
MigratedEmail = postCandidateRequest.MigratedEmail,
MigratedCandidateId = postCandidateRequest.MigratedCandidateId
MigratedCandidateId = postCandidateRequest.MigratedCandidateId,
GovUkIdentifier = postCandidateRequest.GovUkIdentifier
}
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using AutoFixture.NUnit3;
using FluentAssertions;
using Moq;
using SFA.DAS.CandidateAccount.Application.Candidate.Queries.GetCandidateByEmail;
using SFA.DAS.CandidateAccount.Data.Candidate;
using SFA.DAS.CandidateAccount.Domain.Candidate;
using SFA.DAS.Testing.AutoFixture;

namespace SFA.DAS.CandidateAccount.Application.UnitTests.Candidate;

public class WhenHandlingGetCandidateByEmailQuery
{
[Test, RecursiveMoqAutoData]
public async Task Then_The_Query_Is_Handled_And_Data_Candidate_Returned(
GetCandidateByEmailQuery query,
CandidateEntity entity,
[Frozen] Mock<ICandidateRepository> candidateRepository,
GetCandidateByEmailQueryHandler handler)
{
candidateRepository.Setup(x => x.GetCandidateByEmail(query.Email)).ReturnsAsync(entity);

var actual = await handler.Handle(query, CancellationToken.None);

actual.Candidate.Should().BeEquivalentTo(entity, options=>options
.Excluding(c=>c.CandidatePreferences)
.Excluding(c=>c.Address)
.Excluding(c=>c.AboutYou)
.Excluding(c=>c.Applications)
.Excluding(c=>c.Status)
);
}

[Test, MoqAutoData]
public async Task Then_If_Not_Found_Then_Null_Returned(
GetCandidateByEmailQuery query,
[Frozen] Mock<ICandidateRepository> candidateRepository,
GetCandidateByEmailQueryHandler handler)
{
candidateRepository.Setup(x => x.GetCandidateByEmail(query.Email)).ReturnsAsync((CandidateEntity?)null);

var actual = await handler.Handle(query, CancellationToken.None);

actual.Candidate.Should().BeNull();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using MediatR;

namespace SFA.DAS.CandidateAccount.Application.Candidate.Queries.GetCandidateByEmail;

public class GetCandidateByEmailQuery : IRequest<GetCandidateByEmailQueryResult>
{
public string Email { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using MediatR;
using SFA.DAS.CandidateAccount.Data.Candidate;

namespace SFA.DAS.CandidateAccount.Application.Candidate.Queries.GetCandidateByEmail;

public class GetCandidateByEmailQueryHandler(ICandidateRepository candidateRepository) : IRequestHandler<GetCandidateByEmailQuery, GetCandidateByEmailQueryResult>
{
public async Task<GetCandidateByEmailQueryResult> Handle(GetCandidateByEmailQuery request, CancellationToken cancellationToken)
{
var candidate = await candidateRepository.GetCandidateByEmail(request.Email);

return new GetCandidateByEmailQueryResult
{
Candidate = candidate
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace SFA.DAS.CandidateAccount.Application.Candidate.Queries.GetCandidateByEmail;

public class GetCandidateByEmailQueryResult
{
public Domain.Candidate.Candidate? Candidate { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ public async Task AndIdExistsThenCandidateIsUpdatedIfNotNull(
actual.Item1.PhoneNumber.Should().Be(existingCandidate.PhoneNumber);
actual.Item1.MigratedEmail.Should().Be(existingCandidate.MigratedEmail);
actual.Item1.MigratedCandidateId.Should().Be(existingCandidate.MigratedCandidateId);
actual.Item1.GovUkIdentifier.Should().Be(existingCandidate.GovUkIdentifier);
actual.Item1.UpdatedOn.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,15 +110,11 @@ public async Task<Tuple<CandidateEntity,bool>> UpsertCandidate(Domain.Candidate.
.CandidateEntities
.FirstOrDefaultAsync(c => c.Id == candidate.Id);

//TODO look at why we are doing this - and not doing all the fields
if (existingCandidate == null)
{
var newCandidate = (CandidateEntity)candidate;
newCandidate.CreatedOn = DateTime.UtcNow;
newCandidate.UpdatedOn = null;
newCandidate.FirstName = candidate.FirstName;
newCandidate.LastName = candidate.LastName;
newCandidate.DateOfBirth = candidate.DateOfBirth;
await dataContext.CandidateEntities.AddAsync(newCandidate);
await dataContext.SaveChangesAsync();
return new Tuple<CandidateEntity, bool>(newCandidate, true);
Expand All @@ -133,6 +129,7 @@ public async Task<Tuple<CandidateEntity,bool>> UpsertCandidate(Domain.Candidate.
existingCandidate.Status = candidate.Status.HasValue ? (short)candidate.Status : existingCandidate.Status;
existingCandidate.MigratedEmail = candidate.MigratedEmail ?? existingCandidate.MigratedEmail;
existingCandidate.MigratedCandidateId = candidate.MigratedCandidateId ?? existingCandidate.MigratedCandidateId;
existingCandidate.GovUkIdentifier = candidate.GovUkIdentifier ?? existingCandidate.GovUkIdentifier;
dataContext.CandidateEntities.Update(existingCandidate);
await dataContext.SaveChangesAsync();

Expand Down