diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Builders/ISearchOptionsBuilder.cs b/Dfe.Data.SearchPrototype/Infrastructure/Builders/ISearchOptionsBuilder.cs
new file mode 100644
index 0000000..e409e45
--- /dev/null
+++ b/Dfe.Data.SearchPrototype/Infrastructure/Builders/ISearchOptionsBuilder.cs
@@ -0,0 +1,88 @@
+using Azure.Search.Documents;
+using Azure.Search.Documents.Models;
+using Dfe.Data.SearchPrototype.SearchForEstablishments.ByKeyword.Usecase;
+
+namespace Dfe.Data.SearchPrototype.Infrastructure.Builders
+{
+ ///
+ /// Provides an abstraction on which to establish the behaviour
+ /// used to build configured instances.
+ ///
+ public interface ISearchOptionsBuilder
+ {
+ ///
+ /// Sets the number of search items to retrieve.
+ ///
+ ///
+ /// The number of results to return as specified.
+ ///
+ ///
+ /// The updated builder instance.
+ ///
+ ISearchOptionsBuilder WithSize(int? size);
+
+ ///
+ /// Sets the mode of search to invoke, i.e. All or Any.
+ ///
+ ///
+ /// The mode of search to invoke, i.e. any search terms may match (Any),
+ /// or all search terms must match (All).
+ ///
+ ///
+ /// The updated builder instance.
+ ///
+ ISearchOptionsBuilder WithSearchMode(SearchMode searchMode);
+
+ ///
+ /// Sets the option to include the total search results count in the search response.
+ ///
+ ///
+ /// The boolean value used to instruct the total count to be added to the response, or otherwise.
+ ///
+ ///
+ /// The updated builder instance.
+ ///
+ ISearchOptionsBuilder WithIncludeTotalCount(bool? includeTotalCount);
+
+ ///
+ /// Sets the fields on which to establish the search.
+ ///
+ ///
+ /// List of fields over which to specify the search.
+ ///
+ ///
+ /// The updated builder instance.
+ ///
+ ISearchOptionsBuilder WithSearchFields(IList? searchFields);
+
+ ///
+ /// Sets the facets to include in the search response.
+ ///
+ ///
+ /// List of facets to include in the search response.
+ ///
+ ///
+ /// The updated builder instance.
+ ///
+ ISearchOptionsBuilder WithFacets(IList? facets);
+
+ ///
+ /// Sets the filters on which to establish the search.
+ ///
+ ///
+ /// List of filters on which to establish the search.
+ ///
+ ///
+ /// The updated builder instance.
+ ///
+ ISearchOptionsBuilder WithFilters(IList? filters);
+
+ ///
+ /// Builds the configured instance of the type requested.
+ ///
+ ///
+ /// The fully configured (built) instance.
+ ///
+ SearchOptions Build();
+ }
+}
diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Builders/SearchOptionsBuilder.cs b/Dfe.Data.SearchPrototype/Infrastructure/Builders/SearchOptionsBuilder.cs
new file mode 100644
index 0000000..980d74b
--- /dev/null
+++ b/Dfe.Data.SearchPrototype/Infrastructure/Builders/SearchOptionsBuilder.cs
@@ -0,0 +1,154 @@
+using Azure.Search.Documents;
+using Azure.Search.Documents.Models;
+using Dfe.Data.Common.Infrastructure.CognitiveSearch.Filtering;
+using Dfe.Data.SearchPrototype.SearchForEstablishments.ByKeyword.Usecase;
+
+namespace Dfe.Data.SearchPrototype.Infrastructure.Builders
+{
+ ///
+ /// Provides a concrete implementation of the abstraction,
+ /// which establishes a configured instance which conforms to the prescribed behaviour.
+ ///
+ public sealed class SearchOptionsBuilder : ISearchOptionsBuilder
+ {
+ private readonly SearchOptions _searchOptions;
+ private readonly ISearchFilterExpressionsBuilder _searchFilterExpressionsBuilder;
+
+ private SearchMode? _searchMode;
+ private int? _size;
+ private bool? _includeTotalCount;
+ private IList? _searchFields;
+ private IList? _facets;
+ private IList? _filters;
+
+ ///
+ /// The following dependency provides the
+ /// behaviour on which to generate fully configured, search filter string expressions based
+ /// on the provisioned request, the complete implementation of which is defined in the IOC container.
+ ///
+ ///
+ /// Builds the search filter expression required by Azure AI Search
+ ///
+ public SearchOptionsBuilder(ISearchFilterExpressionsBuilder searchFilterExpressionsBuilder)
+ {
+ _searchFilterExpressionsBuilder = searchFilterExpressionsBuilder;
+ _searchOptions = new SearchOptions();
+ }
+
+ ///
+ /// Sets the number of search items to retrieve.
+ ///
+ ///
+ /// The number of results to return as specified.
+ ///
+ ///
+ /// The updated builder instance.
+ ///
+ public ISearchOptionsBuilder WithSize(int? size)
+ {
+ _size = size;
+ return this;
+ }
+
+ ///
+ /// Sets the mode of search to invoke, i.e. All or Any.
+ ///
+ ///
+ /// The mode of search to invoke, i.e. any search terms may match (Any),
+ /// or all search terms must match (All).
+ ///
+ ///
+ /// The updated builder instance.
+ ///
+ public ISearchOptionsBuilder WithSearchMode(SearchMode searchMode)
+ {
+ _searchMode = searchMode;
+ return this;
+ }
+
+ ///
+ /// Sets the option to include the total search results count in the search response.
+ ///
+ ///
+ /// The boolean value used to instruct the total count to be added to the response, or otherwise.
+ ///
+ ///
+ /// The updated builder instance.
+ ///
+ public ISearchOptionsBuilder WithIncludeTotalCount(bool? includeTotalCount)
+ {
+ _includeTotalCount = includeTotalCount;
+ return this;
+ }
+
+ ///
+ /// Sets the fields on which to establish the search.
+ ///
+ ///
+ /// List of fields over which to specify the search.
+ ///
+ ///
+ /// The updated builder instance.
+ ///
+ public ISearchOptionsBuilder WithSearchFields(IList? searchFields)
+ {
+ _searchFields = searchFields;
+ return this;
+ }
+
+ ///
+ /// Sets the facets to include in the search response.
+ ///
+ ///
+ /// List of facets to include in the search response.
+ ///
+ ///
+ /// The updated builder instance.
+ ///
+ public ISearchOptionsBuilder WithFacets(IList? facets)
+ {
+ _facets = facets;
+ return this;
+ }
+
+ ///
+ /// Sets the filters on which to establish the search.
+ ///
+ ///
+ /// List of filters on which to establish the search.
+ ///
+ ///
+ /// The updated builder instance.
+ ///
+ public ISearchOptionsBuilder WithFilters(IList? filters)
+ {
+ _filters = filters;
+ return this;
+ }
+
+ ///
+ /// Builds the configured instance of the type requested.
+ ///
+ ///
+ /// The fully configured (built) instance.
+ ///
+ public SearchOptions Build()
+ {
+ _searchOptions.SearchMode = _searchMode;
+ _searchOptions.Size = _size;
+ _searchOptions.IncludeTotalCount = _includeTotalCount;
+ _searchFields?.ToList().ForEach(_searchOptions.SearchFields.Add);
+ _facets?.ToList().ForEach(_searchOptions.Facets.Add);
+
+ if (_filters?.Count > 0)
+ {
+ _searchOptions.Filter =
+ _searchFilterExpressionsBuilder.BuildSearchFilterExpressions(
+ _filters.Select(filterRequest =>
+ new SearchFilterRequest(filterRequest.FilterName, filterRequest.FilterValues)));
+ }
+
+ return _searchOptions;
+ }
+ }
+}
diff --git a/Dfe.Data.SearchPrototype/Infrastructure/CognitiveSearchServiceAdapter.cs b/Dfe.Data.SearchPrototype/Infrastructure/CognitiveSearchServiceAdapter.cs
index a6bccc0..249be71 100644
--- a/Dfe.Data.SearchPrototype/Infrastructure/CognitiveSearchServiceAdapter.cs
+++ b/Dfe.Data.SearchPrototype/Infrastructure/CognitiveSearchServiceAdapter.cs
@@ -1,9 +1,9 @@
using Azure;
using Azure.Search.Documents;
using Azure.Search.Documents.Models;
-using Dfe.Data.Common.Infrastructure.CognitiveSearch.Filtering;
using Dfe.Data.Common.Infrastructure.CognitiveSearch.SearchByKeyword;
using Dfe.Data.SearchPrototype.Common.Mappers;
+using Dfe.Data.SearchPrototype.Infrastructure.Builders;
using Dfe.Data.SearchPrototype.Infrastructure.Options;
using Dfe.Data.SearchPrototype.SearchForEstablishments.ByKeyword.ServiceAdapters;
using Dfe.Data.SearchPrototype.SearchForEstablishments.Models;
@@ -19,10 +19,10 @@ 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>, EstablishmentResults> _searchResultMapper;
private readonly IMapper>, EstablishmentFacets> _facetsMapper;
private readonly AzureSearchOptions _azureSearchOptions;
- private readonly ISearchFilterExpressionsBuilder _searchFilterExpressionsBuilder;
+ private readonly ISearchOptionsBuilder _searchOptionsBuilder;
///
/// The following dependencies include the core cognitive search service definition,
@@ -40,22 +40,22 @@ public sealed class CognitiveSearchServiceAdapter : ISearchServic
///
/// Maps the raw Azure search response to the required
///
- ///
- /// Builds the search filter expression required by Azure AI Search
+ ///
+ /// Builds the search options by Azure AI Search
///
public CognitiveSearchServiceAdapter(
ISearchByKeywordService searchByKeywordService,
IOptions azureSearchOptions,
- IMapper>, EstablishmentResults> searchResultMapper,
+ IMapper>, EstablishmentResults> searchResultMapper,
IMapper>, EstablishmentFacets> facetsMapper,
- ISearchFilterExpressionsBuilder searchFilterExpressionsBuilder)
+ ISearchOptionsBuilder searchOptionsBuilder)
{
ArgumentNullException.ThrowIfNull(azureSearchOptions.Value);
_azureSearchOptions = azureSearchOptions.Value;
_searchByKeywordService = searchByKeywordService;
_searchResultMapper = searchResultMapper;
_facetsMapper = facetsMapper;
- _searchFilterExpressionsBuilder = searchFilterExpressionsBuilder;
+ _searchOptionsBuilder = searchOptionsBuilder;
}
///
@@ -78,25 +78,15 @@ public CognitiveSearchServiceAdapter(
///
public async Task SearchAsync(SearchServiceAdapterRequest searchServiceAdapterRequest)
{
- SearchOptions searchOptions = new()
- {
- SearchMode = (SearchMode)_azureSearchOptions.SearchMode,
- Size = _azureSearchOptions.Size,
- IncludeTotalCount = _azureSearchOptions.IncludeTotalCount,
- };
-
- searchServiceAdapterRequest.SearchFields?.ToList()
- .ForEach(searchOptions.SearchFields.Add);
-
- searchServiceAdapterRequest.Facets?.ToList()
- .ForEach(searchOptions.Facets.Add);
-
- if (searchServiceAdapterRequest.SearchFilterRequests?.Count > 0)
- {
- searchOptions.Filter = _searchFilterExpressionsBuilder.BuildSearchFilterExpressions(
- searchServiceAdapterRequest.SearchFilterRequests
- .Select(filterRequest => new SearchFilterRequest(filterRequest.FilterName, filterRequest.FilterValues)));
- }
+ SearchOptions searchOptions =
+ _searchOptionsBuilder
+ .WithSearchMode((SearchMode)_azureSearchOptions.SearchMode)
+ .WithSize(_azureSearchOptions.Size)
+ .WithIncludeTotalCount(_azureSearchOptions.IncludeTotalCount)
+ .WithSearchFields(searchServiceAdapterRequest.SearchFields)
+ .WithFacets(searchServiceAdapterRequest.Facets)
+ .WithFilters(searchServiceAdapterRequest.SearchFilterRequests)
+ .Build();
Response> searchResults =
await _searchByKeywordService.SearchAsync(
diff --git a/Dfe.Data.SearchPrototype/Infrastructure/CompositionRoot.cs b/Dfe.Data.SearchPrototype/Infrastructure/CompositionRoot.cs
index d61fc43..7d6205c 100644
--- a/Dfe.Data.SearchPrototype/Infrastructure/CompositionRoot.cs
+++ b/Dfe.Data.SearchPrototype/Infrastructure/CompositionRoot.cs
@@ -1,6 +1,7 @@
using Azure;
using Azure.Search.Documents.Models;
using Dfe.Data.SearchPrototype.Common.Mappers;
+using Dfe.Data.SearchPrototype.Infrastructure.Builders;
using Dfe.Data.SearchPrototype.Infrastructure.Mappers;
using Dfe.Data.SearchPrototype.Infrastructure.Options;
using Dfe.Data.SearchPrototype.SearchForEstablishments.ByKeyword.ServiceAdapters;
@@ -52,6 +53,7 @@ public static void AddCognitiveSearchAdaptorServices(this IServiceCollection ser
.Bind(settings));
services.AddScoped(typeof(ISearchServiceAdapter), typeof(CognitiveSearchServiceAdapter));
+ services.AddScoped();
services.AddSingleton(typeof(IMapper>, EstablishmentResults>), typeof(PageableSearchResultsToEstablishmentResultsMapper));
services.AddSingleton>, EstablishmentFacets>, AzureFacetResultToEstablishmentFacetsMapper>();
services.AddSingleton, AzureSearchResultToAddressMapper>();
diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Builders/SearchOptionsBuilderTests.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Builders/SearchOptionsBuilderTests.cs
new file mode 100644
index 0000000..141efa6
--- /dev/null
+++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Builders/SearchOptionsBuilderTests.cs
@@ -0,0 +1,135 @@
+using Azure.Search.Documents;
+using Azure.Search.Documents.Models;
+using Dfe.Data.Common.Infrastructure.CognitiveSearch.Filtering;
+using Dfe.Data.SearchPrototype.Infrastructure.Builders;
+using Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles;
+using Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles.Shared;
+using Dfe.Data.SearchPrototype.SearchForEstablishments.ByKeyword.Usecase;
+using FluentAssertions;
+using Moq;
+using Xunit;
+
+namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.Builders
+{
+ public sealed class SearchOptionsBuilderTests
+ {
+ [Fact]
+ public void Build_WithSize_SearchOptionsWithCorrectSize()
+ {
+ // arrange
+ ISearchFilterExpressionsBuilder mockSearchFilterExpressionsBuilder = new FilterExpressionBuilderTestDouble().Create();
+
+ ISearchOptionsBuilder searchOptionsBuilder = new SearchOptionsBuilder(mockSearchFilterExpressionsBuilder);
+
+ // act
+ SearchOptions searchOptions = searchOptionsBuilder.WithSize(size:100).Build();
+
+ // assert
+ searchOptions.Should().NotBeNull();
+ searchOptions.Size.Should().Be(100);
+ }
+
+ [Fact]
+ public void Build_WithSearchMode_SearchOptionsWithCorrectSearchMode()
+ {
+ // arrange
+ ISearchFilterExpressionsBuilder mockSearchFilterExpressionsBuilder = new FilterExpressionBuilderTestDouble().Create();
+
+ ISearchOptionsBuilder searchOptionsBuilder = new SearchOptionsBuilder(mockSearchFilterExpressionsBuilder);
+
+ // act
+ SearchOptions searchOptions = searchOptionsBuilder.WithSearchMode(searchMode: SearchMode.Any).Build();
+
+ // assert
+ searchOptions.Should().NotBeNull();
+ searchOptions.SearchMode.Should().Be(SearchMode.Any);
+ }
+
+ [Fact]
+ public void Build_WithIncludeTotalCount_SearchOptionsWithIncludeTotalCount()
+ {
+ // arrange
+ ISearchFilterExpressionsBuilder mockSearchFilterExpressionsBuilder = new FilterExpressionBuilderTestDouble().Create();
+
+ ISearchOptionsBuilder searchOptionsBuilder = new SearchOptionsBuilder(mockSearchFilterExpressionsBuilder);
+
+ // act
+ SearchOptions searchOptions = searchOptionsBuilder.WithIncludeTotalCount(includeTotalCount: true).Build();
+
+ // assert
+ searchOptions.Should().NotBeNull();
+ searchOptions.IncludeTotalCount.Should().BeTrue();
+ }
+
+ [Fact]
+ public void Build_WithSearchFields_SearchOptionsWithWithSearchFields()
+ {
+ // arrange
+ ISearchFilterExpressionsBuilder mockSearchFilterExpressionsBuilder = new FilterExpressionBuilderTestDouble().Create();
+
+ ISearchOptionsBuilder searchOptionsBuilder = new SearchOptionsBuilder(mockSearchFilterExpressionsBuilder);
+
+ // act
+ List searchFields = ["FIELD_1", "FIELD_2", "FIELD_3"];
+
+ SearchOptions searchOptions = searchOptionsBuilder.WithSearchFields(searchFields).Build();
+
+ // assert
+ searchOptions.Should().NotBeNull();
+ searchOptions.SearchFields.Should().BeEquivalentTo(searchFields);
+ }
+
+ [Fact]
+ public void Build_WithFacets_SearchOptionsWithWithFacets()
+ {
+ // arrange
+ ISearchFilterExpressionsBuilder mockSearchFilterExpressionsBuilder = new FilterExpressionBuilderTestDouble().Create();
+
+ ISearchOptionsBuilder searchOptionsBuilder = new SearchOptionsBuilder(mockSearchFilterExpressionsBuilder);
+
+ // act
+ List searchFacets = ["FACET_1", "FACET_2", "FACET_3"];
+
+ SearchOptions searchOptions = searchOptionsBuilder.WithFacets(searchFacets).Build();
+
+ // assert
+ searchOptions.Should().NotBeNull();
+ searchOptions.Facets.Should().BeEquivalentTo(searchFacets);
+ }
+
+ [Fact]
+ public void Build_WithFilters_CallsFilterBuilder_WithComposedFilterRequests()
+ {
+ // arrange
+ var serviceAdapterInputFilterRequest =
+ new List() { FilterRequestFake.Create(), FilterRequestFake.Create() };
+
+ var mockSearchFilterExpressionsBuilder = new Mock();
+ var requestMadeToFilterExpressionBuilder = new List();
+
+ mockSearchFilterExpressionsBuilder
+ .Setup(builder => builder.BuildSearchFilterExpressions(It.IsAny>()))
+ .Callback>((request) =>
+ requestMadeToFilterExpressionBuilder = request.ToList())
+ .Returns("some filter string");
+
+ ISearchOptionsBuilder searchOptionsBuilder =
+ new SearchOptionsBuilder(mockSearchFilterExpressionsBuilder.Object);
+
+ // act
+ _ = searchOptionsBuilder.WithFilters(serviceAdapterInputFilterRequest).Build();
+
+ // assert
+ foreach (var filterRequest in serviceAdapterInputFilterRequest)
+ {
+ var matchingFilterRequest =
+ requestMadeToFilterExpressionBuilder
+ .First(request =>
+ request.FilterKey == filterRequest.FilterName);
+
+ matchingFilterRequest.Should().NotBeNull();
+ matchingFilterRequest.FilterValues.Should().BeEquivalentTo(filterRequest.FilterValues);
+ }
+ }
+ }
+}
diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterAndMapperTests.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterAndMapperTests.cs
index d94a819..672109f 100644
--- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterAndMapperTests.cs
+++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterAndMapperTests.cs
@@ -1,11 +1,10 @@
using Azure;
using Azure.Search.Documents.Models;
-using Dfe.Data.Common.Infrastructure.CognitiveSearch.Filtering;
using Dfe.Data.SearchPrototype.Common.Mappers;
+using Dfe.Data.SearchPrototype.Infrastructure.Builders;
using Dfe.Data.SearchPrototype.Infrastructure.Mappers;
using Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles;
using Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles.Shared;
-using Dfe.Data.SearchPrototype.SearchForEstablishments.ByKeyword;
using Dfe.Data.SearchPrototype.SearchForEstablishments.ByKeyword.ServiceAdapters;
using Dfe.Data.SearchPrototype.SearchForEstablishments.Models;
using FluentAssertions;
@@ -17,10 +16,7 @@ public sealed class CognitiveSearchServiceAdapterAndMapperTests
{
private readonly IMapper>, EstablishmentResults> _searchResponseMapper;
private readonly IMapper>, EstablishmentFacets> _facetsMapper;
- private readonly ISearchFilterExpressionsBuilder _mockSearchFilterExpressionsBuilder =
- new FilterExpressionBuilderTestDouble()
- .WithResponse("some_filter_name le some_value")
- .Create();
+ private readonly ISearchOptionsBuilder _searchOptionsBuilder = SearchOptionsBuilderTestDouble.MockFor(new Azure.Search.Documents.SearchOptions());
public CognitiveSearchServiceAdapterAndMapperTests()
{
@@ -53,7 +49,7 @@ public async Task Search_WithValidSearchContext_ReturnsResults()
IOptionsTestDouble.IOptionsMockFor(options),
_searchResponseMapper,
_facetsMapper,
- _mockSearchFilterExpressionsBuilder);
+ _searchOptionsBuilder);
// act
SearchResults? response =
@@ -90,7 +86,7 @@ public async Task Search_WithNoFacetsReturned_ReturnsNullFacets()
IOptionsTestDouble.IOptionsMockFor(options),
_searchResponseMapper,
_facetsMapper,
- _mockSearchFilterExpressionsBuilder);
+ _searchOptionsBuilder);
// act
SearchResults? response =
@@ -124,7 +120,7 @@ public async Task Search_WithNoResultsReturned_ReturnsEmptyResults()
IOptionsTestDouble.IOptionsMockFor(options),
_searchResponseMapper,
_facetsMapper,
- _mockSearchFilterExpressionsBuilder);
+ _searchOptionsBuilder);
// act.
var response =
diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs
index 96b3da8..b15d4ad 100644
--- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs
+++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs
@@ -1,14 +1,13 @@
using Azure;
using Azure.Search.Documents;
using Azure.Search.Documents.Models;
-using Dfe.Data.Common.Infrastructure.CognitiveSearch.Filtering;
using Dfe.Data.Common.Infrastructure.CognitiveSearch.SearchByKeyword;
using Dfe.Data.SearchPrototype.Common.Mappers;
+using Dfe.Data.SearchPrototype.Infrastructure.Builders;
using Dfe.Data.SearchPrototype.Infrastructure.Options;
using Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles;
using Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles.Shared;
using Dfe.Data.SearchPrototype.SearchForEstablishments.ByKeyword.ServiceAdapters;
-using Dfe.Data.SearchPrototype.SearchForEstablishments.ByKeyword.Usecase;
using Dfe.Data.SearchPrototype.SearchForEstablishments.Models;
using FluentAssertions;
using Microsoft.Extensions.Options;
@@ -19,23 +18,23 @@ namespace Dfe.Data.SearchPrototype.Infrastructure.Tests;
public sealed class CognitiveSearchServiceAdapterTests
{
- private IMapper>, EstablishmentFacets> _mockFacetsMapper
+ private readonly IMapper>, EstablishmentFacets> _mockFacetsMapper
= AzureFacetResultToEstablishmentFacetsMapperTestDouble.DefaultMock();
- private AzureSearchOptions _options
- = AzureSearchOptionsTestDouble.Stub();
- private IMapper>, EstablishmentResults> _mockEstablishmentResultsMapper
+ private readonly AzureSearchOptions _options = AzureSearchOptionsTestDouble.Stub();
+ private readonly IMapper>, EstablishmentResults> _mockEstablishmentResultsMapper
= PageableSearchResultsToEstablishmentResultsMapperTestDouble.DefaultMock();
- private ISearchByKeywordService _mockSearchService;
- private ISearchFilterExpressionsBuilder _mockFilterExpressionBuilder = new FilterExpressionBuilderTestDouble().Create();
+ private readonly ISearchByKeywordService _mockSearchService;
+ private readonly ISearchOptionsBuilder _mockSearchOptionsBuilder =
+ new SearchOptionsBuilder(new FilterExpressionBuilderTestDouble().Create());
private static CognitiveSearchServiceAdapter CreateServiceAdapterWith(
ISearchByKeywordService searchByKeywordService,
IOptions searchOptions,
IMapper>, EstablishmentResults> searchResponseMapper,
IMapper>, EstablishmentFacets> facetsMapper,
- ISearchFilterExpressionsBuilder filterExpressionsBuilder
+ ISearchOptionsBuilder searchOptionsBuilder
) =>
- new(searchByKeywordService, searchOptions, searchResponseMapper, facetsMapper, filterExpressionsBuilder);
+ new(searchByKeywordService, searchOptions, searchResponseMapper, facetsMapper, searchOptionsBuilder);
public CognitiveSearchServiceAdapterTests()
{
@@ -55,8 +54,8 @@ public async Task Search_SendsCorrectRequestToSearchService()
var responseMock = new Mock();
var searchServiceResponse = Response.FromValue(
- SearchModelFactory.SearchResults(
- new SearchResultFakeBuilder().WithSearchResults().Create(), 10, null, null, responseMock.Object), responseMock.Object);
+ SearchModelFactory.SearchResults(
+ new SearchResultFakeBuilder().WithSearchResults().Create(), 10, null, null, responseMock.Object), responseMock.Object);
var mockService = new Mock();
mockService.Setup(service => service.SearchAsync(It.IsAny(), It.IsAny(), It.IsAny()))
@@ -76,66 +75,33 @@ public async Task Search_SendsCorrectRequestToSearchService()
IOptionsTestDouble.IOptionsMockFor(_options),
_mockEstablishmentResultsMapper,
_mockFacetsMapper,
- _mockFilterExpressionBuilder);
+ _mockSearchOptionsBuilder);
// act
- var response = await cognitiveSearchServiceAdapter.SearchAsync(searchServiceAdapterRequest);
+ _ = await cognitiveSearchServiceAdapter.SearchAsync(searchServiceAdapterRequest);
// assert
keywordPassedToSearchService.Should().Be(searchServiceAdapterRequest.SearchKeyword);
indexPassedToSearchService.Should().Be(_options.SearchIndex);
+ searchOptionsPassedToSearchService!.Size.Should().Be(_options.Size);
+ searchOptionsPassedToSearchService!.SearchMode.Should().Be((SearchMode)_options.SearchMode);
+ searchOptionsPassedToSearchService!.IncludeTotalCount.Should().Be(_options.IncludeTotalCount);
searchOptionsPassedToSearchService!.SearchFields.Should().BeEquivalentTo(searchServiceAdapterRequest.SearchFields);
searchOptionsPassedToSearchService?.Facets.Should().BeEquivalentTo(searchServiceAdapterRequest.Facets);
}
- [Fact]
- public void Search_WithFilters_CallsFilterBuilder_WithComposedFilterRequests()
- {
- // arrange
- var serviceAdapterInputFilterRequest = new List() { FilterRequestFake.Create(), FilterRequestFake.Create() };
-
- var mockSearchFilterExpressionsBuilder = new Mock();
- var requestMadeToFilterExpressionBuilder = new List();
- mockSearchFilterExpressionsBuilder
- .Setup(builder => builder.BuildSearchFilterExpressions(It.IsAny>()))
- .Callback>((request) => {
- requestMadeToFilterExpressionBuilder = request.ToList();
- })
- .Returns("some filter string");
-
- var searchServiceAdapterRequest = SearchServiceAdapterRequestTestDouble.WithFilters(serviceAdapterInputFilterRequest);
-
- var adapter = new CognitiveSearchServiceAdapter(
- _mockSearchService,
- IOptionsTestDouble.IOptionsMockFor(_options),
- _mockEstablishmentResultsMapper,
- _mockFacetsMapper,
- mockSearchFilterExpressionsBuilder.Object);
-
- // act
- var response = adapter.SearchAsync(searchServiceAdapterRequest);
-
- // assert
- foreach(var filterRequest in serviceAdapterInputFilterRequest)
- {
- var matchingFilterRequest = requestMadeToFilterExpressionBuilder.First(request => request.FilterKey == filterRequest.FilterName);
- matchingFilterRequest.Should().NotBeNull();
- matchingFilterRequest.FilterValues.Should().BeEquivalentTo(filterRequest.FilterValues);
- }
- }
-
[Fact]
public void Search_WithNoSearchOptions_ThrowsApplicationException()
{
// act, assert
try
{
- var _ = new CognitiveSearchServiceAdapter(
+ _ = new CognitiveSearchServiceAdapter(
_mockSearchService,
IOptionsTestDouble.IOptionsMockFor(null!),
_mockEstablishmentResultsMapper,
_mockFacetsMapper,
- _mockFilterExpressionBuilder);
+ _mockSearchOptionsBuilder);
Assert.True(false);
}
catch (ArgumentNullException)
@@ -157,7 +123,7 @@ public Task Search_MapperThrowsException_ExceptionPassesThrough()
IOptionsTestDouble.IOptionsMockFor(_options),
mockEstablishmentResultsMapper,
_mockFacetsMapper,
- _mockFilterExpressionBuilder);
+ _mockSearchOptionsBuilder);
// act, assert.
return cognitiveSearchServiceAdapter
diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchOptionsBuilderTestDouble.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchOptionsBuilderTestDouble.cs
new file mode 100644
index 0000000..999da1c
--- /dev/null
+++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchOptionsBuilderTestDouble.cs
@@ -0,0 +1,34 @@
+using Azure.Search.Documents;
+using Azure.Search.Documents.Models;
+using Dfe.Data.SearchPrototype.Infrastructure.Builders;
+using Dfe.Data.SearchPrototype.SearchForEstablishments.ByKeyword.Usecase;
+using Moq;
+
+namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles
+{
+ internal static class SearchOptionsBuilderTestDouble
+ {
+ public static ISearchOptionsBuilder MockFor(SearchOptions searchOptions)
+ {
+ var mockSearchOptionsBuilder = new Mock();
+
+ mockSearchOptionsBuilder.Setup(searchOptionsBuilder =>
+ searchOptionsBuilder.WithSize(It.IsAny())).Returns(mockSearchOptionsBuilder.Object);
+ mockSearchOptionsBuilder.Setup(searchOptionsBuilder =>
+ searchOptionsBuilder.WithSearchMode(It.IsAny())).Returns(mockSearchOptionsBuilder.Object);
+ mockSearchOptionsBuilder.Setup(searchOptionsBuilder =>
+ searchOptionsBuilder.WithIncludeTotalCount(It.IsAny())).Returns(mockSearchOptionsBuilder.Object);
+ mockSearchOptionsBuilder.Setup(searchOptionsBuilder =>
+ searchOptionsBuilder.WithSearchFields(It.IsAny?>())).Returns(mockSearchOptionsBuilder.Object);
+ mockSearchOptionsBuilder.Setup(searchOptionsBuilder =>
+ searchOptionsBuilder.WithFacets(It.IsAny>())).Returns(mockSearchOptionsBuilder.Object);
+ mockSearchOptionsBuilder.Setup(searchOptionsBuilder =>
+ searchOptionsBuilder.WithFilters(It.IsAny>())).Returns(mockSearchOptionsBuilder.Object);
+
+ mockSearchOptionsBuilder.Setup(searchOptionsBuilder =>
+ searchOptionsBuilder.Build()).Returns(searchOptions);
+
+ return mockSearchOptionsBuilder.Object;
+ }
+ }
+}