diff --git a/Dfe.Academies.Application/Common/Exceptions/CustomProblemDetails.cs b/Dfe.Academies.Application/Common/Exceptions/CustomProblemDetails.cs new file mode 100644 index 00000000..eb959864 --- /dev/null +++ b/Dfe.Academies.Application/Common/Exceptions/CustomProblemDetails.cs @@ -0,0 +1,34 @@ +using Microsoft.AspNetCore.Mvc; +using System.Net; + +namespace Dfe.Academies.Application.Common.Exceptions +{ + public class CustomProblemDetails : ProblemDetails + { + public CustomProblemDetails(HttpStatusCode statusCode, string? detail = null) + { + Status = (int)statusCode; + Detail = detail; + + Title = statusCode switch + { + HttpStatusCode.NotFound => "Not Found", + HttpStatusCode.Unauthorized => "Unauthorized", + HttpStatusCode.Forbidden => "Forbidden", + HttpStatusCode.BadRequest => "Bad Request", + HttpStatusCode.InternalServerError => "Internal Server Error", + _ => "An error occurred" + }; + + Type = statusCode switch + { + HttpStatusCode.NotFound => "https://tools.ietf.org/html/rfc9110#section-15.5.5", + HttpStatusCode.Unauthorized => "https://tools.ietf.org/html/rfc7235#section-3.1", + HttpStatusCode.Forbidden => "https://tools.ietf.org/html/rfc7231#section-6.5.3", + HttpStatusCode.BadRequest => "https://tools.ietf.org/html/rfc7231#section-6.5.1", + HttpStatusCode.InternalServerError => "https://tools.ietf.org/html/rfc7231#section-6.6.1", + _ => "https://tools.ietf.org/html/rfc7231#section-6.6.1" + }; + } + } +} diff --git a/Dfe.Academies.Application/Common/Models/Result.cs b/Dfe.Academies.Application/Common/Models/Result.cs new file mode 100644 index 00000000..20b878e3 --- /dev/null +++ b/Dfe.Academies.Application/Common/Models/Result.cs @@ -0,0 +1,20 @@ +namespace Dfe.Academies.Application.Common.Models +{ + public class Result + { + public T? Value { get; } + public bool IsSuccess { get; } + public string? Error { get; } + + private Result(T value, bool isSuccess, string? error) + { + Value = value; + IsSuccess = isSuccess; + Error = error; + } + + public static Result Success(T value) => new Result(value, true, null); + public static Result Failure(string error) => new Result(default!, false, error); + } + +} diff --git a/Dfe.Academies.Application/Constituencies/Queries/GetMemberOfParliamentByConstituencies/GetMemberOfParliamentByConstituencies.cs b/Dfe.Academies.Application/Constituencies/Queries/GetMemberOfParliamentByConstituencies/GetMemberOfParliamentByConstituencies.cs index 2646404d..1bdec82f 100644 --- a/Dfe.Academies.Application/Constituencies/Queries/GetMemberOfParliamentByConstituencies/GetMemberOfParliamentByConstituencies.cs +++ b/Dfe.Academies.Application/Constituencies/Queries/GetMemberOfParliamentByConstituencies/GetMemberOfParliamentByConstituencies.cs @@ -9,15 +9,15 @@ namespace Dfe.Academies.Application.Constituencies.Queries.GetMemberOfParliamentByConstituencies { - public record GetMembersOfParliamentByConstituenciesQuery(List ConstituencyNames) : IRequest>; + public record GetMembersOfParliamentByConstituenciesQuery(List ConstituencyNames) : IRequest>>; public class GetMembersOfParliamentByConstituenciesQueryHandler( IConstituencyRepository constituencyRepository, IMapper mapper, ICacheService cacheService) - : IRequestHandler> + : IRequestHandler>> { - public async Task> Handle(GetMembersOfParliamentByConstituenciesQuery request, CancellationToken cancellationToken) + public async Task>> Handle(GetMembersOfParliamentByConstituenciesQuery request, CancellationToken cancellationToken) { var cacheKey = $"MemberOfParliament_{CacheKeyHelper.GenerateHashedCacheKey(request.ConstituencyNames)}"; @@ -28,9 +28,10 @@ public async Task> Handle(GetMembersOfParliamentByConst var membersOfParliament = await constituenciesQuery .ProjectTo(mapper.ConfigurationProvider) - .ToListAsync(cancellationToken); + .ToListAsync(cancellationToken); + + return Result>.Success(membersOfParliament); - return membersOfParliament; }, nameof(GetMembersOfParliamentByConstituenciesQueryHandler)); } } diff --git a/Dfe.Academies.Application/Constituencies/Queries/GetMemberOfParliamentByConstituency/GetMemberOfParliamentByConstituency.cs b/Dfe.Academies.Application/Constituencies/Queries/GetMemberOfParliamentByConstituency/GetMemberOfParliamentByConstituency.cs index 4eab63ef..27d4e56c 100644 --- a/Dfe.Academies.Application/Constituencies/Queries/GetMemberOfParliamentByConstituency/GetMemberOfParliamentByConstituency.cs +++ b/Dfe.Academies.Application/Constituencies/Queries/GetMemberOfParliamentByConstituency/GetMemberOfParliamentByConstituency.cs @@ -7,15 +7,15 @@ namespace Dfe.Academies.Application.Constituencies.Queries.GetMemberOfParliamentByConstituency { - public record GetMemberOfParliamentByConstituencyQuery(string ConstituencyName) : IRequest; + public record GetMemberOfParliamentByConstituencyQuery(string ConstituencyName) : IRequest>; public class GetMemberOfParliamentByConstituencyQueryHandler( IConstituencyRepository constituencyRepository, IMapper mapper, ICacheService cacheService) - : IRequestHandler + : IRequestHandler> { - public async Task Handle(GetMemberOfParliamentByConstituencyQuery request, CancellationToken cancellationToken) + public async Task> Handle(GetMemberOfParliamentByConstituencyQuery request, CancellationToken cancellationToken) { var cacheKey = $"MemberOfParliament_{CacheKeyHelper.GenerateHashedCacheKey(request.ConstituencyName)}"; @@ -24,9 +24,15 @@ public class GetMemberOfParliamentByConstituencyQueryHandler( var constituencyWithMember = await constituencyRepository .GetMemberOfParliamentByConstituencyAsync(request.ConstituencyName, cancellationToken); + if (constituencyWithMember == null) + { + return Result.Failure("Constituency not found."); + } + var result = mapper.Map(constituencyWithMember); - return result; + return Result.Success(result); + }, nameof(GetMemberOfParliamentByConstituencyQueryHandler)); } } diff --git a/Dfe.Academies.Application/Establishment/Queries/GetAllPersonsAssociatedWithAcademyByUrn/GetAllPersonsAssociatedWithAcademyByUrn.cs b/Dfe.Academies.Application/Establishment/Queries/GetAllPersonsAssociatedWithAcademyByUrn/GetAllPersonsAssociatedWithAcademyByUrn.cs index 8f99dc3d..db07cf93 100644 --- a/Dfe.Academies.Application/Establishment/Queries/GetAllPersonsAssociatedWithAcademyByUrn/GetAllPersonsAssociatedWithAcademyByUrn.cs +++ b/Dfe.Academies.Application/Establishment/Queries/GetAllPersonsAssociatedWithAcademyByUrn/GetAllPersonsAssociatedWithAcademyByUrn.cs @@ -9,15 +9,15 @@ namespace Dfe.Academies.Application.Establishment.Queries.GetAllPersonsAssociatedWithAcademyByUrn { - public record GetAllPersonsAssociatedWithAcademyByUrnQuery(int Urn) : IRequest?>; + public record GetAllPersonsAssociatedWithAcademyByUrnQuery(int Urn) : IRequest?>>; public class GetAllPersonsAssociatedWithAcademyByUrnQueryHandler( IEstablishmentQueryService establishmentQueryService, IMapper mapper, ICacheService cacheService) - : IRequestHandler?> + : IRequestHandler?>> { - public async Task?> Handle(GetAllPersonsAssociatedWithAcademyByUrnQuery request, CancellationToken cancellationToken) + public async Task?>> Handle(GetAllPersonsAssociatedWithAcademyByUrnQuery request, CancellationToken cancellationToken) { var cacheKey = $"PersonsAssociatedWithAcademy_{CacheKeyHelper.GenerateHashedCacheKey(request.Urn.ToString())}"; @@ -27,12 +27,12 @@ public class GetAllPersonsAssociatedWithAcademyByUrnQueryHandler( if (query == null) { - return null; + return Result?>.Failure("Academy not found."); } - var result = await query + var result = Result?>.Success(await query .ProjectTo(mapper.ConfigurationProvider) - .ToListAsync(cancellationToken); + .ToListAsync(cancellationToken)); return result; }, nameof(GetAllPersonsAssociatedWithAcademyByUrnQueryHandler)); diff --git a/Dfe.Academies.Application/Establishment/Queries/GetMemberOfParliamentBySchool/GetMemberOfParliamentBySchool.cs b/Dfe.Academies.Application/Establishment/Queries/GetMemberOfParliamentBySchool/GetMemberOfParliamentBySchool.cs new file mode 100644 index 00000000..a48b4caf --- /dev/null +++ b/Dfe.Academies.Application/Establishment/Queries/GetMemberOfParliamentBySchool/GetMemberOfParliamentBySchool.cs @@ -0,0 +1,44 @@ +using AutoMapper; +using Dfe.Academies.Application.Common.Models; +using Dfe.Academies.Domain.Interfaces.Repositories; +using DfE.CoreLibs.Caching.Helpers; +using DfE.CoreLibs.Caching.Interfaces; +using MediatR; + +namespace Dfe.Academies.Application.Establishment.Queries.GetMemberOfParliamentBySchool +{ + public record GetMemberOfParliamentBySchoolQuery(int Urn) : IRequest>; + + public class GetMemberOfParliamentBySchoolQueryHandler( + IEstablishmentRepository establishmentRepository, + IConstituencyRepository constituencyRepository, + IMapper mapper, + ICacheService cacheService) + : IRequestHandler> + { + public async Task> Handle(GetMemberOfParliamentBySchoolQuery request, CancellationToken cancellationToken) + { + var cacheKey = $"MPbySchool_{CacheKeyHelper.GenerateHashedCacheKey(request.Urn.ToString())}"; + + return await cacheService.GetOrAddAsync(cacheKey, async () => + { + var establishment = await establishmentRepository.GetEstablishmentByUrn(request.Urn.ToString(), cancellationToken); + if (establishment == null) + { + return Result.Failure("School not found."); + } + + var constituency = await constituencyRepository.GetMemberOfParliamentByConstituencyAsync(establishment.ParliamentaryConstituency!, cancellationToken); + if (constituency == null) + { + return Result.Failure("Constituency not found for the given establishment."); + } + + var mp = mapper.Map(constituency); + + return Result.Success(mp); + + }, nameof(GetMemberOfParliamentBySchoolQueryHandler)); + } + } +} diff --git a/Dfe.Academies.Application/Establishment/Queries/GetMemberOfParliamentBySchool/GetMemberOfParliamentBySchoolValidator.cs b/Dfe.Academies.Application/Establishment/Queries/GetMemberOfParliamentBySchool/GetMemberOfParliamentBySchoolValidator.cs new file mode 100644 index 00000000..41c5981f --- /dev/null +++ b/Dfe.Academies.Application/Establishment/Queries/GetMemberOfParliamentBySchool/GetMemberOfParliamentBySchoolValidator.cs @@ -0,0 +1,14 @@ +using FluentValidation; + +namespace Dfe.Academies.Application.Establishment.Queries.GetMemberOfParliamentBySchool +{ + public class GetMemberOfParliamentBySchoolQueryValidator : AbstractValidator + { + public GetMemberOfParliamentBySchoolQueryValidator() + { + RuleFor(query => query.Urn) + .GreaterThan(0).WithMessage("URN must be greater than 0.") + .NotEmpty().WithMessage("URN is required."); + } + } +} \ No newline at end of file diff --git a/Dfe.Academies.Application/Trust/Queries/GetAllPersonsAssociatedWithTrustByTrnOrUkprn/GetAllPersonsAssociatedWithTrustByTrnOrUkprn.cs b/Dfe.Academies.Application/Trust/Queries/GetAllPersonsAssociatedWithTrustByTrnOrUkprn/GetAllPersonsAssociatedWithTrustByTrnOrUkprn.cs index 655117e0..a51a1a89 100644 --- a/Dfe.Academies.Application/Trust/Queries/GetAllPersonsAssociatedWithTrustByTrnOrUkprn/GetAllPersonsAssociatedWithTrustByTrnOrUkprn.cs +++ b/Dfe.Academies.Application/Trust/Queries/GetAllPersonsAssociatedWithTrustByTrnOrUkprn/GetAllPersonsAssociatedWithTrustByTrnOrUkprn.cs @@ -11,15 +11,15 @@ namespace Dfe.Academies.Application.Trust.Queries.GetAllPersonsAssociatedWithTrustByTrnOrUkprn { - public record GetAllPersonsAssociatedWithTrustByTrnOrUkprnQuery(string Id) : IRequest?>; + public record GetAllPersonsAssociatedWithTrustByTrnOrUkprnQuery(string Id) : IRequest?>>; public class GetAllPersonsAssociatedWithTrustByTrnOrUkprnQueryHandler( ITrustQueryService trustQueryService, IMapper mapper, ICacheService cacheService) - : IRequestHandler?> + : IRequestHandler?>> { - public async Task?> Handle(GetAllPersonsAssociatedWithTrustByTrnOrUkprnQuery request, CancellationToken cancellationToken) + public async Task?>> Handle(GetAllPersonsAssociatedWithTrustByTrnOrUkprnQuery request, CancellationToken cancellationToken) { var idType = IdentifierHelper.DetermineIdType(request.Id, TrustIdValidator.GetTrustIdValidators()); @@ -31,12 +31,15 @@ public class GetAllPersonsAssociatedWithTrustByTrnOrUkprnQueryHandler( return await cacheService.GetOrAddAsync(cacheKey, async () => { var query = trustQueryService.GetTrustGovernanceByGroupIdOrUkprn(groupId, ukPrn); + if (query == null) + { + return Result?>.Failure("Trust not found."); + } - return query == null - ? null - : await query + return Result?>.Success(await query .ProjectTo(mapper.ConfigurationProvider) - .ToListAsync(cancellationToken); + .ToListAsync(cancellationToken)); + }, nameof(GetAllPersonsAssociatedWithTrustByTrnOrUkprnQueryHandler)); } } diff --git a/Dfe.PersonsApi.Client/Generated/Client.g.cs b/Dfe.PersonsApi.Client/Generated/Client.g.cs index a9b9f13e..bdaa19d6 100644 --- a/Dfe.PersonsApi.Client/Generated/Client.g.cs +++ b/Dfe.PersonsApi.Client/Generated/Client.g.cs @@ -509,7 +509,104 @@ public string BaseUrl if (status_ == 404) { string responseText_ = ( response_.Content == null ) ? string.Empty : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); - throw new PersonsApiException("Academy not found.", status_, responseText_, headers_, null); + throw new PersonsApiException("Academy not found.\nor\nConstituency not found for the given establishment.", status_, responseText_, headers_, null); + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new PersonsApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Get Member of Parliament by School (Urn) + /// + /// The URN. + /// Member of Parliament + /// A server side error occurred. + public virtual System.Threading.Tasks.Task GetMemberOfParliamentBySchoolUrnAsync(int urn) + { + return GetMemberOfParliamentBySchoolUrnAsync(urn, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Get Member of Parliament by School (Urn) + /// + /// The URN. + /// Member of Parliament + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task GetMemberOfParliamentBySchoolUrnAsync(int urn, System.Threading.CancellationToken cancellationToken) + { + if (urn == null) + throw new System.ArgumentNullException("urn"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); + // Operation Path: "v1/Establishments/{urn}/getMpBySchool" + urlBuilder_.Append("v1/Establishments/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(urn, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append("/getMpBySchool"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new PersonsApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 404) + { + string responseText_ = ( response_.Content == null ) ? string.Empty : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new PersonsApiException("School Not found.", status_, responseText_, headers_, null); } else { diff --git a/Dfe.PersonsApi.Client/Generated/Contracts.g.cs b/Dfe.PersonsApi.Client/Generated/Contracts.g.cs index 5fb0bd29..87c282fa 100644 --- a/Dfe.PersonsApi.Client/Generated/Contracts.g.cs +++ b/Dfe.PersonsApi.Client/Generated/Contracts.g.cs @@ -81,6 +81,23 @@ public partial interface IEstablishmentsClient /// A server side error occurred. System.Threading.Tasks.Task> GetAllPersonsAssociatedWithAcademyByUrnAsync(int urn, System.Threading.CancellationToken cancellationToken); + /// + /// Get Member of Parliament by School (Urn) + /// + /// The URN. + /// Member of Parliament + /// A server side error occurred. + System.Threading.Tasks.Task GetMemberOfParliamentBySchoolUrnAsync(int urn); + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Get Member of Parliament by School (Urn) + /// + /// The URN. + /// Member of Parliament + /// A server side error occurred. + System.Threading.Tasks.Task GetMemberOfParliamentBySchoolUrnAsync(int urn, System.Threading.CancellationToken cancellationToken); + } [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0))")] diff --git a/Dfe.PersonsApi.Client/Generated/swagger.json b/Dfe.PersonsApi.Client/Generated/swagger.json index 8a1a99aa..4c72d9c4 100644 --- a/Dfe.PersonsApi.Client/Generated/swagger.json +++ b/Dfe.PersonsApi.Client/Generated/swagger.json @@ -120,7 +120,44 @@ } }, "404": { - "description": "Academy not found." + "description": "Academy not found.\nor\nConstituency not found for the given establishment." + } + } + } + }, + "/v1/Establishments/{urn}/getMpBySchool": { + "get": { + "tags": [ + "Establishments" + ], + "summary": "Get Member of Parliament by School (Urn)", + "operationId": "Establishments_GetMemberOfParliamentBySchoolUrn", + "parameters": [ + { + "name": "urn", + "in": "path", + "required": true, + "description": "The URN.", + "schema": { + "type": "integer", + "format": "int32" + }, + "x-position": 1 + } + ], + "responses": { + "200": { + "description": "Member of Parliament", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MemberOfParliament" + } + } + } + }, + "404": { + "description": "School Not found." } } } diff --git a/PersonsApi/Controllers/ConstituenciesController.cs b/PersonsApi/Controllers/ConstituenciesController.cs index ca91fe67..3f240ddb 100644 --- a/PersonsApi/Controllers/ConstituenciesController.cs +++ b/PersonsApi/Controllers/ConstituenciesController.cs @@ -5,6 +5,8 @@ using Swashbuckle.AspNetCore.Annotations; using Microsoft.AspNetCore.Authorization; using Dfe.Academies.Application.Constituencies.Queries.GetMemberOfParliamentByConstituencies; +using Dfe.Academies.Application.Common.Exceptions; +using System.Net; namespace PersonsApi.Controllers { @@ -27,7 +29,7 @@ public async Task GetMemberOfParliamentByConstituencyAsync([FromR { var result = await sender.Send(new GetMemberOfParliamentByConstituencyQuery(constituencyName), cancellationToken); - return result is null ? NotFound() : Ok(result); + return !result.IsSuccess ? NotFound(new CustomProblemDetails(HttpStatusCode.NotFound, result.Error)) : Ok(result.Value); } /// @@ -42,7 +44,7 @@ public async Task GetMembersOfParliamentByConstituenciesAsync([Fr { var result = await sender.Send(request, cancellationToken); - return Ok(result ?? []); + return Ok(result.Value); } } } diff --git a/PersonsApi/Controllers/EstablishmentsController.cs b/PersonsApi/Controllers/EstablishmentsController.cs index 979ae496..d1eb0657 100644 --- a/PersonsApi/Controllers/EstablishmentsController.cs +++ b/PersonsApi/Controllers/EstablishmentsController.cs @@ -1,9 +1,12 @@ +using Dfe.Academies.Application.Common.Exceptions; using Dfe.Academies.Application.Common.Models; using Dfe.Academies.Application.Establishment.Queries.GetAllPersonsAssociatedWithAcademyByUrn; +using Dfe.Academies.Application.Establishment.Queries.GetMemberOfParliamentBySchool; using MediatR; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Swashbuckle.AspNetCore.Annotations; +using System.Net; namespace PersonsApi.Controllers { @@ -22,11 +25,28 @@ public class EstablishmentsController(ISender sender) : ControllerBase [HttpGet("{urn}/getAssociatedPersons")] [SwaggerResponse(200, "A Collection of Persons Associated With the Academy.", typeof(List))] [SwaggerResponse(404, "Academy not found.")] + [SwaggerResponse(404, "Constituency not found for the given establishment.")] public async Task GetAllPersonsAssociatedWithAcademyByUrnAsync([FromRoute] int urn, CancellationToken cancellationToken) { var result = await sender.Send(new GetAllPersonsAssociatedWithAcademyByUrnQuery(urn), cancellationToken); - return result is null ? NotFound() : Ok(result); + return !result.IsSuccess ? NotFound(new CustomProblemDetails(HttpStatusCode.NotFound, result.Error)) : Ok(result.Value); + } + + /// + /// Get Member of Parliament by School (Urn) + /// + /// The URN. + /// The cancellation token. + /// + [HttpGet("{urn}/getMpBySchool")] + [SwaggerResponse(200, "Member of Parliament", typeof(MemberOfParliament))] + [SwaggerResponse(404, "School Not found.")] + public async Task GetMemberOfParliamentBySchoolUrnAsync([FromRoute] int urn, CancellationToken cancellationToken) + { + var result = await sender.Send(new GetMemberOfParliamentBySchoolQuery(urn), cancellationToken); + + return !result.IsSuccess ? NotFound(new CustomProblemDetails(HttpStatusCode.NotFound, result.Error)) : Ok(result.Value); } } } diff --git a/PersonsApi/Controllers/TrustsController.cs b/PersonsApi/Controllers/TrustsController.cs index 4c439786..90d328ed 100644 --- a/PersonsApi/Controllers/TrustsController.cs +++ b/PersonsApi/Controllers/TrustsController.cs @@ -1,9 +1,11 @@ +using Dfe.Academies.Application.Common.Exceptions; using Dfe.Academies.Application.Common.Models; using Dfe.Academies.Application.Trust.Queries.GetAllPersonsAssociatedWithTrustByTrnOrUkprn; using MediatR; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Swashbuckle.AspNetCore.Annotations; +using System.Net; namespace PersonsApi.Controllers { @@ -18,7 +20,6 @@ public class TrustsController(ISender sender) : ControllerBase /// /// The identifier (UKPRN or TRN). /// The cancellation token. - /// [HttpGet("{id}/getAssociatedPersons")] [SwaggerResponse(200, "A Collection of Persons Associated With the Trust.", typeof(List))] [SwaggerResponse(404, "Trust not found.")] @@ -26,7 +27,7 @@ public async Task GetAllPersonsAssociatedWithTrustByTrnOrUkPrnAsy { var result = await sender.Send(new GetAllPersonsAssociatedWithTrustByTrnOrUkprnQuery(id), cancellationToken); - return result is null ? NotFound() : Ok(result); + return !result.IsSuccess ? NotFound(new CustomProblemDetails(HttpStatusCode.NotFound, result.Error)) : Ok(result.Value); } } } diff --git a/PersonsApi/Middleware/ExceptionHandlerMiddleware.cs b/PersonsApi/Middleware/ExceptionHandlerMiddleware.cs index d4fb252b..8aa284f3 100644 --- a/PersonsApi/Middleware/ExceptionHandlerMiddleware.cs +++ b/PersonsApi/Middleware/ExceptionHandlerMiddleware.cs @@ -1,3 +1,4 @@ +using Dfe.Academies.Application.Common.Exceptions; using Microsoft.AspNetCore.Mvc; using PersonsApi.ResponseModels; using System.Net; @@ -8,6 +9,8 @@ namespace PersonsApi.Middleware; public class ExceptionHandlerMiddleware(RequestDelegate next, ILogger logger) { + private static readonly string? _responseContentType = "application/json"; + public async Task InvokeAsync(HttpContext context) { try @@ -23,6 +26,10 @@ public async Task InvokeAsync(HttpContext context) { await HandleForbiddenResponseAsync(context); } + else if (context.Response.StatusCode == (int)HttpStatusCode.NotFound) + { + await HandleNotFoundResponseAsync(context); + } } catch (ValidationException ex) { @@ -36,6 +43,20 @@ public async Task InvokeAsync(HttpContext context) } } + // Handle 404 Not Found + private static async Task HandleNotFoundResponseAsync(HttpContext context) + { + if (!context.Response.HasStarted) + { + context.Response.ContentType = _responseContentType; + context.Response.StatusCode = StatusCodes.Status404NotFound; + + var errorResponse = new CustomProblemDetails(HttpStatusCode.NotFound, "The requested resource could not be found."); + + await context.Response.WriteAsJsonAsync(errorResponse); + } + } + // Handle validation exceptions private static async Task HandleValidationException(HttpContext httpContext, Exception ex) { @@ -54,7 +75,7 @@ await httpContext.Response.WriteAsJsonAsync(new ValidationProblemDetails(excepti private async Task HandleUnauthorizedResponseAsync(HttpContext context) { logger.LogWarning("Unauthorized access attempt detected."); - context.Response.ContentType = "application/json"; + context.Response.ContentType = _responseContentType; var errorResponse = new ErrorResponse { StatusCode = context.Response.StatusCode, @@ -67,7 +88,7 @@ private async Task HandleUnauthorizedResponseAsync(HttpContext context) private async Task HandleForbiddenResponseAsync(HttpContext context) { logger.LogWarning("Forbidden access attempt detected."); - context.Response.ContentType = "application/json"; + context.Response.ContentType = _responseContentType; var errorResponse = new ErrorResponse { StatusCode = context.Response.StatusCode, @@ -79,7 +100,7 @@ private async Task HandleForbiddenResponseAsync(HttpContext context) // Handle general exceptions (500 Internal Server Error) private async Task HandleExceptionAsync(HttpContext context, Exception exception) { - context.Response.ContentType = "application/json"; + context.Response.ContentType = _responseContentType; context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; var errorResponse = new ErrorResponse { diff --git a/Tests/Dfe.Academies.Application.Tests/QueryHandlers/Constituency/GetMemberOfParliamentByConstituencyQueryHandlerTests.cs b/Tests/Dfe.Academies.Application.Tests/QueryHandlers/Constituency/GetMemberOfParliamentByConstituencyQueryHandlerTests.cs index 653f5d91..56ec1bfc 100644 --- a/Tests/Dfe.Academies.Application.Tests/QueryHandlers/Constituency/GetMemberOfParliamentByConstituencyQueryHandlerTests.cs +++ b/Tests/Dfe.Academies.Application.Tests/QueryHandlers/Constituency/GetMemberOfParliamentByConstituencyQueryHandlerTests.cs @@ -44,11 +44,11 @@ public async Task Handle_ShouldReturnMemberOfParliament_WhenConstituencyExists( mockCacheService.GetOrAddAsync( cacheKey, - Arg.Any>>(), + Arg.Any>>>(), Arg.Any()) .Returns(callInfo => { - var callback = callInfo.ArgAt>>(1); + var callback = callInfo.ArgAt>>>(1); return callback(); }); @@ -57,9 +57,9 @@ public async Task Handle_ShouldReturnMemberOfParliament_WhenConstituencyExists( // Assert Assert.NotNull(result); - Assert.Equal(expectedMp.FirstName, result.FirstName); - Assert.Equal(expectedMp.LastName, result.LastName); - Assert.Equal(expectedMp.ConstituencyName, result.ConstituencyName); + Assert.Equal(expectedMp.FirstName, result.Value!.FirstName); + Assert.Equal(expectedMp.LastName, result.Value!.LastName); + Assert.Equal(expectedMp.ConstituencyName, result.Value!.ConstituencyName); await mockConstituencyRepository.Received(1).GetMemberOfParliamentByConstituencyAsync(query.ConstituencyName, default); } diff --git a/Tests/Dfe.Academies.Application.Tests/QueryHandlers/Constituency/GetMemberOfParliamentsByConstituenciesQueryHandlerTests.cs b/Tests/Dfe.Academies.Application.Tests/QueryHandlers/Constituency/GetMemberOfParliamentsByConstituenciesQueryHandlerTests.cs index 628b4aaf..989c025b 100644 --- a/Tests/Dfe.Academies.Application.Tests/QueryHandlers/Constituency/GetMemberOfParliamentsByConstituenciesQueryHandlerTests.cs +++ b/Tests/Dfe.Academies.Application.Tests/QueryHandlers/Constituency/GetMemberOfParliamentsByConstituenciesQueryHandlerTests.cs @@ -46,10 +46,10 @@ public async Task Handle_ShouldReturnMemberOfParliament_WhenConstituencyExists( mockConstituencyRepository.GetMembersOfParliamentByConstituenciesQueryable(query.ConstituencyNames) .Returns(mock); - mockCacheService.GetOrAddAsync(cacheKey, Arg.Any>>>(), Arg.Any()) + mockCacheService.GetOrAddAsync(cacheKey, Arg.Any>>>>(), Arg.Any()) .Returns(callInfo => { - var callback = callInfo.ArgAt>>>(1); + var callback = callInfo.ArgAt>>>>(1); return callback(); }); @@ -58,12 +58,12 @@ public async Task Handle_ShouldReturnMemberOfParliament_WhenConstituencyExists( // Assert Assert.NotNull(result); - Assert.Equal(expectedMps.Count, result.Count); - for (int i = 0; i < result.Count; i++) + Assert.Equal(expectedMps.Count, result.Value!.Count); + for (int i = 0; i < result.Value!.Count; i++) { - Assert.Equal(expectedMps[i].FirstName, result[i].FirstName); - Assert.Equal(expectedMps[i].LastName, result[i].LastName); - Assert.Equal(expectedMps[i].ConstituencyName, result[i].ConstituencyName); + Assert.Equal(expectedMps[i].FirstName, result.Value![i].FirstName); + Assert.Equal(expectedMps[i].LastName, result.Value![i].LastName); + Assert.Equal(expectedMps[i].ConstituencyName, result.Value![i].ConstituencyName); } mockConstituencyRepository.Received(1).GetMembersOfParliamentByConstituenciesQueryable(query.ConstituencyNames); diff --git a/Tests/Dfe.Academies.Application.Tests/QueryHandlers/Establishment/GetAllPersonsAssociatedWithAcademyByUrnTests.cs b/Tests/Dfe.Academies.Application.Tests/QueryHandlers/Establishment/GetAllPersonsAssociatedWithAcademyByUrnQueryHandlerTests.cs similarity index 84% rename from Tests/Dfe.Academies.Application.Tests/QueryHandlers/Establishment/GetAllPersonsAssociatedWithAcademyByUrnTests.cs rename to Tests/Dfe.Academies.Application.Tests/QueryHandlers/Establishment/GetAllPersonsAssociatedWithAcademyByUrnQueryHandlerTests.cs index 1d32dac4..8e571a9f 100644 --- a/Tests/Dfe.Academies.Application.Tests/QueryHandlers/Establishment/GetAllPersonsAssociatedWithAcademyByUrnTests.cs +++ b/Tests/Dfe.Academies.Application.Tests/QueryHandlers/Establishment/GetAllPersonsAssociatedWithAcademyByUrnQueryHandlerTests.cs @@ -46,10 +46,10 @@ public async Task Handle_ShouldReturnPersonsAssociatedWithAcademy_WhenUrnExists( mockEstablishmentQueryService.GetPersonsAssociatedWithAcademyByUrn(query.Urn) .Returns(mock); - mockCacheService.GetOrAddAsync(cacheKey, Arg.Any>>>(), Arg.Any()) + mockCacheService.GetOrAddAsync(cacheKey, Arg.Any>>>>(), Arg.Any()) .Returns(callInfo => { - var callback = callInfo.ArgAt>>>(1); + var callback = callInfo.ArgAt>>>>(1); return callback(); }); @@ -57,15 +57,15 @@ public async Task Handle_ShouldReturnPersonsAssociatedWithAcademy_WhenUrnExists( var result = await handler.Handle(query, default); // Assert - Assert.NotNull(result); - Assert.Equal(expectedGovernances.Count, result.Count); - for (int i = 0; i < result.Count; i++) + Assert.NotNull(result.Value); + Assert.Equal(expectedGovernances.Count, result.Value!.Count); + for (int i = 0; i < result.Value!.Count; i++) { - Assert.Equal(expectedGovernances[i].FirstName, result[i].FirstName); - Assert.Equal(expectedGovernances[i].LastName, result[i].LastName); + Assert.Equal(expectedGovernances[i].FirstName, result.Value![i].FirstName); + Assert.Equal(expectedGovernances[i].LastName, result.Value![i].LastName); } - await mockCacheService.Received(1).GetOrAddAsync(cacheKey, Arg.Any?>>>(), nameof(GetAllPersonsAssociatedWithAcademyByUrnQueryHandler)); + await mockCacheService.Received(1).GetOrAddAsync(cacheKey, Arg.Any?>>>>(), nameof(GetAllPersonsAssociatedWithAcademyByUrnQueryHandler)); } } } diff --git a/Tests/Dfe.Academies.Application.Tests/QueryHandlers/Establishment/GetMemberOfParliamentBySchoolQueryHandlerTests.cs b/Tests/Dfe.Academies.Application.Tests/QueryHandlers/Establishment/GetMemberOfParliamentBySchoolQueryHandlerTests.cs new file mode 100644 index 00000000..796c5e1f --- /dev/null +++ b/Tests/Dfe.Academies.Application.Tests/QueryHandlers/Establishment/GetMemberOfParliamentBySchoolQueryHandlerTests.cs @@ -0,0 +1,111 @@ +using AutoFixture; +using AutoFixture.Xunit2; +using Dfe.Academies.Application.Common.Models; +using Dfe.Academies.Application.Establishment.Queries.GetMemberOfParliamentBySchool; +using Dfe.Academies.Application.MappingProfiles; +using Dfe.Academies.Domain.Interfaces.Repositories; +using Dfe.Academies.Testing.Common.Customizations.Models; +using Dfe.Academies.Tests.Common.Customizations.Entities; +using DfE.CoreLibs.Caching.Helpers; +using DfE.CoreLibs.Caching.Interfaces; +using DfE.CoreLibs.Testing.AutoFixture.Attributes; +using DfE.CoreLibs.Testing.AutoFixture.Customizations; +using NSubstitute; + +namespace Dfe.Academies.Application.Tests.QueryHandlers.Establishment +{ + public class GetMemberOfParliamentBySchoolQueryHandlerTests + { + [Theory] + [CustomAutoData( + typeof(MemberOfParliamentCustomization), + typeof(ConstituencyCustomization), + typeof(OmitCircularReferenceCustomization), + typeof(AutoMapperCustomization))] + public async Task Handle_ShouldReturnMemberOfParliament_WhenSchoolExists( + [Frozen] IConstituencyRepository mockConstituencyRepository, + [Frozen] IEstablishmentRepository establishmentRepository, + [Frozen] ICacheService mockCacheService, + GetMemberOfParliamentBySchoolQueryHandler handler, + GetMemberOfParliamentBySchoolQuery query, + Domain.Constituencies.Constituency constituency, + Domain.Establishment.Establishment establishment, + IFixture fixture) + { + // Arrange + fixture.Customize(composer => composer + .With(x => x.ParliamentaryConstituency, "ConstituencyName")); + + var expectedMp = fixture.Customize(new MemberOfParliamentCustomization() + { + FirstName = constituency.NameDetails.NameListAs.Split(",")[1].Trim(), + LastName = constituency.NameDetails.NameListAs.Split(",")[0].Trim(), + ConstituencyName = constituency.ConstituencyName, + }).Create(); + + var cacheKey = $"MPbySchool_{CacheKeyHelper.GenerateHashedCacheKey(query.Urn.ToString())}"; + + establishmentRepository.GetEstablishmentByUrn(query.Urn.ToString(), default) + .Returns(establishment); + + mockConstituencyRepository.GetMemberOfParliamentByConstituencyAsync(establishment.ParliamentaryConstituency!, default) + .Returns(constituency); + + mockCacheService.GetOrAddAsync( + cacheKey, + Arg.Any>>>(), + Arg.Any()) + .Returns(callInfo => + { + var callback = callInfo.ArgAt>>>(1); + return callback(); + }); + + // Act + var result = await handler.Handle(query, default); + + // Assert + Assert.NotNull(result); + Assert.Equal(expectedMp.FirstName, result.Value!.FirstName); + Assert.Equal(expectedMp.LastName, result.Value!.LastName); + Assert.Equal(expectedMp.ConstituencyName, result.Value!.ConstituencyName); + } + + [Theory] + [CustomAutoData( + typeof(MemberOfParliamentCustomization), + typeof(ConstituencyCustomization), + typeof(OmitCircularReferenceCustomization), + typeof(AutoMapperCustomization))] + public async Task Handle_ShouldReturnNotFound_WhenSchoolDoesntExists( + [Frozen] IEstablishmentRepository establishmentRepository, + [Frozen] ICacheService mockCacheService, + GetMemberOfParliamentBySchoolQueryHandler handler, + GetMemberOfParliamentBySchoolQuery query) + { + // Arrange + + var cacheKey = $"MPbySchool_{CacheKeyHelper.GenerateHashedCacheKey(query.Urn.ToString())}"; + + establishmentRepository.GetEstablishmentByUrn(query.Urn.ToString(), default) + .Returns((Domain.Establishment.Establishment?)null); + + mockCacheService.GetOrAddAsync( + cacheKey, + Arg.Any>>>(), + Arg.Any()) + .Returns(callInfo => + { + var callback = callInfo.ArgAt>>>(1); + return callback(); + }); + + // Act + var result = await handler.Handle(query, default); + + // Assert + Assert.NotNull(result); + Assert.False(result.IsSuccess); + } + } +} diff --git a/Tests/Dfe.Academies.Application.Tests/QueryHandlers/Trust/GetAllPersonsAssociatedWithTrustByTrnOrUkprnTests.cs b/Tests/Dfe.Academies.Application.Tests/QueryHandlers/Trust/GetAllPersonsAssociatedWithTrustByTrnOrUkprnTests.cs index 22944dd5..5f3bcd11 100644 --- a/Tests/Dfe.Academies.Application.Tests/QueryHandlers/Trust/GetAllPersonsAssociatedWithTrustByTrnOrUkprnTests.cs +++ b/Tests/Dfe.Academies.Application.Tests/QueryHandlers/Trust/GetAllPersonsAssociatedWithTrustByTrnOrUkprnTests.cs @@ -46,10 +46,10 @@ public async Task Handle_ShouldReturnPersonsAssociatedWithTrust_WhenTrustExists( mockTrustQueryService.GetTrustGovernanceByGroupIdOrUkprn(Arg.Any(), Arg.Any()) .Returns(mock); - mockCacheService.GetOrAddAsync(cacheKey, Arg.Any>>>(), Arg.Any()) + mockCacheService.GetOrAddAsync(cacheKey, Arg.Any>>>>(), Arg.Any()) .Returns(callInfo => { - var callback = callInfo.ArgAt>>>(1); + var callback = callInfo.ArgAt>>>>(1); return callback(); }); @@ -57,15 +57,15 @@ public async Task Handle_ShouldReturnPersonsAssociatedWithTrust_WhenTrustExists( var result = await handler.Handle(query, default); // Assert - Assert.NotNull(result); - Assert.Equal(expectedGovernances.Count, result.Count); - for (int i = 0; i < result.Count; i++) + Assert.NotNull(result.Value); + Assert.Equal(expectedGovernances.Count, result.Value!.Count); + for (int i = 0; i < result.Value!.Count; i++) { - Assert.Equal(expectedGovernances[i].FirstName, result[i].FirstName); - Assert.Equal(expectedGovernances[i].LastName, result[i].LastName); + Assert.Equal(expectedGovernances[i].FirstName, result.Value![i].FirstName); + Assert.Equal(expectedGovernances[i].LastName, result.Value![i].LastName); } - await mockCacheService.Received(1).GetOrAddAsync(cacheKey, Arg.Any?>>>(), nameof(GetAllPersonsAssociatedWithTrustByTrnOrUkprnQueryHandler)); + await mockCacheService.Received(1).GetOrAddAsync(cacheKey, Arg.Any?>>>>(), nameof(GetAllPersonsAssociatedWithTrustByTrnOrUkprnQueryHandler)); } } } diff --git a/Tests/Dfe.Academies.PersonsApi.Tests.Integration/Controllers/ConstituenciesControllerTests.cs b/Tests/Dfe.Academies.PersonsApi.Tests.Integration/Controllers/ConstituenciesControllerTests.cs index 81a5207c..cd831d36 100644 --- a/Tests/Dfe.Academies.PersonsApi.Tests.Integration/Controllers/ConstituenciesControllerTests.cs +++ b/Tests/Dfe.Academies.PersonsApi.Tests.Integration/Controllers/ConstituenciesControllerTests.cs @@ -13,15 +13,15 @@ namespace Dfe.Academies.PersonsApi.Tests.Integration.Controllers public class ConstituenciesControllerTests { [Theory] - [CustomAutoData(typeof(CustomWebApplicationDbContextFactoryCustomization))] + [CustomAutoData(typeof(CustomWebApplicationDbContextFactoryCustomization))] public async Task GetMemberOfParliamentByConstituencyAsync_ShouldReturnMp_WhenConstituencyExists( - CustomWebApplicationDbContextFactory factory, + CustomWebApplicationDbContextFactory factory, IConstituenciesClient constituenciesClient) { factory.TestClaims = [new Claim(ClaimTypes.Role, "API.Read")]; // Arrange - var dbContext = factory.GetDbContext(); + var dbContext = factory.GetDbContext(); await dbContext.Constituencies .Where(x => x.ConstituencyName == "Test Constituency 1") @@ -38,9 +38,9 @@ await dbContext.Constituencies } [Theory] - [CustomAutoData(typeof(CustomWebApplicationDbContextFactoryCustomization))] + [CustomAutoData(typeof(CustomWebApplicationDbContextFactoryCustomization))] public async Task GetMemberOfParliamentByConstituencyAsync_ShouldReturnNotFound_WhenConstituencyDoesNotExist( - CustomWebApplicationDbContextFactory factory, + CustomWebApplicationDbContextFactory factory, IConstituenciesClient constituenciesClient) { // Arrange @@ -56,15 +56,15 @@ public async Task GetMemberOfParliamentByConstituencyAsync_ShouldReturnNotFound_ } [Theory] - [CustomAutoData(typeof(CustomWebApplicationDbContextFactoryCustomization))] + [CustomAutoData(typeof(CustomWebApplicationDbContextFactoryCustomization))] public async Task GetMemberOfParliamentByConstituenciesAsync_ShouldReturnMps_WhenConstituenciesExists( - CustomWebApplicationDbContextFactory factory, + CustomWebApplicationDbContextFactory factory, IConstituenciesClient constituenciesClient) { // Arrange factory.TestClaims = [new Claim(ClaimTypes.Role, "API.Read")]; - var dbcontext = factory.GetDbContext(); + var dbcontext = factory.GetDbContext(); await dbcontext.Constituencies.Where(x => x.ConstituencyName == "Test Constituency 1") .ExecuteUpdateAsync(x => x.SetProperty(p => p.ConstituencyName, "NewConstituencyName")); @@ -82,9 +82,9 @@ await dbcontext.Constituencies.Where(x => x.ConstituencyName == "Test Constituen } [Theory] - [CustomAutoData(typeof(CustomWebApplicationDbContextFactoryCustomization))] + [CustomAutoData(typeof(CustomWebApplicationDbContextFactoryCustomization))] public async Task GetMemberOfParliamentByConstituenciesAsync_ShouldReturnEmpty_WhenConstituenciesDontExists( - CustomWebApplicationDbContextFactory factory, + CustomWebApplicationDbContextFactory factory, IConstituenciesClient constituenciesClient) { // Arrange @@ -100,9 +100,9 @@ public async Task GetMemberOfParliamentByConstituenciesAsync_ShouldReturnEmpty_W } [Theory] - [CustomAutoData(typeof(CustomWebApplicationDbContextFactoryCustomization))] + [CustomAutoData(typeof(CustomWebApplicationDbContextFactoryCustomization))] public async Task GetMemberOfParliamentByConstituenciesAsync_ShouldThrowAnException_WhenConstituenciesNotProvided( - CustomWebApplicationDbContextFactory factory, + CustomWebApplicationDbContextFactory factory, IConstituenciesClient constituenciesClient) { // Arrange diff --git a/Tests/Dfe.Academies.PersonsApi.Tests.Integration/Controllers/EstablishmentsControllerTests.cs b/Tests/Dfe.Academies.PersonsApi.Tests.Integration/Controllers/EstablishmentsControllerTests.cs index 914e481a..e71c0c89 100644 --- a/Tests/Dfe.Academies.PersonsApi.Tests.Integration/Controllers/EstablishmentsControllerTests.cs +++ b/Tests/Dfe.Academies.PersonsApi.Tests.Integration/Controllers/EstablishmentsControllerTests.cs @@ -1,26 +1,27 @@ using Dfe.Academies.Infrastructure; using Dfe.Academies.Tests.Common.Customizations; using Dfe.PersonsApi.Client.Contracts; +using DfE.CoreLibs.Testing.AutoFixture.Attributes; +using DfE.CoreLibs.Testing.Mocks.WebApplicationFactory; using Microsoft.EntityFrameworkCore; using PersonsApi; +using System.Net; using System.Security.Claims; -using DfE.CoreLibs.Testing.AutoFixture.Attributes; -using DfE.CoreLibs.Testing.Mocks.WebApplicationFactory; namespace Dfe.Academies.PersonsApi.Tests.Integration.Controllers { public class EstablishmentsControllerTests { [Theory] - [CustomAutoData(typeof(CustomWebApplicationDbContextFactoryCustomization))] + [CustomAutoData(typeof(CustomWebApplicationDbContextFactoryCustomization))] public async Task GetAllPersonsAssociatedWithAcademyAsync_ShouldReturnPeople_WhenAcademyExists( - CustomWebApplicationDbContextFactory factory, + CustomWebApplicationDbContextFactory factory, IEstablishmentsClient establishmentsClient) { // Arrange factory.TestClaims = [new Claim(ClaimTypes.Role, "API.Read")]; - var dbContext = factory.GetDbContext(); + var dbContext = factory.GetDbContext(); await dbContext.Establishments.Where(x => x.SK == 1) .ExecuteUpdateAsync(x => x.SetProperty(p => p.URN, 22)); @@ -38,15 +39,15 @@ await dbContext.Establishments.Where(x => x.SK == 1) } [Theory] - [CustomAutoData(typeof(CustomWebApplicationDbContextFactoryCustomization))] + [CustomAutoData(typeof(CustomWebApplicationDbContextFactoryCustomization))] public async Task GetAllPersonsAssociatedWithAcademyAsync_ShouldReturnEmptyList_WhenAcademyExistWithNoPeople( - CustomWebApplicationDbContextFactory factory, + CustomWebApplicationDbContextFactory factory, IEstablishmentsClient establishmentsClient) { // Arrange factory.TestClaims = [new Claim(ClaimTypes.Role, "API.Read")]; - var dbContext = factory.GetDbContext(); + var dbContext = factory.GetDbContext(); await dbContext.Establishments.Where(x => x.SK == 2) .ExecuteUpdateAsync(x => x.SetProperty(p => p.URN, 33)); @@ -60,9 +61,9 @@ await dbContext.Establishments.Where(x => x.SK == 2) } [Theory] - [CustomAutoData(typeof(CustomWebApplicationDbContextFactoryCustomization))] + [CustomAutoData(typeof(CustomWebApplicationDbContextFactoryCustomization))] public async Task GetAllPersonsAssociatedWithAcademyAsync_ShouldThrowAnException_WhenAcademyDoesntExists( - CustomWebApplicationDbContextFactory factory, + CustomWebApplicationDbContextFactory factory, IEstablishmentsClient establishmentsClient) { // Arrange @@ -74,5 +75,50 @@ public async Task GetAllPersonsAssociatedWithAcademyAsync_ShouldThrowAnException Assert.Contains("Academy not found.", exception.Message); } + + [Theory] + [CustomAutoData(typeof(CustomWebApplicationDbContextFactoryCustomization))] + public async Task GetGetMemberOfParliamentBySchoolUrnAsync_ShouldReturnMP_WhenSchoolExists( + CustomWebApplicationDbContextFactory factory, + IEstablishmentsClient establishmentsClient) + { + // Arrange + factory.TestClaims = [new Claim(ClaimTypes.Role, "API.Read")]; + + // Test access to both dbContexts/ Schemas + var dbContext = factory.GetDbContext(); + var mopDbContext = factory.GetDbContext(); + + await dbContext.Establishments.Where(x => x.SK == 1) + .ExecuteUpdateAsync(x => x.SetProperty(p => p.URN, 44) + .SetProperty(p => p.ParliamentaryConstituency, "Test Constituency 44")); + + await mopDbContext.Constituencies.Where(x => x.ConstituencyName == "Test Constituency 1") + .ExecuteUpdateAsync(x => x.SetProperty(p => p.ConstituencyName, "Test Constituency 44")); + + // Act + var result = await establishmentsClient.GetMemberOfParliamentBySchoolUrnAsync(44); + + // Assert + Assert.NotNull(result); + Assert.Equal("Test Constituency 44", result.ConstituencyName); + + } + + [Theory] + [CustomAutoData(typeof(CustomWebApplicationDbContextFactoryCustomization))] + public async Task GetGetMemberOfParliamentBySchoolUrnAsync_ShouldReturnNull_WhenSchoolDoesntExists( + CustomWebApplicationDbContextFactory factory, + IEstablishmentsClient establishmentsClient) + { + // Arrange + factory.TestClaims = [new Claim(ClaimTypes.Role, "API.Read")]; + + // Act & Assert + var exception = await Assert.ThrowsAsync(async () => + await establishmentsClient.GetMemberOfParliamentBySchoolUrnAsync(111)); + + Assert.Equal(HttpStatusCode.NotFound, (HttpStatusCode)exception.StatusCode); + } } } \ No newline at end of file diff --git a/Tests/Dfe.Academies.PersonsApi.Tests.Integration/Controllers/TrustsControllerTests.cs b/Tests/Dfe.Academies.PersonsApi.Tests.Integration/Controllers/TrustsControllerTests.cs index 2bc839a2..ef96ca2c 100644 --- a/Tests/Dfe.Academies.PersonsApi.Tests.Integration/Controllers/TrustsControllerTests.cs +++ b/Tests/Dfe.Academies.PersonsApi.Tests.Integration/Controllers/TrustsControllerTests.cs @@ -11,9 +11,9 @@ namespace Dfe.Academies.PersonsApi.Tests.Integration.Controllers public class TrustsControllerTests { [Theory] - [CustomAutoData(typeof(CustomWebApplicationDbContextFactoryCustomization))] + [CustomAutoData(typeof(CustomWebApplicationDbContextFactoryCustomization))] public async Task GetAllPersonsAssociatedWithTrustAsync_ShouldReturnPeople_WhenTrustExists( - CustomWebApplicationDbContextFactory factory, + CustomWebApplicationDbContextFactory factory, ITrustsClient trustsClient) { // Arrange @@ -32,9 +32,9 @@ public async Task GetAllPersonsAssociatedWithTrustAsync_ShouldReturnPeople_WhenT } [Theory] - [CustomAutoData(typeof(CustomWebApplicationDbContextFactoryCustomization))] + [CustomAutoData(typeof(CustomWebApplicationDbContextFactoryCustomization))] public async Task GetAllPersonsAssociatedWithTrustAsync_ShouldReturnEmptyList_WhenTrustExistWithNoPeople( - CustomWebApplicationDbContextFactory factory, + CustomWebApplicationDbContextFactory factory, ITrustsClient trustsClient) { // Arrange @@ -49,9 +49,9 @@ public async Task GetAllPersonsAssociatedWithTrustAsync_ShouldReturnEmptyList_Wh } [Theory] - [CustomAutoData(typeof(CustomWebApplicationDbContextFactoryCustomization))] + [CustomAutoData(typeof(CustomWebApplicationDbContextFactoryCustomization))] public async Task GetAllPersonsAssociatedWithTrustAsync_ShouldThrowAnException_WhenTrustDoesntExists( - CustomWebApplicationDbContextFactory factory, + CustomWebApplicationDbContextFactory factory, ITrustsClient trustsClient) { // Arrange @@ -65,9 +65,9 @@ public async Task GetAllPersonsAssociatedWithTrustAsync_ShouldThrowAnException_W } [Theory] - [CustomAutoData(typeof(CustomWebApplicationDbContextFactoryCustomization))] + [CustomAutoData(typeof(CustomWebApplicationDbContextFactoryCustomization))] public async Task GetAllPersonsAssociatedWithTrustAsync_ShouldThrowAnException_WhenInvalidIdProvided( - CustomWebApplicationDbContextFactory factory, + CustomWebApplicationDbContextFactory factory, ITrustsClient trustsClient) { // Arrange diff --git a/Tests/Dfe.Academies.Tests.Common/Customizations/CustomWebApplicationDbContextFactoryCustomization.cs b/Tests/Dfe.Academies.Tests.Common/Customizations/CustomWebApplicationDbContextFactoryCustomization.cs index 1e713bc9..94396f5d 100644 --- a/Tests/Dfe.Academies.Tests.Common/Customizations/CustomWebApplicationDbContextFactoryCustomization.cs +++ b/Tests/Dfe.Academies.Tests.Common/Customizations/CustomWebApplicationDbContextFactoryCustomization.cs @@ -19,19 +19,21 @@ namespace Dfe.Academies.Tests.Common.Customizations { - public class CustomWebApplicationDbContextFactoryCustomization : ICustomization - where TProgram : class where TDbContext : DbContext + public class CustomWebApplicationDbContextFactoryCustomization : ICustomization + where TProgram : class { - public Action? SeedAction { get; set; } - public void Customize(IFixture fixture) { - fixture.Customize>(composer => composer.FromFactory(() => + fixture.Customize>(composer => composer.FromFactory(() => { - var factory = new CustomWebApplicationDbContextFactory() + var factory = new CustomWebApplicationDbContextFactory() { - SeedData = SeedAction ?? DefaultSeedData, + SeedData = new Dictionary> + { + { typeof(MstrContext), context => MstrSeedData((MstrContext)context) }, + { typeof(MopContext), context => MopSeedData((MopContext)context) } + }, ExternalServicesConfiguration = services => { services.PostConfigure(options => @@ -78,206 +80,209 @@ public void Customize(IFixture fixture) })); } - private static void DefaultSeedData(TDbContext context) + private static void MstrSeedData(MstrContext mstrContext) { - if (context is MstrContext mstrContext) + + if (!mstrContext.Trusts.Any() && !mstrContext.Establishments.Any() && + !mstrContext.EducationEstablishmentTrusts.Any() && !mstrContext.GovernanceRoleTypes.Any() && + !mstrContext.EducationEstablishmentGovernances.Any()) { - if (!mstrContext.Trusts.Any() && !mstrContext.Establishments.Any() && - !mstrContext.EducationEstablishmentTrusts.Any() && !mstrContext.GovernanceRoleTypes.Any() && - !mstrContext.EducationEstablishmentGovernances.Any()) - { - // Populate Trust - var trust1 = new Trust - { - SK = 1, - Name = "Trust A", - TrustTypeId = mstrContext.TrustTypes.FirstOrDefault()?.SK, - GroupUID = "G1", - Modified = DateTime.UtcNow, - ModifiedBy = "System", - UKPRN = "12345678", - GroupID = "TR00024" - }; - var trust2 = new Trust - { - SK = 2, - Name = "Trust B", - TrustTypeId = mstrContext.TrustTypes.FirstOrDefault()?.SK, - GroupUID = "G2", - Modified = DateTime.UtcNow, - ModifiedBy = "System", - UKPRN = "87654321", - GroupID = "TR00025" - }; - mstrContext.Trusts.AddRange(trust1, trust2); + // Populate Trust + var trust1 = new Trust + { + SK = 1, + Name = "Trust A", + TrustTypeId = mstrContext.TrustTypes.FirstOrDefault()?.SK, + GroupUID = "G1", + Modified = DateTime.UtcNow, + ModifiedBy = "System", + UKPRN = "12345678", + GroupID = "TR00024" + }; + var trust2 = new Trust + { + SK = 2, + Name = "Trust B", + TrustTypeId = mstrContext.TrustTypes.FirstOrDefault()?.SK, + GroupUID = "G2", + Modified = DateTime.UtcNow, + ModifiedBy = "System", + UKPRN = "87654321", + GroupID = "TR00025" + }; + mstrContext.Trusts.AddRange(trust1, trust2); - // Populate Establishment - var establishment1 = new Establishment - { - SK = 1, - EstablishmentName = "School A", - LocalAuthorityId = mstrContext.LocalAuthorities.FirstOrDefault()?.SK, - EstablishmentTypeId = mstrContext.EstablishmentTypes.FirstOrDefault()?.SK, - Latitude = 54.9784, - Longitude = -1.6174, - MainPhone = "01234567890", - Email = "schoolA@example.com", - Modified = DateTime.UtcNow, - ModifiedBy = "System" - }; - var establishment2 = new Establishment - { - SK = 2, - EstablishmentName = "School B", - LocalAuthorityId = mstrContext.LocalAuthorities.FirstOrDefault()?.SK, - EstablishmentTypeId = mstrContext.EstablishmentTypes.FirstOrDefault()?.SK, - Latitude = 50.3763, - Longitude = -4.1427, - MainPhone = "09876543210", - Email = "schoolB@example.com", - Modified = DateTime.UtcNow, - ModifiedBy = "System" - }; - mstrContext.Establishments.AddRange(establishment1, establishment2); + // Populate Establishment + var establishment1 = new Establishment + { + SK = 1, + URN = 22, + EstablishmentName = "School A", + LocalAuthorityId = mstrContext.LocalAuthorities.FirstOrDefault()?.SK, + EstablishmentTypeId = mstrContext.EstablishmentTypes.FirstOrDefault()?.SK, + Latitude = 54.9784, + Longitude = -1.6174, + MainPhone = "01234567890", + Email = "schoolA@example.com", + Modified = DateTime.UtcNow, + ModifiedBy = "System", + ParliamentaryConstituency = "Test Constituency 1" + }; + var establishment2 = new Establishment + { + SK = 2, + URN = 33, + EstablishmentName = "School B", + LocalAuthorityId = mstrContext.LocalAuthorities.FirstOrDefault()?.SK, + EstablishmentTypeId = mstrContext.EstablishmentTypes.FirstOrDefault()?.SK, + Latitude = 50.3763, + Longitude = -4.1427, + MainPhone = "09876543210", + Email = "schoolB@example.com", + Modified = DateTime.UtcNow, + ModifiedBy = "System", + ParliamentaryConstituency = "Test Constituency 2" + }; + mstrContext.Establishments.AddRange(establishment1, establishment2); - // Populate EducationEstablishmentTrust - var educationEstablishmentTrust1 = new EducationEstablishmentTrust - { - SK = 1, - EducationEstablishmentId = (int)establishment1.SK, - TrustId = (int)trust1.SK, - }; - var educationEstablishmentTrust2 = new EducationEstablishmentTrust - { - SK = 2, - EducationEstablishmentId = (int)establishment2.SK, - TrustId = (int)trust2.SK, + // Populate EducationEstablishmentTrust + var educationEstablishmentTrust1 = new EducationEstablishmentTrust + { + SK = 1, + EducationEstablishmentId = (int)establishment1.SK, + TrustId = (int)trust1.SK, + }; + var educationEstablishmentTrust2 = new EducationEstablishmentTrust + { + SK = 2, + EducationEstablishmentId = (int)establishment2.SK, + TrustId = (int)trust2.SK, - }; - mstrContext.EducationEstablishmentTrusts.AddRange(educationEstablishmentTrust1, educationEstablishmentTrust2); + }; + mstrContext.EducationEstablishmentTrusts.AddRange(educationEstablishmentTrust1, educationEstablishmentTrust2); - // Populate GovernanceRoleType - var governanceRoleType1 = new GovernanceRoleType - { SK = 1, Name = "Chair of Governors", Modified = DateTime.UtcNow, ModifiedBy = "System" }; - var governanceRoleType2 = new GovernanceRoleType - { SK = 2, Name = "Vice Chair of Governors", Modified = DateTime.UtcNow, ModifiedBy = "System" }; - var governanceRoleType3 = new GovernanceRoleType - { SK = 3, Name = "Trustee", Modified = DateTime.UtcNow, ModifiedBy = "System" }; - mstrContext.GovernanceRoleTypes.AddRange(governanceRoleType1, governanceRoleType2, governanceRoleType3); + // Populate GovernanceRoleType + var governanceRoleType1 = new GovernanceRoleType + { SK = 1, Name = "Chair of Governors", Modified = DateTime.UtcNow, ModifiedBy = "System" }; + var governanceRoleType2 = new GovernanceRoleType + { SK = 2, Name = "Vice Chair of Governors", Modified = DateTime.UtcNow, ModifiedBy = "System" }; + var governanceRoleType3 = new GovernanceRoleType + { SK = 3, Name = "Trustee", Modified = DateTime.UtcNow, ModifiedBy = "System" }; + mstrContext.GovernanceRoleTypes.AddRange(governanceRoleType1, governanceRoleType2, governanceRoleType3); - // Populate EducationEstablishmentGovernance - var governance1 = new EducationEstablishmentGovernance - { - SK = 1, - EducationEstablishmentId = establishment1.SK, - GovernanceRoleTypeId = governanceRoleType1.SK, - GID = "GID1", - Title = "Mr.", - Forename1 = "John", - Surname = "Doe", - Email = "johndoe@example.com", - Modified = DateTime.UtcNow, - ModifiedBy = "System" - }; - var governance3 = new EducationEstablishmentGovernance - { - SK = 3, - EducationEstablishmentId = establishment1.SK, - GovernanceRoleTypeId = governanceRoleType2.SK, - GID = "GID2", - Title = "Ms.", - Forename1 = "Anna", - Surname = "Smith", - Email = "annasmith@example.com", - Modified = DateTime.UtcNow, - ModifiedBy = "System" - }; - mstrContext.EducationEstablishmentGovernances.AddRange(governance1, governance3); + // Populate EducationEstablishmentGovernance + var governance1 = new EducationEstablishmentGovernance + { + SK = 1, + EducationEstablishmentId = establishment1.SK, + GovernanceRoleTypeId = governanceRoleType1.SK, + GID = "GID1", + Title = "Mr.", + Forename1 = "John", + Surname = "Doe", + Email = "johndoe@example.com", + Modified = DateTime.UtcNow, + ModifiedBy = "System" + }; + var governance3 = new EducationEstablishmentGovernance + { + SK = 3, + EducationEstablishmentId = establishment1.SK, + GovernanceRoleTypeId = governanceRoleType2.SK, + GID = "GID2", + Title = "Ms.", + Forename1 = "Anna", + Surname = "Smith", + Email = "annasmith@example.com", + Modified = DateTime.UtcNow, + ModifiedBy = "System" + }; + mstrContext.EducationEstablishmentGovernances.AddRange(governance1, governance3); - // Populate TrustGovernance - var trustGovernance1 = new TrustGovernance - { - SK = 1, - TrustId = trust2.SK, - GovernanceRoleTypeId = governanceRoleType3.SK, - GID = "GID1", - Title = "Mr.", - Forename1 = "John", - Surname = "Wood", - Email = "johnWood@example.com", - Modified = DateTime.UtcNow, - ModifiedBy = "System" - }; - var trustGovernance2 = new TrustGovernance - { - SK = 2, - TrustId = trust2.SK, - GovernanceRoleTypeId = governanceRoleType3.SK, - GID = "GID1", - Title = "Mr.", - Forename1 = "Joe", - Surname = "Wood", - Email = "joeWood@example.com", - Modified = DateTime.UtcNow, - ModifiedBy = "System" - }; - mstrContext.TrustGovernances.AddRange(trustGovernance1, trustGovernance2); + // Populate TrustGovernance + var trustGovernance1 = new TrustGovernance + { + SK = 1, + TrustId = trust2.SK, + GovernanceRoleTypeId = governanceRoleType3.SK, + GID = "GID1", + Title = "Mr.", + Forename1 = "John", + Surname = "Wood", + Email = "johnWood@example.com", + Modified = DateTime.UtcNow, + ModifiedBy = "System" + }; + var trustGovernance2 = new TrustGovernance + { + SK = 2, + TrustId = trust2.SK, + GovernanceRoleTypeId = governanceRoleType3.SK, + GID = "GID1", + Title = "Mr.", + Forename1 = "Joe", + Surname = "Wood", + Email = "joeWood@example.com", + Modified = DateTime.UtcNow, + ModifiedBy = "System" + }; + mstrContext.TrustGovernances.AddRange(trustGovernance1, trustGovernance2); - // Save changes - mstrContext.SaveChanges(); - } + // Save changes + mstrContext.SaveChanges(); } + } - if (context is MopContext mopContext) - { - var memberContact1 = new MemberContactDetails( - new MemberId(1), - 1, - "test1@example.com", - null - ); + private static void MopSeedData(MopContext mopContext) + { - var memberContact2 = new MemberContactDetails( - new MemberId(2), - 1, - "test2@example.com", - null - ); + var memberContact1 = new MemberContactDetails( + new MemberId(1), + 1, + "test1@example.com", + null + ); - var constituency1 = new Constituency( - new ConstituencyId(1), - new MemberId(1), - "Test Constituency 1", - new NameDetails( - "Wood, John", - "John Wood", - "Mr. John Wood MP" - ), - DateTime.UtcNow, - null, - memberContact1 - ); + var memberContact2 = new MemberContactDetails( + new MemberId(2), + 1, + "test2@example.com", + null + ); - var constituency2 = new Constituency( - new ConstituencyId(2), - new MemberId(2), - "Test Constituency 2", - new NameDetails( - "Wood, Joe", - "Joe Wood", - "Mr. Joe Wood MP" - ), - DateTime.UtcNow, - null, - memberContact2 - ); + var constituency1 = new Constituency( + new ConstituencyId(1), + new MemberId(1), + "Test Constituency 1", + new NameDetails( + "Wood, John", + "John Wood", + "Mr. John Wood MP" + ), + DateTime.UtcNow, + null, + memberContact1 + ); - mopContext.Constituencies.Add(constituency1); - mopContext.Constituencies.Add(constituency2); + var constituency2 = new Constituency( + new ConstituencyId(2), + new MemberId(2), + "Test Constituency 2", + new NameDetails( + "Wood, Joe", + "Joe Wood", + "Mr. Joe Wood MP" + ), + DateTime.UtcNow, + null, + memberContact2 + ); - mopContext.SaveChanges(); - } + mopContext.Constituencies.Add(constituency1); + mopContext.Constituencies.Add(constituency2); + + mopContext.SaveChanges(); } } } diff --git a/Tests/Dfe.Academies.Tests.Common/Dfe.Academies.Tests.Common.csproj b/Tests/Dfe.Academies.Tests.Common/Dfe.Academies.Tests.Common.csproj index af009cb0..1ad273ce 100644 --- a/Tests/Dfe.Academies.Tests.Common/Dfe.Academies.Tests.Common.csproj +++ b/Tests/Dfe.Academies.Tests.Common/Dfe.Academies.Tests.Common.csproj @@ -20,7 +20,7 @@ - +