diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.Api.Client/CommitmentsApiClient.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.Api.Client/CommitmentsApiClient.cs index 3363c20089..6881eaa99c 100644 --- a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.Api.Client/CommitmentsApiClient.cs +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.Api.Client/CommitmentsApiClient.cs @@ -147,6 +147,11 @@ public Task SecureProviderCheck() return _client.Get("api/test/provider"); } + public Task GetApprovedProviders(long accountId, CancellationToken cancellationToken) + { + return _client.Get($"api/accounts/{accountId}/providers/approved", null, cancellationToken); + } + public Task CreateCohort(CreateEmptyCohortRequest request, CancellationToken cancellationToken = default) { return _client.PostAsJson("api/cohorts/create-empty-cohort", request, cancellationToken); diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.Api.Client/ICommitmentsApiClient.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.Api.Client/ICommitmentsApiClient.cs index 4712f730ab..ebbd0ebf22 100644 --- a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.Api.Client/ICommitmentsApiClient.cs +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.Api.Client/ICommitmentsApiClient.cs @@ -30,6 +30,7 @@ public interface ICommitmentsApiClient Task GetCohorts(GetCohortsRequest request, CancellationToken cancellationToken = default); Task DeleteDraftApprenticeship(long cohortId, long apprenticeshipId, DeleteDraftApprenticeshipRequest request, CancellationToken cancellationToken = default); Task DeleteCohort(long cohortId, UserInfo userInfo, CancellationToken cancellationToken = default); + Task GetApprovedProviders(long accountId, CancellationToken cancellationToken); Task GetAccount(long accountId, CancellationToken cancellationToken = default); } } diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.Api.Types/Responses/GetApprovedProvidersResponse.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.Api.Types/Responses/GetApprovedProvidersResponse.cs new file mode 100644 index 0000000000..b2fa00aa10 --- /dev/null +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.Api.Types/Responses/GetApprovedProvidersResponse.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Linq; + +namespace SFA.DAS.CommitmentsV2.Api.Types.Responses +{ + public class GetApprovedProvidersResponse + { + public long[] ProviderIds { get; } + + public GetApprovedProvidersResponse(IEnumerable providerIds) + { + ProviderIds = providerIds.ToArray(); + } + } +} diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.Api.UnitTests/Controllers/AccountControllerTests.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.Api.UnitTests/Controllers/AccountControllerTests.cs index 07ab0ff5da..22f031cfe0 100644 --- a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.Api.UnitTests/Controllers/AccountControllerTests.cs +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.Api.UnitTests/Controllers/AccountControllerTests.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System.Linq; +using System.Threading; using System.Threading.Tasks; using AutoFixture; using MediatR; @@ -8,6 +9,7 @@ using SFA.DAS.CommitmentsV2.Api.Controllers; using SFA.DAS.CommitmentsV2.Api.Types.Responses; using SFA.DAS.CommitmentsV2.Application.Queries.GetAccountSummary; +using SFA.DAS.CommitmentsV2.Application.Queries.GetApprovedProviders; namespace SFA.DAS.CommitmentsV2.Api.UnitTests.Controllers { @@ -29,12 +31,21 @@ public async Task GetAccount_Should_Return_Valid_Result() _fixture.VerifyResult(); } + [Test] + public async Task GetApprovedProviders_Should_Return_Valid_Result() + { + await _fixture.GetApprovedProviders(); + _fixture.VerifyApprovedProviderResponse(); + } + private class AccountControllerTestsFixture { private AccountController Controller { get; } private Mock Mediator { get; } private long AccountId { get; } private GetAccountSummaryQueryResult MediatorQueryResult { get; } + + private GetApprovedProvidersQueryResult ApprovedProviderQueryResponse { get; } private IActionResult Result { get; set; } public AccountControllerTestsFixture() @@ -46,6 +57,10 @@ public AccountControllerTestsFixture() Mediator.Setup(x => x.Send(It.IsAny(), It.IsAny())) .ReturnsAsync(MediatorQueryResult); + ApprovedProviderQueryResponse = autoFixture.Create(); + Mediator.Setup(x => x.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(ApprovedProviderQueryResponse); + Controller = new AccountController(Mediator.Object); AccountId = autoFixture.Create(); } @@ -55,6 +70,11 @@ public async Task GetAccount() Result = await Controller.GetAccount(AccountId); } + public async Task GetApprovedProviders() + { + Result = await Controller.GetApprovedProviders(AccountId); + } + public void VerifyResult() { Assert.AreEqual(typeof(OkObjectResult), Result.GetType()); @@ -69,6 +89,24 @@ public void VerifyResult() Assert.AreEqual(MediatorQueryResult.HasApprenticeships, response.HasApprenticeships); Assert.AreEqual(MediatorQueryResult.HasCohorts, response.HasCohorts); } + + public void VerifyApprovedProviderResponse() + { + Assert.AreEqual(typeof(OkObjectResult), Result.GetType()); + var objectResult = (OkObjectResult)Result; + Assert.AreEqual(200, objectResult.StatusCode); + + Assert.IsInstanceOf(objectResult.Value); + + var response = (GetApprovedProvidersResponse)objectResult.Value; + + Assert.AreEqual(3, response.ProviderIds.Length); + + foreach (var qr in ApprovedProviderQueryResponse.ProviderIds) + { + Assert.IsTrue(response.ProviderIds.Contains(qr)); + } + } } } diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.Api/Controllers/AccountController.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.Api/Controllers/AccountController.cs index f92eccc0e6..45197b0078 100644 --- a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.Api/Controllers/AccountController.cs +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.Api/Controllers/AccountController.cs @@ -2,10 +2,9 @@ using MediatR; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using SFA.DAS.CommitmentsV2.Api.Extensions; using SFA.DAS.CommitmentsV2.Api.Types.Responses; using SFA.DAS.CommitmentsV2.Application.Queries.GetAccountSummary; +using SFA.DAS.CommitmentsV2.Application.Queries.GetApprovedProviders; namespace SFA.DAS.CommitmentsV2.Api.Controllers { @@ -37,5 +36,16 @@ public async Task GetAccount(long accountId) HasCohorts = employer.HasCohorts }); } + + [HttpGet] + [Route("{AccountId}/providers/approved")] + public async Task GetApprovedProviders(long accountId) + { + var query = new GetApprovedProvidersQuery(accountId); + + var result = await _mediator.Send(query); + + return Ok(new GetApprovedProvidersResponse(result.ProviderIds)); + } } } \ No newline at end of file diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.Api/Controllers/ProviderController.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.Api/Controllers/ProviderController.cs index fe4f14e222..7fb3849530 100644 --- a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.Api/Controllers/ProviderController.cs +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.Api/Controllers/ProviderController.cs @@ -16,7 +16,7 @@ public ProviderController(IMediator mediator) { _mediator = mediator; } - + [HttpGet] [Route("{providerId}")] public async Task GetProvider(long providerId) diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.UnitTests/Application/Queries/GetApprovedProviders/GetApprovedProvidersQueryHandlerTests.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.UnitTests/Application/Queries/GetApprovedProviders/GetApprovedProvidersQueryHandlerTests.cs new file mode 100644 index 0000000000..402978c721 --- /dev/null +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.UnitTests/Application/Queries/GetApprovedProviders/GetApprovedProvidersQueryHandlerTests.cs @@ -0,0 +1,177 @@ +using MediatR; +using Microsoft.EntityFrameworkCore; +using NUnit.Framework; +using SFA.DAS.CommitmentsV2.Application.Queries.GetApprovedProviders; +using SFA.DAS.CommitmentsV2.Data; +using SFA.DAS.CommitmentsV2.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace SFA.DAS.CommitmentsV2.UnitTests.Application.Queries.GetApprovedProviders +{ + [TestFixture] + [Parallelizable] + public class GetApprovedProvidersQueryHandlerTests + { + private GetApprovedProvidersQueryHandlerFixture _fixture; + + [SetUp] + public void SetUp() + { + _fixture = new GetApprovedProvidersQueryHandlerFixture(); + } + + [Test] + public async Task Handle_WhenCohortApprovedByBoth_And_TransferSender_IdIsNull_ThenShouldReturnResult() + { + var result = await _fixture.AddApprovedCohortAndProviderForAccount() + .AddApprovedCohortAndProviderForAccount() + .AddNotApprovedCohortAndProviderForAccount() + .SeedDb().Handle(); + + Assert.AreEqual(2, result.ProviderIds.Count()); + } + + [Test] + public async Task Handle_WhenCohortApprovedByBoth_And_TransferSenderNotApproved_ThenShouldNotReturnResult() + { + var result = await _fixture.AddCohortAndProvider_WithTransfserSenderNotApproved() + .SeedDb().Handle(); + + Assert.AreEqual(0, result.ProviderIds.Count()); + } + + [Test] + public async Task Handle_WhenCohortApprovedByBoth_And_TransferSenderApproved_ThenShouldReturnResult() + { + var result = await _fixture.AddCohortAndProvider_WithTransferSenderApproved() + .SeedDb().Handle(); + + Assert.AreEqual(1, result.ProviderIds.Count()); + } + + [Test] + public async Task Handle_WhenCohortNotFullyApproved_ThenShouldNotReturnResult() + { + var result = await _fixture.AddNotApprovedCohortAndProviderForAccount().SeedDb().Handle(); + + Assert.AreEqual(0, result.ProviderIds.Count()); + } + } + + public class GetApprovedProvidersQueryHandlerFixture + { + public GetApprovedProvidersQuery Query { get; set; } + public List Cohorts { get; set; } + public List Provider { get; set; } + public ProviderCommitmentsDbContext Db { get; set; } + public IRequestHandler Handler { get; set; } + + public long AccountId => 1; + + public GetApprovedProvidersQueryHandlerFixture() + { + Cohorts = new List(); + Provider = new List(); + Query = new GetApprovedProvidersQuery(AccountId); + Db = new ProviderCommitmentsDbContext(new DbContextOptionsBuilder().UseInMemoryDatabase(Guid.NewGuid().ToString()).Options); + Handler = new GetApprovedProvidersQueryHandler(new Lazy(() => Db)); + } + + public GetApprovedProvidersQueryHandlerFixture AddCohortAndProvider_WithTransfserSenderNotApproved() + { + var provider = new Provider(GetNextProviderId(), "Foo", DateTime.UtcNow, DateTime.UtcNow); + + Provider.Add(provider); + + Cohorts.Add(new Cohort + { + EmployerAccountId = AccountId, + Id = GetNextCohortId(), + EditStatus = 0, + TransferSenderId = 1, + ProviderId = provider.UkPrn, + TransferApprovalStatus = Types.TransferApprovalStatus.Pending + }); + + return this; + } + + public GetApprovedProvidersQueryHandlerFixture AddCohortAndProvider_WithTransferSenderApproved() + { + var provider = new Provider(GetNextProviderId(), "Foo", DateTime.UtcNow, DateTime.UtcNow); + + Provider.Add(provider); + + Cohorts.Add(new Cohort + { + EmployerAccountId = AccountId, + Id = GetNextCohortId(), + EditStatus = 0, + TransferSenderId = 1, + ProviderId = provider.UkPrn, + TransferApprovalStatus = Types.TransferApprovalStatus.Approved + }); + + return this; + } + + public GetApprovedProvidersQueryHandlerFixture AddApprovedCohortAndProviderForAccount() + { + var provider = new Provider(GetNextProviderId(), "Foo", DateTime.UtcNow, DateTime.UtcNow); + + Provider.Add(provider); + + Cohorts.Add(new Cohort + { + EmployerAccountId = AccountId, + Id = GetNextCohortId(), + EditStatus = 0, + TransferSenderId = null, + ProviderId = provider.UkPrn + }); + + return this; + } + + public GetApprovedProvidersQueryHandlerFixture AddNotApprovedCohortAndProviderForAccount() + { + Provider.Add( + new Provider(GetNextProviderId(), "Foo", DateTime.UtcNow, DateTime.UtcNow) + ); + + Cohorts.Add(new Cohort + { + EmployerAccountId = AccountId, + Id = GetNextCohortId(), + EditStatus = Types.EditStatus.EmployerOnly, + TransferSenderId = null, + ProviderId = 1 + }); + + return this; + } + + public Task Handle() + { + return Handler.Handle(Query, CancellationToken.None); + } + + public GetApprovedProvidersQueryHandlerFixture SeedDb() + { + Db.Cohorts.AddRange(Cohorts); + Db.Providers.AddRange(Provider); + Db.SaveChanges(); + + return this; + } + + private long GetNextCohortId() => Cohorts.Count == 0 ? 1 : Cohorts.Max(x => x.Id) + 1; + + private long GetNextProviderId() => Provider.Count == 0 ? 1: Provider.Max(x => x.UkPrn) + 1; + + } +} diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.UnitTests/Application/Queries/GetApprovedProviders/GetApprovedProvidersQueryValidatorTests.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.UnitTests/Application/Queries/GetApprovedProviders/GetApprovedProvidersQueryValidatorTests.cs new file mode 100644 index 0000000000..1577b44de9 --- /dev/null +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.UnitTests/Application/Queries/GetApprovedProviders/GetApprovedProvidersQueryValidatorTests.cs @@ -0,0 +1,44 @@ +using FluentValidation.Results; +using NUnit.Framework; +using SFA.DAS.CommitmentsV2.Application.Queries.GetApprovedProviders; + +namespace SFA.DAS.CommitmentsV2.UnitTests.Application.Queries.GetProvider +{ + [TestFixture] + [Parallelizable] + public class GetApprovedProvidersQueryValidatorTests + { + private GetApproveProvidersdQueryValidatorTestsFixture _fixture; + + [SetUp] + public void SetUp() + { + _fixture = new GetApproveProvidersdQueryValidatorTestsFixture(); + } + + [TestCase(-1, false)] + [TestCase( 0, false)] + [TestCase( 1, true)] + public void Validate_WhenValidating_ThenShouldValidate(int accountId, bool isValid) + { + var validationResult = _fixture.Validate(accountId); + + Assert.AreEqual(isValid, validationResult.IsValid); + } + } + + public class GetApproveProvidersdQueryValidatorTestsFixture + { + public GetApprovedProvidersQueryValidator Validator { get; set; } + + public GetApproveProvidersdQueryValidatorTestsFixture() + { + Validator = new GetApprovedProvidersQueryValidator(); + } + + public ValidationResult Validate(long providerId) + { + return Validator.Validate(new GetApprovedProvidersQuery(providerId)); + } + } +} \ No newline at end of file diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2/Application/Queries/GetApprovedProviders/GetApprovedProvidersQuery.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2/Application/Queries/GetApprovedProviders/GetApprovedProvidersQuery.cs new file mode 100644 index 0000000000..05d4adcb84 --- /dev/null +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2/Application/Queries/GetApprovedProviders/GetApprovedProvidersQuery.cs @@ -0,0 +1,17 @@ +using MediatR; +using System; +using System.Collections.Generic; +using System.Text; + +namespace SFA.DAS.CommitmentsV2.Application.Queries.GetApprovedProviders +{ + public class GetApprovedProvidersQuery : IRequest + { + public long? AccountId { get; set; } + + public GetApprovedProvidersQuery(long? accountId) + { + AccountId = accountId; + } + } +} diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2/Application/Queries/GetApprovedProviders/GetApprovedProvidersQueryHandler.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2/Application/Queries/GetApprovedProviders/GetApprovedProvidersQueryHandler.cs new file mode 100644 index 0000000000..b37a4f96ca --- /dev/null +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2/Application/Queries/GetApprovedProviders/GetApprovedProvidersQueryHandler.cs @@ -0,0 +1,33 @@ +using MediatR; +using Microsoft.EntityFrameworkCore; +using SFA.DAS.CommitmentsV2.Data; +using SFA.DAS.CommitmentsV2.Data.Expressions; +using SFA.DAS.CommitmentsV2.Models; +using SFA.DAS.CommitmentsV2.Types; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace SFA.DAS.CommitmentsV2.Application.Queries.GetApprovedProviders +{ + public class GetApprovedProvidersQueryHandler : IRequestHandler + { + private readonly Lazy _db; + + public GetApprovedProvidersQueryHandler(Lazy db) + { + _db = db; + } + + public async Task Handle(GetApprovedProvidersQuery request, CancellationToken cancellationToken) + { + var accountQuery = PredicateBuilder.True().And(c => c.EmployerAccountId == request.AccountId); + + var result = await _db.Value.Cohorts.Where(accountQuery.And(CohortQueries.IsFullyApproved())) + .Select(x => x.ProviderId.Value).ToListAsync(cancellationToken); + + return new GetApprovedProvidersQueryResult(result); + } + } +} diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2/Application/Queries/GetApprovedProviders/GetApprovedProvidersQueryResult.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2/Application/Queries/GetApprovedProviders/GetApprovedProvidersQueryResult.cs new file mode 100644 index 0000000000..153abffa5b --- /dev/null +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2/Application/Queries/GetApprovedProviders/GetApprovedProvidersQueryResult.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Linq; + +namespace SFA.DAS.CommitmentsV2.Application.Queries.GetApprovedProviders +{ + public class GetApprovedProvidersQueryResult + { + public long[] ProviderIds { get; } + + public GetApprovedProvidersQueryResult(IEnumerable providerIds) + { + ProviderIds = providerIds.ToArray(); + } + } +} diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2/Application/Queries/GetApprovedProviders/GetApprovedProvidersQueryValidator.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2/Application/Queries/GetApprovedProviders/GetApprovedProvidersQueryValidator.cs new file mode 100644 index 0000000000..b6ba9fb967 --- /dev/null +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2/Application/Queries/GetApprovedProviders/GetApprovedProvidersQueryValidator.cs @@ -0,0 +1,12 @@ +using FluentValidation; + +namespace SFA.DAS.CommitmentsV2.Application.Queries.GetApprovedProviders +{ + public class GetApprovedProvidersQueryValidator : AbstractValidator + { + public GetApprovedProvidersQueryValidator() + { + RuleFor(q => q.AccountId).GreaterThan(0).WithMessage("The account ID must be supplied"); + } + } +} \ No newline at end of file diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2/Data/Expressions/CohortQueries.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2/Data/Expressions/CohortQueries.cs index 4651ee3e25..87451c1f18 100644 --- a/src/CommitmentsV2/SFA.DAS.CommitmentsV2/Data/Expressions/CohortQueries.cs +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2/Data/Expressions/CohortQueries.cs @@ -14,5 +14,13 @@ public static Expression> IsNotFullyApproved() cohort.TransferApprovalStatus == TransferApprovalStatus.Approved)); } + + public static Expression> IsFullyApproved() + { + return cohort => cohort.EditStatus == EditStatus.Both && + (!cohort.TransferSenderId.HasValue || + cohort.TransferApprovalStatus == + TransferApprovalStatus.Approved); + } } } \ No newline at end of file