Skip to content
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,6 @@
namespace SFA.DAS.CommitmentsV2.Api.Types.Requests;

public class ProcessApprenticeshipApprovalRequest : SaveDataRequest
{
public bool ApplyChanges { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using AutoFixture.NUnit3;
using Microsoft.AspNetCore.Mvc;
using SFA.DAS.CommitmentsV2.Api.Controllers;
using SFA.DAS.CommitmentsV2.Application.Queries.GetApprenticeshipApproval;
using SFA.DAS.Testing.AutoFixture;

namespace SFA.DAS.CommitmentsV2.Api.UnitTests.Controllers.ApprenticeshipApprovalControllerTests
{
public class WhenGettingApprenticeshipApprovalRequest
{
[Test, MoqAutoData]
public async Task Then_The_Request_Is_Passed_To_Mediator_And_Data_Returned(
GetApprenticeshipApprovalQueryResult result,
long apprenticeshipId,
Guid ApprovalRequestId,
[Frozen] Mock<IMediator> mediator,
[Greedy] ApprenticeshipApprovalsController controller)
{
mediator.Setup(x => x.Send(It.Is<GetApprenticeshipApprovalQuery>(q => q.ApprenticeshipId == apprenticeshipId && q.ApprovalRequestId == ApprovalRequestId),
CancellationToken.None)).ReturnsAsync(result);

var actual = await controller.GetApprenticeshipApproval(apprenticeshipId, ApprovalRequestId) as OkObjectResult;

actual.Should().NotBeNull();
var model = actual.Value as GetApprenticeshipApprovalQueryResult;
model.Should().Be(result);
}

[Test, MoqAutoData]
public async Task Then_The_Request_Is_Passed_To_Mediator_And_NoData_Returned(
long apprenticeshipId,
Guid ApprovalRequestId,
[Frozen] Mock<IMediator> mediator,
[Greedy] ApprenticeshipApprovalsController controller)
{
mediator.Setup(x => x.Send(It.Is<GetApprenticeshipApprovalQuery>(q => q.ApprenticeshipId == apprenticeshipId && q.ApprovalRequestId == ApprovalRequestId),
CancellationToken.None)).ReturnsAsync((GetApprenticeshipApprovalQueryResult)null);

var actual = await controller.GetApprenticeshipApproval(apprenticeshipId, ApprovalRequestId) as NotFoundResult;

actual.Should().NotBeNull();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using AutoFixture.NUnit3;
using Microsoft.AspNetCore.Mvc;
using SFA.DAS.CommitmentsV2.Api.Controllers;
using SFA.DAS.CommitmentsV2.Api.Types.Requests;
using SFA.DAS.CommitmentsV2.Application.Commands.ProcessApprenticeshipApproval;
using SFA.DAS.Testing.AutoFixture;

namespace SFA.DAS.CommitmentsV2.Api.UnitTests.Controllers.ApprenticeshipApprovalControllerTests
{
public class WhenProcessingApprenticeshipApprovalRequest
{
[Test, MoqAutoData]
public async Task Then_The_Request_Is_Passed_To_Mediator_And_Ok_Returned(
long apprenticeshipId,
Guid ApprovalRequestId,
ProcessApprenticeshipApprovalRequest request,
[Frozen] Mock<IMediator> mediator,
[Greedy] ApprenticeshipApprovalsController controller)
{
mediator.Setup(x => x.Send(It.IsAny<ProcessApprenticeshipApprovalCommand>(), CancellationToken.None));

var actual = await controller.PostApprenticeshipApproval(apprenticeshipId, ApprovalRequestId, request) as OkResult;

actual.Should().NotBeNull();
mediator.Verify(x => x.Send(It.Is<ProcessApprenticeshipApprovalCommand>(c =>
c.ApprenticeshipId == apprenticeshipId &&
c.ApprovalRequestId == ApprovalRequestId &&
c.ApplyChanges == request.ApplyChanges &&
c.UserInfo == request.UserInfo), CancellationToken.None), Times.Once);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Microsoft.AspNetCore.Authorization;
using SFA.DAS.CommitmentsV2.Api.Types.Requests;
using SFA.DAS.CommitmentsV2.Application.Commands.ProcessApprenticeshipApproval;
using SFA.DAS.CommitmentsV2.Application.Queries.GetApprenticeshipApproval;

namespace SFA.DAS.CommitmentsV2.Api.Controllers;

[ApiController]
[Authorize]
[Route("api/apprenticeships/{ApprenticeshipId:long}/approvals/{ApprovalRequestId:Guid}")]
public class ApprenticeshipApprovalsController(IMediator mediator) : ControllerBase
{
[HttpGet]
public async Task<IActionResult> GetApprenticeshipApproval(long apprenticeshipId, Guid approvalRequestId)
{
var result = await mediator.Send(new GetApprenticeshipApprovalQuery(apprenticeshipId, approvalRequestId));

if(result == null)
{
return NotFound();
}

return Ok(result);
}

[HttpPost]
public async Task<IActionResult> PostApprenticeshipApproval(long apprenticeshipId, Guid approvalRequestId, [FromBody] ProcessApprenticeshipApprovalRequest request)
{
await mediator.Send(new ProcessApprenticeshipApprovalCommand
{
ApprenticeshipId = apprenticeshipId,
ApprovalRequestId = approvalRequestId,
ApplyChanges = request.ApplyChanges,
UserInfo = request.UserInfo
});

return Ok();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;

namespace SFA.DAS.CommitmentsV2.Messages.Events;


public class LearningChangeApprovedEvent : LearningChangeEvent { }

public class LearningChangeRejectedEvent : LearningChangeEvent { }


public class LearningChangeEvent
{
public Guid LearningKey { get; set; }
public long ApprenticeshipId { get; set; }
public Dictionary<string, Change> Changes { get; set; }

public class Change
{
public string Old { get; set; }
public string New { get; set; }
public DateTime? EffectiveFromDate { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
using NServiceBus;
using SFA.DAS.CommitmentsV2.Application.Commands.ProcessApprenticeshipApproval;
using SFA.DAS.CommitmentsV2.Data;
using SFA.DAS.CommitmentsV2.Domain.Exceptions;
using SFA.DAS.CommitmentsV2.Messages.Commands;
using SFA.DAS.CommitmentsV2.Messages.Events;
using SFA.DAS.CommitmentsV2.Models;
using SFA.DAS.CommitmentsV2.Types;

namespace SFA.DAS.CommitmentsV2.UnitTests.Application.Commands;
[TestFixture]
public class ProcessApprenticeshipApprovalCommandHandlerTests
{
private Fixture _autoFixture;
private ProcessApprenticeshipApprovalCommandHandlerTestsFixture _fixture;

[SetUp]
public void Arrange()
{
_autoFixture = new Fixture();
_fixture = new ProcessApprenticeshipApprovalCommandHandlerTestsFixture();
}

[Test]
public async Task When_HandlingCommand_And_ApprovalRequest_NotFound_Throw_Exception()
{
_fixture.ApprovalRequest = null;
var act = async () => await _fixture.Handle();
await act.Should().ThrowAsync<Exception>().WithMessage($"Approval request {_fixture.Command.ApprovalRequestId} not found");
}

[Test]
public async Task When_HandlingCommand_And_ApprovalRequest_Found_But_Wrong_Apprenticeship_Throw_Exception()
{
_fixture.ApprovalRequest.ApprenticeshipId = _fixture.Command.ApprenticeshipId + 1;
await _fixture.SeedData();
var act = async () => await _fixture.Handle();
await act.Should().ThrowAsync<Exception>().WithMessage($"Approval request {_fixture.Command.ApprovalRequestId} not found for apprenticeship {_fixture.Command.ApprenticeshipId}");
}

[Test]
public async Task When_HandlingCommand_And_ApprovalRequest_Found_But_Status_Not_Pending_Throw_Exception()
{
var items = new List<ApprovalFieldRequest>();
_fixture.ApprovalRequest.Status = CocApprovalResultStatus.Cancelled;
await _fixture.SeedData();
var act = async () => await _fixture.Handle();
await act.Should().ThrowAsync<Exception>().WithMessage($"Approval request {_fixture.Command.ApprovalRequestId} is no longer pending. It's status is {_fixture.ApprovalRequest.Status}");
}

[Test]
public async Task When_HandlingCommand_And_ApprovalRequest_TNPValues_Exceed_Upper_Limit_Throw_DomainException()
{
var items = new List<ApprovalFieldRequest>();
await _fixture.SeedData();
_fixture.ApprovalRequest.Items.First(x => x.Field == "TNP1").New = "100001";
var act = async () => await _fixture.Handle();
await act.Should().ThrowAsync<DomainException>();
}

[Test]
public async Task When_HandlingCommand_Should_Send_Command_ToChangeHistory()
{
await _fixture.SeedData();
await _fixture.Handle();

_fixture.MessageSession.Verify(y => y.Send(It.Is<StoreLearningHistoryCommand>(x => x.ApprenticeshipId == _fixture.Command.ApprenticeshipId &&
x.Source == LearningSourceType.ApprovalAPI &&
x.ChangeType == (_fixture.Command.ApplyChanges ? LearningChangeType.EmployerApproved : LearningChangeType.EmployerRejected) &&
x.Description == "Total price change from £1,100 to £2,200"
), It.IsAny<SendOptions>()), Times.Once);
}

[Test]
public async Task When_HandlingCommand_Should_SaveRequestAsApproved()
{
_fixture.Command.ApplyChanges = true;
await _fixture.SeedData();
await _fixture.Handle();

var request = await _fixture.Db.ApprovalRequests.FirstOrDefaultAsync(x => x.Id == _fixture.Command.ApprovalRequestId);

request.Status.Should().Be(CocApprovalResultStatus.Complete);
request.Items.First().Status.Should().Be(CocApprovalItemStatus.EmployerApproved);
request.Items.First().ApproverId.Should().Be(_fixture.Command.UserInfo.UserId);
request.Items.Last().Status.Should().Be(CocApprovalItemStatus.EmployerApproved);
request.Items.Last().ApproverId.Should().Be(_fixture.Command.UserInfo.UserId);
}

[Test]
public async Task When_HandlingCommand_Should_SaveRequestAsRejected()
{
_fixture.Command.ApplyChanges = false;
await _fixture.SeedData();
await _fixture.Handle();

var request = await _fixture.Db.ApprovalRequests.FirstOrDefaultAsync(x => x.Id == _fixture.Command.ApprovalRequestId);

request.Status.Should().Be(CocApprovalResultStatus.Complete);
request.Items.First().Status.Should().Be(CocApprovalItemStatus.EmployerRejected);
request.Items.First().ApproverId.Should().Be(_fixture.Command.UserInfo.UserId);
request.Items.Last().Status.Should().Be(CocApprovalItemStatus.EmployerRejected);
request.Items.Last().ApproverId.Should().Be(_fixture.Command.UserInfo.UserId);
}

[Test]
public async Task When_HandlingCommand_Should_Publish_LearningChangeApprovedEvent()
{
_fixture.Command.ApplyChanges = true;
await _fixture.SeedData();
await _fixture.Handle();

_fixture.MessageSession.Verify(y => y.Publish(It.Is<LearningChangeApprovedEvent>(x => x.ApprenticeshipId == _fixture.Command.ApprenticeshipId &&
x.LearningKey == _fixture.ApprovalRequest.LearningKey &&
x.Changes["TrainingPrice"].Old == "1000" &&
x.Changes["TrainingPrice"].New == "2000" &&
x.Changes["AssessmentPrice"].Old == "100" &&
x.Changes["AssessmentPrice"].New == "200"), It.IsAny<PublishOptions>()), Times.Once);
}

[Test]
public async Task When_HandlingCommand_Should_Publish_LearningChangeRejectedEvent()
{
_fixture.Command.ApplyChanges = false;
await _fixture.SeedData();
await _fixture.Handle();

_fixture.MessageSession.Verify(y => y.Publish(It.Is<LearningChangeRejectedEvent>(x => x.ApprenticeshipId == _fixture.Command.ApprenticeshipId &&
x.LearningKey == _fixture.ApprovalRequest.LearningKey &&
x.Changes["TrainingPrice"].Old == "1000" &&
x.Changes["TrainingPrice"].New == "2000" &&
x.Changes["AssessmentPrice"].Old == "100" &&
x.Changes["AssessmentPrice"].New == "200"), It.IsAny<PublishOptions>()), Times.Once);
}


public class ProcessApprenticeshipApprovalCommandHandlerTestsFixture
{
public ProcessApprenticeshipApprovalCommandHandler Handler;
public ProcessApprenticeshipApprovalCommand Command;
public ProviderCommitmentsDbContext Db { get; set; }
public ApprovalRequest ApprovalRequest;
public List<ApprovalFieldRequest> Items;
public Mock<IMessageSession> MessageSession;

public ProcessApprenticeshipApprovalCommandHandlerTestsFixture()
{
var autoFixture = new Fixture();
Db = new ProviderCommitmentsDbContext(new DbContextOptionsBuilder<ProviderCommitmentsDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options);
MessageSession = new Mock<IMessageSession>();

Handler = new ProcessApprenticeshipApprovalCommandHandler(
new Lazy<ProviderCommitmentsDbContext>(() => Db),
MessageSession.Object);

Command = autoFixture.Create<ProcessApprenticeshipApprovalCommand>();
Items =
[
autoFixture.Build<ApprovalFieldRequest>()
.With(x => x.Id, Guid.NewGuid())
.With(x => x.Field, "TNP1")
.With(x => x.Old, "1000")
.With(x => x.New, "2000")
.Without(x => x.ApprovalRequestId)
.Without(x => x.ApprovalRequest)
.Create(),
autoFixture.Build<ApprovalFieldRequest>()
.With(x => x.Id, Guid.NewGuid())
.With(x => x.Field, "TNP2")
.With(x => x.Old, "100")
.With(x => x.New, "200")
.Without(x => x.ApprovalRequestId)
.Without(x => x.ApprovalRequest)
.Create(),
];

ApprovalRequest = autoFixture.Build<ApprovalRequest>()
.With(x => x.Items, Items)
.With(x => x.Id, Command.ApprovalRequestId)
.With(x => x.ApprenticeshipId, Command.ApprenticeshipId)
.With(x => x.Status, CocApprovalResultStatus.Pending).Create();
}

public async Task Handle()
{
await Handler.Handle(Command, CancellationToken.None);
}

public async Task<ProcessApprenticeshipApprovalCommandHandlerTestsFixture> SeedData()
{
if (ApprovalRequest != null)
{
Db.ApprovalRequests.Add(ApprovalRequest);
await Db.SaveChangesAsync();
}
return this;
}
}
}
Loading
Loading