From 39895e45171c133c2c39e0af12b27efd35eb0091 Mon Sep 17 00:00:00 2001 From: Catherine Lawlor <55924986+CathLass@users.noreply.github.com> Date: Thu, 8 Aug 2024 11:19:23 +0100 Subject: [PATCH] Cl/handle search infrastructure exceptions (#35) * rename EstablishmentStatusCode * Models folder * remove NoResults status code * build warnings * PR comments --- .../CognitiveSearchServiceAdapter.cs | 5 ++ ...archResponseToEstablishmentResultMapper.cs | 9 ++- .../AzureSearchResultToAddressMapper.cs | 2 +- ...AzureSearchResultToEducationPhaseMapper.cs | 2 +- .../AzureSearchResultToEstablishmentMapper.cs | 12 +-- .../CognitiveSearchServiceAdapterTests.cs | 73 +++++++++++++------ ...esponseToEstablishmentResultMapperTests.cs | 8 +- ...eSearchResultToEstablishmentMapperTests.cs | 20 ++--- ...ResponseToSearchResultsMapperTestDouble.cs | 13 ++-- .../TestDoubles/EstablishmentTestDouble.cs | 4 +- .../SearchOptionsFactoryTestDouble.cs | 2 +- .../TestDoubles/SearchServiceTestDouble.cs | 2 +- .../EstablishmentTestExtensionMethods.cs | 2 +- .../ISearchServiceAdapter.cs | 4 +- .../{ => Models}/Address.cs | 2 +- .../{ => Models}/EducationPhase.cs | 2 +- .../{ => Models}/Establishment.cs | 8 +- .../{ => Models}/EstablishmentResults.cs | 2 +- .../Models/EstablishmentStatusCode.cs | 8 ++ .../ResultsToResponseMapper.cs | 9 ++- .../SearchByKeywordResponse.cs | 17 ++++- .../SearchByKeywordUseCase.cs | 24 ++++-- .../SearchResponseStatus.cs | 8 ++ .../SearchForEstablishments/StatusCode.cs | 8 -- .../ResultsToResponseMapperTests.cs | 17 ++++- .../SearchByKeywordUseCaseTests.cs | 63 ++++++++++++---- .../EstablishmentResultsTestDouble.cs | 2 +- .../TestDoubles/EstablishmentTestDouble.cs | 6 +- .../SearchServiceAdapterTestDouble.cs | 1 + 29 files changed, 231 insertions(+), 104 deletions(-) rename Dfe.Data.SearchPrototype/SearchForEstablishments/{ => Models}/Address.cs (95%) rename Dfe.Data.SearchPrototype/SearchForEstablishments/{ => Models}/EducationPhase.cs (96%) rename Dfe.Data.SearchPrototype/SearchForEstablishments/{ => Models}/Establishment.cs (85%) rename Dfe.Data.SearchPrototype/SearchForEstablishments/{ => Models}/EstablishmentResults.cs (92%) create mode 100644 Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentStatusCode.cs create mode 100644 Dfe.Data.SearchPrototype/SearchForEstablishments/SearchResponseStatus.cs delete mode 100644 Dfe.Data.SearchPrototype/SearchForEstablishments/StatusCode.cs diff --git a/Dfe.Data.SearchPrototype/Infrastructure/CognitiveSearchServiceAdapter.cs b/Dfe.Data.SearchPrototype/Infrastructure/CognitiveSearchServiceAdapter.cs index 70aef74..720bdbf 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/CognitiveSearchServiceAdapter.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/CognitiveSearchServiceAdapter.cs @@ -5,6 +5,7 @@ using Dfe.Data.SearchPrototype.Common.Mappers; using Dfe.Data.SearchPrototype.Infrastructure.Options; using Dfe.Data.SearchPrototype.SearchForEstablishments; +using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; namespace Dfe.Data.SearchPrototype.Infrastructure; @@ -57,6 +58,10 @@ public CognitiveSearchServiceAdapter( /// is unrecoverable, or no azure search results are returned which should never be the /// case given no matches should return an empty wrapper result object. /// + /// + /// Exception thrown if the data cannot be mapped + /// + public async Task SearchAsync(SearchContext searchContext) { SearchOptions searchOptions = diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureSearchResponseToEstablishmentResultMapper.cs b/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureSearchResponseToEstablishmentResultMapper.cs index b2230df..48627d1 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureSearchResponseToEstablishmentResultMapper.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureSearchResponseToEstablishmentResultMapper.cs @@ -1,7 +1,7 @@ using Azure; using Azure.Search.Documents.Models; using Dfe.Data.SearchPrototype.Common.Mappers; -using Dfe.Data.SearchPrototype.SearchForEstablishments; +using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; namespace Dfe.Data.SearchPrototype.Infrastructure.Mappers; @@ -11,7 +11,7 @@ namespace Dfe.Data.SearchPrototype.Infrastructure.Mappers; /// public sealed class AzureSearchResponseToEstablishmentResultMapper : IMapper>, EstablishmentResults> { - private readonly IMapper _azureSearchResultToEstablishmentMapper; + private readonly IMapper _azureSearchResultToEstablishmentMapper; /// /// The following mapping dependency provides the functionality to map from a raw Azure @@ -21,7 +21,7 @@ public sealed class AzureSearchResponseToEstablishmentResultMapper : IMapper /// Mapper used to map from the raw Azure search result to a T:Dfe.Data.SearchPrototype.Search.Establishment instance. /// - public AzureSearchResponseToEstablishmentResultMapper(IMapper azureSearchResultToEstablishmentMapper) + public AzureSearchResponseToEstablishmentResultMapper(IMapper azureSearchResultToEstablishmentMapper) { _azureSearchResultToEstablishmentMapper = azureSearchResultToEstablishmentMapper; } @@ -40,6 +40,9 @@ public AzureSearchResponseToEstablishmentResultMapper(IMapper /// Exception thrown if an invalid document is derived from the Azure search result. /// + /// + /// Exception thrown if the data cannot be mapped + /// public EstablishmentResults MapFrom(Response> input) { ArgumentNullException.ThrowIfNull(input); diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureSearchResultToAddressMapper.cs b/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureSearchResultToAddressMapper.cs index 28a37dd..1303179 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureSearchResultToAddressMapper.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureSearchResultToAddressMapper.cs @@ -1,5 +1,5 @@ using Dfe.Data.SearchPrototype.Common.Mappers; -using Dfe.Data.SearchPrototype.SearchForEstablishments; +using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; namespace Dfe.Data.SearchPrototype.Infrastructure.Mappers; diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureSearchResultToEducationPhaseMapper.cs b/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureSearchResultToEducationPhaseMapper.cs index 1fbe1a1..3b97e04 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureSearchResultToEducationPhaseMapper.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureSearchResultToEducationPhaseMapper.cs @@ -1,5 +1,5 @@ using Dfe.Data.SearchPrototype.Common.Mappers; -using Dfe.Data.SearchPrototype.SearchForEstablishments; +using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; namespace Dfe.Data.SearchPrototype.Infrastructure.Mappers; diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureSearchResultToEstablishmentMapper.cs b/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureSearchResultToEstablishmentMapper.cs index 1ea09fa..c8cc437 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureSearchResultToEstablishmentMapper.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureSearchResultToEstablishmentMapper.cs @@ -1,5 +1,5 @@ using Dfe.Data.SearchPrototype.Common.Mappers; -using Dfe.Data.SearchPrototype.SearchForEstablishments; +using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; namespace Dfe.Data.SearchPrototype.Infrastructure.Mappers; @@ -7,7 +7,7 @@ namespace Dfe.Data.SearchPrototype.Infrastructure.Mappers; /// Facilitates mapping from the received T:Dfe.Data.SearchPrototype.Infrastructure.Establishment /// into the required T:Dfe.Data.SearchPrototype.SearchForEstablishments.Establishment object. /// -public sealed class AzureSearchResultToEstablishmentMapper : IMapper +public sealed class AzureSearchResultToEstablishmentMapper : IMapper { private readonly IMapper _addressMapper; private readonly IMapper _educationPhaseMapper; @@ -39,17 +39,17 @@ public AzureSearchResultToEstablishmentMapper( /// /// Exception thrown if the id, name, or type of an establishment is not provided /// - public SearchForEstablishments.Establishment MapFrom(Establishment input) + public SearchForEstablishments.Models.Establishment MapFrom(Establishment input) { ArgumentException.ThrowIfNullOrEmpty(input.id, nameof(input.id)); ArgumentException.ThrowIfNullOrEmpty(input.ESTABLISHMENTNAME, nameof(input.ESTABLISHMENTNAME)); ArgumentException.ThrowIfNullOrEmpty(input.TYPEOFESTABLISHMENTNAME, nameof(input.ESTABLISHMENTNAME)); var statusCode = input.ESTABLISHMENTSTATUSCODE == "1" - ? StatusCode.Open + ? EstablishmentStatusCode.Open : input.ESTABLISHMENTSTATUSCODE == "0" - ? StatusCode.Closed - : StatusCode.Unknown; + ? EstablishmentStatusCode.Closed + : EstablishmentStatusCode.Unknown; return new( urn: input.id, diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs index 6671aaf..a594a0a 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs @@ -5,6 +5,7 @@ using Dfe.Data.SearchPrototype.Infrastructure.Options; using Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles; using Dfe.Data.SearchPrototype.SearchForEstablishments; +using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; using FluentAssertions; using Moq; using Xunit; @@ -21,12 +22,12 @@ IMapper>, EstablishmentResults> searchResp new(searchByKeywordService, searchOptionsFactory, searchResponseMapper); [Fact] - public async Task Search_With_Valid_SearchContext_Returns_Configured_Results() + public async Task Search_WithValidSearchContext_ReturnsConfiguredResults() { // arrange - var mockSearchOptionsFactory = SearchOptionsFactoryTestDouble.MockSearchOptionsFactory(); var mockService = SearchServiceTestDouble.MockSearchService("SearchKeyword", "TargetCollection"); - var mockMapper = AzureSearchResponseToSearchResultsMapperTestDouble.MockDefaultMapper(); + var mockSearchOptionsFactory = SearchOptionsFactoryTestDouble.MockSearchOptionsFactory(); + var mockMapper = AzureSearchResponseToSearchResultsMapperTestDouble.MockFor(new EstablishmentResults()); ISearchServiceAdapter cognitiveSearchServiceAdapter = CreateServiceAdapterWith( @@ -49,14 +50,18 @@ await cognitiveSearchServiceAdapter.SearchAsync( } [Fact] - public Task Search_With_Valid_SearchContext_No_Options_Returned_Throws_ApplicationException() + public Task Search_WithNoSearchOptions_ThrowsApplicationException() { + var mockService = SearchServiceTestDouble.MockSearchService("SearchKeyword", "TargetCollection"); + var mockSearchOptionsFactory = SearchOptionsFactoryTestDouble.MockForNoOptions(); + var mockMapper = AzureSearchResponseToSearchResultsMapperTestDouble.MockFor(new EstablishmentResults()); + // arrange ISearchServiceAdapter cognitiveSearchServiceAdapter = CreateServiceAdapterWith( - SearchServiceTestDouble.MockSearchService("SearchKeyword", "TargetCollection"), - SearchOptionsFactoryTestDouble.MockForDefaultResult(), - AzureSearchResponseToSearchResultsMapperTestDouble.MockDefaultMapper()); + mockService, + mockSearchOptionsFactory, + mockMapper); // act. return cognitiveSearchServiceAdapter @@ -65,30 +70,54 @@ await serviceAdapter.SearchAsync( new SearchContext( searchKeyword: "SearchKeyword", targetCollection: "TargetCollection"))) - .Should() - .ThrowAsync() - .WithMessage("Search options cannot be derived for TargetCollection."); + .Should() + .ThrowAsync() + .WithMessage("Search options cannot be derived for TargetCollection."); } [Fact] - public Task Search_With_Valid_SearchContext_No_Results_Returned_Throws_ApplicationException() + public async Task Search_WithNoResultsReturned_ReturnsEmptyResults() { // arrange + var mockService = SearchServiceTestDouble.MockSearchService("SearchKeyword", "TargetCollection"); + var mockSearchOptionsFactory = SearchOptionsFactoryTestDouble.MockSearchOptionsFactory(); + var mockMapper = AzureSearchResponseToSearchResultsMapperTestDouble.MockFor(new EstablishmentResults()); + ISearchServiceAdapter cognitiveSearchServiceAdapter = CreateServiceAdapterWith( - SearchServiceTestDouble.DefaultMock(), - SearchOptionsFactoryTestDouble.MockSearchOptionsFactory(), - AzureSearchResponseToSearchResultsMapperTestDouble.MockDefaultMapper()); + mockService, + mockSearchOptionsFactory, + mockMapper); // act. + var response = await cognitiveSearchServiceAdapter.SearchAsync(new SearchContext( + searchKeyword: "SearchKeyword", + targetCollection: "TargetCollection")); + + // assert + response.Establishments.Should().BeEmpty(); + } + + [Fact] + public Task Search_MapperThrowsException_ExceptionPassesThrough() + { + // arrange + var mockService = SearchServiceTestDouble.MockSearchService("SearchKeyword", "TargetCollection"); + var mockSearchOptionsFactory = SearchOptionsFactoryTestDouble.MockSearchOptionsFactory(); + var mockMapper = AzureSearchResponseToSearchResultsMapperTestDouble.MockMapperThrowingArgumentException(); + + ISearchServiceAdapter cognitiveSearchServiceAdapter = + CreateServiceAdapterWith( + mockService, + mockSearchOptionsFactory, + mockMapper); + + // act, assert. return cognitiveSearchServiceAdapter - .Invoking(async serviceAdapter => - await serviceAdapter.SearchAsync( - new SearchContext( - searchKeyword: "SearchKeyword", - targetCollection: "TargetCollection"))) - .Should() - .ThrowAsync() - .WithMessage("Unable to derive search results based on input SearchKeyword."); + .Invoking(adapter => adapter.SearchAsync(new SearchContext( + searchKeyword: "SearchKeyword", + targetCollection: "TargetCollection"))) + .Should() + .ThrowAsync< ArgumentException>(); } } diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/AzureSearchResponseToEstablishmentResultMapperTests.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/AzureSearchResponseToEstablishmentResultMapperTests.cs index 074ec06..e4b3676 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/AzureSearchResponseToEstablishmentResultMapperTests.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/AzureSearchResponseToEstablishmentResultMapperTests.cs @@ -4,7 +4,7 @@ using Dfe.Data.SearchPrototype.Infrastructure.Mappers; using Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles; using Dfe.Data.SearchPrototype.Infrastructure.Tests.TestHelpers; -using Dfe.Data.SearchPrototype.SearchForEstablishments; +using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; using FluentAssertions; using Xunit; @@ -12,10 +12,10 @@ namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.Mappers; public sealed class AzureSearchResponseToEstablishmentResultMapperTests { - IMapper _searchResultToEstablishmentMapper; + IMapper _searchResultToEstablishmentMapper; IMapper>, EstablishmentResults> _searchResponseMapper; - IMapper _searchResultToAddressMapper; - IMapper _searchResultToEducationPhaseMapper; + IMapper _searchResultToAddressMapper; + IMapper _searchResultToEducationPhaseMapper; public AzureSearchResponseToEstablishmentResultMapperTests() { diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/AzureSearchResultToEstablishmentMapperTests.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/AzureSearchResultToEstablishmentMapperTests.cs index a16c00f..312e792 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/AzureSearchResultToEstablishmentMapperTests.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/AzureSearchResultToEstablishmentMapperTests.cs @@ -1,7 +1,7 @@ using Dfe.Data.SearchPrototype.Common.Mappers; using Dfe.Data.SearchPrototype.Infrastructure.Mappers; using Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles; -using Dfe.Data.SearchPrototype.SearchForEstablishments; +using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; using FluentAssertions; using Xunit; @@ -9,9 +9,9 @@ namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.Mappers; public sealed class AzureSearchResultToEstablishmentMapperTests { - IMapper _establishmentMapper; - IMapper _addressMapper; - IMapper _educationPhaseMapper; + IMapper _establishmentMapper; + IMapper _addressMapper; + IMapper _educationPhaseMapper; public AzureSearchResultToEstablishmentMapperTests() { @@ -21,16 +21,16 @@ public AzureSearchResultToEstablishmentMapperTests() } [Theory] - [InlineData("1", StatusCode.Open)] - [InlineData("0", StatusCode.Closed)] - [InlineData("", StatusCode.Unknown)] - public void MapFrom_With_Valid_Search_Result_Returns_Configured_Establishment(string statusCode, StatusCode expectedStatusCode) + [InlineData("1", EstablishmentStatusCode.Open)] + [InlineData("0", EstablishmentStatusCode.Closed)] + [InlineData("", EstablishmentStatusCode.Unknown)] + public void MapFrom_With_Valid_Search_Result_Returns_Configured_Establishment(string statusCode, EstablishmentStatusCode expectedStatusCode) { // arrange Establishment establishmentFake = EstablishmentTestDouble.CreateWithStatusCode(statusCode); // act - SearchForEstablishments.Establishment? result = _establishmentMapper.MapFrom(establishmentFake); + SearchForEstablishments.Models.Establishment? result = _establishmentMapper.MapFrom(establishmentFake); // assert result.Should().NotBeNull(); @@ -150,7 +150,7 @@ public void MapFrom_With_NullAddressValues_Returns_Configured_Establishment( }; // act - SearchForEstablishments.Establishment? result = _establishmentMapper.MapFrom(establishmentFake); + SearchForEstablishments.Models.Establishment? result = _establishmentMapper.MapFrom(establishmentFake); // assert result.Should().NotBeNull(); diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/AzureSearchResponseToSearchResultsMapperTestDouble.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/AzureSearchResponseToSearchResultsMapperTestDouble.cs index e49239d..1d28f33 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/AzureSearchResponseToSearchResultsMapperTestDouble.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/AzureSearchResponseToSearchResultsMapperTestDouble.cs @@ -1,7 +1,7 @@ using Azure; using Azure.Search.Documents.Models; using Dfe.Data.SearchPrototype.Common.Mappers; -using Dfe.Data.SearchPrototype.SearchForEstablishments; +using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; using Moq; using System.Linq.Expressions; @@ -23,12 +23,13 @@ public static IMapper>, EstablishmentResul return mapperMock.Object; } - public static IMapper>, EstablishmentResults> MockDefaultMapper() + public static IMapper>, EstablishmentResults> MockMapperThrowingArgumentException() { - var mockMapper = new Mock>, EstablishmentResults>>(); - mockMapper.Setup(mapper => mapper.MapFrom(It.IsAny>>())) - .Returns(new EstablishmentResults()); - return mockMapper.Object; + var mapperMock = new Mock>, EstablishmentResults>>(); + + mapperMock.Setup(MapFrom()).Throws(new ArgumentException()); + + return mapperMock.Object; } internal static class EstablishmentFakes diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/EstablishmentTestDouble.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/EstablishmentTestDouble.cs index d298b03..5dcd59b 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/EstablishmentTestDouble.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/EstablishmentTestDouble.cs @@ -1,6 +1,4 @@ -using Bogus; -using Dfe.Data.SearchPrototype.SearchForEstablishments; -using System.IO; +using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles; diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchOptionsFactoryTestDouble.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchOptionsFactoryTestDouble.cs index e175d34..a98e8be 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchOptionsFactoryTestDouble.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchOptionsFactoryTestDouble.cs @@ -23,7 +23,7 @@ public static ISearchOptionsFactory MockFor(SearchOptions searchOptions) public static ISearchOptionsFactory MockSearchOptionsFactory() => MockFor(SearchOptionsFake); - public static ISearchOptionsFactory MockForDefaultResult() => MockFor(default!); + public static ISearchOptionsFactory MockForNoOptions() => MockFor(default!); public static SearchOptions SearchOptionsFake => new() { SearchMode = SearchMode.Any, diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchServiceTestDouble.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchServiceTestDouble.cs index 3e2da9b..ee379c4 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchServiceTestDouble.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchServiceTestDouble.cs @@ -41,6 +41,6 @@ public static ISearchByKeywordService MockForDefaultResult() var validServiceResponseFake = Task.FromResult>>(default!); - return MockFor(validServiceResponseFake, string.Empty, string.Empty); + return MockFor(validServiceResponseFake, It.IsAny(), It.IsAny()); } } diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestHelpers/EstablishmentTestExtensionMethods.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestHelpers/EstablishmentTestExtensionMethods.cs index 4e51b23..52a9724 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestHelpers/EstablishmentTestExtensionMethods.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestHelpers/EstablishmentTestExtensionMethods.cs @@ -1,5 +1,5 @@ using Azure.Search.Documents.Models; -using Dfe.Data.SearchPrototype.SearchForEstablishments; +using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; using FluentAssertions; namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.TestHelpers; diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/ISearchServiceAdapter.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/ISearchServiceAdapter.cs index 808114d..74c7417 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/ISearchServiceAdapter.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/ISearchServiceAdapter.cs @@ -1,4 +1,6 @@ -namespace Dfe.Data.SearchPrototype.SearchForEstablishments; +using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; + +namespace Dfe.Data.SearchPrototype.SearchForEstablishments; /// /// Describes behaviour for an adaption of core search services infrastructure to allow diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/Address.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/Address.cs similarity index 95% rename from Dfe.Data.SearchPrototype/SearchForEstablishments/Address.cs rename to Dfe.Data.SearchPrototype/SearchForEstablishments/Models/Address.cs index cd95f91..a5c3fa0 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/Address.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/Address.cs @@ -1,4 +1,4 @@ -namespace Dfe.Data.SearchPrototype.SearchForEstablishments; +namespace Dfe.Data.SearchPrototype.SearchForEstablishments.Models; public class Address { diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/EducationPhase.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EducationPhase.cs similarity index 96% rename from Dfe.Data.SearchPrototype/SearchForEstablishments/EducationPhase.cs rename to Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EducationPhase.cs index 26b1a22..4849574 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/EducationPhase.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EducationPhase.cs @@ -1,4 +1,4 @@ -namespace Dfe.Data.SearchPrototype.SearchForEstablishments; +namespace Dfe.Data.SearchPrototype.SearchForEstablishments.Models; /// /// Object used to encapsulate the education phase of the retrieved establishment. diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/Establishment.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/Establishment.cs similarity index 85% rename from Dfe.Data.SearchPrototype/SearchForEstablishments/Establishment.cs rename to Dfe.Data.SearchPrototype/SearchForEstablishments/Models/Establishment.cs index 11658e1..16db28f 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/Establishment.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/Establishment.cs @@ -1,4 +1,4 @@ -namespace Dfe.Data.SearchPrototype.SearchForEstablishments; +namespace Dfe.Data.SearchPrototype.SearchForEstablishments.Models; /// /// Object used to encapsulate the establishment search result. @@ -20,12 +20,12 @@ public class Establishment /// /// The read-only type of the establishment. /// - public string EstablishmentType { get; } + public string EstablishmentType { get; } public EducationPhase EducationPhase { get; } /// /// The read-only status of the establishment. /// - public StatusCode EstablishmentStatusCode { get; } + public EstablishmentStatusCode EstablishmentStatusCode { get; } /// /// Establishes an immutable establishment instance via the constructor arguments specified. /// @@ -44,7 +44,7 @@ public class Establishment /// /// /// The status of the given establishment. /// - public Establishment(string urn, string name, Address address, string establishmentType, EducationPhase educationPhase, StatusCode establishmentStatusCode) + public Establishment(string urn, string name, Address address, string establishmentType, EducationPhase educationPhase, EstablishmentStatusCode establishmentStatusCode) { Urn = urn; Name = name; diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/EstablishmentResults.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentResults.cs similarity index 92% rename from Dfe.Data.SearchPrototype/SearchForEstablishments/EstablishmentResults.cs rename to Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentResults.cs index 59022ff..dfe7a36 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/EstablishmentResults.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentResults.cs @@ -1,4 +1,4 @@ -namespace Dfe.Data.SearchPrototype.SearchForEstablishments; +namespace Dfe.Data.SearchPrototype.SearchForEstablishments.Models; /// /// Object used to encapsulate the aggregation of establishment search results. diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentStatusCode.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentStatusCode.cs new file mode 100644 index 0000000..adb76d5 --- /dev/null +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentStatusCode.cs @@ -0,0 +1,8 @@ +namespace Dfe.Data.SearchPrototype.SearchForEstablishments.Models; + +public enum EstablishmentStatusCode +{ + Closed = 0, + Open = 1, + Unknown +} \ No newline at end of file diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/ResultsToResponseMapper.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/ResultsToResponseMapper.cs index 0892d6c..1a4a61d 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/ResultsToResponseMapper.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/ResultsToResponseMapper.cs @@ -1,4 +1,5 @@ using Dfe.Data.SearchPrototype.Common.Mappers; +using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; namespace Dfe.Data.SearchPrototype.SearchForEstablishments; @@ -21,8 +22,10 @@ public class ResultsToResponseMapper : IMapper public SearchByKeywordResponse MapFrom(EstablishmentResults input) { - SearchByKeywordResponse response = new(input.Establishments); - - return response; + if(input == null) + { + return new() { Status = SearchResponseStatus.SearchServiceError }; + } + else return new(input.Establishments) { Status = SearchResponseStatus.Success }; } } diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchByKeywordResponse.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchByKeywordResponse.cs index 72ae7d7..369f384 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchByKeywordResponse.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchByKeywordResponse.cs @@ -1,4 +1,6 @@ -namespace Dfe.Data.SearchPrototype.SearchForEstablishments; +using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; + +namespace Dfe.Data.SearchPrototype.SearchForEstablishments; /// /// This is the object that carries the response (output) back from the @@ -10,7 +12,16 @@ public sealed class SearchByKeywordResponse /// /// The readonly collection of T:Dfe.Data.SearchPrototype.Search.Establishment search results. /// - public IReadOnlyCollection? EstablishmentResults { get;} + public IReadOnlyCollection EstablishmentResults { get;} + public SearchResponseStatus Status { get; set; } + + /// + /// Default constructor + /// + public SearchByKeywordResponse() + { + EstablishmentResults = new List(); + } /// /// The following argument is passed via the constructor and is not changeable @@ -19,7 +30,7 @@ public sealed class SearchByKeywordResponse /// /// The readonly collection of T:Dfe.Data.SearchPrototype.Search.Establishment search results. /// - public SearchByKeywordResponse(IReadOnlyCollection? establishments) + public SearchByKeywordResponse(IReadOnlyCollection establishments) { EstablishmentResults = establishments; } diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchByKeywordUseCase.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchByKeywordUseCase.cs index 7a52e31..bd81c5a 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchByKeywordUseCase.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchByKeywordUseCase.cs @@ -1,5 +1,6 @@ using Dfe.Data.SearchPrototype.Common.Mappers; using Dfe.Data.SearchPrototype.Common.CleanArchitecture.Application.UseCase; +using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; namespace Dfe.Data.SearchPrototype.SearchForEstablishments; @@ -47,11 +48,24 @@ public SearchByKeywordUseCase( /// public async Task HandleRequest(SearchByKeywordRequest request) { - ArgumentNullException.ThrowIfNull(request, nameof(SearchByKeywordRequest)); - ArgumentNullException.ThrowIfNull(request.Context, nameof(SearchContext)); + if ((request == null) || (request.Context == null)) { + return new SearchByKeywordResponse() + { + Status = SearchResponseStatus.InvalidRequest + }; + }; - EstablishmentResults establishmentResults = await _searchServiceAdapter.SearchAsync(request.Context); - - return _resultsToResponseMapper.MapFrom(establishmentResults); + try + { + EstablishmentResults establishmentResults = await _searchServiceAdapter.SearchAsync(request.Context); + return _resultsToResponseMapper.MapFrom(establishmentResults); + } + catch (Exception) // something went wrong in the infrastructure + { + return new SearchByKeywordResponse() + { + Status = SearchResponseStatus.SearchServiceError + }; + } } } diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchResponseStatus.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchResponseStatus.cs new file mode 100644 index 0000000..623f2f0 --- /dev/null +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchResponseStatus.cs @@ -0,0 +1,8 @@ +namespace Dfe.Data.SearchPrototype.SearchForEstablishments; + +public enum SearchResponseStatus +{ + Success, + InvalidRequest, + SearchServiceError +} diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/StatusCode.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/StatusCode.cs deleted file mode 100644 index d4bce65..0000000 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/StatusCode.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Dfe.Data.SearchPrototype.SearchForEstablishments; - -public enum StatusCode -{ - Closed = 0, - Open = 1, - Unknown -} \ No newline at end of file diff --git a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ResultsToResponseMapperTests.cs b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ResultsToResponseMapperTests.cs index db8762e..efbe368 100644 --- a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ResultsToResponseMapperTests.cs +++ b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ResultsToResponseMapperTests.cs @@ -1,5 +1,6 @@ using Dfe.Data.SearchPrototype.Common.Mappers; using Dfe.Data.SearchPrototype.SearchForEstablishments; +using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; using Dfe.Data.SearchPrototype.Tests.SearchForEstablishments.TestDoubles; using FluentAssertions; using Xunit; @@ -13,15 +14,29 @@ public void MapFrom_ValidInput_ReturnsCorrectResponse() { // arrange. EstablishmentResults input = EstablishmentResultsTestDouble.Create(); + IMapper mapper = new ResultsToResponseMapper(); // act. - IMapper mapper = new ResultsToResponseMapper(); SearchByKeywordResponse response = mapper.MapFrom(input); //assert. response.Should().NotBeNull(); + response.Status.Should().Be(SearchResponseStatus.Success); response.EstablishmentResults.Should().HaveCountGreaterThanOrEqualTo(1); response.EstablishmentResults!.First().Urn.Should().Be(input.Establishments.First().Urn); response.EstablishmentResults!.First().Name.Should().Be(input.Establishments.First().Name); } + + [Fact] + public void MapFrom_NullInput_ReturnsErrorResponse() + { + // arrange. + IMapper mapper = new ResultsToResponseMapper(); + + // act + SearchByKeywordResponse response = mapper.MapFrom(null!); + + // assert + response.Status.Should().Be(SearchResponseStatus.SearchServiceError); + } } diff --git a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/SearchByKeywordUseCaseTests.cs b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/SearchByKeywordUseCaseTests.cs index a8bf26b..bf22945 100644 --- a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/SearchByKeywordUseCaseTests.cs +++ b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/SearchByKeywordUseCaseTests.cs @@ -1,7 +1,9 @@ using Dfe.Data.SearchPrototype.Common.Mappers; using Dfe.Data.SearchPrototype.SearchForEstablishments; +using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; using Dfe.Data.SearchPrototype.Tests.SearchForEstablishments.TestDoubles; using FluentAssertions; +using Moq; using Xunit; namespace Dfe.Data.SearchPrototype.Tests.SearchForEstablishments; @@ -9,20 +11,22 @@ namespace Dfe.Data.SearchPrototype.Tests.SearchForEstablishments; public sealed class SearchByKeywordUseCaseTests { private readonly SearchByKeywordUseCase _useCase; + private ISearchServiceAdapter _searchServiceAdapter; + private IMapper _mapper; public SearchByKeywordUseCaseTests() { // arrange - ISearchServiceAdapter searchServiceAdapter = + _searchServiceAdapter = SearchServiceAdapterTestDouble.MockFor( EstablishmentResultsTestDouble.Create()); - IMapper mapper = new ResultsToResponseMapper(); - _useCase = new(searchServiceAdapter, mapper); + _mapper = new ResultsToResponseMapper(); + _useCase = new(_searchServiceAdapter, _mapper); } [Fact] - public async Task UseCase_ValidRequest_ReturnsResponse() + public async Task HandleRequest_ValidRequest_ReturnsResponse() { // arrange SearchByKeywordRequest request = new("searchkeyword", "target collection"); @@ -31,18 +35,51 @@ public async Task UseCase_ValidRequest_ReturnsResponse() SearchByKeywordResponse response = await _useCase.HandleRequest(request); // assert - response.Should().NotBeNull(); + response.Status.Should().Be(SearchResponseStatus.Success); + } + + [Fact] + public async Task HandleRequest_NullSearchByKeywordRequest_ReturnsErrorStatus() + { + // act + var response = await _useCase.HandleRequest(request: null!); + + // assert + response.Status.Should() + .Be(SearchResponseStatus.InvalidRequest); + } + + [Fact] + public async Task HandleRequest_ServiceAdapterThrowsException_ReturnsErrorStatus() + { + // arrange + SearchByKeywordRequest request = new("searchkeyword", "target collection"); + Mock.Get(_searchServiceAdapter) + .Setup(adapter => adapter.SearchAsync(It.IsAny())) + .ThrowsAsync(new ApplicationException()); + + // act + var response = await _useCase.HandleRequest(request); + + // assert + response.Status.Should() + .Be(SearchResponseStatus.SearchServiceError); } [Fact] - public Task UseCase_NullSearchByKeywordRequest_ThrowsArgumentNullException() + public async Task HandleRequest_NoResults_ReturnsSuccess() { - // act, assert - return _useCase.Invoking( - async usecase => await usecase - .HandleRequest(request: null!)) - .Should() - .ThrowAsync() - .WithMessage("Value cannot be null. (Parameter 'SearchByKeywordRequest')"); + // arrange + SearchByKeywordRequest request = new("searchkeyword", "target collection"); + Mock.Get(_searchServiceAdapter) + .Setup(adapter => adapter.SearchAsync(It.IsAny())) + .ReturnsAsync(EstablishmentResultsTestDouble.CreateWithNoResults); + + // act + var response = await _useCase.HandleRequest(request); + + // assert + response.Status.Should() + .Be(SearchResponseStatus.Success); } } diff --git a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/EstablishmentResultsTestDouble.cs b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/EstablishmentResultsTestDouble.cs index 55ca2c2..a609df6 100644 --- a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/EstablishmentResultsTestDouble.cs +++ b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/EstablishmentResultsTestDouble.cs @@ -1,4 +1,4 @@ -using Dfe.Data.SearchPrototype.SearchForEstablishments; +using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; namespace Dfe.Data.SearchPrototype.Tests.SearchForEstablishments.TestDoubles; diff --git a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/EstablishmentTestDouble.cs b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/EstablishmentTestDouble.cs index 503f7b3..90b1ec0 100644 --- a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/EstablishmentTestDouble.cs +++ b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/EstablishmentTestDouble.cs @@ -1,5 +1,5 @@ using Bogus; -using Dfe.Data.SearchPrototype.SearchForEstablishments; +using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; using System.IO; namespace Dfe.Data.SearchPrototype.Tests.SearchForEstablishments.TestDoubles; @@ -30,8 +30,8 @@ private static string GetEstablishmentPostcodeFake() => private static string GetEstablishmentTypeFake() => new Faker().Random.Word(); - private static StatusCode GetEstablishmentStatusCodeFake() => - (StatusCode)new Faker().Random.Int(0, 2); + private static EstablishmentStatusCode GetEstablishmentStatusCodeFake() => + (EstablishmentStatusCode)new Faker().Random.Int(0, 2); private static string GetEstablishmentEducationPhaseFake() => new Faker().Random.Int(0, 1).ToString(); diff --git a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/SearchServiceAdapterTestDouble.cs b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/SearchServiceAdapterTestDouble.cs index ee8b422..d8a6d96 100644 --- a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/SearchServiceAdapterTestDouble.cs +++ b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/SearchServiceAdapterTestDouble.cs @@ -1,4 +1,5 @@ using Dfe.Data.SearchPrototype.SearchForEstablishments; +using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; using Moq; namespace Dfe.Data.SearchPrototype.Tests.SearchForEstablishments.TestDoubles;