diff --git a/Dfe.Data.SearchPrototype/Infrastructure/CognitiveSearchServiceAdapter.cs b/Dfe.Data.SearchPrototype/Infrastructure/CognitiveSearchServiceAdapter.cs index a800ef3..89b2619 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/CognitiveSearchServiceAdapter.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/CognitiveSearchServiceAdapter.cs @@ -1,12 +1,13 @@ using Azure; using Azure.Search.Documents; -using Azure.Search.Documents.Models; using Dfe.Data.Common.Infrastructure.CognitiveSearch.SearchByKeyword; using Dfe.Data.SearchPrototype.Common.Mappers; using Dfe.Data.SearchPrototype.Infrastructure.Options; using Dfe.Data.SearchPrototype.SearchForEstablishments; using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; +using AzureModels = Azure.Search.Documents.Models; + namespace Dfe.Data.SearchPrototype.Infrastructure; /// @@ -17,7 +18,8 @@ public sealed class CognitiveSearchServiceAdapter : ISearchServic { private readonly ISearchByKeywordService _searchByKeywordService; private readonly ISearchOptionsFactory _searchOptionsFactory; - private readonly IMapper>, EstablishmentResults> _searchResponseMapper; + private readonly IMapper>, EstablishmentResults> _searchResultMapper; + private readonly IMapper>, EstablishmentFacets> _facetsMapper; /// /// The following dependencies include the core cognitive search service definition, @@ -29,17 +31,22 @@ public sealed class CognitiveSearchServiceAdapter : ISearchServic /// /// Factory class definition for prescribing the requested search options (by collection context). /// - /// - /// Maps the raw azure search response to the required "T:Dfe.Data.SearchPrototype.Search.Domain.AgregateRoot.Establishments" + /// + /// Maps the raw Azure search response to the required + /// + /// + /// Maps the the raw Azure search response to the required /// public CognitiveSearchServiceAdapter( ISearchByKeywordService searchByKeywordService, ISearchOptionsFactory searchOptionsFactory, - IMapper>, EstablishmentResults> searchResponseMapper) + IMapper>, EstablishmentResults> searchResultMapper, + IMapper>, EstablishmentFacets> facetsMapper) { _searchOptionsFactory = searchOptionsFactory; _searchByKeywordService = searchByKeywordService; - _searchResponseMapper = searchResponseMapper; + _searchResultMapper = searchResultMapper; + _facetsMapper = facetsMapper; } /// @@ -62,14 +69,14 @@ public CognitiveSearchServiceAdapter( /// Exception thrown if the data cannot be mapped /// - public async Task SearchAsync(SearchContext searchContext) + public async Task SearchAsync(SearchContext searchContext) { SearchOptions searchOptions = _searchOptionsFactory.GetSearchOptions(searchContext.TargetCollection) ?? throw new ApplicationException( $"Search options cannot be derived for {searchContext.TargetCollection}."); - Response> searchResults = + Response> searchResults = await _searchByKeywordService.SearchAsync( searchContext.SearchKeyword, searchContext.TargetCollection, @@ -79,6 +86,14 @@ await _searchByKeywordService.SearchAsync( throw new ApplicationException( $"Unable to derive search results based on input {searchContext.SearchKeyword}."); - return _searchResponseMapper.MapFrom(searchResults.Value.GetResults()); + var results = new SearchResults() + { + Establishments = _searchResultMapper.MapFrom(searchResults.Value.GetResults()), + Facets = searchResults.Value.Facets != null + ? _facetsMapper.MapFrom(searchResults.Value.Facets.ToDictionary>()) + : null + }; + + return results; } } diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureFacetResultToEstablishmentFacetsMapper.cs b/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureFacetResultToEstablishmentFacetsMapper.cs new file mode 100644 index 0000000..080c5fd --- /dev/null +++ b/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureFacetResultToEstablishmentFacetsMapper.cs @@ -0,0 +1,33 @@ +using Dfe.Data.SearchPrototype.Common.Mappers; +using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; + +namespace Dfe.Data.SearchPrototype.Infrastructure.Mappers; + +using AzureFacetResult = Azure.Search.Documents.Models.FacetResult; + +/// +/// Maps from an Azure facet result to a collection of +/// T:Dfe.Data.SearchPrototype.SearchForEstablishments.Models.EstablishmentFacet +/// +public class AzureFacetResultToEstablishmentFacetsMapper : IMapper>, EstablishmentFacets> +{ + /// + /// Map from an Azure facet result to a collection of + /// T:Dfe.Data.SearchPrototype.SearchForEstablishments.Models.EstablishmentFacet + /// + /// The Azure facet result + /// + public EstablishmentFacets MapFrom(Dictionary> facetResult) + { + var establishmentFacets = new List(); + + foreach (var facetCategory in facetResult.Where(facet => facet.Value != null)) + { + var values = facetCategory.Value.Select(f => new FacetResult((string)f.Value, f.Count)).ToList(); + var establishmentFacet = new EstablishmentFacet(facetCategory.Key, values); + + establishmentFacets.Add(establishmentFacet); + } + return new EstablishmentFacets (establishmentFacets); + } +} diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureSearchResultToEstablishmentMapper.cs b/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureSearchResultToEstablishmentMapper.cs index 0732510..1bae09a 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureSearchResultToEstablishmentMapper.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureSearchResultToEstablishmentMapper.cs @@ -37,6 +37,7 @@ public AzureSearchResultToEstablishmentMapper( /// public SearchForEstablishments.Models.Establishment MapFrom(Establishment input) { + // TODO - only throw for really essential stuff ArgumentException.ThrowIfNullOrEmpty(input.id, nameof(input.id)); ArgumentException.ThrowIfNullOrEmpty(input.ESTABLISHMENTNAME, nameof(input.ESTABLISHMENTNAME)); ArgumentException.ThrowIfNullOrEmpty(input.TYPEOFESTABLISHMENTNAME, nameof(input.TYPEOFESTABLISHMENTNAME)); diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterAndMapperTests.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterAndMapperTests.cs new file mode 100644 index 0000000..30c37a5 --- /dev/null +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterAndMapperTests.cs @@ -0,0 +1,125 @@ +using Azure; +using Azure.Search.Documents.Models; +using Dfe.Data.SearchPrototype.Common.Mappers; +using Dfe.Data.SearchPrototype.Infrastructure.Mappers; +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 Xunit; + +namespace Dfe.Data.SearchPrototype.Infrastructure.Tests; + +public sealed class CognitiveSearchServiceAdapterAndMapperTests +{ + private ISearchOptionsFactory _mockSearchOptionsFactory; + private IMapper>, EstablishmentResults> _searchResponseMapper; + private IMapper>, EstablishmentFacets> _facetsMapper; + + public CognitiveSearchServiceAdapterAndMapperTests() + { + _mockSearchOptionsFactory = SearchOptionsFactoryTestDouble.MockSearchOptionsFactory(); + _searchResponseMapper = new PageableSearchResultsToEstablishmentResultsMapper( + new AzureSearchResultToEstablishmentMapper( + new AzureSearchResultToAddressMapper())); + _facetsMapper = new AzureFacetResultToEstablishmentFacetsMapper(); + } + + [Fact] + public async Task Search_WithValidSearchContext_ReturnsResults() + { + // arrange + var establishmentSearchResults = new SearchResultFakeBuilder() + .WithSearchResults() + .Create(); + var facetResults = new FacetsResultsFakeBuilder() + .WithAutoGeneratedFacets() + .Create(); + var mockService = new SearchServiceMockBuilder() + .WithSearchOptions("SearchKeyword", "TargetCollection") + .WithSearchResults(establishmentSearchResults) + .WithFacets(facetResults) + .Create(); + + ISearchServiceAdapter cognitiveSearchServiceAdapter = new CognitiveSearchServiceAdapter( + mockService, + _mockSearchOptionsFactory, + _searchResponseMapper, + _facetsMapper); + + // act + SearchResults? response = + await cognitiveSearchServiceAdapter.SearchAsync( + new SearchContext( + searchKeyword: "SearchKeyword", + targetCollection: "TargetCollection")); + + // assert + response.Should().NotBeNull(); + response.Establishments.Should().NotBeNull(); + response.Establishments!.Establishments.Count().Should().Be(establishmentSearchResults.Count); + response.Facets.Should().NotBeNull(); + response.Facets!.Facets.Count().Should().Be(facetResults.Count()); + } + + [Fact] + public async Task Search_WithNoFacetsReturned_ReturnsNullFacets() + { + // arrange + var mockService = new SearchServiceMockBuilder() + .WithSearchOptions("SearchKeyword", "TargetCollection") + .WithSearchResults( + new SearchResultFakeBuilder() + .WithSearchResults() + .Create()) + .Create(); + var mockSearchOptionsFactory = SearchOptionsFactoryTestDouble.MockSearchOptionsFactory(); + + ISearchServiceAdapter cognitiveSearchServiceAdapter = new CognitiveSearchServiceAdapter( + mockService, + mockSearchOptionsFactory, + _searchResponseMapper, + _facetsMapper); + + // act + SearchResults? response = + await cognitiveSearchServiceAdapter.SearchAsync( + new SearchContext( + searchKeyword: "SearchKeyword", + targetCollection: "TargetCollection")); + + // assert + response.Should().NotBeNull(); + response.Facets.Should().BeNull(); + } + + [Fact] + public async Task Search_WithNoResultsReturned_ReturnsEmptyResults() + { + // arrange + var mockService = new SearchServiceMockBuilder() + .WithSearchOptions("SearchKeyword", "TargetCollection") + .WithSearchResults( + new SearchResultFakeBuilder() + .WithEmptySearchResult() + .Create()) + .Create(); + + ISearchServiceAdapter cognitiveSearchServiceAdapter = new CognitiveSearchServiceAdapter( + mockService, + _mockSearchOptionsFactory, + _searchResponseMapper, + _facetsMapper); + + // act. + var response = await cognitiveSearchServiceAdapter.SearchAsync(new SearchContext( + searchKeyword: "SearchKeyword", + targetCollection: "TargetCollection")); + + // assert + response.Should().NotBeNull(); + response.Establishments.Should().NotBeNull(); + response.Establishments!.Establishments.Should().BeEmpty(); + } +} diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs index 65dd914..72f5656 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs @@ -7,7 +7,6 @@ using Dfe.Data.SearchPrototype.SearchForEstablishments; using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; using FluentAssertions; -using Moq; using Xunit; namespace Dfe.Data.SearchPrototype.Infrastructure.Tests; @@ -17,51 +16,27 @@ public sealed class CognitiveSearchServiceAdapterTests private static CognitiveSearchServiceAdapter CreateServiceAdapterWith( ISearchByKeywordService searchByKeywordService, ISearchOptionsFactory searchOptionsFactory, - IMapper>, EstablishmentResults> searchResponseMapper + IMapper>, EstablishmentResults> searchResponseMapper, + IMapper>, EstablishmentFacets> facetsMapper ) => - new(searchByKeywordService, searchOptionsFactory, searchResponseMapper); - - [Fact] - public async Task Search_WithValidSearchContext_ReturnsConfiguredResults() - { - // arrange - var mockService = SearchServiceTestDouble.MockSearchService("SearchKeyword", "TargetCollection"); - var mockSearchOptionsFactory = SearchOptionsFactoryTestDouble.MockSearchOptionsFactory(); - var mockMapper = PageableSearchResultsToEstablishmentResultsMapperTestDouble.MockFor(new EstablishmentResults()); - - ISearchServiceAdapter cognitiveSearchServiceAdapter = - CreateServiceAdapterWith( - mockService, - mockSearchOptionsFactory, - mockMapper); - - // act - EstablishmentResults? response = - await cognitiveSearchServiceAdapter.SearchAsync( - new SearchContext( - searchKeyword: "SearchKeyword", - targetCollection: "TargetCollection")); - - // assert - response.Establishments.Should().NotBeNull(); - Mock.Get(mockService).Verify(SearchServiceTestDouble.SearchRequest("SearchKeyword", "TargetCollection"),Times.Once()); - Mock.Get(mockSearchOptionsFactory).Verify(SearchOptionsFactoryTestDouble.SearchOption(), Times.Once()); - Mock.Get(mockMapper).Verify(PageableSearchResultsToEstablishmentResultsMapperTestDouble.MapFrom(), Times.Once()); - } + new(searchByKeywordService, searchOptionsFactory, searchResponseMapper, facetsMapper); [Fact] public Task Search_WithNoSearchOptions_ThrowsApplicationException() { - var mockService = SearchServiceTestDouble.MockSearchService("SearchKeyword", "TargetCollection"); + var mockServiceBuilder = new SearchServiceMockBuilder(); + var mockService = mockServiceBuilder.MockSearchService("SearchKeyword", "TargetCollection"); var mockSearchOptionsFactory = SearchOptionsFactoryTestDouble.MockForNoOptions(); - var mockMapper = PageableSearchResultsToEstablishmentResultsMapperTestDouble.MockFor(new EstablishmentResults()); + var mockEstablishmentResultsMapper = PageableSearchResultsToEstablishmentResultsMapperTestDouble.DefaultMock(); + var mockFacetsMapper = AzureFacetResultToEstablishmentFacetsMapperTestDouble.DefaultMock(); // arrange ISearchServiceAdapter cognitiveSearchServiceAdapter = CreateServiceAdapterWith( mockService, mockSearchOptionsFactory, - mockMapper); + mockEstablishmentResultsMapper, + mockFacetsMapper); // act. return cognitiveSearchServiceAdapter @@ -75,42 +50,21 @@ await serviceAdapter.SearchAsync( .WithMessage("Search options cannot be derived for TargetCollection."); } - [Fact] - public async Task Search_WithNoResultsReturned_ReturnsEmptyResults() - { - // arrange - var mockService = SearchServiceTestDouble.MockSearchService("SearchKeyword", "TargetCollection"); - var mockSearchOptionsFactory = SearchOptionsFactoryTestDouble.MockSearchOptionsFactory(); - var mockMapper = PageableSearchResultsToEstablishmentResultsMapperTestDouble.MockFor(new EstablishmentResults()); - - ISearchServiceAdapter cognitiveSearchServiceAdapter = - CreateServiceAdapterWith( - 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 mockService = new SearchServiceMockBuilder().MockSearchService("SearchKeyword", "TargetCollection"); var mockSearchOptionsFactory = SearchOptionsFactoryTestDouble.MockSearchOptionsFactory(); - var mockMapper = PageableSearchResultsToEstablishmentResultsMapperTestDouble.MockMapperThrowingArgumentException(); + var mockEstablishmentResultsMapper = PageableSearchResultsToEstablishmentResultsMapperTestDouble.MockMapperThrowingArgumentException(); + var mockFacetsMapper = AzureFacetResultToEstablishmentFacetsMapperTestDouble.DefaultMock(); ISearchServiceAdapter cognitiveSearchServiceAdapter = CreateServiceAdapterWith( mockService, mockSearchOptionsFactory, - mockMapper); + mockEstablishmentResultsMapper, + mockFacetsMapper); // act, assert. return cognitiveSearchServiceAdapter diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/AzureFacetResultToEstablishmentFacetsMapperTests.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/AzureFacetResultToEstablishmentFacetsMapperTests.cs new file mode 100644 index 0000000..6e0754d --- /dev/null +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/AzureFacetResultToEstablishmentFacetsMapperTests.cs @@ -0,0 +1,64 @@ +using Dfe.Data.SearchPrototype.Common.Mappers; +using Dfe.Data.SearchPrototype.Infrastructure.Mappers; +using Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles; +using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; +using FluentAssertions; +using Xunit; + +using AzureFacetResult = Azure.Search.Documents.Models.FacetResult; + +namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.Mappers; + +public sealed class AzureFacetResultToEstablishmentFacetsMapperTests +{ + AzureFacetResultToEstablishmentFacetsMapper _facetResultToFacetMapper = new(); + + [Fact] + public void MapFrom_WithStringFacetResults_ReturnsFacets() + { + // arrange + var azureFacetsResults = new FacetsResultsFakeBuilder() + .WithEducationPhaseFacet() + .WithAutoGeneratedFacet() + .Create(); + + // act + var mappedResult = _facetResultToFacetMapper.MapFrom(azureFacetsResults); + + // assert + mappedResult.Should().NotBeNull(); + mappedResult.Facets.Should().NotBeNullOrEmpty(); + foreach (var azureFacet in azureFacetsResults) + { + mappedResult.Facets.First(facet => facet.Name == azureFacet.Key).Results.Should().NotBeNullOrEmpty(); + foreach(var expectedFacet in azureFacetsResults) + { + var mappedFacet = mappedResult.Facets.Single(facet => facet.Name == expectedFacet.Key); + mappedFacet.Should().NotBeNull(); + foreach(var expectedFacetValue in expectedFacet.Value) + { + var mappedfacetValue = mappedFacet.Results.Single(facetValue => facetValue.Value == expectedFacetValue.Value.ToString()); + mappedFacet.Should().NotBeNull(); + mappedfacetValue.Count.Should().Be(expectedFacetValue.Count); + } + } + } + } + + [Fact] + public void MapFrom_WithNonStringFacetResults_ThrowsInvalidCastException() + { + // arrange + var azureFacetsResults = new FacetsResultsFakeBuilder() + .WithFacet(new List() { true, "string2"}) + .Create(); + + // act, assert + Action failedAction = + () => _facetResultToFacetMapper.MapFrom(azureFacetsResults); + + InvalidCastException exception = Assert.Throws(failedAction); + + exception.Message.Should().Be("Unable to cast object of type 'System.Boolean' to type 'System.String'."); + } +} diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/PageableSearchResultsToEstablishmentResultsMapperTests.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/PageableSearchResultsToEstablishmentResultsMapperTests.cs index 2340319..1f3a179 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/PageableSearchResultsToEstablishmentResultsMapperTests.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/PageableSearchResultsToEstablishmentResultsMapperTests.cs @@ -12,17 +12,14 @@ namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.Mappers; public sealed class PageableSearchResultsToEstablishmentResultsMapperTests { - IMapper _searchResultToEstablishmentMapper; IMapper>, EstablishmentResults> _searchResultsMapper; - IMapper _searchResultToAddressMapper; public PageableSearchResultsToEstablishmentResultsMapperTests() { - _searchResultToAddressMapper = new AzureSearchResultToAddressMapper(); - _searchResultToEstablishmentMapper = - new AzureSearchResultToEstablishmentMapper(_searchResultToAddressMapper); _searchResultsMapper = - new PageableSearchResultsToEstablishmentResultsMapper(_searchResultToEstablishmentMapper); + new PageableSearchResultsToEstablishmentResultsMapper( + new AzureSearchResultToEstablishmentMapper( + new AzureSearchResultToAddressMapper())); } [Fact] diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/AzureFacetResultToEstablishmentFacetsMapperTestDouble.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/AzureFacetResultToEstablishmentFacetsMapperTestDouble.cs new file mode 100644 index 0000000..8d1153f --- /dev/null +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/AzureFacetResultToEstablishmentFacetsMapperTestDouble.cs @@ -0,0 +1,34 @@ +using Dfe.Data.SearchPrototype.Common.Mappers; +using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; +using Moq; +using System.Linq.Expressions; +using AzureFacetResult = Azure.Search.Documents.Models.FacetResult; + +namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles; + +internal static class AzureFacetResultToEstablishmentFacetsMapperTestDouble +{ + public static IMapper>, EstablishmentFacets> DefaultMock() => + Mock.Of>, EstablishmentFacets>>(); + + public static Expression< Func >, EstablishmentFacets>, EstablishmentFacets>> MapFrom() => + mapper => mapper.MapFrom(It.IsAny>>()); + + public static IMapper>, EstablishmentFacets> MockFor(EstablishmentFacets establishments) + { + var mapperMock = new Mock>, EstablishmentFacets>>(); + + mapperMock.Setup(MapFrom()).Returns(establishments); + + return mapperMock.Object; + } + + public static IMapper>, EstablishmentFacets> MockMapperThrowingArgumentException() + { + var mapperMock = new Mock>, EstablishmentFacets>>(); + + mapperMock.Setup(MapFrom()).Throws(new ArgumentException()); + + return mapperMock.Object; + } +} diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/AzureSearchResponseTestDoubleBuilder.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/AzureSearchResponseTestDoubleBuilder.cs new file mode 100644 index 0000000..5d0caaf --- /dev/null +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/AzureSearchResponseTestDoubleBuilder.cs @@ -0,0 +1,31 @@ +using Azure.Search.Documents.Models; +using Azure; +using Moq; + +namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles; + +public class AzureSearchResponseTestDoubleBuilder +{ + private IEnumerable>? _searchResults; + private Dictionary>? _facetResults; + + public AzureSearchResponseTestDoubleBuilder WithSearchResults(IEnumerable>? searchResults) + { + _searchResults = searchResults; + return this; + } + + public AzureSearchResponseTestDoubleBuilder WithFacets(Dictionary>? facetResults) + { + _facetResults = facetResults; + return this; + } + + public Response> Create() + { + var responseMock = new Mock(); + return Response.FromValue( + SearchModelFactory.SearchResults( + _searchResults, 100, _facetResults, null, responseMock.Object), responseMock.Object); + } +} diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/FacetsResultsFakeBuilder.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/FacetsResultsFakeBuilder.cs new file mode 100644 index 0000000..44cb8ab --- /dev/null +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/FacetsResultsFakeBuilder.cs @@ -0,0 +1,105 @@ +using Azure.Search.Documents.Models; + +namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles; + +public class FacetsResultsFakeBuilder +{ + private Dictionary> _facets = new(); + + public FacetsResultsFakeBuilder WithEducationPhaseFacet() + { + var facetResults = new List(); + int resultsCountAnyNumber = new Bogus.Faker().Random.Int(1, 50); + var facetResult = new Dictionary() + { + ["value"] = "Primary" + }; + facetResults.Add(SearchModelFactory.FacetResult(resultsCountAnyNumber, facetResult)); + facetResult = new Dictionary() + { + ["value"] = "Secondary" + }; + facetResults.Add(SearchModelFactory.FacetResult(resultsCountAnyNumber, facetResult)); + facetResult = new Dictionary() + { + ["value"] = "Post16" + }; + facetResults.Add(SearchModelFactory.FacetResult(resultsCountAnyNumber, facetResult)); + Dictionary> facet = new() { ["EducationPhase"] = facetResults }; + + foreach(var kvp in facet) + { + _facets.Add(kvp.Key, kvp.Value); + } + return this; + } + + public FacetsResultsFakeBuilder WithAutoGeneratedFacets() + { + var facetsCount = new Bogus.Faker().Random.Int(1, 10); + for(int i=0; i(); + int resultsCountAnyNumber = new Bogus.Faker().Random.Int(1, 50); + var facetValuesCount = new Bogus.Faker().Random.Int(1, 10); + for(int i=0; i() + { + ["value"] = new Bogus.Faker().Name.JobTitle() + }; + facetResults.Add(SearchModelFactory.FacetResult(resultsCountAnyNumber, facetResult)); + } + + Dictionary> facet = new() { [new Bogus.Faker().Name.JobType()] = facetResults }; + + foreach (var kvp in facet) + { + _facets.Add(kvp.Key, kvp.Value); + } + return this; + } + + public FacetsResultsFakeBuilder WithFacet(List facetParams) + { + var facetResults = new List(); + int resultsCountAnyNumber = new Bogus.Faker().Random.Int(1, 50); + var facetValuesCount = new Bogus.Faker().Random.Int(1, 10); + foreach (var facetParam in facetParams) + { + var facetResult = new Dictionary() + { + ["value"] = facetParam + }; + facetResults.Add(SearchModelFactory.FacetResult(resultsCountAnyNumber, facetResult)); + } + + Dictionary> facet = new() { [new Bogus.Faker().Name.JobType()] = facetResults }; + + if (_facets == null) + { + _facets = new Dictionary>(); + } + foreach (var kvp in facet) + { + _facets.Add(kvp.Key, kvp.Value); + } + return this; + } + + public Dictionary> Create() + { + if (_facets == null) + { + throw new NullReferenceException("Facet fake has no facets set"); + } + return _facets; + } +} diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/PageableSearchResultsToEstablishmentResultsMapperTestDouble.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/PageableSearchResultsToEstablishmentResultsMapperTestDouble.cs index 0352935..0bc9eb7 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/PageableSearchResultsToEstablishmentResultsMapperTestDouble.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/PageableSearchResultsToEstablishmentResultsMapperTestDouble.cs @@ -32,16 +32,4 @@ public static IMapper>, EstablishmentResult return mapperMock.Object; } - - internal static class EstablishmentFakes - { - public static Establishment GetEstablishmentFake() => - new() { id = GetEstablishmentIdentifierFake(), ESTABLISHMENTNAME = GetEstablishmentNameFake() }; - - private static string GetEstablishmentNameFake() => - new Bogus.Faker().Company.CompanyName(); - - private static string GetEstablishmentIdentifierFake() => - new Bogus.Faker().Random.Int().ToString(); - } } diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchResultFakeBuilder.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchResultFakeBuilder.cs new file mode 100644 index 0000000..005a7af --- /dev/null +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchResultFakeBuilder.cs @@ -0,0 +1,52 @@ +using Azure.Search.Documents.Models; + +namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles; + +public class SearchResultFakeBuilder +{ + private List>? _establishmentSearchResults; + + public SearchResultFakeBuilder WithEmptySearchResult() + { + _establishmentSearchResults = new List>(); + return this; + } + + public SearchResultFakeBuilder WithSearchResults() + { + int amount = new Bogus.Faker().Random.Number(1, 10); + var searchResults = new List>(); + + for (int i = 0; i < amount; i++) + { + searchResults.Add( + SearchResultWithDocument( + EstablishmentTestDouble.Create() + )); + } + _establishmentSearchResults = searchResults; + return this; + } + + public SearchResultFakeBuilder IncludeNullDocument() + { + if(_establishmentSearchResults == null){ + _establishmentSearchResults= new List>(); + } + _establishmentSearchResults.Add(SearchModelFactory + .SearchResult( + null!, 1.00, new Dictionary>())); + return this; + } + + public static SearchResult SearchResultWithDocument(Establishment? document) => + SearchModelFactory + .SearchResult( + document!, 1.00, new Dictionary>()); + + public List> Create() + { + return _establishmentSearchResults ?? throw new NullReferenceException(); + } +} + diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchServiceMockBuilder.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchServiceMockBuilder.cs new file mode 100644 index 0000000..98b6c72 --- /dev/null +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchServiceMockBuilder.cs @@ -0,0 +1,74 @@ +using Azure; +using Azure.Search.Documents; +using Azure.Search.Documents.Models; +using Dfe.Data.Common.Infrastructure.CognitiveSearch.SearchByKeyword; +using Moq; +using System.Linq.Expressions; + +namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles; + +internal class SearchServiceMockBuilder +{ + public static ISearchByKeywordService DefaultMock() => Mock.Of(); + public static Expression>>>> SearchRequest(string keyword, string collection) => + searchService => searchService.SearchAsync(keyword, collection, It.IsAny()); + + private string _keyword = string.Empty; + private string _collection = string.Empty; + private long _count = 100; + private IEnumerable>? _searchResults; + private Dictionary>? _facets; + + public ISearchByKeywordService MockFor(Response> searchResult, string keyword, string collection) + { + var searchServiceMock = new Mock(); + + searchServiceMock.Setup(SearchRequest(keyword, collection)) + .Returns(Task.FromResult(searchResult)) + .Verifiable(); + + return searchServiceMock.Object; + } + + public SearchServiceMockBuilder WithSearchOptions(string keyword, string collection) + { + _keyword = keyword; + _collection = collection; + return this; + } + + public SearchServiceMockBuilder WithSearchResults(IEnumerable> results) + { + _searchResults = results; + return this; + } + + public SearchServiceMockBuilder WithFacets(Dictionary> facets) + { + _facets = facets; + return this; + } + + public ISearchByKeywordService Create() + { + var response = new AzureSearchResponseTestDoubleBuilder() + .WithSearchResults(_searchResults) + .WithFacets(_facets) + .Create(); + return MockFor(response, _keyword, _collection); + } + + public ISearchByKeywordService MockSearchService(string keyword, string collection) + { + _keyword = keyword; + _collection = collection; + + var responseMock = new Mock(); + var response = + Response.FromValue( + SearchModelFactory.SearchResults( + new SearchResultFakeBuilder().WithSearchResults().Create(), _count, null, null, responseMock.Object), responseMock.Object); + + return MockFor(response, _keyword, _collection); + } +} diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchServiceTestDouble.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchServiceTestDouble.cs deleted file mode 100644 index ee379c4..0000000 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchServiceTestDouble.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Azure; -using Azure.Search.Documents; -using Azure.Search.Documents.Models; -using Dfe.Data.Common.Infrastructure.CognitiveSearch.SearchByKeyword; -using Moq; -using System.Linq.Expressions; - -namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles; - -internal static class SearchServiceTestDouble -{ - public static ISearchByKeywordService DefaultMock() => Mock.Of(); - public static Expression>>>> SearchRequest(string keyword, string collection) => - searchService => searchService.SearchAsync(keyword, collection, It.IsAny()); - - public static ISearchByKeywordService MockFor(Task>> searchResult, string keyword, string collection) - { - var searchServiceMock = new Mock(); - - searchServiceMock.Setup(SearchRequest(keyword, collection)) - .Returns(searchResult); - - return searchServiceMock.Object; - } - - public static ISearchByKeywordService MockSearchService(string keyword, string collection) - { - var responseMock = new Mock(); - - var validServiceResponseFake = - Task.FromResult( - Response.FromValue( - SearchModelFactory.SearchResults( - SearchResultFake.SearchResults(), 100, null, null, responseMock.Object), responseMock.Object)); - - return MockFor(validServiceResponseFake, keyword, collection); - } - - public static ISearchByKeywordService MockForDefaultResult() - { - var validServiceResponseFake = - Task.FromResult>>(default!); - - return MockFor(validServiceResponseFake, It.IsAny(), It.IsAny()); - } -} diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/ISearchServiceAdapter.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/ISearchServiceAdapter.cs index 74c7417..3127cc1 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/ISearchServiceAdapter.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/ISearchServiceAdapter.cs @@ -19,5 +19,5 @@ public interface ISearchServiceAdapter /// A configured "T:Dfe.Data.SearchPrototype.Search.Domain.AgregateRoot.Establishments" /// object hydrated from the results of the azure search. /// - Task SearchAsync(SearchContext searchContext); + Task SearchAsync(SearchContext searchContext); } diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentFacets.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentFacets.cs new file mode 100644 index 0000000..3aab72e --- /dev/null +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentFacets.cs @@ -0,0 +1,31 @@ +namespace Dfe.Data.SearchPrototype.SearchForEstablishments.Models; + +/// +/// Object used to encapsulate the aggregation of facets returned from search. +/// +public class EstablishmentFacets +{ + /// + /// The readonly collection of facets from the search + /// + public IReadOnlyCollection Facets => _establishmentsFacets.AsReadOnly(); + + private readonly List _establishmentsFacets; + + /// + /// Default constuctor + /// + public EstablishmentFacets() + { + _establishmentsFacets = new(); + } + + /// + /// Constructor with the following parameters + /// + /// List of Establishments + public EstablishmentFacets(IEnumerable establishmentFacets) + { + _establishmentsFacets = establishmentFacets.ToList(); + } +} diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/SearchResults.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/SearchResults.cs new file mode 100644 index 0000000..e01e35c --- /dev/null +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/SearchResults.cs @@ -0,0 +1,16 @@ +namespace Dfe.Data.SearchPrototype.SearchForEstablishments.Models; + +/// +/// The search results +/// +public class SearchResults +{ + /// + /// The returned from the Establishment search + /// + public EstablishmentResults? Establishments { get; init; } + /// + /// The resturned from the Establishment search + /// + public EstablishmentFacets? Facets { get; init; } +} diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchByKeywordResponse.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchByKeywordResponse.cs index eee36ec..8b2cab2 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchByKeywordResponse.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchByKeywordResponse.cs @@ -9,27 +9,28 @@ namespace Dfe.Data.SearchPrototype.SearchForEstablishments; public sealed class SearchByKeywordResponse { /// - /// The readonly collection of T:Dfe.Data.SearchPrototype.Search.Establishment search results. + /// The result object that encapsulates the search results + /// returned by the Establishment search /// - public IReadOnlyCollection EstablishmentResults { get;} + public EstablishmentResults? EstablishmentResults { get; init; } /// - /// The readonly collection of T:Dfe.Data.SearchPrototype.Search.EstablishmentFacet returned by the Establishment search + /// The result object that encapsulates the returned by the Establishment search /// - public IReadOnlyCollection? EstablishmentFacetResults { get; } + public EstablishmentFacets? EstablishmentFacetResults { get; init; } /// - /// The return status of the call to the - /// T:Dfe.Data.SearchPrototype.SearchForEstablishments.SearchByKeywordUseCase instance + /// The return status of the call to the instance /// - public SearchResponseStatus Status { get; set; } + public SearchResponseStatus Status { get; } /// - /// Default constructor + /// /// - public SearchByKeywordResponse() + /// + public SearchByKeywordResponse(SearchResponseStatus status) { - EstablishmentResults = new List(); + Status = status; } /// @@ -37,14 +38,18 @@ public SearchByKeywordResponse() /// once an instance is created, this ensures we preserve immutability. /// /// - /// The readonly collection of T:Dfe.Data.SearchPrototype.Search.Establishment search results. + /// The readonly collection of /// /// - /// The readonly collection of T:Dfe.Data.SearchPrototype.Search.EstablishmentFacet + /// The readonly collection of /// - public SearchByKeywordResponse(IReadOnlyCollection establishments, IReadOnlyCollection? facetResults = null) + /// + /// The of the result of the search + /// + public SearchByKeywordResponse(EstablishmentResults establishments, EstablishmentFacets facetResults, SearchResponseStatus status) { EstablishmentResults = establishments; EstablishmentFacetResults = facetResults; + Status = status; } } diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchByKeywordUseCase.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchByKeywordUseCase.cs index 3b2a5ee..2eeebf5 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchByKeywordUseCase.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchByKeywordUseCase.cs @@ -41,28 +41,25 @@ public SearchByKeywordUseCase( public async Task HandleRequest(SearchByKeywordRequest request) { if ((request == null) || (request.Context == null)) { - return new SearchByKeywordResponse() - { - Status = SearchResponseStatus.InvalidRequest - }; + return new SearchByKeywordResponse(SearchResponseStatus.InvalidRequest); }; try { - EstablishmentResults establishmentResults = await _searchServiceAdapter.SearchAsync(request.Context); + SearchResults results = await _searchServiceAdapter.SearchAsync(request.Context); - return establishmentResults switch + return results switch { - null => new() { Status = SearchResponseStatus.SearchServiceError }, - _ => new(establishmentResults.Establishments) { Status = SearchResponseStatus.Success } + null => new(status: SearchResponseStatus.SearchServiceError), + _ => new(status: SearchResponseStatus.Success) { + EstablishmentResults = results.Establishments, + EstablishmentFacetResults = results.Facets + } }; } catch (Exception) // something went wrong in the infrastructure tier { - return new() - { - Status = SearchResponseStatus.SearchServiceError - }; + return new(status: SearchResponseStatus.SearchServiceError); } } } diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchContext.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchContext.cs index a4d52c9..70e9638 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchContext.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchContext.cs @@ -10,6 +10,11 @@ public sealed class SearchContext /// public string SearchKeyword { get; } + /// + /// The facets to be returned + /// + public IList? Facets { get; } + /// /// The target collection on which to apply the search. /// @@ -25,8 +30,11 @@ public sealed class SearchContext /// /// The target collection on which to apply the search. /// + /// + /// The facets to be returned. + /// /// - public SearchContext(string searchKeyword, string targetCollection) + public SearchContext(string searchKeyword, string targetCollection, IList? facets = null) { SearchKeyword = string.IsNullOrWhiteSpace(searchKeyword) ? @@ -35,6 +43,8 @@ public SearchContext(string searchKeyword, string targetCollection) TargetCollection = string.IsNullOrWhiteSpace(targetCollection) ? throw new ArgumentNullException(nameof(targetCollection)) : targetCollection; + + Facets = facets; } /// @@ -46,8 +56,11 @@ public SearchContext(string searchKeyword, string targetCollection) /// /// The underlying collection on which to undertake the search. /// + /// + /// The facets to be returned. + /// /// /// A configured T:Dfe.Data.SearchPrototype.Search.SearchContext instance. /// - public static SearchContext Create(string searchKeyword, string targetCollection) => new(searchKeyword, targetCollection); -} \ No newline at end of file + public static SearchContext Create(string searchKeyword, string targetCollection, IList? facets = null) => new(searchKeyword, targetCollection, facets); +} diff --git a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/SearchByKeywordUseCaseTests.cs b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/SearchByKeywordUseCaseTests.cs index 1a6b8db..0385128 100644 --- a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/SearchByKeywordUseCaseTests.cs +++ b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/SearchByKeywordUseCaseTests.cs @@ -1,4 +1,5 @@ using Dfe.Data.SearchPrototype.SearchForEstablishments; +using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; using Dfe.Data.SearchPrototype.Tests.SearchForEstablishments.TestDoubles; using FluentAssertions; using Moq; @@ -10,13 +11,14 @@ public sealed class SearchByKeywordUseCaseTests { private readonly SearchByKeywordUseCase _useCase; private ISearchServiceAdapter _searchServiceAdapter; + private SearchResults _searchResults; public SearchByKeywordUseCaseTests() { // arrange + _searchResults = SearchResultsTestDouble.Create(); _searchServiceAdapter = - SearchServiceAdapterTestDouble.MockFor( - EstablishmentResultsTestDouble.Create()); + SearchServiceAdapterTestDouble.MockFor(_searchResults); _useCase = new(_searchServiceAdapter); } @@ -32,6 +34,8 @@ public async Task HandleRequest_ValidRequest_ReturnsResponse() // assert response.Status.Should().Be(SearchResponseStatus.Success); + response.EstablishmentResults!.Establishments.Should().Contain(_searchResults.Establishments!.Establishments); + response.EstablishmentFacetResults!.Facets.Should().Contain(_searchResults.Facets!.Facets); } [Fact] @@ -69,7 +73,7 @@ public async Task HandleRequest_NoResults_ReturnsSuccess() SearchByKeywordRequest request = new("searchkeyword", "target collection"); Mock.Get(_searchServiceAdapter) .Setup(adapter => adapter.SearchAsync(It.IsAny())) - .ReturnsAsync(EstablishmentResultsTestDouble.CreateWithNoResults); + .ReturnsAsync(SearchResultsTestDouble.CreateWithNoResults); // act var response = await _useCase.HandleRequest(request); diff --git a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/EstablishmentFacetsTestDouble.cs b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/EstablishmentFacetsTestDouble.cs new file mode 100644 index 0000000..c0a4bb0 --- /dev/null +++ b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/EstablishmentFacetsTestDouble.cs @@ -0,0 +1,16 @@ +using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; + +namespace Dfe.Data.SearchPrototype.Tests.SearchForEstablishments.TestDoubles; + +public static class EstablishmentFacetsTestDouble +{ + public static EstablishmentFacets Create() + { + var facets = new List( + new List() + { + new EstablishmentFacet("name", new List() { new FacetResult("value1", 1)}) + }); + return new EstablishmentFacets(facets); + } +} diff --git a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/SearchResultsTestDouble.cs b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/SearchResultsTestDouble.cs new file mode 100644 index 0000000..ca70edc --- /dev/null +++ b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/SearchResultsTestDouble.cs @@ -0,0 +1,20 @@ +using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; + +namespace Dfe.Data.SearchPrototype.Tests.SearchForEstablishments.TestDoubles; + +public static class SearchResultsTestDouble +{ + public static SearchResults Create() + { + return new SearchResults() + { + Facets = EstablishmentFacetsTestDouble.Create(), + Establishments = EstablishmentResultsTestDouble.Create() + }; + } + + public static SearchResults CreateWithNoResults() + { + return new SearchResults(); + } +} diff --git a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/SearchServiceAdapterTestDouble.cs b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/SearchServiceAdapterTestDouble.cs index d8a6d96..571b6a1 100644 --- a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/SearchServiceAdapterTestDouble.cs +++ b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/SearchServiceAdapterTestDouble.cs @@ -6,13 +6,13 @@ namespace Dfe.Data.SearchPrototype.Tests.SearchForEstablishments.TestDoubles; public static class SearchServiceAdapterTestDouble { - public static ISearchServiceAdapter MockFor(EstablishmentResults establishmentResults) + public static ISearchServiceAdapter MockFor(SearchResults searchResults) { Mock searchServiceAdapter = new(); searchServiceAdapter .Setup(adapter => adapter.SearchAsync(It.IsAny())) - .ReturnsAsync(establishmentResults); + .ReturnsAsync(searchResults); return searchServiceAdapter.Object; }