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

Flp 918 get learner ref from dc #1826

Open
wants to merge 21 commits into
base: ps-release-7.6
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
01e90d6
Initial setup
MikeV-TalentConsulting Oct 23, 2024
0c4409d
Working endpoint
MikeV-TalentConsulting Oct 24, 2024
5562daf
Unit tests
MikeV-TalentConsulting Oct 24, 2024
b0ea2f4
tidy up
MikeV-TalentConsulting Oct 24, 2024
dac66e2
Update launchSettings.json
MikeV-TalentConsulting Oct 29, 2024
6e53f3d
Update LearnerReferencesResponse.cs
MikeV-TalentConsulting Oct 29, 2024
8518aa9
Added YAML pipeline file for Payments
Nov 5, 2024
09a7052
Allow no header to be returned when using stub endpoint
MikeV-TalentConsulting Nov 7, 2024
cefa805
Removed VG
debrr07 Nov 19, 2024
b084f68
Changed file structure - Payment Outer API contains own deploy.yml an…
Nov 20, 2024
81e145f
Changed file paths of YAML file references
Nov 20, 2024
f0ad9ad
Merge branch 'ps-release-7.4' into FLP-918-GetLearnerRefFromDC
ShakilUmar Dec 5, 2024
310da81
Merge branch 'ps-release-7.4' into FLP-918-GetLearnerRefFromDC
ShakilUmar Dec 10, 2024
ed06c9e
Add collection calendar controller
MichaelYoung1981 Dec 11, 2024
2279475
Merge branch 'FLP-918-GetLearnerRefFromDC' of https://github.com/Skil…
MichaelYoung1981 Dec 11, 2024
e5c59f8
Merge branch 'master' into FLP-918-GetLearnerRefFromDC
MichaelYoung1981 Dec 11, 2024
5b9592a
Add missing registrations
MichaelYoung1981 Dec 12, 2024
e1c2bc3
Merge branch 'master' into FLP-918-GetLearnerRefFromDC
MichaelYoung1981 Dec 12, 2024
0969c64
Remove nlog
MichaelYoung1981 Dec 12, 2024
9d97d6e
Use shared pipeline
MichaelYoung1981 Dec 12, 2024
cb0061f
Merge branch 'ps-release-7.6' into FLP-918-GetLearnerRefFromDC
ShakilUmar Dec 18, 2024
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
30 changes: 30 additions & 0 deletions src/APIM.sln
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SFA.DAS.SharedOuterApi.Appr
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SFA.DAS.SharedOuterApi.Apprentice.GovUK.Auth.UnitTests", "Shared\SFA.DAS.SharedOuterApi.Apprentice.GovUK.Auth.UnitTests\SFA.DAS.SharedOuterApi.Apprentice.GovUK.Auth.UnitTests.csproj", "{86F8DB4C-5EB3-4B5E-A232-7C09095ADEA2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Payments", "Payments", "{1AC73DE4-60FE-4E67-82DC-05656F74D7B3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SFA.DAS.Payments", "Payments\SFA.DAS.Payments\SFA.DAS.Payments.csproj", "{20D6BE73-2729-4B9D-BC69-C60663964486}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SFA.DAS.Payments.Api", "Payments\SFA.DAS.Payments.Api\SFA.DAS.Payments.Api.csproj", "{91EA5AEF-58FB-4930-9736-FE05E6DFBF64}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SFA.DAS.Payments.Api.UnitTests", "Payments\SFA.DAS.Payments.Api.UnitTests\SFA.DAS.Payments.Api.UnitTests.csproj", "{9C81947D-6B60-4019-A799-9B66F2166D1F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SFA.DAS.Payments.UnitTests", "Payments\SFA.DAS.Payments.UnitTests\SFA.DAS.Payments.UnitTests.csproj", "{5F6EE783-C6B9-4E8B-BA5F-C2CD327245A5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -1318,6 +1328,22 @@ Global
{86F8DB4C-5EB3-4B5E-A232-7C09095ADEA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{86F8DB4C-5EB3-4B5E-A232-7C09095ADEA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{86F8DB4C-5EB3-4B5E-A232-7C09095ADEA2}.Release|Any CPU.Build.0 = Release|Any CPU
{20D6BE73-2729-4B9D-BC69-C60663964486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{20D6BE73-2729-4B9D-BC69-C60663964486}.Debug|Any CPU.Build.0 = Debug|Any CPU
{20D6BE73-2729-4B9D-BC69-C60663964486}.Release|Any CPU.ActiveCfg = Release|Any CPU
{20D6BE73-2729-4B9D-BC69-C60663964486}.Release|Any CPU.Build.0 = Release|Any CPU
{91EA5AEF-58FB-4930-9736-FE05E6DFBF64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{91EA5AEF-58FB-4930-9736-FE05E6DFBF64}.Debug|Any CPU.Build.0 = Debug|Any CPU
{91EA5AEF-58FB-4930-9736-FE05E6DFBF64}.Release|Any CPU.ActiveCfg = Release|Any CPU
{91EA5AEF-58FB-4930-9736-FE05E6DFBF64}.Release|Any CPU.Build.0 = Release|Any CPU
{9C81947D-6B60-4019-A799-9B66F2166D1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9C81947D-6B60-4019-A799-9B66F2166D1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9C81947D-6B60-4019-A799-9B66F2166D1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9C81947D-6B60-4019-A799-9B66F2166D1F}.Release|Any CPU.Build.0 = Release|Any CPU
{5F6EE783-C6B9-4E8B-BA5F-C2CD327245A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5F6EE783-C6B9-4E8B-BA5F-C2CD327245A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5F6EE783-C6B9-4E8B-BA5F-C2CD327245A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5F6EE783-C6B9-4E8B-BA5F-C2CD327245A5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1525,6 +1551,10 @@ Global
{F014EBD4-A9FA-4196-A39C-BD71805449D6} = {2FBDD130-2DBC-4BBB-8219-D9AF89DE0BF8}
{3BA94F50-4E8B-463E-87FA-03A9C060FC36} = {9C327515-DF6A-4277-BF53-4C86D510C718}
{86F8DB4C-5EB3-4B5E-A232-7C09095ADEA2} = {9C327515-DF6A-4277-BF53-4C86D510C718}
{20D6BE73-2729-4B9D-BC69-C60663964486} = {1AC73DE4-60FE-4E67-82DC-05656F74D7B3}
{91EA5AEF-58FB-4930-9736-FE05E6DFBF64} = {1AC73DE4-60FE-4E67-82DC-05656F74D7B3}
{9C81947D-6B60-4019-A799-9B66F2166D1F} = {1AC73DE4-60FE-4E67-82DC-05656F74D7B3}
{5F6EE783-C6B9-4E8B-BA5F-C2CD327245A5} = {1AC73DE4-60FE-4E67-82DC-05656F74D7B3}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2353A4D0-2859-4472-B2D4-BBD8087B4E22}
Expand Down
54 changes: 54 additions & 0 deletions src/Payments/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
## ⛔Never push sensitive information such as client id's, secrets or keys into repositories including in the README file⛔

# Payments Outer API

<img src="https://avatars.githubusercontent.com/u/9841374?s=200&v=4" align="right" alt="UK Government logo">

[![Build Status](https://dev.azure.com/sfa-gov-uk/Digital%20Apprenticeship%20Service/_apis/build/status/das-apim-endpoints-Apprenticeships?branchName=master)](https://dev.azure.com/sfa-gov-uk/Digital%20Apprenticeship%20Service/_build/latest?definitionId=das-apim-endpoints-Payments&branchName=master)
[![Jira Project](https://img.shields.io/badge/Jira-Project-blue)](https://skillsfundingagency.atlassian.net/jira/software/c/projects/FLP/boards/753)
[![Confluence Project](https://img.shields.io/badge/Confluence-Project-blue)](https://skillsfundingagency.atlassian.net/wiki/spaces/NDL/pages/3480354918/Flexible+Payments+Models)
[![License](https://img.shields.io/badge/license-MIT-lightgrey.svg?longCache=true&style=flat-square)](https://en.wikipedia.org/wiki/MIT_License)

The Payments outer provides endpoints used by das-funding-payments and should not be used by any other applications.

## How It Works

The Outer API orchestrates calls to multiple inner APIs in order to provide the functionality required by the UI. Each UI page operation (GET, POST etc) should call a single outer API endpoint only.

## 🚀 Installation

### Pre-Requisites

* A clone of this repository
* A code editor that supports .Net8
* Azure Storage Emulator (Azureite)

### Config

Most of the application configuration is taken from the [das-employer-config repository](https://github.com/SkillsFundingAgency/das-employer-config) and the default values can be used in most cases. The config json will need to be added to the local Azure Storage instance with a a PartitionKey of LOCAL and a RowKey of SFA.DAS.Payments.OuterAPI_1.0.

| Name | Description | Stub Value |
| ------------------------------------------------------------- | ------------------------------------------------- |-------------------------------------------|
| LearnerDataApiConfiguration:Url | Url of the data endpoint | https://localhost:4000/learner-data-api |
| LearnerDataApiConfiguration:TokenSettings:Url | Url of the token endpoint | |
| LearnerDataApiConfiguration:TokenSettings:Scope | Token settings | |
| LearnerDataApiConfiguration:TokenSettings:ClientId | Token settings | |
| LearnerDataApiConfiguration:TokenSettings:Tenant | Token settings | |
| LearnerDataApiConfiguration:TokenSettings:ClientSecret | Token settings | |
| LearnerDataApiConfiguration:TokenSettings:GrantType | Token settings | |
| LearnerDataApiConfiguration:TokenSettings:ShouldSkipForLocal | For local use only, skips calling token endpoint | true |




## 🔗 External Dependencies

The Outer API has many external dependancies which can all be configured to use stubs by following the config above.

## Running Locally

* Make sure Azure Storage Emulator (Azureite) is running
* Make sure the config has been updated to call any Stub APIs required
* Run the [Commitments Stubs](https://github.com/SkillsFundingAgency/das-commitments-stubs)
* Run the Apprenticeships Inner API
* Run the application
18 changes: 18 additions & 0 deletions src/Payments/SFA.DAS.Payments.Api.UnitTests/AssertExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using FluentAssertions;

namespace SFA.DAS.Payments.Api.UnitTests
{
public static class AssertExtensions
{
//extension that asserts that the object is of the expected type and returns it
public static T ShouldBeOfType<T>(this object? actual)
{
if (actual == null)
throw new AssertionException($"Expected object of type {typeof(T).Name} but was null");

actual.Should().BeOfType<T>();
return (T)actual;
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using Moq;
using SFA.DAS.Payments.Api.Controllers;
using SFA.DAS.SharedOuterApi.Configuration;
using SFA.DAS.SharedOuterApi.InnerApi.Requests.CollectionCalendar;
using SFA.DAS.SharedOuterApi.InnerApi.Responses.CollectionCalendar;
using SFA.DAS.SharedOuterApi.Interfaces;
using SFA.DAS.Testing.AutoFixture;

namespace SFA.DAS.Payments.Api.UnitTests.Controllers.CollectionCalendar;

public class WhenGettingAcademicYear
{
[Test, MoqAutoData]
public async Task Then_Gets_ApprenticeshipKey_From_ApiClient(
GetAcademicYearsResponse expectedResponse,
DateTime searchDate,
Mock<ICollectionCalendarApiClient<CollectionCalendarApiConfiguration>> mockCollectionCalendarApiClient)
{
// Arrange
mockCollectionCalendarApiClient.Setup(x => x.Get<GetAcademicYearsResponse>(It.IsAny<GetAcademicYearByDateRequest>())).ReturnsAsync(expectedResponse);

var controller = new CollectionCalendarController(mockCollectionCalendarApiClient.Object);

// Act
var result = await controller.GetAcademicYear(searchDate);

// Assert
var okObjectResult = result.ShouldBeOfType<OkObjectResult>();
var actualResponse = okObjectResult.Value.ShouldBeOfType<GetAcademicYearsResponse>();
actualResponse.Should().Be(expectedResponse);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using AutoFixture;
using FluentAssertions;
using MediatR;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Moq;
using SFA.DAS.Payments.Api.Controllers;
using SFA.DAS.Payments.Api.Models;
using SFA.DAS.Payments.Application.Learners;
using SFA.DAS.Payments.Models.Responses;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace SFA.DAS.Payments.Api.UnitTests.Controllers
{
[TestFixture]
internal class WhenGetLearnerReferences
{
#pragma warning disable CS8618 // Non-nullable field is uninitialized. - nUnit initializes fields in SetUp
private Fixture _fixture;
private Mock<ILogger<IlrController>> _loggerMock;
private Mock<IMediator> _mediatorMock;
private IlrController _controller;
#pragma warning restore CS8618 // Non-nullable field is uninitialized.

[SetUp]
public void SetUp()
{
_fixture = new Fixture();
_loggerMock = new Mock<ILogger<IlrController>>();
_mediatorMock = new Mock<IMediator>();
_controller = new IlrController(_loggerMock.Object, _mediatorMock.Object);
}

[Test]
public async Task Then_ReturnsOkResult_WithLearnerReferences()
{
// Arrange
var ukprn = _fixture.Create<string>();
var academicYear = _fixture.Create<short>();
var learnersQueryResult = _fixture.Create<IEnumerable<LearnerResponse>>();

_mediatorMock.Setup(m => m.Send(It.IsAny<GetLearnersQuery>(), default)).ReturnsAsync(learnersQueryResult);

// Act
var result = await _controller.GetLearnerReferences(ukprn, academicYear);

// Assert
result.Should().BeOfType<OkObjectResult>();
var okResult = result.Should().BeOfType<OkObjectResult>().Subject;
okResult.Value.Should().BeEquivalentTo(learnersQueryResult.ToLearnerReferenceResponse());
}

[Test]
public async Task Then_ReturnsBadRequest_OnException()
{
// Arrange
var ukprn = _fixture.Create<string>();
var academicYear = _fixture.Create<short>();
_mediatorMock.Setup(m => m.Send(It.IsAny<GetLearnersQuery>(), default))
.ThrowsAsync(new Exception("Test exception"));

// Act
var result = await _controller.GetLearnerReferences(ukprn, academicYear);

// Assert
result.Should().BeOfType<BadRequestResult>();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AutoFixture" />
<PackageReference Include="AutoFixture.AutoMoq" />
<PackageReference Include="FluentAssertions" />
<PackageReference Include="coverlet.msbuild">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" />
<PackageReference Include="coverlet.msbuild" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Moq" />
<PackageReference Include="NUnit" />
<PackageReference Include="NUnit3TestAdapter" />
<PackageReference Include="SFA.DAS.Testing.AutoFixture" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\SFA.DAS.Payments.Api\SFA.DAS.Payments.Api.csproj" />
</ItemGroup>

<ItemGroup>
<Using Include="NUnit.Framework" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using Microsoft.Extensions.Options;
using SFA.DAS.Api.Common.Configuration;
using SFA.DAS.Api.Common.Infrastructure;
using SFA.DAS.Api.Common.Interfaces;
using SFA.DAS.SharedOuterApi.Configuration;
using SFA.DAS.SharedOuterApi.Infrastructure;
using SFA.DAS.SharedOuterApi.Interfaces;
using SFA.DAS.SharedOuterApi.Services;
using System.Diagnostics.CodeAnalysis;

namespace SFA.DAS.Payments.Api.AppStart;

[ExcludeFromCodeCoverage]
public static class AddServiceRegistrationExtensions
{
public static void AddServiceRegistration(this IServiceCollection services, IConfiguration configuration)
{
services.AddHttpClient();
services.AddTransient<IAzureClientCredentialHelper, AzureClientCredentialHelper>();
services.AddTransient(typeof(IInternalApiClient<>), typeof(InternalApiClient<>));
services.AddTransient(typeof(IAccessTokenApiClient<>), typeof(AccessTokenApiClient<>));
services.AddTransient<ILearnerDataApiClient<LearnerDataApiConfiguration>, LearnerDataApiClient>();
services.AddTransient<ICollectionCalendarApiClient<CollectionCalendarApiConfiguration>, CollectionCalendarApiClient>();
}
}

[ExcludeFromCodeCoverage]
public static class AddConfigurationOptionsExtension
{
public static void AddConfigurationOptions(this IServiceCollection services, IConfiguration configuration)
{
services.AddOptions();

services.Configure<AzureActiveDirectoryConfiguration>(configuration.GetSection("AzureAd"));
services.AddSingleton(cfg => cfg.GetService<IOptions<AzureActiveDirectoryConfiguration>>()!.Value);

services.Configure<LearnerDataApiConfiguration>(configuration.GetSection(nameof(LearnerDataApiConfiguration)));
services.AddSingleton(cfg => cfg.GetService<IOptions<LearnerDataApiConfiguration>>()!.Value);

services.Configure<CollectionCalendarApiConfiguration>(configuration.GetSection(nameof(CollectionCalendarApiConfiguration)));
services.AddSingleton(cfg => cfg.GetService<IOptions<CollectionCalendarApiConfiguration>>()!.Value);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Mvc;
using SFA.DAS.SharedOuterApi.Configuration;
using SFA.DAS.SharedOuterApi.InnerApi.Requests.CollectionCalendar;
using SFA.DAS.SharedOuterApi.InnerApi.Responses.CollectionCalendar;
using SFA.DAS.SharedOuterApi.Interfaces;

namespace SFA.DAS.Payments.Api.Controllers;

[ApiController]
[Route("[controller]")]
public class CollectionCalendarController : ControllerBase
{
private readonly ICollectionCalendarApiClient<CollectionCalendarApiConfiguration> _collectionCalendarApiClient;

public CollectionCalendarController(ICollectionCalendarApiClient<CollectionCalendarApiConfiguration> collectionCalendarApiClient)
{
_collectionCalendarApiClient = collectionCalendarApiClient;
}

[HttpGet]
[Route("academicYear/{searchDate}")]
public async Task<IActionResult> GetAcademicYear(DateTime searchDate)
{
return Ok(await _collectionCalendarApiClient.Get<GetAcademicYearsResponse>(new GetAcademicYearByDateRequest(searchDate)));
}
}
38 changes: 38 additions & 0 deletions src/Payments/SFA.DAS.Payments.Api/Controllers/IlrController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using MediatR;
using Microsoft.AspNetCore.Mvc;
using SFA.DAS.Payments.Api.Models;
using SFA.DAS.Payments.Application.Learners;

namespace SFA.DAS.Payments.Api.Controllers
{
[ApiController]
[Route("ILR")]
public class IlrController : ControllerBase
{
private readonly ILogger<IlrController> _logger;
private readonly IMediator _mediator;

public IlrController(ILogger<IlrController> logger, IMediator mediator)
{
_logger = logger;
_mediator = mediator;
}

[HttpGet]
[Route("{ukprn}/{academicYear}")]
public async Task<IActionResult> GetLearnerReferences(string ukprn, short academicYear)
{
try
{
var result = await _mediator.Send(new GetLearnersQuery(ukprn, academicYear));
var learnerReferences = result.ToLearnerReferenceResponse();
return Ok(learnerReferences);
}
catch(Exception e)
{
_logger.LogError(e, "Error attempting to get Learner References");
return BadRequest();
}
}
}
}
Loading