From 4b45645f1b4441688b60c58688bc287fd22b51fc Mon Sep 17 00:00:00 2001 From: AsiaWi <123170965+AsiaWi@users.noreply.github.com> Date: Mon, 21 Oct 2024 17:54:54 +0100 Subject: [PATCH 01/11] Add total count and map to model --- .../Infrastructure/CognitiveSearchServiceAdapter.cs | 8 +++++--- ...ableSearchResultsToEstablishmentResultsMapper.cs | 12 ++++++------ .../CognitiveSearchServiceAdapterAndMapperTests.cs | 2 +- .../Tests/CognitiveSearchServiceAdapterTests.cs | 4 ++-- ...ResultsToEstablishmentResultsMapperTestDouble.cs | 12 ++++++------ .../ByKeyword/Usecase/SearchByKeywordResponse.cs | 6 +++++- .../Models/EstablishmentResults.cs | 13 ++++++++++++- 7 files changed, 37 insertions(+), 20 deletions(-) diff --git a/Dfe.Data.SearchPrototype/Infrastructure/CognitiveSearchServiceAdapter.cs b/Dfe.Data.SearchPrototype/Infrastructure/CognitiveSearchServiceAdapter.cs index 249be71..c5cc3e6 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/CognitiveSearchServiceAdapter.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/CognitiveSearchServiceAdapter.cs @@ -19,7 +19,7 @@ namespace Dfe.Data.SearchPrototype.Infrastructure; public sealed class CognitiveSearchServiceAdapter : ISearchServiceAdapter where TSearchResult : class { private readonly ISearchByKeywordService _searchByKeywordService; - private readonly IMapper>, EstablishmentResults> _searchResultMapper; + private readonly IMapper<(Pageable>, long?), EstablishmentResults> _searchResultMapper; private readonly IMapper>, EstablishmentFacets> _facetsMapper; private readonly AzureSearchOptions _azureSearchOptions; private readonly ISearchOptionsBuilder _searchOptionsBuilder; @@ -46,7 +46,7 @@ public sealed class CognitiveSearchServiceAdapter : ISearchServic public CognitiveSearchServiceAdapter( ISearchByKeywordService searchByKeywordService, IOptions azureSearchOptions, - IMapper>, EstablishmentResults> searchResultMapper, + IMapper<(Pageable>, long?), EstablishmentResults> searchResultMapper, IMapper>, EstablishmentFacets> facetsMapper, ISearchOptionsBuilder searchOptionsBuilder) { @@ -100,7 +100,9 @@ await _searchByKeywordService.SearchAsync( var results = new SearchResults() { - Establishments = _searchResultMapper.MapFrom(searchResults.Value.GetResults()), + Establishments = + _searchResultMapper.MapFrom( + (searchResults.Value.GetResults(), searchResults.Value.TotalCount)), Facets = searchResults.Value.Facets != null ? _facetsMapper.MapFrom(searchResults.Value.Facets.ToDictionary()) : null diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Mappers/PageableSearchResultsToEstablishmentResultsMapper.cs b/Dfe.Data.SearchPrototype/Infrastructure/Mappers/PageableSearchResultsToEstablishmentResultsMapper.cs index 6fff1cd..043fb84 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Mappers/PageableSearchResultsToEstablishmentResultsMapper.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Mappers/PageableSearchResultsToEstablishmentResultsMapper.cs @@ -10,7 +10,7 @@ namespace Dfe.Data.SearchPrototype.Infrastructure.Mappers; /// Facilitates mapping from the received /// into the required object. /// -public sealed class PageableSearchResultsToEstablishmentResultsMapper : IMapper>, Models.EstablishmentResults> +public sealed class PageableSearchResultsToEstablishmentResultsMapper : IMapper<(Pageable>, long?), Models.EstablishmentResults> { private readonly IMapper _azureSearchResultToEstablishmentMapper; @@ -44,19 +44,19 @@ public PageableSearchResultsToEstablishmentResultsMapper(IMapper /// Exception thrown if the data cannot be mapped /// - public Models.EstablishmentResults MapFrom(Pageable> input) + public Models.EstablishmentResults MapFrom((Pageable>, long?) input) { ArgumentNullException.ThrowIfNull(input); - - if (input.Any()) + + if (input.Item1.Any()) { - var mappedResults = input.Select(result => + var mappedResults = input.Item1.Select(result => result.Document != null ? _azureSearchResultToEstablishmentMapper.MapFrom(result.Document) : throw new InvalidOperationException( "Search result document object cannot be null.") ); - return new Models.EstablishmentResults(mappedResults); + return new Models.EstablishmentResults(mappedResults, input.Item2); } else { diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterAndMapperTests.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterAndMapperTests.cs index 672109f..64dca17 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterAndMapperTests.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterAndMapperTests.cs @@ -14,7 +14,7 @@ namespace Dfe.Data.SearchPrototype.Infrastructure.Tests; public sealed class CognitiveSearchServiceAdapterAndMapperTests { - private readonly IMapper>, EstablishmentResults> _searchResponseMapper; + private readonly IMapper<(Pageable>, long?), EstablishmentResults> _searchResponseMapper; private readonly IMapper>, EstablishmentFacets> _facetsMapper; private readonly ISearchOptionsBuilder _searchOptionsBuilder = SearchOptionsBuilderTestDouble.MockFor(new Azure.Search.Documents.SearchOptions()); diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs index b15d4ad..98e2974 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs @@ -21,7 +21,7 @@ public sealed class CognitiveSearchServiceAdapterTests private readonly IMapper>, EstablishmentFacets> _mockFacetsMapper = AzureFacetResultToEstablishmentFacetsMapperTestDouble.DefaultMock(); private readonly AzureSearchOptions _options = AzureSearchOptionsTestDouble.Stub(); - private readonly IMapper>, EstablishmentResults> _mockEstablishmentResultsMapper + private readonly IMapper<(Pageable>, long?), EstablishmentResults> _mockEstablishmentResultsMapper = PageableSearchResultsToEstablishmentResultsMapperTestDouble.DefaultMock(); private readonly ISearchByKeywordService _mockSearchService; private readonly ISearchOptionsBuilder _mockSearchOptionsBuilder = @@ -30,7 +30,7 @@ public sealed class CognitiveSearchServiceAdapterTests private static CognitiveSearchServiceAdapter CreateServiceAdapterWith( ISearchByKeywordService searchByKeywordService, IOptions searchOptions, - IMapper>, EstablishmentResults> searchResponseMapper, + IMapper<(Pageable>, long?), EstablishmentResults> searchResponseMapper, IMapper>, EstablishmentFacets> facetsMapper, ISearchOptionsBuilder searchOptionsBuilder ) => diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/PageableSearchResultsToEstablishmentResultsMapperTestDouble.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/PageableSearchResultsToEstablishmentResultsMapperTestDouble.cs index 5ec8f79..6c2b65f 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/PageableSearchResultsToEstablishmentResultsMapperTestDouble.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/PageableSearchResultsToEstablishmentResultsMapperTestDouble.cs @@ -9,11 +9,11 @@ namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles; internal static class PageableSearchResultsToEstablishmentResultsMapperTestDouble { - public static IMapper>, EstablishmentResults> DefaultMock() => - Mock.Of>, EstablishmentResults>>(); + public static IMapper<(Pageable>, long?), EstablishmentResults> DefaultMock() => + Mock.Of>, long?), EstablishmentResults>>(); - public static Expression>, EstablishmentResults>, EstablishmentResults>> MapFrom() => - mapper => mapper.MapFrom(It.IsAny>>()); + public static Expression>, long?>>, EstablishmentResults>, EstablishmentResults>> MapFrom() => + mapper => mapper.MapFrom(It.IsAny>, long?>>()); public static IMapper>, EstablishmentResults> MockFor(EstablishmentResults establishments) { @@ -24,9 +24,9 @@ internal static class PageableSearchResultsToEstablishmentResultsMapperTestDoubl return mapperMock.Object; } - public static IMapper>, EstablishmentResults> MockMapperThrowingArgumentException() + public static IMapper<(Pageable>, long?), EstablishmentResults> MockMapperThrowingArgumentException() { - var mapperMock = new Mock>, EstablishmentResults>>(); + var mapperMock = new Mock>, long?), EstablishmentResults>>(); mapperMock.Setup(MapFrom()).Throws(new ArgumentException()); diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordResponse.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordResponse.cs index e5608e5..a595788 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordResponse.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordResponse.cs @@ -47,7 +47,11 @@ public SearchByKeywordResponse(SearchResponseStatus status) /// /// The of the result of the search. /// - public SearchByKeywordResponse(EstablishmentResults establishments, EstablishmentFacets facetResults, SearchResponseStatus status) + public SearchByKeywordResponse( + EstablishmentResults establishments, + EstablishmentFacets facetResults, + SearchResponseStatus status + ) { EstablishmentResults = establishments; EstablishmentFacetResults = facetResults; diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentResults.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentResults.cs index 3e950e4..37753e5 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentResults.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentResults.cs @@ -14,6 +14,12 @@ public sealed class EstablishmentResults private readonly List _establishments; + /// + /// The Total Count returned from Establishment search gives us a total + /// of all avaialable records which correlates with the given search criteria. + /// + public long? TotalNumberOfEstablishments { get; } + /// /// Default constructor initialises a new readonly /// collection of instances. @@ -30,8 +36,13 @@ public EstablishmentResults() /// /// Collection of configured instances. /// - public EstablishmentResults(IEnumerable establishments) + /// + /// The Total Count returned from Establishment search gives us a total + /// of all avaialable records which correlates with the given search criteria. + /// + public EstablishmentResults(IEnumerable establishments, long? totalNumberOfEstablishments) { _establishments = establishments.ToList(); + TotalNumberOfEstablishments = totalNumberOfEstablishments; } } \ No newline at end of file From 40abd802ff2606d99fb37497ed542b89ae46f7d5 Mon Sep 17 00:00:00 2001 From: Spencer O'HEGARTY Date: Wed, 23 Oct 2024 10:22:52 +0100 Subject: [PATCH 02/11] Fixed test issues --- .../Common/Dfe.Data.SearchPrototype.Common.csproj | 1 + .../Dfe.Data.SearchPrototype.csproj | 1 + .../Infrastructure/CompositionRoot.cs | 2 +- .../Dfe.Data.SearchPrototype.Infrastructure.csproj | 1 + ...earchResultsToEstablishmentResultsMapperTests.cs | 13 ++++++------- ...ResultsToEstablishmentResultsMapperTestDouble.cs | 8 ++++---- .../Models/EstablishmentResults.cs | 8 ++++---- .../TestDoubles/EstablishmentResultsTestDouble.cs | 2 +- 8 files changed, 19 insertions(+), 17 deletions(-) diff --git a/Dfe.Data.SearchPrototype/Common/Dfe.Data.SearchPrototype.Common.csproj b/Dfe.Data.SearchPrototype/Common/Dfe.Data.SearchPrototype.Common.csproj index 65ed135..6db048c 100644 --- a/Dfe.Data.SearchPrototype/Common/Dfe.Data.SearchPrototype.Common.csproj +++ b/Dfe.Data.SearchPrototype/Common/Dfe.Data.SearchPrototype.Common.csproj @@ -18,6 +18,7 @@ true README.md MIT + True diff --git a/Dfe.Data.SearchPrototype/Dfe.Data.SearchPrototype.csproj b/Dfe.Data.SearchPrototype/Dfe.Data.SearchPrototype.csproj index 00d6dca..aa2b2d9 100644 --- a/Dfe.Data.SearchPrototype/Dfe.Data.SearchPrototype.csproj +++ b/Dfe.Data.SearchPrototype/Dfe.Data.SearchPrototype.csproj @@ -18,6 +18,7 @@ true README.md MIT + True diff --git a/Dfe.Data.SearchPrototype/Infrastructure/CompositionRoot.cs b/Dfe.Data.SearchPrototype/Infrastructure/CompositionRoot.cs index 47dbe99..d8487bb 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/CompositionRoot.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/CompositionRoot.cs @@ -47,7 +47,7 @@ public static void AddCognitiveSearchAdaptorServices(this IServiceCollection ser services.AddScoped(typeof(ISearchServiceAdapter), typeof(CognitiveSearchServiceAdapter)); services.AddScoped(); - services.AddSingleton(typeof(IMapper>, EstablishmentResults>), typeof(PageableSearchResultsToEstablishmentResultsMapper)); + services.AddSingleton(typeof(IMapper<(Pageable>, long?), EstablishmentResults>), typeof(PageableSearchResultsToEstablishmentResultsMapper)); services.AddSingleton>, EstablishmentFacets>, AzureFacetResultToEstablishmentFacetsMapper>(); services.AddSingleton, AzureSearchResultToAddressMapper>(); services.AddSingleton, AzureSearchResultToEstablishmentMapper>(); diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Dfe.Data.SearchPrototype.Infrastructure.csproj b/Dfe.Data.SearchPrototype/Infrastructure/Dfe.Data.SearchPrototype.Infrastructure.csproj index 9318291..82c32f7 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Dfe.Data.SearchPrototype.Infrastructure.csproj +++ b/Dfe.Data.SearchPrototype/Infrastructure/Dfe.Data.SearchPrototype.Infrastructure.csproj @@ -18,6 +18,7 @@ true README.md MIT + True diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/PageableSearchResultsToEstablishmentResultsMapperTests.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/PageableSearchResultsToEstablishmentResultsMapperTests.cs index aba90d2..a1309f3 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/PageableSearchResultsToEstablishmentResultsMapperTests.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/PageableSearchResultsToEstablishmentResultsMapperTests.cs @@ -1,7 +1,6 @@ using Azure; using Azure.Search.Documents.Models; using Dfe.Data.SearchPrototype.Common.Mappers; -using Dfe.Data.SearchPrototype.Infrastructure.DataTransferObjects; using Dfe.Data.SearchPrototype.Infrastructure.Mappers; using Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles; using Dfe.Data.SearchPrototype.Infrastructure.Tests.TestHelpers; @@ -13,7 +12,7 @@ namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.Mappers; public sealed class PageableSearchResultsToEstablishmentResultsMapperTests { - IMapper>, EstablishmentResults> _searchResultsMapper; + IMapper<(Pageable>, long?), EstablishmentResults> _searchResultsMapper; public PageableSearchResultsToEstablishmentResultsMapperTests() { @@ -32,7 +31,7 @@ public void MapFrom_WithValidSearchResults_ReturnsConfiguredEstablishments() var pageableSearchResults = PageableTestDouble.FromResults(searchResultDocuments); // act - EstablishmentResults? mappedResult = _searchResultsMapper.MapFrom(pageableSearchResults); + EstablishmentResults? mappedResult = _searchResultsMapper.MapFrom((pageableSearchResults, 100)); // assert mappedResult.Should().NotBeNull(); @@ -47,7 +46,7 @@ public void MapFrom_WithValidSearchResults_ReturnsConfiguredEstablishments() public void MapFrom_WithEmptySearchResults_ReturnsEmptyList() { // act - EstablishmentResults? result = _searchResultsMapper.MapFrom(PageableTestDouble.FromResults(SearchResultFake.EmptySearchResult())); + EstablishmentResults? result = _searchResultsMapper.MapFrom((PageableTestDouble.FromResults(SearchResultFake.EmptySearchResult()), 100)); // assert result.Should().NotBeNull(); @@ -60,10 +59,10 @@ public void MapFrom_WithNullSearchResults_ThrowsArgumentNullException() // act. _searchResultsMapper .Invoking(mapper => - mapper.MapFrom(null!)) + mapper.MapFrom((null!, 0))) .Should() .Throw() - .WithMessage("Value cannot be null. (Parameter 'input')"); + .WithMessage("Value cannot be null. (Parameter 'source')"); } [Fact] @@ -77,7 +76,7 @@ public void MapFrom_WithANullSearchResult_ThrowsInvalidOperationException() // act. _searchResultsMapper .Invoking(mapper => - mapper.MapFrom(PageableTestDouble.FromResults(searchResultDocuments))) + mapper.MapFrom((PageableTestDouble.FromResults(searchResultDocuments), 100))) .Should() .Throw() .WithMessage("Search result document object cannot be null."); diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/PageableSearchResultsToEstablishmentResultsMapperTestDouble.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/PageableSearchResultsToEstablishmentResultsMapperTestDouble.cs index 6c2b65f..667f735 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/PageableSearchResultsToEstablishmentResultsMapperTestDouble.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/PageableSearchResultsToEstablishmentResultsMapperTestDouble.cs @@ -12,12 +12,12 @@ internal static class PageableSearchResultsToEstablishmentResultsMapperTestDoubl public static IMapper<(Pageable>, long?), EstablishmentResults> DefaultMock() => Mock.Of>, long?), EstablishmentResults>>(); - public static Expression>, long?>>, EstablishmentResults>, EstablishmentResults>> MapFrom() => - mapper => mapper.MapFrom(It.IsAny>, long?>>()); + public static Expression>, long?), EstablishmentResults>, EstablishmentResults>> MapFrom() => + mapper => mapper.MapFrom(It.IsAny<(Pageable>, long ?)>()); - public static IMapper>, EstablishmentResults> MockFor(EstablishmentResults establishments) + public static IMapper<(Pageable>, long?), EstablishmentResults> MockFor(EstablishmentResults establishments) { - var mapperMock = new Mock>, EstablishmentResults>>(); + var mapperMock = new Mock>, long?), EstablishmentResults>>(); mapperMock.Setup(MapFrom()).Returns(establishments); diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentResults.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentResults.cs index 37753e5..490b5d0 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentResults.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentResults.cs @@ -6,17 +6,17 @@ /// public sealed class EstablishmentResults { + private readonly List _establishments; + /// /// The readonly collection of /// types derived from the underlying search mechanism. /// public IReadOnlyCollection Establishments => _establishments.AsReadOnly(); - private readonly List _establishments; - /// /// The Total Count returned from Establishment search gives us a total - /// of all avaialable records which correlates with the given search criteria. + /// of all available records which correlates with the given search criteria. /// public long? TotalNumberOfEstablishments { get; } @@ -38,7 +38,7 @@ public EstablishmentResults() /// /// /// The Total Count returned from Establishment search gives us a total - /// of all avaialable records which correlates with the given search criteria. + /// of all available records which correlates with the given search criteria. /// public EstablishmentResults(IEnumerable establishments, long? totalNumberOfEstablishments) { diff --git a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/TestDoubles/EstablishmentResultsTestDouble.cs b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/TestDoubles/EstablishmentResultsTestDouble.cs index a9ec567..1b6fb68 100644 --- a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/TestDoubles/EstablishmentResultsTestDouble.cs +++ b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/TestDoubles/EstablishmentResultsTestDouble.cs @@ -13,7 +13,7 @@ public static EstablishmentResults Create() establishments.Add( EstablishmentTestDouble.Create()); } - return new EstablishmentResults(establishments); + return new EstablishmentResults(establishments, 100); } public static EstablishmentResults CreateWithNoResults() From 8b2062d778a81741cb4606bdcc531cec2d6d1a63 Mon Sep 17 00:00:00 2001 From: Spencer O'HEGARTY Date: Wed, 23 Oct 2024 17:52:28 +0100 Subject: [PATCH 03/11] WIP - added offset, tests need adding to exercise different offset and limits --- .../Builders/ISearchOptionsBuilder.cs | 12 ++++++++++ .../Builders/SearchOptionsBuilder.cs | 18 ++++++++++++++ .../CognitiveSearchServiceAdapter.cs | 1 + ...itiveSearchServiceAdapterAndMapperTests.cs | 3 +++ .../CognitiveSearchServiceAdapterTests.cs | 2 +- .../SearchOptionsBuilderTestDouble.cs | 2 ++ .../SearchServiceAdapterRequestTestDouble.cs | 2 ++ .../SearchServiceAdapterRequest.cs | 24 ++++++++++++++++--- .../Usecase/SearchByKeywordRequest.cs | 17 +++++++++++-- .../Usecase/SearchByKeywordUseCase.cs | 1 + 10 files changed, 76 insertions(+), 6 deletions(-) diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Builders/ISearchOptionsBuilder.cs b/Dfe.Data.SearchPrototype/Infrastructure/Builders/ISearchOptionsBuilder.cs index e409e45..40f0ece 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Builders/ISearchOptionsBuilder.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Builders/ISearchOptionsBuilder.cs @@ -21,6 +21,18 @@ public interface ISearchOptionsBuilder /// ISearchOptionsBuilder WithSize(int? size); + /// + /// Sets the value used to define how many + /// records are skipped in the search response (if any). + /// + /// + /// The number of initial search results to skip. + /// + /// + /// The updated builder instance. + /// + ISearchOptionsBuilder WithOffset(int? offset); + /// /// Sets the mode of search to invoke, i.e. All or Any. /// diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Builders/SearchOptionsBuilder.cs b/Dfe.Data.SearchPrototype/Infrastructure/Builders/SearchOptionsBuilder.cs index 05b64ad..214f540 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Builders/SearchOptionsBuilder.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Builders/SearchOptionsBuilder.cs @@ -16,6 +16,7 @@ public sealed class SearchOptionsBuilder : ISearchOptionsBuilder private SearchMode? _searchMode; private int? _size; + private int? _offset; private bool? _includeTotalCount; private IList? _searchFields; private IList? _facets; @@ -53,6 +54,22 @@ public ISearchOptionsBuilder WithSize(int? size) return this; } + /// + /// Sets the value used to define how many + /// records are skipped in the search response (if any). + /// + /// + /// The number of initial search results to skip. + /// + /// + /// The updated builder instance. + /// + public ISearchOptionsBuilder WithOffset(int? offset) + { + _offset = offset; + return this; + } + /// /// Sets the mode of search to invoke, i.e. All or Any. /// @@ -139,6 +156,7 @@ public SearchOptions Build() { _searchOptions.SearchMode = _searchMode; _searchOptions.Size = _size; + _searchOptions.Skip = _offset; _searchOptions.IncludeTotalCount = _includeTotalCount; _searchFields?.ToList().ForEach(_searchOptions.SearchFields.Add); _facets?.ToList().ForEach(_searchOptions.Facets.Add); diff --git a/Dfe.Data.SearchPrototype/Infrastructure/CognitiveSearchServiceAdapter.cs b/Dfe.Data.SearchPrototype/Infrastructure/CognitiveSearchServiceAdapter.cs index c5cc3e6..82f56c5 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/CognitiveSearchServiceAdapter.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/CognitiveSearchServiceAdapter.cs @@ -82,6 +82,7 @@ public async Task SearchAsync(SearchServiceAdapterRequest searchS _searchOptionsBuilder .WithSearchMode((SearchMode)_azureSearchOptions.SearchMode) .WithSize(_azureSearchOptions.Size) + .WithOffset(searchServiceAdapterRequest.Offset) .WithIncludeTotalCount(_azureSearchOptions.IncludeTotalCount) .WithSearchFields(searchServiceAdapterRequest.SearchFields) .WithFacets(searchServiceAdapterRequest.Facets) diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterAndMapperTests.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterAndMapperTests.cs index 64dca17..368bebe 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterAndMapperTests.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterAndMapperTests.cs @@ -56,6 +56,7 @@ public async Task Search_WithValidSearchContext_ReturnsResults() await cognitiveSearchServiceAdapter.SearchAsync( new SearchServiceAdapterRequest( searchKeyword: "SearchKeyword", + offset: 0, searchFields: ["FIELD1", "FIELD2", "FIELD2"], facets: ["FACET1", "FACET2", "FACET3"])); @@ -93,6 +94,7 @@ public async Task Search_WithNoFacetsReturned_ReturnsNullFacets() await cognitiveSearchServiceAdapter.SearchAsync( new SearchServiceAdapterRequest( searchKeyword: "SearchKeyword", + offset: 0, searchFields: ["FIELD1", "FIELD2", "FIELD2"], facets: ["FACET1", "FACET2", "FACET3"])); @@ -127,6 +129,7 @@ public async Task Search_WithNoResultsReturned_ReturnsEmptyResults() await cognitiveSearchServiceAdapter.SearchAsync( new SearchServiceAdapterRequest( searchKeyword: "SearchKeyword", + offset: 0, searchFields: ["FIELD1", "FIELD2", "FIELD2"], facets: ["FACET1", "FACET2", "FACET3"])); diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs index 98e2974..106da15 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs @@ -129,7 +129,7 @@ public Task Search_MapperThrowsException_ExceptionPassesThrough() return cognitiveSearchServiceAdapter .Invoking(adapter => adapter.SearchAsync(new SearchServiceAdapterRequest( - searchKeyword: "SearchKeyword", [], []))) + searchKeyword: "SearchKeyword", offset:0, [], []))) .Should() .ThrowAsync< ArgumentException>(); } diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchOptionsBuilderTestDouble.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchOptionsBuilderTestDouble.cs index 999da1c..af4ce9c 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchOptionsBuilderTestDouble.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchOptionsBuilderTestDouble.cs @@ -14,6 +14,8 @@ public static ISearchOptionsBuilder MockFor(SearchOptions searchOptions) mockSearchOptionsBuilder.Setup(searchOptionsBuilder => searchOptionsBuilder.WithSize(It.IsAny())).Returns(mockSearchOptionsBuilder.Object); + mockSearchOptionsBuilder.Setup(searchOptionsBuilder => + searchOptionsBuilder.WithOffset(It.IsAny())).Returns(mockSearchOptionsBuilder.Object); mockSearchOptionsBuilder.Setup(searchOptionsBuilder => searchOptionsBuilder.WithSearchMode(It.IsAny())).Returns(mockSearchOptionsBuilder.Object); mockSearchOptionsBuilder.Setup(searchOptionsBuilder => diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchServiceAdapterRequestTestDouble.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchServiceAdapterRequestTestDouble.cs index b9077de..7a131ce 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchServiceAdapterRequestTestDouble.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchServiceAdapterRequestTestDouble.cs @@ -10,6 +10,7 @@ public static SearchServiceAdapterRequest Create() { return new SearchServiceAdapterRequest( "searchKeyword", + 0, new List() { "searchField1", "searchField2" }, new List { "facet1", "facet2" }, new List() {FilterRequestFake.Create() } @@ -20,6 +21,7 @@ public static SearchServiceAdapterRequest WithFilters(IList filte { return new SearchServiceAdapterRequest( "searchKeyword", + 0, new List() { "searchField1", "searchField2"}, new List { "facet1", "facet2" }, filters diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs index 1b31fd2..88365ea 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs @@ -13,6 +13,11 @@ public sealed class SearchServiceAdapterRequest /// public string SearchKeyword { get; } + /// + /// The value used to define how many records are skipped in the search response (if any). + /// + public int Offset { get; } + /// /// The collection of fields in the underlying collection to search over. /// @@ -35,6 +40,9 @@ public sealed class SearchServiceAdapterRequest /// /// The search keyword(s) to be applied. /// + /// + /// The value used to define how many records are skipped in the search response (if any). + /// /// /// The collection of fields in the underlying collection to search over. /// @@ -51,7 +59,12 @@ public sealed class SearchServiceAdapterRequest /// The exception type thrown if either a null or empty collection of search fields, /// or search facets are prescribed. /// - public SearchServiceAdapterRequest(string searchKeyword, IList searchFields, IList facets, IList? searchFilterRequests = null) + public SearchServiceAdapterRequest( + string searchKeyword, + int offset, + IList searchFields, + IList facets, + IList? searchFilterRequests = null) { SearchKeyword = string.IsNullOrWhiteSpace(searchKeyword) ? @@ -64,6 +77,8 @@ public SearchServiceAdapterRequest(string searchKeyword, IList searchFie throw new ArgumentException("", nameof(facets)) : facets; SearchFilterRequests = searchFilterRequests; + + Offset = offset; } /// @@ -72,6 +87,9 @@ public SearchServiceAdapterRequest(string searchKeyword, IList searchFie /// /// The keyword string which defines the search. /// + /// /// + /// The value used to define how many records are skipped in the search response (if any). + /// /// /// The collection of fields in the underlying collection to search over. /// @@ -82,6 +100,6 @@ public SearchServiceAdapterRequest(string searchKeyword, IList searchFie /// A configured instance. /// public static SearchServiceAdapterRequest Create( - string searchKeyword, IList searchFields, IList facets) => - new(searchKeyword, searchFields, facets); + string searchKeyword, int offset, IList searchFields, IList facets) => + new(searchKeyword, offset, searchFields, facets); } diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordRequest.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordRequest.cs index 0dc7d39..8a72e12 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordRequest.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordRequest.cs @@ -14,11 +14,15 @@ public sealed class SearchByKeywordRequest /// /// The string keyword used to search the collection specified. /// - public SearchByKeywordRequest(string searchKeyword) + /// + /// The value used to define how many records are skipped in the search response (if any). + /// + public SearchByKeywordRequest(string searchKeyword, int offset = 0) { ArgumentException.ThrowIfNullOrEmpty(nameof(searchKeyword)); SearchKeyword = searchKeyword; + Offset = offset; } /// @@ -32,7 +36,11 @@ public SearchByKeywordRequest(string searchKeyword) /// /// The used to refine the search criteria. /// - public SearchByKeywordRequest(string searchKeyword, IList filterRequests) : this(searchKeyword) + /// + /// The value used to define how many records are skipped in the search response (if any). + /// + public SearchByKeywordRequest( + string searchKeyword,IList filterRequests, int offset = 0) : this(searchKeyword, offset) { FilterRequests = filterRequests; } @@ -42,6 +50,11 @@ public SearchByKeywordRequest(string searchKeyword, IList filterR /// public string SearchKeyword { get; } + /// + /// The value used to define how many records are skipped in the search response (if any). + /// + public int Offset { get; } + /// /// The filter (key/values) used to refine the search criteria. /// diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordUseCase.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordUseCase.cs index e708f0a..ba7ffb8 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordUseCase.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordUseCase.cs @@ -77,6 +77,7 @@ public async Task HandleRequest(SearchByKeywordRequest await _searchServiceAdapter.SearchAsync( new SearchServiceAdapterRequest( request.SearchKeyword, + request.Offset, _searchByKeywordCriteriaOptions.SearchFields, _searchByKeywordCriteriaOptions.Facets, request.FilterRequests)); From 57cb284c1a3932f5d8bfb43f49664a6b73545e0c Mon Sep 17 00:00:00 2001 From: Spencer O'HEGARTY Date: Mon, 4 Nov 2024 10:47:09 +0000 Subject: [PATCH 04/11] WIP - test setup --- .../Dfe.Data.SearchPrototype.Common.csproj | 1 - .../Dfe.Data.SearchPrototype.csproj | 1 - ...Data.SearchPrototype.Infrastructure.csproj | 1 - ...archResultsToEstablishmentResultsMapper.cs | 26 +++++++++---------- .../CognitiveSearchServiceAdapterTests.cs | 2 +- ...esultsToEstablishmentResultsMapperTests.cs | 4 ++- .../SearchServiceAdapterRequestTestDouble.cs | 2 -- .../SearchServiceAdapterRequest.cs | 14 +++++----- .../Usecase/SearchByKeywordUseCase.cs | 4 +-- .../CompositionRoot.cs | 1 + 10 files changed, 27 insertions(+), 29 deletions(-) diff --git a/Dfe.Data.SearchPrototype/Common/Dfe.Data.SearchPrototype.Common.csproj b/Dfe.Data.SearchPrototype/Common/Dfe.Data.SearchPrototype.Common.csproj index 6db048c..65ed135 100644 --- a/Dfe.Data.SearchPrototype/Common/Dfe.Data.SearchPrototype.Common.csproj +++ b/Dfe.Data.SearchPrototype/Common/Dfe.Data.SearchPrototype.Common.csproj @@ -18,7 +18,6 @@ true README.md MIT - True diff --git a/Dfe.Data.SearchPrototype/Dfe.Data.SearchPrototype.csproj b/Dfe.Data.SearchPrototype/Dfe.Data.SearchPrototype.csproj index aa2b2d9..00d6dca 100644 --- a/Dfe.Data.SearchPrototype/Dfe.Data.SearchPrototype.csproj +++ b/Dfe.Data.SearchPrototype/Dfe.Data.SearchPrototype.csproj @@ -18,7 +18,6 @@ true README.md MIT - True diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Dfe.Data.SearchPrototype.Infrastructure.csproj b/Dfe.Data.SearchPrototype/Infrastructure/Dfe.Data.SearchPrototype.Infrastructure.csproj index 82c32f7..9318291 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Dfe.Data.SearchPrototype.Infrastructure.csproj +++ b/Dfe.Data.SearchPrototype/Infrastructure/Dfe.Data.SearchPrototype.Infrastructure.csproj @@ -18,7 +18,6 @@ true README.md MIT - True diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Mappers/PageableSearchResultsToEstablishmentResultsMapper.cs b/Dfe.Data.SearchPrototype/Infrastructure/Mappers/PageableSearchResultsToEstablishmentResultsMapper.cs index 043fb84..1e5f163 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Mappers/PageableSearchResultsToEstablishmentResultsMapper.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Mappers/PageableSearchResultsToEstablishmentResultsMapper.cs @@ -8,11 +8,11 @@ namespace Dfe.Data.SearchPrototype.Infrastructure.Mappers; /// /// Facilitates mapping from the received -/// into the required object. +/// into the required object. /// -public sealed class PageableSearchResultsToEstablishmentResultsMapper : IMapper<(Pageable>, long?), Models.EstablishmentResults> +public sealed class PageableSearchResultsToEstablishmentResultsMapper : IMapper<(Pageable>, long?), Models.EstablishmentResults> { - private readonly IMapper _azureSearchResultToEstablishmentMapper; + private readonly IMapper _azureSearchResultToEstablishmentMapper; /// /// The following mapping dependency provides the functionality to map from a raw Azure @@ -22,7 +22,7 @@ public sealed class PageableSearchResultsToEstablishmentResultsMapper : IMapper< /// /// Mapper used to map from the raw Azure search result to a instance. /// - public PageableSearchResultsToEstablishmentResultsMapper(IMapper azureSearchResultToEstablishmentMapper) + public PageableSearchResultsToEstablishmentResultsMapper(IMapper azureSearchResultToEstablishmentMapper) { _azureSearchResultToEstablishmentMapper = azureSearchResultToEstablishmentMapper; } @@ -44,23 +44,23 @@ public PageableSearchResultsToEstablishmentResultsMapper(IMapper /// Exception thrown if the data cannot be mapped /// - public Models.EstablishmentResults MapFrom((Pageable>, long?) input) + public Models.EstablishmentResults MapFrom((Pageable>, long?) input) { ArgumentNullException.ThrowIfNull(input); - + Models.EstablishmentResults establishmentResults = new(); + if (input.Item1.Any()) { var mappedResults = input.Item1.Select(result => result.Document != null ? _azureSearchResultToEstablishmentMapper.MapFrom(result.Document) : throw new InvalidOperationException( - "Search result document object cannot be null.") - ); - return new Models.EstablishmentResults(mappedResults, input.Item2); - } - else - { - return new Models.EstablishmentResults(); + "Search result document object cannot be null.")); + + establishmentResults = + new Models.EstablishmentResults(mappedResults, input.Item2); } + + return establishmentResults; } } diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs index 106da15..98e2974 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs @@ -129,7 +129,7 @@ public Task Search_MapperThrowsException_ExceptionPassesThrough() return cognitiveSearchServiceAdapter .Invoking(adapter => adapter.SearchAsync(new SearchServiceAdapterRequest( - searchKeyword: "SearchKeyword", offset:0, [], []))) + searchKeyword: "SearchKeyword", [], []))) .Should() .ThrowAsync< ArgumentException>(); } diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/PageableSearchResultsToEstablishmentResultsMapperTests.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/PageableSearchResultsToEstablishmentResultsMapperTests.cs index a1309f3..36c3aea 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/PageableSearchResultsToEstablishmentResultsMapperTests.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/PageableSearchResultsToEstablishmentResultsMapperTests.cs @@ -28,6 +28,7 @@ public void MapFrom_WithValidSearchResults_ReturnsConfiguredEstablishments() // arrange List> searchResultDocuments = SearchResultFake.SearchResults(); + var pageableSearchResults = PageableTestDouble.FromResults(searchResultDocuments); // act @@ -35,7 +36,8 @@ public void MapFrom_WithValidSearchResults_ReturnsConfiguredEstablishments() // assert mappedResult.Should().NotBeNull(); - mappedResult.Establishments.Should().HaveCount(searchResultDocuments.Count()); + mappedResult.Establishments.Should().HaveCount(searchResultDocuments.Count); + foreach (var searchResult in searchResultDocuments) { searchResult.ShouldHaveMatchingMappedEstablishment(mappedResult); diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchServiceAdapterRequestTestDouble.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchServiceAdapterRequestTestDouble.cs index 7a131ce..b9077de 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchServiceAdapterRequestTestDouble.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchServiceAdapterRequestTestDouble.cs @@ -10,7 +10,6 @@ public static SearchServiceAdapterRequest Create() { return new SearchServiceAdapterRequest( "searchKeyword", - 0, new List() { "searchField1", "searchField2" }, new List { "facet1", "facet2" }, new List() {FilterRequestFake.Create() } @@ -21,7 +20,6 @@ public static SearchServiceAdapterRequest WithFilters(IList filte { return new SearchServiceAdapterRequest( "searchKeyword", - 0, new List() { "searchField1", "searchField2"}, new List { "facet1", "facet2" }, filters diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs index 88365ea..2d1f48c 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs @@ -40,9 +40,6 @@ public sealed class SearchServiceAdapterRequest /// /// The search keyword(s) to be applied. /// - /// - /// The value used to define how many records are skipped in the search response (if any). - /// /// /// The collection of fields in the underlying collection to search over. /// @@ -52,6 +49,9 @@ public sealed class SearchServiceAdapterRequest /// /// Dictionary of search filter requests where key is the name of the filter and the value is the list of filter values. /// + /// + /// The value used to define how many records are skipped in the search response (if any). + /// /// /// The exception thrown if an invalid search keyword (null or whitespace) is prescribed. /// @@ -61,10 +61,10 @@ public sealed class SearchServiceAdapterRequest /// public SearchServiceAdapterRequest( string searchKeyword, - int offset, IList searchFields, IList facets, - IList? searchFilterRequests = null) + IList? searchFilterRequests = null, + int offset = 0) { SearchKeyword = string.IsNullOrWhiteSpace(searchKeyword) ? @@ -100,6 +100,6 @@ public SearchServiceAdapterRequest( /// A configured instance. /// public static SearchServiceAdapterRequest Create( - string searchKeyword, int offset, IList searchFields, IList facets) => - new(searchKeyword, offset, searchFields, facets); + string searchKeyword, IList searchFields, IList facets) => + new(searchKeyword, searchFields, facets); } diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordUseCase.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordUseCase.cs index ba7ffb8..ddf9adc 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordUseCase.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordUseCase.cs @@ -77,10 +77,10 @@ public async Task HandleRequest(SearchByKeywordRequest await _searchServiceAdapter.SearchAsync( new SearchServiceAdapterRequest( request.SearchKeyword, - request.Offset, _searchByKeywordCriteriaOptions.SearchFields, _searchByKeywordCriteriaOptions.Facets, - request.FilterRequests)); + request.FilterRequests, + request.Offset)); return results switch { diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/CompositionRoot.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/CompositionRoot.cs index 147bf9f..c79ccf0 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/CompositionRoot.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/CompositionRoot.cs @@ -38,6 +38,7 @@ public static void AddSearchForEstablishmentServices(this IServiceCollection ser configuration .GetSection(nameof(SearchByKeywordCriteria)) .Bind(settings)); + services.AddScoped, SearchByKeywordUseCase>(); } } From 52aedc402f72e4fdea900b84326a8e6666233f44 Mon Sep 17 00:00:00 2001 From: Spencer O'HEGARTY Date: Mon, 4 Nov 2024 10:50:29 +0000 Subject: [PATCH 05/11] Some stragglers --- .../ServiceAdapters/SearchServiceAdapterRequest.cs | 8 ++++---- .../ByKeyword/Usecase/SearchByKeywordUseCase.cs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs index 2d1f48c..8249ec5 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs @@ -86,16 +86,16 @@ public SearchServiceAdapterRequest( /// /// /// The keyword string which defines the search. - /// - /// /// - /// The value used to define how many records are skipped in the search response (if any). - /// /// /// The collection of fields in the underlying collection to search over. /// /// /// The collection of facets to apply in the search request. /// + /// + /// + /// The value used to define how many records are skipped in the search response (if any). + /// /// /// A configured instance. /// diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordUseCase.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordUseCase.cs index b3f4345..f48540a 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordUseCase.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordUseCase.cs @@ -76,8 +76,8 @@ public async Task HandleRequest(SearchByKeywordRequest await _searchServiceAdapter.SearchAsync( new SearchServiceAdapterRequest( request.SearchKeyword, - _searchByKeywordCriteriaOptions.SearchFields, - _searchByKeywordCriteriaOptions.Facets, + _searchByKeywordCriteria.SearchFields, + _searchByKeywordCriteria.Facets, request.FilterRequests, request.Offset)); From c3d1a6d0180c5cd26501aad1966c427baa02e272 Mon Sep 17 00:00:00 2001 From: Spencer O'HEGARTY Date: Mon, 4 Nov 2024 10:51:16 +0000 Subject: [PATCH 06/11] Corrected code comment issue --- .../ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs index 8249ec5..04e934d 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs @@ -92,7 +92,6 @@ public SearchServiceAdapterRequest( /// /// The collection of facets to apply in the search request. /// - /// /// /// The value used to define how many records are skipped in the search response (if any). /// From 51bebad2c9defbec842c0de353bcd2059978c7b3 Mon Sep 17 00:00:00 2001 From: Spencer O'HEGARTY Date: Mon, 4 Nov 2024 10:59:51 +0000 Subject: [PATCH 07/11] Removed unused factory method in SearchServiceAdapterRequest --- .../SearchServiceAdapterRequest.cs | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs index 04e934d..c2f9fba 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs @@ -80,25 +80,4 @@ public SearchServiceAdapterRequest( Offset = offset; } - - /// - /// Factory method to allow implicit creation of a T:Dfe.Data.SearchPrototype.Search.SearchContext instance. - /// - /// - /// The keyword string which defines the search. - /// - /// The collection of fields in the underlying collection to search over. - /// - /// - /// The collection of facets to apply in the search request. - /// - /// - /// The value used to define how many records are skipped in the search response (if any). - /// - /// - /// A configured instance. - /// - public static SearchServiceAdapterRequest Create( - string searchKeyword, IList searchFields, IList facets) => - new(searchKeyword, searchFields, facets); } From 818874609226cebea2a3c78b5002322926fc8c54 Mon Sep 17 00:00:00 2001 From: Spencer O'HEGARTY Date: Mon, 4 Nov 2024 11:58:58 +0000 Subject: [PATCH 08/11] Added offset test to search options builder --- .../Tests/Builders/SearchOptionsBuilderTests.cs | 16 ++++++++++++++++ ...ognitiveSearchServiceAdapterAndMapperTests.cs | 12 ++++++------ .../SearchServiceAdapterRequest.cs | 3 ++- .../ByKeyword/Usecase/SearchByKeywordRequest.cs | 3 ++- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Builders/SearchOptionsBuilderTests.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Builders/SearchOptionsBuilderTests.cs index b94a5b0..a79301d 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Builders/SearchOptionsBuilderTests.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Builders/SearchOptionsBuilderTests.cs @@ -29,6 +29,22 @@ public void Build_WithSize_SearchOptionsWithCorrectSize() searchOptions.Size.Should().Be(100); } + [Fact] + public void Build_WithOfset_SearchOptionsWithCorrectOffset() + { + // arrange + ISearchFilterExpressionsBuilder mockSearchFilterExpressionsBuilder = new FilterExpressionBuilderTestDouble().Create(); + + ISearchOptionsBuilder searchOptionsBuilder = new SearchOptionsBuilder(mockSearchFilterExpressionsBuilder); + + // act + SearchOptions searchOptions = searchOptionsBuilder.WithOffset(offset: 39).Build(); + + // assert + searchOptions.Should().NotBeNull(); + searchOptions.Skip.Should().Be(39); + } + [Fact] public void Build_WithSearchMode_SearchOptionsWithCorrectSearchMode() { diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterAndMapperTests.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterAndMapperTests.cs index 368bebe..00eca4e 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterAndMapperTests.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterAndMapperTests.cs @@ -56,9 +56,9 @@ public async Task Search_WithValidSearchContext_ReturnsResults() await cognitiveSearchServiceAdapter.SearchAsync( new SearchServiceAdapterRequest( searchKeyword: "SearchKeyword", - offset: 0, searchFields: ["FIELD1", "FIELD2", "FIELD2"], - facets: ["FACET1", "FACET2", "FACET3"])); + facets: ["FACET1", "FACET2", "FACET3"], + offset: 99)); // assert response.Should().NotBeNull(); @@ -94,9 +94,9 @@ public async Task Search_WithNoFacetsReturned_ReturnsNullFacets() await cognitiveSearchServiceAdapter.SearchAsync( new SearchServiceAdapterRequest( searchKeyword: "SearchKeyword", - offset: 0, searchFields: ["FIELD1", "FIELD2", "FIELD2"], - facets: ["FACET1", "FACET2", "FACET3"])); + facets: ["FACET1", "FACET2", "FACET3"], + offset: 0)); // assert response.Should().NotBeNull(); @@ -129,9 +129,9 @@ public async Task Search_WithNoResultsReturned_ReturnsEmptyResults() await cognitiveSearchServiceAdapter.SearchAsync( new SearchServiceAdapterRequest( searchKeyword: "SearchKeyword", - offset: 0, searchFields: ["FIELD1", "FIELD2", "FIELD2"], - facets: ["FACET1", "FACET2", "FACET3"])); + facets: ["FACET1", "FACET2", "FACET3"], + offset: 0)); // assert response.Should().NotBeNull(); diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs index c2f9fba..6407940 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs @@ -50,7 +50,8 @@ public sealed class SearchServiceAdapterRequest /// Dictionary of search filter requests where key is the name of the filter and the value is the list of filter values. /// /// - /// The value used to define how many records are skipped in the search response (if any). + /// The value used to define how many records are skipped in the search response + /// (if any), by default we choose not to skip any records. /// /// /// The exception thrown if an invalid search keyword (null or whitespace) is prescribed. diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordRequest.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordRequest.cs index 8a72e12..44bd97b 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordRequest.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordRequest.cs @@ -15,7 +15,8 @@ public sealed class SearchByKeywordRequest /// The string keyword used to search the collection specified. /// /// - /// The value used to define how many records are skipped in the search response (if any). + /// The value used to define how many records are skipped in the search + /// response (if any), by default we choose not to skip any records. /// public SearchByKeywordRequest(string searchKeyword, int offset = 0) { From dad53654ed42b78c1c36cfeb01f1dab02807ec01 Mon Sep 17 00:00:00 2001 From: AsiaWi <123170965+AsiaWi@users.noreply.github.com> Date: Mon, 4 Nov 2024 12:04:49 +0000 Subject: [PATCH 09/11] Search Keyword Request Offset Tests --- .../ByKeyword/SearchByKeywordRequestTests.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/SearchByKeywordRequestTests.cs b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/SearchByKeywordRequestTests.cs index 09b41dc..340dcb5 100644 --- a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/SearchByKeywordRequestTests.cs +++ b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/SearchByKeywordRequestTests.cs @@ -37,4 +37,28 @@ public void Constructor_WithNoFilterParam_HasFilterRequestsNull() // assert request.FilterRequests.Should().BeNull(); } + + [Fact] + public void Constructor_WithSetOffsetValue_AssignsCorrectPropertyValue() + { + //arrange + const int Offset = 10; + // act + var request = new SearchByKeywordRequest("searchKeyword", Offset); + + // assert + request.Offset. + Should().Be(Offset); + } + + [Fact] + public void Constructor_WithDefaultOffsetValue_AssignsDefaultPropertyValue() + { + // act + var request = new SearchByKeywordRequest("searchKeyword"); + + // assert + request.Offset. + Should().Be(0);//value of zero ensures no records are skipped + } } From 42de2ec8b132624e72c9165bbeaa8db87d9681e7 Mon Sep 17 00:00:00 2001 From: AsiaWi <123170965+AsiaWi@users.noreply.github.com> Date: Mon, 4 Nov 2024 14:11:54 +0000 Subject: [PATCH 10/11] Add search service adapter request tests --- .../SearchServiceAdapterRequest.cs | 4 +- .../SearchServiceAdapterRequestTests.cs | 108 ++++++++++++++++++ 2 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequestTests.cs diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs index 6407940..2a1916c 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs @@ -72,10 +72,10 @@ public SearchServiceAdapterRequest( throw new ArgumentNullException(nameof(searchKeyword)) : searchKeyword; SearchFields = searchFields == null || searchFields.Count <= 0 ? - throw new ArgumentException("", nameof(searchFields)) : searchFields; + throw new ArgumentException($"A valid {nameof(searchFields)} argument must be provided.") : searchFields; Facets = facets == null || facets.Count <= 0 ? - throw new ArgumentException("", nameof(facets)) : facets; + throw new ArgumentException($"A valid {nameof(facets)} argument must be provided.") : facets; SearchFilterRequests = searchFilterRequests; diff --git a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequestTests.cs b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequestTests.cs new file mode 100644 index 0000000..6e90717 --- /dev/null +++ b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequestTests.cs @@ -0,0 +1,108 @@ +using Dfe.Data.SearchPrototype.SearchForEstablishments.ByKeyword.ServiceAdapters; +using Dfe.Data.SearchPrototype.SearchForEstablishments.ByKeyword.Usecase; +using FluentAssertions; +using Xunit; + +namespace Dfe.Data.SearchPrototype.Tests.SearchForEstablishments.ByKeyword.ServiceAdapters; + +public class SearchServiceAdapterRequestTests +{ + [Fact] + public void Constructor_WithNoFilterParam_AssignsCorrectPropertyValues() + { + // act + var request = new SearchServiceAdapterRequest( + searchKeyword:"searchKeyword", + searchFields:["ESTABLISHMENTNAME", "TOWN"], + facets:["facet1", "facet2"], + offset: 10 + ); + + // assert + request.SearchKeyword.Should().Be("searchKeyword"); + request.SearchFields[0].Should().Be("ESTABLISHMENTNAME"); + request.SearchFields.Count().Should().Be(2); + request.Facets.Contains("facet2"); + request.Facets.Should().HaveCount(2); + request.Offset.Should().Be(10); + request.SearchFilterRequests.Should().BeNull(); + } + + [Fact] + public void Constructor_WithFilterParam_PopulatesFilterRequests() + { + // arrange + var filterList = new List() + { + new FilterRequest("EducationPhase", new List() {"Primary", "Secondary"}), + new FilterRequest("MaybeATypeCode", new List() {1,2}) + }; + + // act + var request = new SearchServiceAdapterRequest( + searchKeyword :"searchKeyword", + searchFields: ["ESTABLISHMENTNAME", "TOWN"], + facets: ["facet1", "facet2"], + searchFilterRequests: filterList + ); + + // assert + request.SearchFilterRequests.Should().NotBeNull(); + foreach (var item in filterList) + { + var matchingRequest = request.SearchFilterRequests!.First(x => x.FilterName == item.FilterName); + matchingRequest.FilterValues.Should().BeEquivalentTo(item.FilterValues); + } + } + + [Fact] + public void Constructor_WithNullSearchKeyword_ThrowsArgumentNullException() + { + // act + Action request =() => new SearchServiceAdapterRequest( + searchKeyword: "", + searchFields : ["ESTABLISHMENTNAME", "TOWN"], + facets : ["facet1", "facet2"], + offset: 10 + ); + + // assert + request.Should().Throw() + .WithMessage("Value cannot be null. (Parameter 'searchKeyword')") + .And.ParamName.Should().Be("searchKeyword"); + } + + [Fact] + public void Constructor_WithNullSearchFields_ThrowsArgumentException() + { + // act + Action request = () => + new SearchServiceAdapterRequest( + searchKeyword: "searchKeyword", + searchFields: null!, + facets: ["facet1", "facet2"], + offset: 10 + ); + + // assert + request.Should().Throw() + .WithMessage("A valid searchFields argument must be provided."); + } + + [Fact] + public void Constructor_WithNullFacets_ThrowsArgumentException() + { + // act + Action request = () => + new SearchServiceAdapterRequest( + searchKeyword:"searchKeyword", + searchFields: ["ESTABLISHMENTNAME", "TOWN"], + facets: null!, + offset: 10 + ); + + // assert + request.Should().Throw() + .WithMessage("A valid facets argument must be provided."); + } +} From a01d572b498f217f1d41140c3feef3af6e632b87 Mon Sep 17 00:00:00 2001 From: Spencer O'HEGARTY Date: Tue, 5 Nov 2024 14:32:05 +0000 Subject: [PATCH 11/11] Changes made due to PR feedback --- .../Builders/ISearchOptionsBuilder.cs | 7 ++++--- .../Builders/SearchOptionsBuilder.cs | 9 +++++---- .../CognitiveSearchServiceAdapter.cs | 10 +++++----- .../Infrastructure/CompositionRoot.cs | 2 +- ...eSearchResultsToEstablishmentResultsMapper.cs | 12 ++++++------ .../Tests/Builders/SearchOptionsBuilderTests.cs | 2 +- ...ognitiveSearchServiceAdapterAndMapperTests.cs | 2 +- .../Tests/CognitiveSearchServiceAdapterTests.cs | 4 ++-- ...chResultsToEstablishmentResultsMapperTests.cs | 14 ++++++++------ ...ultsToEstablishmentResultsMapperTestDouble.cs | 16 ++++++++-------- .../SearchOptionsBuilderTestDouble.cs | 4 ++-- .../SearchServiceAdapterRequest.cs | 5 +++-- .../Models/EstablishmentResults.cs | 13 +------------ .../Models/SearchResults.cs | 6 ++++++ .../EstablishmentResultsTestDouble.cs | 2 +- 15 files changed, 54 insertions(+), 54 deletions(-) diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Builders/ISearchOptionsBuilder.cs b/Dfe.Data.SearchPrototype/Infrastructure/Builders/ISearchOptionsBuilder.cs index 40f0ece..85dbc00 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Builders/ISearchOptionsBuilder.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Builders/ISearchOptionsBuilder.cs @@ -23,15 +23,16 @@ public interface ISearchOptionsBuilder /// /// Sets the value used to define how many - /// records are skipped in the search response (if any). + /// records are skipped in the search response (if any), + /// by default we have an offset of zero and so choose not to skip any records. /// /// - /// The number of initial search results to skip. + /// The number of initial search results to skip, none (zero) by default . /// /// /// The updated builder instance. /// - ISearchOptionsBuilder WithOffset(int? offset); + ISearchOptionsBuilder WithOffset(int offset = 0); /// /// Sets the mode of search to invoke, i.e. All or Any. diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Builders/SearchOptionsBuilder.cs b/Dfe.Data.SearchPrototype/Infrastructure/Builders/SearchOptionsBuilder.cs index 214f540..bfb43f1 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Builders/SearchOptionsBuilder.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Builders/SearchOptionsBuilder.cs @@ -16,7 +16,7 @@ public sealed class SearchOptionsBuilder : ISearchOptionsBuilder private SearchMode? _searchMode; private int? _size; - private int? _offset; + private int _offset; private bool? _includeTotalCount; private IList? _searchFields; private IList? _facets; @@ -56,15 +56,16 @@ public ISearchOptionsBuilder WithSize(int? size) /// /// Sets the value used to define how many - /// records are skipped in the search response (if any). + /// records are skipped in the search response (if any), + /// by default we have an offset of zero and so choose not to skip any records. /// /// - /// The number of initial search results to skip. + /// The number of initial search results to skip, none (zero) by default . /// /// /// The updated builder instance. /// - public ISearchOptionsBuilder WithOffset(int? offset) + public ISearchOptionsBuilder WithOffset(int offset = 0) { _offset = offset; return this; diff --git a/Dfe.Data.SearchPrototype/Infrastructure/CognitiveSearchServiceAdapter.cs b/Dfe.Data.SearchPrototype/Infrastructure/CognitiveSearchServiceAdapter.cs index 82f56c5..8cfe377 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/CognitiveSearchServiceAdapter.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/CognitiveSearchServiceAdapter.cs @@ -19,7 +19,7 @@ namespace Dfe.Data.SearchPrototype.Infrastructure; public sealed class CognitiveSearchServiceAdapter : ISearchServiceAdapter where TSearchResult : class { private readonly ISearchByKeywordService _searchByKeywordService; - private readonly IMapper<(Pageable>, long?), EstablishmentResults> _searchResultMapper; + private readonly IMapper>, EstablishmentResults> _searchResultMapper; private readonly IMapper>, EstablishmentFacets> _facetsMapper; private readonly AzureSearchOptions _azureSearchOptions; private readonly ISearchOptionsBuilder _searchOptionsBuilder; @@ -46,7 +46,7 @@ public sealed class CognitiveSearchServiceAdapter : ISearchServic public CognitiveSearchServiceAdapter( ISearchByKeywordService searchByKeywordService, IOptions azureSearchOptions, - IMapper<(Pageable>, long?), EstablishmentResults> searchResultMapper, + IMapper>, EstablishmentResults> searchResultMapper, IMapper>, EstablishmentFacets> facetsMapper, ISearchOptionsBuilder searchOptionsBuilder) { @@ -102,11 +102,11 @@ await _searchByKeywordService.SearchAsync( var results = new SearchResults() { Establishments = - _searchResultMapper.MapFrom( - (searchResults.Value.GetResults(), searchResults.Value.TotalCount)), + _searchResultMapper.MapFrom(searchResults.Value.GetResults()), Facets = searchResults.Value.Facets != null ? _facetsMapper.MapFrom(searchResults.Value.Facets.ToDictionary()) - : null + : null, + TotalNumberOfEstablishments = searchResults.Value.TotalCount }; return results; diff --git a/Dfe.Data.SearchPrototype/Infrastructure/CompositionRoot.cs b/Dfe.Data.SearchPrototype/Infrastructure/CompositionRoot.cs index c2a569a..e4ed4b1 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/CompositionRoot.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/CompositionRoot.cs @@ -44,7 +44,7 @@ public static void AddCognitiveSearchAdaptorServices(this IServiceCollection ser services.AddScoped(typeof(ISearchServiceAdapter), typeof(CognitiveSearchServiceAdapter)); services.AddScoped(); - services.AddSingleton(typeof(IMapper<(Pageable>, long?), EstablishmentResults>), typeof(PageableSearchResultsToEstablishmentResultsMapper)); + services.AddSingleton(typeof(IMapper>, EstablishmentResults>), typeof(PageableSearchResultsToEstablishmentResultsMapper)); services.AddSingleton>, EstablishmentFacets>, AzureFacetResultToEstablishmentFacetsMapper>(); services.AddSingleton, AzureSearchResultToAddressMapper>(); services.AddSingleton, AzureSearchResultToEstablishmentMapper>(); diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Mappers/PageableSearchResultsToEstablishmentResultsMapper.cs b/Dfe.Data.SearchPrototype/Infrastructure/Mappers/PageableSearchResultsToEstablishmentResultsMapper.cs index 1e5f163..3ba464e 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Mappers/PageableSearchResultsToEstablishmentResultsMapper.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Mappers/PageableSearchResultsToEstablishmentResultsMapper.cs @@ -7,10 +7,10 @@ namespace Dfe.Data.SearchPrototype.Infrastructure.Mappers; /// -/// Facilitates mapping from the received +/// Facilitates mapping from the received /// into the required object. /// -public sealed class PageableSearchResultsToEstablishmentResultsMapper : IMapper<(Pageable>, long?), Models.EstablishmentResults> +public sealed class PageableSearchResultsToEstablishmentResultsMapper : IMapper>, Models.EstablishmentResults> { private readonly IMapper _azureSearchResultToEstablishmentMapper; @@ -44,21 +44,21 @@ public PageableSearchResultsToEstablishmentResultsMapper(IMapper /// Exception thrown if the data cannot be mapped /// - public Models.EstablishmentResults MapFrom((Pageable>, long?) input) + public Models.EstablishmentResults MapFrom(Pageable> input) { ArgumentNullException.ThrowIfNull(input); Models.EstablishmentResults establishmentResults = new(); - if (input.Item1.Any()) + if (input.Any()) { - var mappedResults = input.Item1.Select(result => + var mappedResults = input.Select(result => result.Document != null ? _azureSearchResultToEstablishmentMapper.MapFrom(result.Document) : throw new InvalidOperationException( "Search result document object cannot be null.")); establishmentResults = - new Models.EstablishmentResults(mappedResults, input.Item2); + new Models.EstablishmentResults(mappedResults); } return establishmentResults; diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Builders/SearchOptionsBuilderTests.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Builders/SearchOptionsBuilderTests.cs index a79301d..c7095a0 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Builders/SearchOptionsBuilderTests.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Builders/SearchOptionsBuilderTests.cs @@ -30,7 +30,7 @@ public void Build_WithSize_SearchOptionsWithCorrectSize() } [Fact] - public void Build_WithOfset_SearchOptionsWithCorrectOffset() + public void Build_WithOffset_SearchOptionsWithCorrectOffset() { // arrange ISearchFilterExpressionsBuilder mockSearchFilterExpressionsBuilder = new FilterExpressionBuilderTestDouble().Create(); diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterAndMapperTests.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterAndMapperTests.cs index 00eca4e..dc63627 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterAndMapperTests.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterAndMapperTests.cs @@ -14,7 +14,7 @@ namespace Dfe.Data.SearchPrototype.Infrastructure.Tests; public sealed class CognitiveSearchServiceAdapterAndMapperTests { - private readonly IMapper<(Pageable>, long?), EstablishmentResults> _searchResponseMapper; + private readonly IMapper>, EstablishmentResults> _searchResponseMapper; private readonly IMapper>, EstablishmentFacets> _facetsMapper; private readonly ISearchOptionsBuilder _searchOptionsBuilder = SearchOptionsBuilderTestDouble.MockFor(new Azure.Search.Documents.SearchOptions()); diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs index 98e2974..b15d4ad 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs @@ -21,7 +21,7 @@ public sealed class CognitiveSearchServiceAdapterTests private readonly IMapper>, EstablishmentFacets> _mockFacetsMapper = AzureFacetResultToEstablishmentFacetsMapperTestDouble.DefaultMock(); private readonly AzureSearchOptions _options = AzureSearchOptionsTestDouble.Stub(); - private readonly IMapper<(Pageable>, long?), EstablishmentResults> _mockEstablishmentResultsMapper + private readonly IMapper>, EstablishmentResults> _mockEstablishmentResultsMapper = PageableSearchResultsToEstablishmentResultsMapperTestDouble.DefaultMock(); private readonly ISearchByKeywordService _mockSearchService; private readonly ISearchOptionsBuilder _mockSearchOptionsBuilder = @@ -30,7 +30,7 @@ public sealed class CognitiveSearchServiceAdapterTests private static CognitiveSearchServiceAdapter CreateServiceAdapterWith( ISearchByKeywordService searchByKeywordService, IOptions searchOptions, - IMapper<(Pageable>, long?), EstablishmentResults> searchResponseMapper, + IMapper>, EstablishmentResults> searchResponseMapper, IMapper>, EstablishmentFacets> facetsMapper, ISearchOptionsBuilder searchOptionsBuilder ) => diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/PageableSearchResultsToEstablishmentResultsMapperTests.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/PageableSearchResultsToEstablishmentResultsMapperTests.cs index 36c3aea..266ab84 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/PageableSearchResultsToEstablishmentResultsMapperTests.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/PageableSearchResultsToEstablishmentResultsMapperTests.cs @@ -12,7 +12,7 @@ namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.Mappers; public sealed class PageableSearchResultsToEstablishmentResultsMapperTests { - IMapper<(Pageable>, long?), EstablishmentResults> _searchResultsMapper; + IMapper>, EstablishmentResults> _searchResultsMapper; public PageableSearchResultsToEstablishmentResultsMapperTests() { @@ -32,7 +32,7 @@ public void MapFrom_WithValidSearchResults_ReturnsConfiguredEstablishments() var pageableSearchResults = PageableTestDouble.FromResults(searchResultDocuments); // act - EstablishmentResults? mappedResult = _searchResultsMapper.MapFrom((pageableSearchResults, 100)); + EstablishmentResults? mappedResult = _searchResultsMapper.MapFrom(pageableSearchResults); // assert mappedResult.Should().NotBeNull(); @@ -48,7 +48,9 @@ public void MapFrom_WithValidSearchResults_ReturnsConfiguredEstablishments() public void MapFrom_WithEmptySearchResults_ReturnsEmptyList() { // act - EstablishmentResults? result = _searchResultsMapper.MapFrom((PageableTestDouble.FromResults(SearchResultFake.EmptySearchResult()), 100)); + EstablishmentResults? result = + _searchResultsMapper.MapFrom( + PageableTestDouble.FromResults(SearchResultFake.EmptySearchResult())); // assert result.Should().NotBeNull(); @@ -61,10 +63,10 @@ public void MapFrom_WithNullSearchResults_ThrowsArgumentNullException() // act. _searchResultsMapper .Invoking(mapper => - mapper.MapFrom((null!, 0))) + mapper.MapFrom(null!)) .Should() .Throw() - .WithMessage("Value cannot be null. (Parameter 'source')"); + .WithMessage("Value cannot be null. (Parameter 'input')"); } [Fact] @@ -78,7 +80,7 @@ public void MapFrom_WithANullSearchResult_ThrowsInvalidOperationException() // act. _searchResultsMapper .Invoking(mapper => - mapper.MapFrom((PageableTestDouble.FromResults(searchResultDocuments), 100))) + mapper.MapFrom(PageableTestDouble.FromResults(searchResultDocuments))) .Should() .Throw() .WithMessage("Search result document object cannot be null."); diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/PageableSearchResultsToEstablishmentResultsMapperTestDouble.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/PageableSearchResultsToEstablishmentResultsMapperTestDouble.cs index 667f735..5ec8f79 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/PageableSearchResultsToEstablishmentResultsMapperTestDouble.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/PageableSearchResultsToEstablishmentResultsMapperTestDouble.cs @@ -9,24 +9,24 @@ namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles; internal static class PageableSearchResultsToEstablishmentResultsMapperTestDouble { - public static IMapper<(Pageable>, long?), EstablishmentResults> DefaultMock() => - Mock.Of>, long?), EstablishmentResults>>(); + public static IMapper>, EstablishmentResults> DefaultMock() => + Mock.Of>, EstablishmentResults>>(); - public static Expression>, long?), EstablishmentResults>, EstablishmentResults>> MapFrom() => - mapper => mapper.MapFrom(It.IsAny<(Pageable>, long ?)>()); + public static Expression>, EstablishmentResults>, EstablishmentResults>> MapFrom() => + mapper => mapper.MapFrom(It.IsAny>>()); - public static IMapper<(Pageable>, long?), EstablishmentResults> MockFor(EstablishmentResults establishments) + public static IMapper>, EstablishmentResults> MockFor(EstablishmentResults establishments) { - var mapperMock = new Mock>, long?), EstablishmentResults>>(); + var mapperMock = new Mock>, EstablishmentResults>>(); mapperMock.Setup(MapFrom()).Returns(establishments); return mapperMock.Object; } - public static IMapper<(Pageable>, long?), EstablishmentResults> MockMapperThrowingArgumentException() + public static IMapper>, EstablishmentResults> MockMapperThrowingArgumentException() { - var mapperMock = new Mock>, long?), EstablishmentResults>>(); + var mapperMock = new Mock>, EstablishmentResults>>(); mapperMock.Setup(MapFrom()).Throws(new ArgumentException()); diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchOptionsBuilderTestDouble.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchOptionsBuilderTestDouble.cs index af4ce9c..04d6a44 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchOptionsBuilderTestDouble.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchOptionsBuilderTestDouble.cs @@ -13,9 +13,9 @@ public static ISearchOptionsBuilder MockFor(SearchOptions searchOptions) var mockSearchOptionsBuilder = new Mock(); mockSearchOptionsBuilder.Setup(searchOptionsBuilder => - searchOptionsBuilder.WithSize(It.IsAny())).Returns(mockSearchOptionsBuilder.Object); + searchOptionsBuilder.WithSize(It.IsAny())).Returns(mockSearchOptionsBuilder.Object); mockSearchOptionsBuilder.Setup(searchOptionsBuilder => - searchOptionsBuilder.WithOffset(It.IsAny())).Returns(mockSearchOptionsBuilder.Object); + searchOptionsBuilder.WithOffset(It.IsAny())).Returns(mockSearchOptionsBuilder.Object); mockSearchOptionsBuilder.Setup(searchOptionsBuilder => searchOptionsBuilder.WithSearchMode(It.IsAny())).Returns(mockSearchOptionsBuilder.Object); mockSearchOptionsBuilder.Setup(searchOptionsBuilder => diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs index 6407940..8a21dde 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs @@ -14,9 +14,10 @@ public sealed class SearchServiceAdapterRequest public string SearchKeyword { get; } /// - /// The value used to define how many records are skipped in the search response (if any). + /// The value used to define how many records are skipped in the search response (if any), + /// by default we have an offset of zero and so choose not to skip any records. /// - public int Offset { get; } + public int Offset { get; } = 0; /// /// The collection of fields in the underlying collection to search over. diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentResults.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentResults.cs index 490b5d0..7d7bda1 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentResults.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentResults.cs @@ -14,12 +14,6 @@ public sealed class EstablishmentResults /// public IReadOnlyCollection Establishments => _establishments.AsReadOnly(); - /// - /// The Total Count returned from Establishment search gives us a total - /// of all available records which correlates with the given search criteria. - /// - public long? TotalNumberOfEstablishments { get; } - /// /// Default constructor initialises a new readonly /// collection of instances. @@ -36,13 +30,8 @@ public EstablishmentResults() /// /// Collection of configured instances. /// - /// - /// The Total Count returned from Establishment search gives us a total - /// of all available records which correlates with the given search criteria. - /// - public EstablishmentResults(IEnumerable establishments, long? totalNumberOfEstablishments) + public EstablishmentResults(IEnumerable establishments) { _establishments = establishments.ToList(); - TotalNumberOfEstablishments = totalNumberOfEstablishments; } } \ No newline at end of file diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/SearchResults.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/SearchResults.cs index 86a77dc..8653abf 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/SearchResults.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/SearchResults.cs @@ -19,4 +19,10 @@ public class SearchResults /// that is built from the underlying search response. /// public EstablishmentFacets? Facets { get; init; } + + /// + /// The Total Count returned from Establishment search gives us a total + /// of all available records which correlates with the given search criteria. + /// + public long? TotalNumberOfEstablishments { get; init; } } diff --git a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/TestDoubles/EstablishmentResultsTestDouble.cs b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/TestDoubles/EstablishmentResultsTestDouble.cs index 1b6fb68..a9ec567 100644 --- a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/TestDoubles/EstablishmentResultsTestDouble.cs +++ b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/TestDoubles/EstablishmentResultsTestDouble.cs @@ -13,7 +13,7 @@ public static EstablishmentResults Create() establishments.Add( EstablishmentTestDouble.Create()); } - return new EstablishmentResults(establishments, 100); + return new EstablishmentResults(establishments); } public static EstablishmentResults CreateWithNoResults()