From cd946a1abb95575b4bab13aec9a0428c2c836e9b Mon Sep 17 00:00:00 2001 From: Catherine Lawlor <55924986+CathLass@users.noreply.github.com> Date: Thu, 12 Sep 2024 09:41:05 +0100 Subject: [PATCH] Cl/facet fixes (#46) * refactor the config for application and infrastructure tiers * Tidied-up and improved comments. * Fixed broken tests. * A bit of a re-jig of the core project structure. * remove project entry for nuget generation * Correction for mapper registration in DI container * Removed package generation --------- Co-authored-by: Spencer O'HEGARTY --- .../Dfe.Data.SearchPrototype.Common.csproj | 2 - .../Dfe.Data.SearchPrototype.csproj | 4 +- .../CognitiveSearchServiceAdapter.cs | 56 +++++++------ .../Infrastructure/CompositionRoot.cs | 60 ++++++++++++++ .../Establishment.cs | 15 +++- .../Infrastructure/DependencyRegistration.cs | 35 --------- ...Data.SearchPrototype.Infrastructure.csproj | 3 +- ...eFacetResultToEstablishmentFacetsMapper.cs | 14 ++-- .../AzureSearchResultToAddressMapper.cs | 16 ++-- .../AzureSearchResultToEstablishmentMapper.cs | 31 ++++---- ...archResultsToEstablishmentResultsMapper.cs | 29 +++---- .../Options/AzureSearchOptions.cs | 26 +++++++ .../Options/ISearchOptionsFactory.cs | 24 ------ .../SearchOptionsToAzureOptionsMapper.cs | 46 ----------- .../Options/SearchOptionsFactory.cs | 58 -------------- .../Options/SearchSettingsOptions.cs | 39 ---------- ...itiveSearchServiceAdapterAndMapperTests.cs | 61 ++++++++------- .../CognitiveSearchServiceAdapterTests.cs | 58 +++++++------- ...earchPrototype.Infrastructure.Tests.csproj | 1 - ...eSearchResultToEstablishmentMapperTests.cs | 30 +++---- ...esultsToEstablishmentResultsMapperTests.cs | 5 +- .../SearchOptionsToAzureOptionsMapperTests.cs | 38 --------- .../Options/SearchOptionsFactoryTests.cs | 78 ------------------- ...hOptionsToAzureOptionsMapperTestDoubles.cs | 23 ------ ...SearchSettingsOptionsSnapshotTestDouble.cs | 18 ----- .../SearchSettingsOptionsTestDouble.cs | 21 ----- .../AzureSearchOptionsTestDouble.cs | 14 ++++ .../AzureSearchResponseTestDoubleBuilder.cs | 1 + .../TestDoubles/EstablishmentTestDouble.cs | 4 +- .../TestDoubles/FacetsResultsFakeBuilder.cs | 6 +- ...sToEstablishmentResultsMapperTestDouble.cs | 16 ++-- .../Tests/TestDoubles/PageableTestDouble.cs | 1 + .../SearchOptionsFactoryTestDouble.cs | 34 -------- .../Tests/TestDoubles/SearchResultFake.cs | 1 + .../TestDoubles/SearchResultFakeBuilder.cs | 1 + .../TestDoubles/SearchServiceMockBuilder.cs | 3 +- .../TestDoubles/Shared/IOptionsTestDouble.cs | 21 +++++ .../EstablishmentTestExtensionMethods.cs | 11 ++- .../ServiceAdapters}/ISearchServiceAdapter.cs | 10 +-- .../SearchServiceAdapterRequest.cs | 75 ++++++++++++++++++ .../Usecase/SearchByKeywordRequest.cs | 28 +++++++ .../Usecase}/SearchByKeywordResponse.cs | 21 ++--- .../Usecase}/SearchByKeywordUseCase.cs | 51 +++++++++--- .../ByKeyword/Usecase/SearchResponseStatus.cs | 20 +++++ .../CompositionRoot.cs | 33 ++++++++ .../DependencyRegistration.cs | 20 ----- .../SearchForEstablishments/Models/Address.cs | 9 ++- .../Models/Establishment.cs | 8 +- .../Models/EstablishmentFacet.cs | 22 ++++-- .../Models/EstablishmentFacets.cs | 18 +++-- .../Models/EstablishmentResults.cs | 18 +++-- .../Models/FacetResult.cs | 17 ++-- .../Models/SearchByKeywordCriteria.cs | 20 +++++ .../Models/SearchResults.cs | 10 ++- .../SearchByKeywordRequest.cs | 29 ------- .../SearchForEstablishments/SearchContext.cs | 66 ---------------- .../SearchResponseStatus.cs | 20 ----- .../Dfe.Data.SearchPrototype.Tests.csproj | 1 + .../SearchByKeywordUseCaseTests.cs | 23 +++--- .../EstablishmentFacetsTestDouble.cs | 2 +- .../EstablishmentResultsTestDouble.cs | 2 +- .../TestDoubles/EstablishmentTestDouble.cs | 5 +- .../TestDoubles/SearchByKeywordOptions.cs | 15 ++++ .../TestDoubles/SearchResultsTestDouble.cs | 4 +- .../SearchServiceAdapterTestDouble.cs | 6 +- 65 files changed, 665 insertions(+), 792 deletions(-) create mode 100644 Dfe.Data.SearchPrototype/Infrastructure/CompositionRoot.cs rename Dfe.Data.SearchPrototype/Infrastructure/{ => DataTransferObjects}/Establishment.cs (85%) delete mode 100644 Dfe.Data.SearchPrototype/Infrastructure/DependencyRegistration.cs create mode 100644 Dfe.Data.SearchPrototype/Infrastructure/Options/AzureSearchOptions.cs delete mode 100644 Dfe.Data.SearchPrototype/Infrastructure/Options/ISearchOptionsFactory.cs delete mode 100644 Dfe.Data.SearchPrototype/Infrastructure/Options/Mappers/SearchOptionsToAzureOptionsMapper.cs delete mode 100644 Dfe.Data.SearchPrototype/Infrastructure/Options/SearchOptionsFactory.cs delete mode 100644 Dfe.Data.SearchPrototype/Infrastructure/Options/SearchSettingsOptions.cs delete mode 100644 Dfe.Data.SearchPrototype/Infrastructure/Tests/Options/Mappers/SearchOptionsToAzureOptionsMapperTests.cs delete mode 100644 Dfe.Data.SearchPrototype/Infrastructure/Tests/Options/SearchOptionsFactoryTests.cs delete mode 100644 Dfe.Data.SearchPrototype/Infrastructure/Tests/Options/TestDoubles/SearchOptionsToAzureOptionsMapperTestDoubles.cs delete mode 100644 Dfe.Data.SearchPrototype/Infrastructure/Tests/Options/TestDoubles/SearchSettingsOptionsSnapshotTestDouble.cs delete mode 100644 Dfe.Data.SearchPrototype/Infrastructure/Tests/Options/TestDoubles/SearchSettingsOptionsTestDouble.cs create mode 100644 Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/AzureSearchOptionsTestDouble.cs delete mode 100644 Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchOptionsFactoryTestDouble.cs create mode 100644 Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/Shared/IOptionsTestDouble.cs rename Dfe.Data.SearchPrototype/SearchForEstablishments/{ => ByKeyword/ServiceAdapters}/ISearchServiceAdapter.cs (65%) create mode 100644 Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs create mode 100644 Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordRequest.cs rename Dfe.Data.SearchPrototype/SearchForEstablishments/{ => ByKeyword/Usecase}/SearchByKeywordResponse.cs (80%) rename Dfe.Data.SearchPrototype/SearchForEstablishments/{ => ByKeyword/Usecase}/SearchByKeywordUseCase.cs (52%) create mode 100644 Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchResponseStatus.cs create mode 100644 Dfe.Data.SearchPrototype/SearchForEstablishments/CompositionRoot.cs delete mode 100644 Dfe.Data.SearchPrototype/SearchForEstablishments/DependencyRegistration.cs create mode 100644 Dfe.Data.SearchPrototype/SearchForEstablishments/Models/SearchByKeywordCriteria.cs delete mode 100644 Dfe.Data.SearchPrototype/SearchForEstablishments/SearchByKeywordRequest.cs delete mode 100644 Dfe.Data.SearchPrototype/SearchForEstablishments/SearchContext.cs delete mode 100644 Dfe.Data.SearchPrototype/SearchForEstablishments/SearchResponseStatus.cs rename Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/{ => ByKeyword}/SearchByKeywordUseCaseTests.cs (76%) rename Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/{ => ByKeyword}/TestDoubles/EstablishmentFacetsTestDouble.cs (95%) rename Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/{ => ByKeyword}/TestDoubles/EstablishmentResultsTestDouble.cs (96%) rename Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/{ => ByKeyword}/TestDoubles/EstablishmentTestDouble.cs (95%) create mode 100644 Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/TestDoubles/SearchByKeywordOptions.cs rename Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/{ => ByKeyword}/TestDoubles/SearchResultsTestDouble.cs (93%) rename Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/{ => ByKeyword}/TestDoubles/SearchServiceAdapterTestDouble.cs (79%) diff --git a/Dfe.Data.SearchPrototype/Common/Dfe.Data.SearchPrototype.Common.csproj b/Dfe.Data.SearchPrototype/Common/Dfe.Data.SearchPrototype.Common.csproj index 237b645..65ed135 100644 --- a/Dfe.Data.SearchPrototype/Common/Dfe.Data.SearchPrototype.Common.csproj +++ b/Dfe.Data.SearchPrototype/Common/Dfe.Data.SearchPrototype.Common.csproj @@ -4,8 +4,6 @@ net8.0 enable enable - - Dfe.Data.SearchPrototype.Common diff --git a/Dfe.Data.SearchPrototype/Dfe.Data.SearchPrototype.csproj b/Dfe.Data.SearchPrototype/Dfe.Data.SearchPrototype.csproj index 1d495c2..0480578 100644 --- a/Dfe.Data.SearchPrototype/Dfe.Data.SearchPrototype.csproj +++ b/Dfe.Data.SearchPrototype/Dfe.Data.SearchPrototype.csproj @@ -4,7 +4,6 @@ net8.0 enable enable - Dfe.Data.SearchPrototype @@ -22,7 +21,7 @@ - + @@ -50,6 +49,7 @@ + diff --git a/Dfe.Data.SearchPrototype/Infrastructure/CognitiveSearchServiceAdapter.cs b/Dfe.Data.SearchPrototype/Infrastructure/CognitiveSearchServiceAdapter.cs index 89b2619..fe464d8 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/CognitiveSearchServiceAdapter.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/CognitiveSearchServiceAdapter.cs @@ -1,11 +1,12 @@ using Azure; using Azure.Search.Documents; +using Azure.Search.Documents.Models; using Dfe.Data.Common.Infrastructure.CognitiveSearch.SearchByKeyword; using Dfe.Data.SearchPrototype.Common.Mappers; using Dfe.Data.SearchPrototype.Infrastructure.Options; -using Dfe.Data.SearchPrototype.SearchForEstablishments; +using Dfe.Data.SearchPrototype.SearchForEstablishments.ByKeyword.ServiceAdapters; using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; - +using Microsoft.Extensions.Options; using AzureModels = Azure.Search.Documents.Models; namespace Dfe.Data.SearchPrototype.Infrastructure; @@ -17,9 +18,9 @@ namespace Dfe.Data.SearchPrototype.Infrastructure; public sealed class CognitiveSearchServiceAdapter : ISearchServiceAdapter where TSearchResult : class { private readonly ISearchByKeywordService _searchByKeywordService; - private readonly ISearchOptionsFactory _searchOptionsFactory; private readonly IMapper>, EstablishmentResults> _searchResultMapper; - private readonly IMapper>, EstablishmentFacets> _facetsMapper; + private readonly IMapper>, EstablishmentFacets> _facetsMapper; + private readonly AzureSearchOptions _azureSearchOptions; /// /// The following dependencies include the core cognitive search service definition, @@ -28,22 +29,23 @@ public sealed class CognitiveSearchServiceAdapter : ISearchServic /// /// Cognitive search (search by keyword) service definition injected via IOC container. /// - /// - /// Factory class definition for prescribing the requested search options (by collection context). + /// + /// the search options provided through the app-settings /// /// /// Maps the raw Azure search response to the required /// /// - /// Maps the the raw Azure search response to the required + /// Maps the raw Azure search response to the required /// public CognitiveSearchServiceAdapter( ISearchByKeywordService searchByKeywordService, - ISearchOptionsFactory searchOptionsFactory, + IOptions azureSearchOptions, IMapper>, EstablishmentResults> searchResultMapper, IMapper>, EstablishmentFacets> facetsMapper) { - _searchOptionsFactory = searchOptionsFactory; + ArgumentNullException.ThrowIfNull(azureSearchOptions.Value); + _azureSearchOptions = azureSearchOptions.Value; _searchByKeywordService = searchByKeywordService; _searchResultMapper = searchResultMapper; _facetsMapper = facetsMapper; @@ -51,14 +53,13 @@ public CognitiveSearchServiceAdapter( /// /// Makes call to underlying azure cognitive search service and uses the prescribed mapper - /// to adapt the raw Azure search results to the "T:Dfe.Data.SearchPrototype.Search.Domain.AgregateRoot.Establishments" type. + /// to adapt the raw Azure search results to the type. /// - /// + /// /// Prescribes the context of the search including the keyword and collection target. /// /// - /// A configured "T:Dfe.Data.SearchPrototype.Search.Domain.AgregateRoot.Establishments" - /// object hydrated from the results of the azure search. + /// A configured < object hydrated from the results of the azure search. /// /// /// An application exception is thrown if we either have no options configured, which @@ -68,29 +69,36 @@ public CognitiveSearchServiceAdapter( /// /// Exception thrown if the data cannot be mapped /// - - public async Task SearchAsync(SearchContext searchContext) + public async Task SearchAsync(SearchServiceAdapterRequest searchServiceAdapterRequest) { - SearchOptions searchOptions = - _searchOptionsFactory.GetSearchOptions(searchContext.TargetCollection) ?? - throw new ApplicationException( - $"Search options cannot be derived for {searchContext.TargetCollection}."); + 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); - Response> searchResults = + Response> searchResults = await _searchByKeywordService.SearchAsync( - searchContext.SearchKeyword, - searchContext.TargetCollection, + searchServiceAdapterRequest.SearchKeyword, + _azureSearchOptions.SearchIndex, searchOptions ) .ConfigureAwait(false) ?? throw new ApplicationException( - $"Unable to derive search results based on input {searchContext.SearchKeyword}."); + $"Unable to derive search results based on input {searchServiceAdapterRequest.SearchKeyword}."); var results = new SearchResults() { Establishments = _searchResultMapper.MapFrom(searchResults.Value.GetResults()), Facets = searchResults.Value.Facets != null - ? _facetsMapper.MapFrom(searchResults.Value.Facets.ToDictionary>()) + ? _facetsMapper.MapFrom(searchResults.Value.Facets.ToDictionary()) : null }; diff --git a/Dfe.Data.SearchPrototype/Infrastructure/CompositionRoot.cs b/Dfe.Data.SearchPrototype/Infrastructure/CompositionRoot.cs new file mode 100644 index 0000000..d61fc43 --- /dev/null +++ b/Dfe.Data.SearchPrototype/Infrastructure/CompositionRoot.cs @@ -0,0 +1,60 @@ +using Azure; +using Azure.Search.Documents.Models; +using Dfe.Data.SearchPrototype.Common.Mappers; +using Dfe.Data.SearchPrototype.Infrastructure.Mappers; +using Dfe.Data.SearchPrototype.Infrastructure.Options; +using Dfe.Data.SearchPrototype.SearchForEstablishments.ByKeyword.ServiceAdapters; +using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Dfe.Data.SearchPrototype.Infrastructure; + +/// +/// The composition root provides a unified location in the application where the composition +/// of the object graphs for the application take place, using the IOC container. +/// +public static class CompositionRoot +{ + /// + /// Extension method which provides all the pre-registrations required to + /// access the AI azure search service adapter, and perform searches across provisioned indexes. + /// + /// + /// The originating application services onto which to register the search dependencies. + /// + /// + /// The originating configuration block from which to derive search service settings. + /// + /// + /// The exception thrown if no valid is provisioned. + /// + public static void AddCognitiveSearchAdaptorServices(this IServiceCollection services, IConfiguration configuration) + { + if (services is null) + { + throw new ArgumentNullException(nameof(services), + "A service collection is required to configure the azure AI search adapter dependencies."); + } + + services.AddOptions() + .Configure( + (settings, configuration) => + configuration + .GetSection(nameof(AzureSearchOptions)) + .Bind(settings)); + + services.AddOptions() + .Configure( + (settings, configuration) => + configuration + .GetSection(nameof(SearchByKeywordCriteria)) + .Bind(settings)); + + services.AddScoped(typeof(ISearchServiceAdapter), typeof(CognitiveSearchServiceAdapter)); + services.AddSingleton(typeof(IMapper>, EstablishmentResults>), typeof(PageableSearchResultsToEstablishmentResultsMapper)); + services.AddSingleton>, EstablishmentFacets>, AzureFacetResultToEstablishmentFacetsMapper>(); + services.AddSingleton, AzureSearchResultToAddressMapper>(); + services.AddSingleton, AzureSearchResultToEstablishmentMapper>(); + } +} \ No newline at end of file diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Establishment.cs b/Dfe.Data.SearchPrototype/Infrastructure/DataTransferObjects/Establishment.cs similarity index 85% rename from Dfe.Data.SearchPrototype/Infrastructure/Establishment.cs rename to Dfe.Data.SearchPrototype/Infrastructure/DataTransferObjects/Establishment.cs index 34a4d4f..94ac6f0 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Establishment.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/DataTransferObjects/Establishment.cs @@ -1,7 +1,7 @@ -namespace Dfe.Data.SearchPrototype.Infrastructure; +namespace Dfe.Data.SearchPrototype.Infrastructure.DataTransferObjects; /// -/// Object used to encapsulate the core response from an Azure cognitive search result. +/// Data transformation object used to encapsulate the core response from an Azure cognitive search result. /// public class Establishment { @@ -9,38 +9,47 @@ public class Establishment /// The unique identifier of the retrieved establishment result. /// public string? id { get; set; } + /// /// The name associated with the retrieved establishment result. /// public string? ESTABLISHMENTNAME { get; set; } + /// /// First line of the address /// public string? STREET { get; set; } + /// /// Second line of the address of the retrieved establishment result /// public string? LOCALITY { get; set; } + /// /// Third line of the address of the retrieved establishment result /// public string? ADDRESS3 { get; set; } + /// /// Fourth line of the address of the retrieved establishment result /// public string? TOWN { get; set; } + /// /// Postcode of the retrieved establishment result /// public string? POSTCODE { get; set; } + /// /// The type of the establishment of the retrieved establishment result /// public string? TYPEOFESTABLISHMENTNAME { get; set; } + /// /// The education phase of the retrieved establishment result /// - public string? PHASEOFEDUCATION { get; set; } + public string? PHASEOFEDUCATION { get; set; } + /// /// Status of retrieved establishment result. /// diff --git a/Dfe.Data.SearchPrototype/Infrastructure/DependencyRegistration.cs b/Dfe.Data.SearchPrototype/Infrastructure/DependencyRegistration.cs deleted file mode 100644 index 2ab8138..0000000 --- a/Dfe.Data.SearchPrototype/Infrastructure/DependencyRegistration.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Azure.Search.Documents.Models; -using Azure; -using Dfe.Data.SearchPrototype.Common.Mappers; -using Dfe.Data.SearchPrototype.Infrastructure.Mappers; -using Dfe.Data.SearchPrototype.SearchForEstablishments; -using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; -using Microsoft.Extensions.DependencyInjection; -using Azure.Search.Documents; -using Dfe.Data.SearchPrototype.Infrastructure.Options.Mappers; -using Dfe.Data.SearchPrototype.Infrastructure.Options; - -namespace Dfe.Data.SearchPrototype.Infrastructure; - -/// -/// Extension method which provides all the pre-registrations required to -/// access services to adapt the Dfe.Data.Common.Infrastructure.CognitiveSearch infrastructure -/// to SearchPrototype application layer -/// -public static class DependencyRegistration -{ - /// - /// Register the necessary infrastucture adaptor services - /// - /// - public static void AddCognitiveSearchAdaptorServices(this IServiceCollection services) - { - services.AddScoped(typeof(ISearchServiceAdapter), typeof(CognitiveSearchServiceAdapter)); - services.AddSingleton(typeof(IMapper>, EstablishmentResults>), typeof(PageableSearchResultsToEstablishmentResultsMapper)); - services.AddSingleton, SearchOptionsToAzureOptionsMapper>(); - services.AddSingleton, AzureSearchResultToAddressMapper>(); - services.AddSingleton, AzureSearchResultToEstablishmentMapper>(); - services.AddScoped(); - } -} - diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Dfe.Data.SearchPrototype.Infrastructure.csproj b/Dfe.Data.SearchPrototype/Infrastructure/Dfe.Data.SearchPrototype.Infrastructure.csproj index 31b78f3..d06b1d8 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Dfe.Data.SearchPrototype.Infrastructure.csproj +++ b/Dfe.Data.SearchPrototype/Infrastructure/Dfe.Data.SearchPrototype.Infrastructure.csproj @@ -4,7 +4,6 @@ net8.0 enable enable - Dfe.Data.SearchPrototype.Infrastructure @@ -22,7 +21,7 @@ - + diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureFacetResultToEstablishmentFacetsMapper.cs b/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureFacetResultToEstablishmentFacetsMapper.cs index 080c5fd..bc378f2 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureFacetResultToEstablishmentFacetsMapper.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureFacetResultToEstablishmentFacetsMapper.cs @@ -6,24 +6,26 @@ namespace Dfe.Data.SearchPrototype.Infrastructure.Mappers; using AzureFacetResult = Azure.Search.Documents.Models.FacetResult; /// -/// Maps from an Azure facet result to a collection of -/// T:Dfe.Data.SearchPrototype.SearchForEstablishments.Models.EstablishmentFacet +/// Maps from an Azure facet result to a collection of types. /// public class AzureFacetResultToEstablishmentFacetsMapper : IMapper>, EstablishmentFacets> { /// - /// Map from an Azure facet result to a collection of - /// T:Dfe.Data.SearchPrototype.SearchForEstablishments.Models.EstablishmentFacet + /// Map from an Azure facet result to a collection of types. /// /// The Azure facet result - /// + /// + /// A configured instance. + /// public EstablishmentFacets MapFrom(Dictionary> facetResult) { var establishmentFacets = new List(); foreach (var facetCategory in facetResult.Where(facet => facet.Value != null)) { - var values = facetCategory.Value.Select(f => new FacetResult((string)f.Value, f.Count)).ToList(); + var values = facetCategory.Value.Select(facetResult => + new FacetResult((string)facetResult.Value, facetResult.Count)).ToList(); + var establishmentFacet = new EstablishmentFacet(facetCategory.Key, values); establishmentFacets.Add(establishmentFacet); diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureSearchResultToAddressMapper.cs b/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureSearchResultToAddressMapper.cs index 1303179..ef2e830 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureSearchResultToAddressMapper.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureSearchResultToAddressMapper.cs @@ -4,23 +4,23 @@ namespace Dfe.Data.SearchPrototype.Infrastructure.Mappers; /// -/// Facilitates mapping from the received T:Dfe.Data.SearchPrototype.Infrastructure.Establishment -/// into the required T:Dfe.Data.SearchPrototype.SearchForEstablishments.Address object. +/// Facilitates mapping from the received instance +/// into the required object. /// -public class AzureSearchResultToAddressMapper : IMapper +public class AzureSearchResultToAddressMapper : IMapper { /// /// The following mapping dependency provides the functionality to map from a raw Azure - /// search result, to a configured T:Dfe.Data.SearchPrototype.SearchForEstablishments.Address - /// instance, the complete implementation of which is defined in the IOC container. + /// search result, to a configured instance, the complete + /// implementation of which is defined in the IOC container. /// /// - /// The raw T:Dfe.Data.SearchPrototype.Infrastructure.Establishment used to map from. + /// The raw instance used to map from. /// /// - /// The configured T:Dfe.Data.SearchPrototype.SearchForEstablishments.Address instance expected. + /// The configured instance expected. /// - public Address MapFrom(Establishment input) + public Address MapFrom(DataTransferObjects.Establishment input) { return new() { diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureSearchResultToEstablishmentMapper.cs b/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureSearchResultToEstablishmentMapper.cs index 1bae09a..e38e702 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureSearchResultToEstablishmentMapper.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Mappers/AzureSearchResultToEstablishmentMapper.cs @@ -1,4 +1,5 @@ using Dfe.Data.SearchPrototype.Common.Mappers; +using Dfe.Data.SearchPrototype.Infrastructure.DataTransferObjects; using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; namespace Dfe.Data.SearchPrototype.Infrastructure.Mappers; @@ -7,42 +8,44 @@ namespace Dfe.Data.SearchPrototype.Infrastructure.Mappers; /// Facilitates mapping from the received T:Dfe.Data.SearchPrototype.Infrastructure.Establishment /// into the required T:Dfe.Data.SearchPrototype.SearchForEstablishments.Establishment object. /// -public sealed class AzureSearchResultToEstablishmentMapper : IMapper +public sealed class AzureSearchResultToEstablishmentMapper : IMapper { - private readonly IMapper _addressMapper; + private readonly IMapper _addressMapper; /// - /// Constructor + /// The following mapping dependency provides the functionality to map from a + /// object, to a configured instance, the complete + /// implementation of which is defined in the IOC container. /// - /// Address mapper instance + /// Address mapper instance. public AzureSearchResultToEstablishmentMapper( - IMapper addressMapper) + IMapper addressMapper) { _addressMapper = addressMapper; } /// /// The following mapping dependency provides the functionality to map from a raw Azure - /// search result, to a configured T:Dfe.Data.SearchPrototype.Search.Establishment + /// search result, to a configured /// instance, the complete implementation of which is defined in the IOC container. /// /// - /// The raw T:Dfe.Data.SearchPrototype.Infrastructure.Establishment used to map from. + /// The raw instance used to map from. /// /// - /// The configured T:Dfe.Data.SearchPrototype.Search.Establishment instance expected. + /// The configured instance expected. /// /// /// Exception thrown if the id, name, or type of an establishment is not provided /// - public SearchForEstablishments.Models.Establishment MapFrom(Establishment input) + public SearchForEstablishments.Models.Establishment MapFrom(DataTransferObjects.Establishment input) { // TODO - only throw for really essential stuff - ArgumentException.ThrowIfNullOrEmpty(input.id, nameof(input.id)); - ArgumentException.ThrowIfNullOrEmpty(input.ESTABLISHMENTNAME, nameof(input.ESTABLISHMENTNAME)); - ArgumentException.ThrowIfNullOrEmpty(input.TYPEOFESTABLISHMENTNAME, nameof(input.TYPEOFESTABLISHMENTNAME)); - ArgumentException.ThrowIfNullOrEmpty(input.PHASEOFEDUCATION, nameof(input.PHASEOFEDUCATION)); - ArgumentException.ThrowIfNullOrEmpty(input.ESTABLISHMENTSTATUSNAME, nameof(input.ESTABLISHMENTSTATUSNAME)); + ArgumentException.ThrowIfNullOrEmpty(input.id); + ArgumentException.ThrowIfNullOrEmpty(input.ESTABLISHMENTNAME); + ArgumentException.ThrowIfNullOrEmpty(input.TYPEOFESTABLISHMENTNAME); + ArgumentException.ThrowIfNullOrEmpty(input.PHASEOFEDUCATION); + ArgumentException.ThrowIfNullOrEmpty(input.ESTABLISHMENTSTATUSNAME); return new( urn: input.id, diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Mappers/PageableSearchResultsToEstablishmentResultsMapper.cs b/Dfe.Data.SearchPrototype/Infrastructure/Mappers/PageableSearchResultsToEstablishmentResultsMapper.cs index d2b67cf..6fff1cd 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Mappers/PageableSearchResultsToEstablishmentResultsMapper.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Mappers/PageableSearchResultsToEstablishmentResultsMapper.cs @@ -1,41 +1,42 @@ using Azure; using Azure.Search.Documents.Models; using Dfe.Data.SearchPrototype.Common.Mappers; -using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; +using Dfe.Data.SearchPrototype.Infrastructure.DataTransferObjects; +using Models = Dfe.Data.SearchPrototype.SearchForEstablishments.Models; namespace Dfe.Data.SearchPrototype.Infrastructure.Mappers; /// -/// Facilitates mapping from the received T:Azure.Search.Documents.Models.SearchResults -/// into the required T:Dfe.Data.SearchPrototype.Search.EstablishmentResults object. +/// Facilitates mapping from the received +/// into the required object. /// -public sealed class PageableSearchResultsToEstablishmentResultsMapper : IMapper>, EstablishmentResults> +public sealed class PageableSearchResultsToEstablishmentResultsMapper : IMapper>, Models.EstablishmentResults> { - private readonly IMapper _azureSearchResultToEstablishmentMapper; + private readonly IMapper _azureSearchResultToEstablishmentMapper; /// /// The following mapping dependency provides the functionality to map from a raw Azure - /// search result, to a configured T:Dfe.Data.SearchPrototype.Search.Establishment + /// search result, to a configured /// instance, the complete implementation of which is defined in the IOC container. /// /// - /// Mapper used to map from the raw Azure search result to a T:Dfe.Data.SearchPrototype.Search.Establishment instance. + /// Mapper used to map from the raw Azure search result to a instance. /// - public PageableSearchResultsToEstablishmentResultsMapper(IMapper azureSearchResultToEstablishmentMapper) + public PageableSearchResultsToEstablishmentResultsMapper(IMapper azureSearchResultToEstablishmentMapper) { _azureSearchResultToEstablishmentMapper = azureSearchResultToEstablishmentMapper; } /// - /// The mapping input is the raw Azure search response T:Azure.Search.Documents.Models.SearchResults + /// The mapping input is the raw Azure search response /// and if any results are contained within the response a new Dfe.Data.SearchPrototype.Search.Establishments /// instance is created, with the responsibility of hydrating this root object and children delegated to the sub-mappers. /// /// - /// A configured T:Azure.Search.Documents.Models.SearchResults instance. + /// A configured instance. /// /// - /// A configured T:Dfe.Data.SearchPrototype.Search.EstablishmentResults instance. + /// A configured instance. /// /// /// Exception thrown if an invalid document is derived from the Azure search result. @@ -43,7 +44,7 @@ public PageableSearchResultsToEstablishmentResultsMapper(IMapper /// Exception thrown if the data cannot be mapped /// - public EstablishmentResults MapFrom(Pageable> input) + public Models.EstablishmentResults MapFrom(Pageable> input) { ArgumentNullException.ThrowIfNull(input); @@ -55,11 +56,11 @@ public EstablishmentResults MapFrom(Pageable> input) : throw new InvalidOperationException( "Search result document object cannot be null.") ); - return new EstablishmentResults(mappedResults); + return new Models.EstablishmentResults(mappedResults); } else { - return new EstablishmentResults(); + return new Models.EstablishmentResults(); } } } diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Options/AzureSearchOptions.cs b/Dfe.Data.SearchPrototype/Infrastructure/Options/AzureSearchOptions.cs new file mode 100644 index 0000000..47b82e8 --- /dev/null +++ b/Dfe.Data.SearchPrototype/Infrastructure/Options/AzureSearchOptions.cs @@ -0,0 +1,26 @@ +namespace Dfe.Data.SearchPrototype.Infrastructure.Options; + +/// +/// The search options to use by the +/// which is set using the IOptions interface +/// +public class AzureSearchOptions +{ + /// + /// The Azure AI Search index used to target for search requests. + /// + public string SearchIndex { get; set; } = string.Empty; + /// + /// The Azure Search mode + /// see documentation for details + /// + public int SearchMode { get; set; } + /// + /// The number of search results returned. + /// + public int Size { get; set; } + /// + /// The number of search results returned. + /// + public bool IncludeTotalCount { get; set; } +} diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Options/ISearchOptionsFactory.cs b/Dfe.Data.SearchPrototype/Infrastructure/Options/ISearchOptionsFactory.cs deleted file mode 100644 index 98c11a3..0000000 --- a/Dfe.Data.SearchPrototype/Infrastructure/Options/ISearchOptionsFactory.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Azure.Search.Documents; - -namespace Dfe.Data.SearchPrototype.Infrastructure.Options; - -/// -/// Contract in support of concrete implementations defined in order to -/// return an "T:Azure.Search.Documents.SearchOptions" instance based -/// on the key target collection specified. -/// -public interface ISearchOptionsFactory -{ - /// - /// Specifies the behavior for retrieving an - /// "T:Dfe.Data.SearchPrototype.Infrastructure.Options.SearchSettingsOptions" - /// instance by the specified target collection key. - /// - /// - /// The key used to target the configuration block describing the collection under scrutiny. - /// - /// - /// A configured instance of "T:Azure.Search.Documents.SearchOptions". - /// - public SearchOptions? GetSearchOptions(string targetCollection); -} diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Options/Mappers/SearchOptionsToAzureOptionsMapper.cs b/Dfe.Data.SearchPrototype/Infrastructure/Options/Mappers/SearchOptionsToAzureOptionsMapper.cs deleted file mode 100644 index 5ec3466..0000000 --- a/Dfe.Data.SearchPrototype/Infrastructure/Options/Mappers/SearchOptionsToAzureOptionsMapper.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Azure.Search.Documents; -using Dfe.Data.SearchPrototype.Common.Mappers; - -namespace Dfe.Data.SearchPrototype.Infrastructure.Options.Mappers; - -/// -/// Concrete mapper implementation which translates the results returned from the internal -/// "T:Dfe.Data.SearchPrototype.Infrastructure.Options.SearchSettingsOptions", -/// and maps them to the "T:Azure.Search.Documents.SearchOptions" -/// type required to configure and invoke the Azure search. -/// -public sealed class SearchOptionsToAzureOptionsMapper : IMapper -{ - /// - /// Entry point for mapping from the received - /// "T:Dfe.Data.SearchPrototype.Infrastructure.Options.SearchSettingsOptions" - /// to the returned "T:Azure.Search.Documents.SearchOptions" instance. - /// - /// - /// The internally configured search settings which - /// seed the "T:Azure.Search.Documents.SearchOptions" mapping. - /// - /// - /// A fully configured "T:Azure.Search.Documents.SearchOptions" instance. - /// - public SearchOptions MapFrom(SearchSettingsOptions input) - { - ArgumentNullException.ThrowIfNull(input); - - var searchOptions = new SearchOptions() - { - SearchMode = input.SearchMode, - Size = input.Size, - IncludeTotalCount = input.IncludeTotalCount - }; - - input.SearchFields?.ToList() - .ForEach(searchfield => - searchOptions.SearchFields.Add(searchfield)); - - input.SelectFields?.ToList() - .ForEach(searchOptions.Select.Add); - - return searchOptions; - } -} diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Options/SearchOptionsFactory.cs b/Dfe.Data.SearchPrototype/Infrastructure/Options/SearchOptionsFactory.cs deleted file mode 100644 index d7139fc..0000000 --- a/Dfe.Data.SearchPrototype/Infrastructure/Options/SearchOptionsFactory.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Azure.Search.Documents; -using Dfe.Data.SearchPrototype.Common.Mappers; -using Microsoft.Extensions.Options; - -namespace Dfe.Data.SearchPrototype.Infrastructure.Options; - -/// -/// Concrete implementation for deriving "T:Dfe.Data.SearchPrototype.Infrastructure.Options.SearchSettingsOptions" -/// configuration snapshots and mapping them to the expected "T:Azure.Search.Documents.SearchOptions" instance. -/// -public class SearchOptionsFactory : ISearchOptionsFactory -{ - private readonly IOptionsSnapshot _searchSettingsOptions; - private readonly IMapper _searchOptionsToAzureOptionsMapper; - - /// - /// Snapshot and concrete mapper implementation injected in order to support - /// retrieval of the targeted configuration snapshot, and map to the expected - /// "T:Azure.Search.Documents.SearchOptions" instance. - /// - /// - /// The internally configured options. - /// - /// - /// The search options required to successfully perform an Azure search. - /// - public SearchOptionsFactory( - IOptionsSnapshot searchSettingsOptions, - IMapper searchOptionsToAzureOptionsMapper) - { - _searchSettingsOptions = searchSettingsOptions; - _searchOptionsToAzureOptionsMapper = searchOptionsToAzureOptionsMapper; - } - - /// - /// Retrieves the "T:Dfe.Data.SearchPrototype.Infrastructure.Options.SearchSettingsOptions" - /// instance (if configured) and attempts to map to "T:Azure.Search.Documents.SearchOptions" instance. - /// - /// - /// The key used to target the configuration block describing the collection under scrutiny. - /// - /// - /// A configured instance of "T:Azure.Search.Documents.SearchOptions". - /// - /// - /// Exception thrown if no configured target collection key is provided. - /// - public SearchOptions? GetSearchOptions(string targetCollection) - { - ArgumentNullException.ThrowIfNull(targetCollection); - - SearchSettingsOptions searchSettingsOptions = - _searchSettingsOptions.Get(targetCollection); - - return (searchSettingsOptions == null) ? - default : _searchOptionsToAzureOptionsMapper.MapFrom(searchSettingsOptions); - } -} diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Options/SearchSettingsOptions.cs b/Dfe.Data.SearchPrototype/Infrastructure/Options/SearchSettingsOptions.cs deleted file mode 100644 index 5d9ef4b..0000000 --- a/Dfe.Data.SearchPrototype/Infrastructure/Options/SearchSettingsOptions.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Azure.Search.Documents.Models; - -namespace Dfe.Data.SearchPrototype.Infrastructure.Options; - -/// -/// Configuration options used to define the internal Azure cognitive search. -/// -public class SearchSettingsOptions -{ - /// - /// The search index name of the collection under scrutiny. - /// - public string? SearchIndex { get; set; } - /// - /// The search mode defines whether to use any, or all search terms provisioned. - /// - public SearchMode SearchMode { get; set; } - /// - /// The size of the allowable search response. - /// - public int Size { get; set; } - /// - /// Allows the count of the total number of search records - /// retrieved to be returned alongside the response. - /// - public bool IncludeTotalCount { get; set; } - /// - /// Specifies the fields over which the search will be conducted. - /// - public IList? SearchFields { get; set; } - /// - /// Specifies the fields which will be returned in the search response. - /// - public IList? SelectFields { get; set; } - /// - /// Specifies the allowable facets/filters to include in the search response. - /// - public IList? Facets { get; set; } -} diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterAndMapperTests.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterAndMapperTests.cs index 30c37a5..bb0426f 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterAndMapperTests.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterAndMapperTests.cs @@ -2,9 +2,10 @@ using Azure.Search.Documents.Models; using Dfe.Data.SearchPrototype.Common.Mappers; using Dfe.Data.SearchPrototype.Infrastructure.Mappers; -using Dfe.Data.SearchPrototype.Infrastructure.Options; using Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles; -using Dfe.Data.SearchPrototype.SearchForEstablishments; +using Dfe.Data.SearchPrototype.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; using Xunit; @@ -13,13 +14,11 @@ namespace Dfe.Data.SearchPrototype.Infrastructure.Tests; public sealed class CognitiveSearchServiceAdapterAndMapperTests { - private ISearchOptionsFactory _mockSearchOptionsFactory; - private IMapper>, EstablishmentResults> _searchResponseMapper; - private IMapper>, EstablishmentFacets> _facetsMapper; + private readonly IMapper>, EstablishmentResults> _searchResponseMapper; + private readonly IMapper>, EstablishmentFacets> _facetsMapper; public CognitiveSearchServiceAdapterAndMapperTests() { - _mockSearchOptionsFactory = SearchOptionsFactoryTestDouble.MockSearchOptionsFactory(); _searchResponseMapper = new PageableSearchResultsToEstablishmentResultsMapper( new AzureSearchResultToEstablishmentMapper( new AzureSearchResultToAddressMapper())); @@ -36,24 +35,27 @@ public async Task Search_WithValidSearchContext_ReturnsResults() var facetResults = new FacetsResultsFakeBuilder() .WithAutoGeneratedFacets() .Create(); + var options = AzureSearchOptionsTestDouble.Stub(); var mockService = new SearchServiceMockBuilder() - .WithSearchOptions("SearchKeyword", "TargetCollection") + .WithSearchKeywordAndCollection("SearchKeyword", options.SearchIndex) .WithSearchResults(establishmentSearchResults) .WithFacets(facetResults) .Create(); - ISearchServiceAdapter cognitiveSearchServiceAdapter = new CognitiveSearchServiceAdapter( - mockService, - _mockSearchOptionsFactory, - _searchResponseMapper, - _facetsMapper); + ISearchServiceAdapter cognitiveSearchServiceAdapter = + new CognitiveSearchServiceAdapter( + mockService, + IOptionsTestDouble.IOptionsMockFor(options), + _searchResponseMapper, + _facetsMapper); // act SearchResults? response = await cognitiveSearchServiceAdapter.SearchAsync( - new SearchContext( + new SearchServiceAdapterRequest( searchKeyword: "SearchKeyword", - targetCollection: "TargetCollection")); + searchFields: ["FIELD1", "FIELD2", "FIELD2"], + facets: ["FACET1", "FACET2", "FACET3"])); // assert response.Should().NotBeNull(); @@ -67,27 +69,29 @@ await cognitiveSearchServiceAdapter.SearchAsync( public async Task Search_WithNoFacetsReturned_ReturnsNullFacets() { // arrange + var options = AzureSearchOptionsTestDouble.Stub(); var mockService = new SearchServiceMockBuilder() - .WithSearchOptions("SearchKeyword", "TargetCollection") + .WithSearchKeywordAndCollection("SearchKeyword", options.SearchIndex) .WithSearchResults( new SearchResultFakeBuilder() .WithSearchResults() .Create()) .Create(); - var mockSearchOptionsFactory = SearchOptionsFactoryTestDouble.MockSearchOptionsFactory(); - ISearchServiceAdapter cognitiveSearchServiceAdapter = new CognitiveSearchServiceAdapter( + ISearchServiceAdapter cognitiveSearchServiceAdapter = + new CognitiveSearchServiceAdapter( mockService, - mockSearchOptionsFactory, + IOptionsTestDouble.IOptionsMockFor(options), _searchResponseMapper, _facetsMapper); // act SearchResults? response = await cognitiveSearchServiceAdapter.SearchAsync( - new SearchContext( + new SearchServiceAdapterRequest( searchKeyword: "SearchKeyword", - targetCollection: "TargetCollection")); + searchFields: ["FIELD1", "FIELD2", "FIELD2"], + facets: ["FACET1", "FACET2", "FACET3"])); // assert response.Should().NotBeNull(); @@ -98,24 +102,29 @@ await cognitiveSearchServiceAdapter.SearchAsync( public async Task Search_WithNoResultsReturned_ReturnsEmptyResults() { // arrange + var options = AzureSearchOptionsTestDouble.Stub(); var mockService = new SearchServiceMockBuilder() - .WithSearchOptions("SearchKeyword", "TargetCollection") + .WithSearchKeywordAndCollection("SearchKeyword", options.SearchIndex) .WithSearchResults( new SearchResultFakeBuilder() .WithEmptySearchResult() .Create()) .Create(); - ISearchServiceAdapter cognitiveSearchServiceAdapter = new CognitiveSearchServiceAdapter( + ISearchServiceAdapter cognitiveSearchServiceAdapter = + new CognitiveSearchServiceAdapter( mockService, - _mockSearchOptionsFactory, + IOptionsTestDouble.IOptionsMockFor(options), _searchResponseMapper, _facetsMapper); // act. - var response = await cognitiveSearchServiceAdapter.SearchAsync(new SearchContext( - searchKeyword: "SearchKeyword", - targetCollection: "TargetCollection")); + var response = + await cognitiveSearchServiceAdapter.SearchAsync( + new SearchServiceAdapterRequest( + searchKeyword: "SearchKeyword", + searchFields: ["FIELD1", "FIELD2", "FIELD2"], + facets: ["FACET1", "FACET2", "FACET3"])); // assert response.Should().NotBeNull(); diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs index 72f5656..cfbfd6d 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/CognitiveSearchServiceAdapterTests.cs @@ -4,73 +4,69 @@ using Dfe.Data.SearchPrototype.Common.Mappers; using Dfe.Data.SearchPrototype.Infrastructure.Options; using Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles; -using Dfe.Data.SearchPrototype.SearchForEstablishments; +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; +using Microsoft.Extensions.Options; using Xunit; namespace Dfe.Data.SearchPrototype.Infrastructure.Tests; public sealed class CognitiveSearchServiceAdapterTests { - private static CognitiveSearchServiceAdapter CreateServiceAdapterWith( + private static CognitiveSearchServiceAdapter CreateServiceAdapterWith( ISearchByKeywordService searchByKeywordService, - ISearchOptionsFactory searchOptionsFactory, - IMapper>, EstablishmentResults> searchResponseMapper, + IOptions searchOptions, + IMapper>, EstablishmentResults> searchResponseMapper, IMapper>, EstablishmentFacets> facetsMapper ) => - new(searchByKeywordService, searchOptionsFactory, searchResponseMapper, facetsMapper); + new(searchByKeywordService, searchOptions, searchResponseMapper, facetsMapper); [Fact] - public Task Search_WithNoSearchOptions_ThrowsApplicationException() + public void Search_WithNoSearchOptions_ThrowsApplicationException() { - var mockServiceBuilder = new SearchServiceMockBuilder(); - var mockService = mockServiceBuilder.MockSearchService("SearchKeyword", "TargetCollection"); - var mockSearchOptionsFactory = SearchOptionsFactoryTestDouble.MockForNoOptions(); + var mockService = new SearchServiceMockBuilder().MockSearchService("SearchKeyword", ""); var mockEstablishmentResultsMapper = PageableSearchResultsToEstablishmentResultsMapperTestDouble.DefaultMock(); var mockFacetsMapper = AzureFacetResultToEstablishmentFacetsMapperTestDouble.DefaultMock(); - // arrange - ISearchServiceAdapter cognitiveSearchServiceAdapter = - CreateServiceAdapterWith( - mockService, - mockSearchOptionsFactory, + // act + try + { + var _ = new CognitiveSearchServiceAdapter(mockService, + IOptionsTestDouble.IOptionsMockFor(null!), mockEstablishmentResultsMapper, mockFacetsMapper); - - // act. - return cognitiveSearchServiceAdapter - .Invoking(async serviceAdapter => - await serviceAdapter.SearchAsync( - new SearchContext( - searchKeyword: "SearchKeyword", - targetCollection: "TargetCollection"))) - .Should() - .ThrowAsync() - .WithMessage("Search options cannot be derived for TargetCollection."); + Assert.True(false); + } + catch (ArgumentNullException) + { + Assert.True(true); + } } [Fact] public Task Search_MapperThrowsException_ExceptionPassesThrough() { // arrange - var mockService = new SearchServiceMockBuilder().MockSearchService("SearchKeyword", "TargetCollection"); - var mockSearchOptionsFactory = SearchOptionsFactoryTestDouble.MockSearchOptionsFactory(); + var options = AzureSearchOptionsTestDouble.Stub(); + var mockService = new SearchServiceMockBuilder().MockSearchService("SearchKeyword", options.SearchIndex); var mockEstablishmentResultsMapper = PageableSearchResultsToEstablishmentResultsMapperTestDouble.MockMapperThrowingArgumentException(); var mockFacetsMapper = AzureFacetResultToEstablishmentFacetsMapperTestDouble.DefaultMock(); ISearchServiceAdapter cognitiveSearchServiceAdapter = CreateServiceAdapterWith( mockService, - mockSearchOptionsFactory, + IOptionsTestDouble.IOptionsMockFor(options), mockEstablishmentResultsMapper, mockFacetsMapper); // act, assert. return cognitiveSearchServiceAdapter - .Invoking(adapter => adapter.SearchAsync(new SearchContext( - searchKeyword: "SearchKeyword", - targetCollection: "TargetCollection"))) + .Invoking(adapter => + adapter.SearchAsync(new SearchServiceAdapterRequest( + searchKeyword: "SearchKeyword", [], []))) .Should() .ThrowAsync< ArgumentException>(); } diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Dfe.Data.SearchPrototype.Infrastructure.Tests.csproj b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Dfe.Data.SearchPrototype.Infrastructure.Tests.csproj index 32f514a..3282bd9 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Dfe.Data.SearchPrototype.Infrastructure.Tests.csproj +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Dfe.Data.SearchPrototype.Infrastructure.Tests.csproj @@ -12,7 +12,6 @@ - diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/AzureSearchResultToEstablishmentMapperTests.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/AzureSearchResultToEstablishmentMapperTests.cs index e0328ea..41c715e 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/AzureSearchResultToEstablishmentMapperTests.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/AzureSearchResultToEstablishmentMapperTests.cs @@ -9,8 +9,8 @@ namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.Mappers; public sealed class AzureSearchResultToEstablishmentMapperTests { - IMapper _establishmentMapper; - IMapper _addressMapper; + private readonly IMapper _establishmentMapper; + private readonly IMapper _addressMapper; public AzureSearchResultToEstablishmentMapperTests() { @@ -22,7 +22,7 @@ public AzureSearchResultToEstablishmentMapperTests() public void MapFrom_With_Valid_Search_Result_Returns_Configured_Establishment() { // arrange - Establishment establishmentFake = EstablishmentTestDouble.Create(); + DataTransferObjects.Establishment establishmentFake = EstablishmentTestDouble.Create(); // act SearchForEstablishments.Models.Establishment? result = _establishmentMapper.MapFrom(establishmentFake); @@ -45,7 +45,7 @@ public void MapFrom_With_Valid_Search_Result_Returns_Configured_Establishment() public void MapFrom_With_Null_Search_Result_Throws_Expected_Argument_Null_Exception() { // act. - Establishment establishmentFake = null!; + DataTransferObjects.Establishment establishmentFake = null!; // act. _establishmentMapper @@ -59,7 +59,7 @@ public void MapFrom_With_Null_Search_Result_Throws_Expected_Argument_Null_Except public void MapFrom_With_Null_id_Throws_Expected_Argument_Exception() { // arrange - var establishmentFake = new Establishment() + var establishmentFake = new DataTransferObjects.Establishment() { id = null!, ESTABLISHMENTNAME = "Test Establishment", @@ -74,14 +74,14 @@ public void MapFrom_With_Null_id_Throws_Expected_Argument_Exception() mapper.MapFrom(establishmentFake)) .Should() .Throw() - .WithMessage("Value cannot be null. (Parameter 'id')"); + .WithMessage("Value cannot be null. (Parameter 'input.id')"); } [Fact] public void MapFrom_With_Null_Name_Throws_Expected_Argument_Exception() { // arrange - var establishmentFake = new Establishment() + var establishmentFake = new DataTransferObjects.Establishment() { id = "123456", TYPEOFESTABLISHMENTNAME = "secondaryFake", @@ -96,14 +96,14 @@ public void MapFrom_With_Null_Name_Throws_Expected_Argument_Exception() mapper.MapFrom(establishmentFake)) .Should() .Throw() - .WithMessage("Value cannot be null. (Parameter 'ESTABLISHMENTNAME')"); + .WithMessage("Value cannot be null. (Parameter 'input.ESTABLISHMENTNAME')"); } [Fact] public void MapFrom_With_NullPhaseOfEducation_Throws_Expected_Argument_Exception() { // arrange - var establishmentFake = new Establishment() + var establishmentFake = new DataTransferObjects.Establishment() { id = "123456", ESTABLISHMENTNAME = "Test Establishment", @@ -118,14 +118,14 @@ public void MapFrom_With_NullPhaseOfEducation_Throws_Expected_Argument_Exception mapper.MapFrom(establishmentFake)) .Should() .Throw() - .WithMessage("Value cannot be null. (Parameter 'PHASEOFEDUCATION')"); + .WithMessage("Value cannot be null. (Parameter 'input.PHASEOFEDUCATION')"); } [Fact] public void MapFrom_With_NullTypeOfEstablishment_Throws_Expected_Argument_Exception() { // arrange - var establishmentFake = new Establishment() + var establishmentFake = new DataTransferObjects.Establishment() { id = "1111", ESTABLISHMENTNAME = "Test Establishment", @@ -140,14 +140,14 @@ public void MapFrom_With_NullTypeOfEstablishment_Throws_Expected_Argument_Except mapper.MapFrom(establishmentFake)) .Should() .Throw() - .WithMessage("Value cannot be null. (Parameter 'TYPEOFESTABLISHMENTNAME')"); + .WithMessage("Value cannot be null. (Parameter 'input.TYPEOFESTABLISHMENTNAME')"); } [Fact] public void MapFrom_With_NullEstablishmentStatus_Throws_Expected_Argument_Exception() { // arrange - var establishmentFake = new Establishment() + var establishmentFake = new DataTransferObjects.Establishment() { id = "1111", ESTABLISHMENTNAME = "Test Establishment", @@ -162,7 +162,7 @@ public void MapFrom_With_NullEstablishmentStatus_Throws_Expected_Argument_Except mapper.MapFrom(establishmentFake)) .Should() .Throw() - .WithMessage("Value cannot be null. (Parameter 'ESTABLISHMENTSTATUSNAME')"); + .WithMessage("Value cannot be null. (Parameter 'input.ESTABLISHMENTSTATUSNAME')"); } [Theory] @@ -174,7 +174,7 @@ public void MapFrom_With_NullAddressValues_Returns_Configured_Establishment( string street, string locality, string address3, string town, string postcode) { // arrange - Establishment establishmentFake = new Establishment() + DataTransferObjects.Establishment establishmentFake = new() { id = "000000", ESTABLISHMENTNAME = "fakename", diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/PageableSearchResultsToEstablishmentResultsMapperTests.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/PageableSearchResultsToEstablishmentResultsMapperTests.cs index 1f3a179..aba90d2 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/PageableSearchResultsToEstablishmentResultsMapperTests.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Mappers/PageableSearchResultsToEstablishmentResultsMapperTests.cs @@ -1,6 +1,7 @@ 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; @@ -12,7 +13,7 @@ namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.Mappers; public sealed class PageableSearchResultsToEstablishmentResultsMapperTests { - IMapper>, EstablishmentResults> _searchResultsMapper; + IMapper>, EstablishmentResults> _searchResultsMapper; public PageableSearchResultsToEstablishmentResultsMapperTests() { @@ -26,7 +27,7 @@ public PageableSearchResultsToEstablishmentResultsMapperTests() public void MapFrom_WithValidSearchResults_ReturnsConfiguredEstablishments() { // arrange - List> searchResultDocuments = + List> searchResultDocuments = SearchResultFake.SearchResults(); var pageableSearchResults = PageableTestDouble.FromResults(searchResultDocuments); diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Options/Mappers/SearchOptionsToAzureOptionsMapperTests.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Options/Mappers/SearchOptionsToAzureOptionsMapperTests.cs deleted file mode 100644 index 3219083..0000000 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Options/Mappers/SearchOptionsToAzureOptionsMapperTests.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Dfe.Data.SearchPrototype.Infrastructure.Options; -using Dfe.Data.SearchPrototype.Infrastructure.Options.Mappers; -using Dfe.Data.SearchPrototype.Infrastructure.Tests.Options.TestDoubles; -using FluentAssertions; -using Xunit; - -namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.Options.Mappers; - -public sealed class SearchOptionsToAzureOptionsMapperTests -{ - [Fact] - public void MapFrom_With_Valid_Search_Settings_Options_Returns_Search_Options() - { - // arrange - SearchSettingsOptions options = SearchSettingsOptionsTestDouble.MockFor().Value; - - // act - var result = new SearchOptionsToAzureOptionsMapper().MapFrom(options); - - // assert - result.Should().NotBeNull(); - } - - [Fact] - public void MapFrom_With_Null_Search_Settings_Options_Throws_Expected_ArgumentNullException() - { - // arrange - var mapper = new SearchOptionsToAzureOptionsMapper(); - - // assert - mapper - .Invoking(mapper => - mapper.MapFrom(input: null!)) - .Should() - .Throw() - .WithMessage("Value cannot be null. (Parameter 'input')"); - } -} diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Options/SearchOptionsFactoryTests.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Options/SearchOptionsFactoryTests.cs deleted file mode 100644 index a7245f2..0000000 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Options/SearchOptionsFactoryTests.cs +++ /dev/null @@ -1,78 +0,0 @@ -using Azure.Search.Documents; -using Dfe.Data.SearchPrototype.Infrastructure.Options; -using Dfe.Data.SearchPrototype.Infrastructure.Tests.Options.TestDoubles; -using FluentAssertions; -using Xunit; - -namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.Options; - -public sealed class SearchOptionsFactoryTests -{ - [Fact] - public void GetSearchOptions_Returns_Configured_Options() - { - // arrange - const string TargetCollection = "Establishment"; - var options = SearchSettingsOptionsTestDouble.MockFor().Value; - var searchOptionsToAzureOptionsMapperTestDoubles = - SearchOptionsToAzureOptionsMapperTestDoubles.MockDefaultMapper(); - var optionsSnapshot = - SearchSettingsOptionsSnapshotTestDouble - .MockForNamedOptions(TargetCollection, options); - - var searchOptionsFactory = - new SearchOptionsFactory(optionsSnapshot, searchOptionsToAzureOptionsMapperTestDoubles); - - // act - SearchOptions? result = searchOptionsFactory.GetSearchOptions(TargetCollection); - - // assert - result.Should().NotBeNull(); - } - - [Fact] - public void GetSearchOptions_No_Search_Settings_Options_Returns_Null() - { - // arrange - const string TargetCollection = "Establishment"; - var options = SearchSettingsOptionsTestDouble.MockFor().Value; - var searchOptionsToAzureOptionsMapperTestDoubles = - SearchOptionsToAzureOptionsMapperTestDoubles.Dummy(); - var optionsSnapshot = - SearchSettingsOptionsSnapshotTestDouble - .MockForNamedOptions(TargetCollection, options); - - var searchOptionsFactory = - new SearchOptionsFactory(optionsSnapshot, searchOptionsToAzureOptionsMapperTestDoubles); - - // act - var result = searchOptionsFactory.GetSearchOptions(TargetCollection); - - // assert - result.Should().BeNull(); - } - - [Fact] - public void GetSearchOptions_With_Null_Target_Collection_Returns_Configured_Options() - { - // arrange - const string TargetCollection = "Establishment"; - var options = SearchSettingsOptionsTestDouble.MockFor().Value; - var searchOptionsToAzureOptionsMapperTestDoubles = - SearchOptionsToAzureOptionsMapperTestDoubles.MockDefaultMapper(); - var optionsSnapshot = - SearchSettingsOptionsSnapshotTestDouble - .MockForNamedOptions(TargetCollection, options); - - var searchOptionsFactory = - new SearchOptionsFactory(optionsSnapshot, searchOptionsToAzureOptionsMapperTestDoubles); - - // act. - searchOptionsFactory - .Invoking(searchOptionsFactory => - searchOptionsFactory.GetSearchOptions(targetCollection: null!)) - .Should() - .Throw() - .WithMessage("Value cannot be null. (Parameter 'targetCollection')"); - } -} diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Options/TestDoubles/SearchOptionsToAzureOptionsMapperTestDoubles.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Options/TestDoubles/SearchOptionsToAzureOptionsMapperTestDoubles.cs deleted file mode 100644 index 3e67fe5..0000000 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Options/TestDoubles/SearchOptionsToAzureOptionsMapperTestDoubles.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Azure.Search.Documents; -using Dfe.Data.SearchPrototype.Common.Mappers; -using Dfe.Data.SearchPrototype.Infrastructure.Options; -using Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles; -using Moq; - -namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.Options.TestDoubles; - -internal static class SearchOptionsToAzureOptionsMapperTestDoubles -{ - public static IMapper Dummy() => Mock.Of>(); - - public static IMapper MockDefaultMapper() - { - var searchOptionsToAzureOptionsMapperMock = new Mock>(); - - searchOptionsToAzureOptionsMapperMock.Setup(mapper => - mapper.MapFrom(It.IsAny())) - .Returns(SearchOptionsFactoryTestDouble.SearchOptionsFake); - - return searchOptionsToAzureOptionsMapperMock.Object; - } -} diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Options/TestDoubles/SearchSettingsOptionsSnapshotTestDouble.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Options/TestDoubles/SearchSettingsOptionsSnapshotTestDouble.cs deleted file mode 100644 index 0e93af1..0000000 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Options/TestDoubles/SearchSettingsOptionsSnapshotTestDouble.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Dfe.Data.SearchPrototype.Infrastructure.Options; -using Microsoft.Extensions.Options; -using Moq; - -namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.Options.TestDoubles; - -internal static class SearchSettingsOptionsSnapshotTestDouble -{ - public static IOptionsSnapshot MockForNamedOptions(string optionName, SearchSettingsOptions options) - { - var optionsSnapshot = new Mock>(); - optionsSnapshot.Setup(searchSettingsOptions => - searchSettingsOptions.Get(optionName)) - .Returns(options); - - return optionsSnapshot.Object; - } -} diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Options/TestDoubles/SearchSettingsOptionsTestDouble.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/Options/TestDoubles/SearchSettingsOptionsTestDouble.cs deleted file mode 100644 index 43a394a..0000000 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/Options/TestDoubles/SearchSettingsOptionsTestDouble.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Dfe.Data.SearchPrototype.Infrastructure.Options; -using Microsoft.Extensions.Options; -using Moq; - -namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.Options.TestDoubles; - -internal static class SearchSettingsOptionsTestDouble -{ - public static IOptions Dummy() => Mock.Of>(); - - public static IOptions MockFor() - { - var searchSettingsOptionsMock = new Mock>(); - var searchSettingsOptions = new SearchSettingsOptions() { SearchIndex = "TestIndex" }; - - searchSettingsOptionsMock.SetupGet(options => - options.Value).Returns(searchSettingsOptions); - - return searchSettingsOptionsMock.Object; - } -} diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/AzureSearchOptionsTestDouble.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/AzureSearchOptionsTestDouble.cs new file mode 100644 index 0000000..f1b3646 --- /dev/null +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/AzureSearchOptionsTestDouble.cs @@ -0,0 +1,14 @@ +using Dfe.Data.SearchPrototype.Infrastructure.Options; + +namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles; + +internal static class AzureSearchOptionsTestDouble +{ + public static AzureSearchOptions Stub() => new() + { + SearchMode = 0, + Size = 100, + IncludeTotalCount = true, + SearchIndex = "establishments" + }; +} diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/AzureSearchResponseTestDoubleBuilder.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/AzureSearchResponseTestDoubleBuilder.cs index 5d0caaf..dae0a00 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/AzureSearchResponseTestDoubleBuilder.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/AzureSearchResponseTestDoubleBuilder.cs @@ -1,6 +1,7 @@ using Azure.Search.Documents.Models; using Azure; using Moq; +using Dfe.Data.SearchPrototype.Infrastructure.DataTransferObjects; namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles; diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/EstablishmentTestDouble.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/EstablishmentTestDouble.cs index 59e9c02..5e74742 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/EstablishmentTestDouble.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/EstablishmentTestDouble.cs @@ -1,4 +1,6 @@ -namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles; +using Dfe.Data.SearchPrototype.Infrastructure.DataTransferObjects; + +namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles; public static class EstablishmentTestDouble { diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/FacetsResultsFakeBuilder.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/FacetsResultsFakeBuilder.cs index 44cb8ab..ae19d11 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/FacetsResultsFakeBuilder.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/FacetsResultsFakeBuilder.cs @@ -39,12 +39,12 @@ public FacetsResultsFakeBuilder WithAutoGeneratedFacets() var facetsCount = new Bogus.Faker().Random.Int(1, 10); for(int i=0; i(); int resultsCountAnyNumber = new Bogus.Faker().Random.Int(1, 50); @@ -58,7 +58,7 @@ public FacetsResultsFakeBuilder WithAutoGeneratedFacet() facetResults.Add(SearchModelFactory.FacetResult(resultsCountAnyNumber, facetResult)); } - Dictionary> facet = new() { [new Bogus.Faker().Name.JobType()] = facetResults }; + Dictionary> facet = new() { [new Bogus.Faker().Name.JobType() + appendFacetName] = facetResults }; foreach (var kvp in facet) { diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/PageableSearchResultsToEstablishmentResultsMapperTestDouble.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/PageableSearchResultsToEstablishmentResultsMapperTestDouble.cs index 0bc9eb7..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>, EstablishmentResults> DefaultMock() => - Mock.Of>, EstablishmentResults>>(); + public static IMapper>, EstablishmentResults> DefaultMock() => + Mock.Of>, EstablishmentResults>>(); - public static Expression>, EstablishmentResults>, EstablishmentResults>> MapFrom() => - mapper => mapper.MapFrom(It.IsAny>>()); + public static Expression>, EstablishmentResults>, EstablishmentResults>> MapFrom() => + mapper => mapper.MapFrom(It.IsAny>>()); - public static IMapper>, EstablishmentResults> MockFor(EstablishmentResults establishments) + public static IMapper>, EstablishmentResults> MockFor(EstablishmentResults establishments) { - var mapperMock = new Mock>, EstablishmentResults>>(); + var mapperMock = new Mock>, EstablishmentResults>>(); mapperMock.Setup(MapFrom()).Returns(establishments); return mapperMock.Object; } - public static IMapper>, EstablishmentResults> MockMapperThrowingArgumentException() + public static IMapper>, EstablishmentResults> MockMapperThrowingArgumentException() { - var mapperMock = new Mock>, EstablishmentResults>>(); + var mapperMock = new Mock>, EstablishmentResults>>(); mapperMock.Setup(MapFrom()).Throws(new ArgumentException()); diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/PageableTestDouble.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/PageableTestDouble.cs index 9816fb5..76652d8 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/PageableTestDouble.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/PageableTestDouble.cs @@ -1,5 +1,6 @@ using Azure; using Azure.Search.Documents.Models; +using Dfe.Data.SearchPrototype.Infrastructure.DataTransferObjects; using Moq; namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles; diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchOptionsFactoryTestDouble.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchOptionsFactoryTestDouble.cs deleted file mode 100644 index a98e8be..0000000 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchOptionsFactoryTestDouble.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Azure.Search.Documents; -using Azure.Search.Documents.Models; -using Dfe.Data.SearchPrototype.Infrastructure.Options; -using Moq; -using System.Linq.Expressions; - -namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles; - -internal static class SearchOptionsFactoryTestDouble -{ - public static ISearchOptionsFactory DefaultMock() => Mock.Of(); - public static Expression> SearchOption() => - searchOptionsFactory => searchOptionsFactory.GetSearchOptions(It.IsAny()); - - public static ISearchOptionsFactory MockFor(SearchOptions searchOptions) - { - var searchOptionsFactoryMock = new Mock(); - - searchOptionsFactoryMock.Setup(SearchOption()).Returns(searchOptions); - - return searchOptionsFactoryMock.Object; - } - - public static ISearchOptionsFactory MockSearchOptionsFactory() => MockFor(SearchOptionsFake); - - public static ISearchOptionsFactory MockForNoOptions() => MockFor(default!); - public static SearchOptions SearchOptionsFake => new() - { - SearchMode = SearchMode.Any, - Size = 100, - IncludeTotalCount = true, - SearchFields = { "ESTABLISHMENTNAME" } - }; -} diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchResultFake.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchResultFake.cs index 1f1c29f..859a20c 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchResultFake.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchResultFake.cs @@ -1,5 +1,6 @@ using Azure; using Azure.Search.Documents.Models; +using Dfe.Data.SearchPrototype.Infrastructure.DataTransferObjects; using Moq; namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles; diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchResultFakeBuilder.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchResultFakeBuilder.cs index 005a7af..c467aac 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchResultFakeBuilder.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchResultFakeBuilder.cs @@ -1,4 +1,5 @@ using Azure.Search.Documents.Models; +using Dfe.Data.SearchPrototype.Infrastructure.DataTransferObjects; namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles; diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchServiceMockBuilder.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchServiceMockBuilder.cs index 98b6c72..70dee2a 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchServiceMockBuilder.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/SearchServiceMockBuilder.cs @@ -2,6 +2,7 @@ using Azure.Search.Documents; using Azure.Search.Documents.Models; using Dfe.Data.Common.Infrastructure.CognitiveSearch.SearchByKeyword; +using Dfe.Data.SearchPrototype.Infrastructure.DataTransferObjects; using Moq; using System.Linq.Expressions; @@ -30,7 +31,7 @@ public ISearchByKeywordService MockFor(Response> se return searchServiceMock.Object; } - public SearchServiceMockBuilder WithSearchOptions(string keyword, string collection) + public SearchServiceMockBuilder WithSearchKeywordAndCollection(string keyword, string collection) { _keyword = keyword; _collection = collection; diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/Shared/IOptionsTestDouble.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/Shared/IOptionsTestDouble.cs new file mode 100644 index 0000000..a09538c --- /dev/null +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestDoubles/Shared/IOptionsTestDouble.cs @@ -0,0 +1,21 @@ +using Microsoft.Extensions.Options; +using Moq; + +namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles.Shared; + +public static class IOptionsTestDouble +{ + public static Mock> IOptionsMock() + where TOptionSetting : class => new(); + + public static IOptions IOptionsMockFor(TOptionsSetting optionsSettings) + where TOptionsSetting : class + { + var optionsMock = IOptionsMock(); + + optionsMock.Setup(options => + options.Value).Returns(optionsSettings); + + return optionsMock.Object; + } +} \ No newline at end of file diff --git a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestHelpers/EstablishmentTestExtensionMethods.cs b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestHelpers/EstablishmentTestExtensionMethods.cs index 52a9724..0438218 100644 --- a/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestHelpers/EstablishmentTestExtensionMethods.cs +++ b/Dfe.Data.SearchPrototype/Infrastructure/Tests/TestHelpers/EstablishmentTestExtensionMethods.cs @@ -6,9 +6,14 @@ namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.TestHelpers; public static class EstablishmentTestExtensionMethods { - public static void ShouldHaveMatchingMappedEstablishment(this SearchResult searchResult, EstablishmentResults mappedEstablishments) + public static void ShouldHaveMatchingMappedEstablishment(this SearchResult searchResult, EstablishmentResults mappedEstablishments) { - var mappedEstablishment = mappedEstablishments.Establishments.ToList().Find(x => x.Urn == searchResult.Document.id); - mappedEstablishment!.Name.Should().Be(searchResult.Document.ESTABLISHMENTNAME); + var mappedEstablishment = + mappedEstablishments.Establishments.ToList() + .Find(establishment => + establishment.Urn == searchResult.Document.id); + + mappedEstablishment!.Name + .Should().Be(searchResult.Document.ESTABLISHMENTNAME); } } diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/ISearchServiceAdapter.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/ISearchServiceAdapter.cs similarity index 65% rename from Dfe.Data.SearchPrototype/SearchForEstablishments/ISearchServiceAdapter.cs rename to Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/ISearchServiceAdapter.cs index 3127cc1..421335f 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/ISearchServiceAdapter.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/ISearchServiceAdapter.cs @@ -1,6 +1,6 @@ using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; -namespace Dfe.Data.SearchPrototype.SearchForEstablishments; +namespace Dfe.Data.SearchPrototype.SearchForEstablishments.ByKeyword.ServiceAdapters; /// /// Describes behaviour for an adaption of core search services infrastructure to allow @@ -10,14 +10,14 @@ public interface ISearchServiceAdapter { /// /// Describes the required call to the underlying search service infrastructure and the expected - /// "T:Dfe.Data.SearchPrototype.Search.Domain.AgregateRoot.Establishments" type to be returned. + /// type to be returned. /// - /// + /// /// Prescribes the context of the search including the keyword and collection target. /// /// - /// A configured "T:Dfe.Data.SearchPrototype.Search.Domain.AgregateRoot.Establishments" + /// A configured /// object hydrated from the results of the azure search. /// - Task SearchAsync(SearchContext searchContext); + Task SearchAsync(SearchServiceAdapterRequest searchServiceAdapterRequest); } diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs new file mode 100644 index 0000000..bd12931 --- /dev/null +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/ServiceAdapters/SearchServiceAdapterRequest.cs @@ -0,0 +1,75 @@ +namespace Dfe.Data.SearchPrototype.SearchForEstablishments.ByKeyword.ServiceAdapters; + +/// +/// Prescribes the context of the search including +/// the keyword, search fields, and facets to use. +public sealed class SearchServiceAdapterRequest +{ + /// + /// The search keyword(s) to be applied. + /// + public string SearchKeyword { get; } + + /// + /// The collection of fields in the underlying collection to search over. + /// + public IList SearchFields { get; } + + /// + /// The collection of facets to apply in the search request. + /// + public IList Facets { get; } + + /// + /// The following arguments are passed via the constructor and are not changeable + /// once an instance is created, this ensures we preserve immutability. + /// + /// + /// The search keyword(s) to be applied. + /// + /// + /// The collection of fields in the underlying collection to search over. + /// + /// + /// The collection of facets to apply in the search request. + /// + /// + /// The exception thrown if an invalid search keyword (null or whitespace) is prescribed. + /// + /// + /// 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) + { + SearchKeyword = + string.IsNullOrWhiteSpace(searchKeyword) ? + throw new ArgumentNullException(nameof(searchKeyword)) : searchKeyword; + + SearchFields = searchFields == null || searchFields.Count <= 0 ? + throw new ArgumentException("", nameof(searchFields)) : searchFields; + + Facets = facets == null || facets.Count <= 0 ? + throw new ArgumentException("", nameof(facets)) : facets; + + } + + /// + /// 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. + /// + /// + /// A configured instance. + /// + public static SearchServiceAdapterRequest Create( + string searchKeyword, IList searchFields, IList facets) => + new(searchKeyword, searchFields, facets); +} diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordRequest.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordRequest.cs new file mode 100644 index 0000000..5e585ec --- /dev/null +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordRequest.cs @@ -0,0 +1,28 @@ +namespace Dfe.Data.SearchPrototype.SearchForEstablishments.ByKeyword.Usecase; + +/// +/// This is the object used to make requests (send input) through to the +/// instance. +/// +public sealed class SearchByKeywordRequest +{ + /// + /// The following arguments are passed via the constructor and used to create + /// an immutable instance which encapsulates + /// the parameters required to formulate a valid search request. + /// + /// + /// The string keyword used to search the collection specified. + /// + public SearchByKeywordRequest(string searchKeyword) + { + ArgumentException.ThrowIfNullOrEmpty(nameof(searchKeyword)); + + SearchKeyword = searchKeyword; + } + + /// + /// The string keyword used to search the collection specified. + /// + public string SearchKeyword { get; } +} diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchByKeywordResponse.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordResponse.cs similarity index 80% rename from Dfe.Data.SearchPrototype/SearchForEstablishments/SearchByKeywordResponse.cs rename to Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordResponse.cs index 8b2cab2..e5608e5 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchByKeywordResponse.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordResponse.cs @@ -1,31 +1,32 @@ using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; -namespace Dfe.Data.SearchPrototype.SearchForEstablishments; +namespace Dfe.Data.SearchPrototype.SearchForEstablishments.ByKeyword.Usecase; /// /// This is the object that carries the response (output) back from the -/// T:Dfe.Data.SearchPrototype.SearchForEstablishments.SearchByKeywordUseCase instance. +/// instance. /// public sealed class SearchByKeywordResponse { /// - /// The result object that encapsulates the search results - /// returned by the Establishment search + /// The result object that encapsulates the + /// search results returned by the Establishment search. /// public EstablishmentResults? EstablishmentResults { get; init; } /// - /// The result object that encapsulates the returned by the Establishment search + /// The result object that encapsulates the + /// returned by the Establishment search. /// public EstablishmentFacets? EstablishmentFacetResults { get; init; } /// - /// The return status of the call to the instance + /// The return status of the call to the instance. /// public SearchResponseStatus Status { get; } /// - /// + /// Establishes the status of the underlying search response, i.e. Success or otherwise. /// /// public SearchByKeywordResponse(SearchResponseStatus status) @@ -38,13 +39,13 @@ public SearchByKeywordResponse(SearchResponseStatus status) /// once an instance is created, this ensures we preserve immutability. /// /// - /// The readonly collection of + /// The readonly collection of . /// /// - /// The readonly collection of + /// The readonly collection of . /// /// - /// The of the result of the search + /// The of the result of the search. /// public SearchByKeywordResponse(EstablishmentResults establishments, EstablishmentFacets facetResults, SearchResponseStatus status) { diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchByKeywordUseCase.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordUseCase.cs similarity index 52% rename from Dfe.Data.SearchPrototype/SearchForEstablishments/SearchByKeywordUseCase.cs rename to Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordUseCase.cs index 2eeebf5..926a16c 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchByKeywordUseCase.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchByKeywordUseCase.cs @@ -1,7 +1,9 @@ using Dfe.Data.SearchPrototype.Common.CleanArchitecture.Application.UseCase; +using Dfe.Data.SearchPrototype.SearchForEstablishments.ByKeyword.ServiceAdapters; using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; +using Microsoft.Extensions.Options; -namespace Dfe.Data.SearchPrototype.SearchForEstablishments; +namespace Dfe.Data.SearchPrototype.SearchForEstablishments.ByKeyword.Usecase; /// /// This use case is responsible for handling keyword search requests. The use case will delegate responsibility @@ -12,6 +14,7 @@ namespace Dfe.Data.SearchPrototype.SearchForEstablishments; public sealed class SearchByKeywordUseCase : IUseCase { private readonly ISearchServiceAdapter _searchServiceAdapter; + private readonly SearchByKeywordCriteria _searchByKeywordCriteriaOptions; /// /// The following dependencies include the core cognitive search service definition, @@ -21,9 +24,30 @@ public sealed class SearchByKeywordUseCase : IUseCase + /// + /// The define the search fields and facets on + /// which to conduct the underlying search. This is defined in configuration using + /// the options pattern as follows (note: fields and facets used are for explanatory use only), + /// + /// "SearchByKeywordCriteria": { + /// "SearchFields": [ + /// "ESTABLISHMENTNAME", + /// "TOWN", + /// "PHASEOFEDUCATION" + /// ], + /// "Facets": [ + /// "PHASEOFEDUCATION", + /// "ESTABLISHMENTSTATUSNAME" + /// ] + /// } + /// + /// public SearchByKeywordUseCase( - ISearchServiceAdapter searchServiceAdapter) + ISearchServiceAdapter searchServiceAdapter, + IOptions searchByKeywordCriteriaOptions) { + ArgumentNullException.ThrowIfNull(searchByKeywordCriteriaOptions); + _searchByKeywordCriteriaOptions = searchByKeywordCriteriaOptions.Value; _searchServiceAdapter = searchServiceAdapter; } @@ -33,31 +57,40 @@ public SearchByKeywordUseCase( /// status of the completed work-flow. /// /// - /// The T:Dfe.Data.SearchPrototype.SearchForEstablishments.SearchByKeywordRequest input parameter. + /// The parameter is the object used + /// to allow requests (send input) through to the use-case (i.e. acts as an input port). /// /// - /// The T:Dfe.Data.SearchPrototype.SearchForEstablishments.SearchByKeywordResponse output parameter. + /// The output parameter is the object used + /// to encapsulate the response from the use-case (send output) from the use-case (i.e. acts as an output port). /// public async Task HandleRequest(SearchByKeywordRequest request) { - if ((request == null) || (request.Context == null)) { + if (request == null || string.IsNullOrWhiteSpace(request.SearchKeyword)) + { return new SearchByKeywordResponse(SearchResponseStatus.InvalidRequest); - }; + } try { - SearchResults results = await _searchServiceAdapter.SearchAsync(request.Context); + SearchResults results = + await _searchServiceAdapter.SearchAsync( + new SearchServiceAdapterRequest( + request.SearchKeyword, + _searchByKeywordCriteriaOptions.SearchFields, + _searchByKeywordCriteriaOptions.Facets)); return results switch { null => new(status: SearchResponseStatus.SearchServiceError), - _ => new(status: SearchResponseStatus.Success) { + _ => new(status: SearchResponseStatus.Success) + { EstablishmentResults = results.Establishments, EstablishmentFacetResults = results.Facets } }; } - catch (Exception) // something went wrong in the infrastructure tier + catch (Exception) // something went wrong in the infrastructure tier. { return new(status: SearchResponseStatus.SearchServiceError); } diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchResponseStatus.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchResponseStatus.cs new file mode 100644 index 0000000..1a53797 --- /dev/null +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/ByKeyword/Usecase/SearchResponseStatus.cs @@ -0,0 +1,20 @@ +namespace Dfe.Data.SearchPrototype.SearchForEstablishments.ByKeyword.Usecase; + +/// +/// Defines the status of the search response. +/// +public enum SearchResponseStatus +{ + /// + /// The search request completed successfully. + /// + Success, + /// + /// The request was not valid. + /// + InvalidRequest, + /// + /// The request was submitted and resulted in an error. + /// + SearchServiceError +} diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/CompositionRoot.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/CompositionRoot.cs new file mode 100644 index 0000000..a1f5741 --- /dev/null +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/CompositionRoot.cs @@ -0,0 +1,33 @@ +using Dfe.Data.SearchPrototype.Common.CleanArchitecture.Application.UseCase; +using Dfe.Data.SearchPrototype.SearchForEstablishments.ByKeyword.Usecase; +using Microsoft.Extensions.DependencyInjection; + +namespace Dfe.Data.SearchPrototype.SearchForEstablishments; + +/// +/// The composition root provides a unified location in the application where the composition +/// of the object graphs for the application take place, using the IOC container. +/// +public static class CompositionRoot +{ + /// + /// Extension method which provides all the pre-registrations required to + /// access the AI azure search service adapter, and perform searches across provisioned indexes. + /// + /// + /// The originating application services onto which to register the search dependencies. + /// + /// + /// The exception thrown if no valid is provisioned. + /// + public static void AddSearchForEstablishmentServices(this IServiceCollection services) + { + if (services is null) + { + throw new ArgumentNullException(nameof(services), + "A service collection is required to configure the search by keyword use-case."); + } + + services.AddScoped, SearchByKeywordUseCase>(); + } +} diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/DependencyRegistration.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/DependencyRegistration.cs deleted file mode 100644 index 421f5a5..0000000 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/DependencyRegistration.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Dfe.Data.SearchPrototype.Common.CleanArchitecture.Application.UseCase; -using Microsoft.Extensions.DependencyInjection; - -namespace Dfe.Data.SearchPrototype.SearchForEstablishments; - -/// -/// Extension method which provides all the pre-registrations required to -/// use the SearchForEstablishments services -/// -public static class DependencyRegistration -{ - /// - /// Register all the necessary SearchForEstablishments services - /// - /// - public static void AddSearchForEstablishmentServices(this IServiceCollection services) - { - services.AddScoped, SearchByKeywordUseCase>(); - } -} diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/Address.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/Address.cs index b518566..eed28c5 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/Address.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/Address.cs @@ -1,7 +1,7 @@ namespace Dfe.Data.SearchPrototype.SearchForEstablishments.Models; /// -/// Encapsulates the address details of an Establishment +/// Encapsulates the address details of an Establishment. /// public class Address { @@ -9,18 +9,22 @@ public class Address /// The first line of the address. /// public string? Street { get; init; } + /// /// The second line of the address. /// public string? Locality { get; init; } + /// /// The third line of the address. /// public string? Address3 { get; init; } + /// /// The fourth line of the address. /// public string? Town { get; init; } + /// /// The postcode /// @@ -29,8 +33,7 @@ public class Address /// /// default constructor /// - public Address() - { + public Address(){ } /// diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/Establishment.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/Establishment.cs index 6040864..07b86dc 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/Establishment.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/Establishment.cs @@ -1,7 +1,7 @@ namespace Dfe.Data.SearchPrototype.SearchForEstablishments.Models; /// -/// Object used to encapsulate the establishment search result. +/// Encapsulates the establishment search result. /// public class Establishment { @@ -9,26 +9,32 @@ public class Establishment /// The read-only URN (unique identifier) of the given establishment. /// public string Urn { get; } + /// /// The read-only name associated with the given establishment. /// public string Name { get; } + /// /// The read-only address associated with the given establishment /// public Address Address { get; } + /// /// The read-only type of the establishment. /// public string EstablishmentType { get; } + /// /// The read-only education phase of establishment. /// public string PhaseOfEducation { get; } + /// /// The read-only status of the establishment. /// public string EstablishmentStatusName { get; } + /// /// Establishes an immutable establishment instance via the constructor arguments specified. /// diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentFacet.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentFacet.cs index 8bfb94a..c8d57ac 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentFacet.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentFacet.cs @@ -1,26 +1,32 @@ -namespace Dfe.Data.SearchPrototype.SearchForEstablishments.Models; +using Dfe.Data.SearchPrototype.SearchForEstablishments.ByKeyword.Usecase; + +namespace Dfe.Data.SearchPrototype.SearchForEstablishments.Models; /// -/// The object that encapsulates the Faceted results returned by the -/// T:Dfe.Data.SearchPrototype.SearchForEstablishments.SearchByKeywordUseCase instance +/// Encapsulates the Faceted results returned by the instance. /// public class EstablishmentFacet { /// - /// The facet (field) name + /// The facet (field) name. /// public string Name { get; } /// - /// The collection of T:Dfe.Data.SearchPrototype.SearchForEstablishments.Models.FacetResult + /// The collection of instances. /// public IList Results { get; } /// - /// Constructor + /// Establishes an immutable instance via the constructor arguments specified. /// - /// - /// + /// + /// The name of the facet on which to assign the prescribed results. + /// + /// + /// The collection of instances that carry + /// the facet values and count of matched items found. + /// public EstablishmentFacet(string facetName, IList facetResults) { Name = facetName; diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentFacets.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentFacets.cs index 3aab72e..bb218a9 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentFacets.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentFacets.cs @@ -1,31 +1,35 @@ namespace Dfe.Data.SearchPrototype.SearchForEstablishments.Models; /// -/// Object used to encapsulate the aggregation of facets returned from search. +/// Encapsulates the aggregation of facets returned from the underlying search system. /// public class EstablishmentFacets { /// - /// The readonly collection of facets from the search + /// The readonly collection of facets derived from the underlying search mechanism. /// public IReadOnlyCollection Facets => _establishmentsFacets.AsReadOnly(); private readonly List _establishmentsFacets; /// - /// Default constuctor + /// Default constructor initialises a new readonly + /// collection of instances. /// public EstablishmentFacets() { - _establishmentsFacets = new(); + _establishmentsFacets = []; } /// - /// Constructor with the following parameters + /// Establishes an immutable collection of + /// instance via the constructor argument specified. /// - /// List of Establishments + /// + /// Collection of configured instances. + /// public EstablishmentFacets(IEnumerable establishmentFacets) { _establishmentsFacets = establishmentFacets.ToList(); } -} +} \ No newline at end of file diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentResults.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentResults.cs index dfe7a36..3e950e4 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentResults.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/EstablishmentResults.cs @@ -1,29 +1,35 @@ namespace Dfe.Data.SearchPrototype.SearchForEstablishments.Models; /// -/// Object used to encapsulate the aggregation of establishment search results. +/// Encapsulates the aggregation of +/// types returned from the underlying search system. /// public sealed class EstablishmentResults { /// - /// The readonly collection of establishment search results + /// The readonly collection of + /// types derived from the underlying search mechanism. /// public IReadOnlyCollection Establishments => _establishments.AsReadOnly(); private readonly List _establishments; /// - /// Default constuctor + /// Default constructor initialises a new readonly + /// collection of instances. /// public EstablishmentResults() { - _establishments = new(); + _establishments = []; } /// - /// Constructor with the following parameters + /// Establishes an immutable collection of + /// instance via the constructor argument specified. /// - /// List of Establishments + /// + /// Collection of configured instances. + /// public EstablishmentResults(IEnumerable establishments) { _establishments = establishments.ToList(); diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/FacetResult.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/FacetResult.cs index 8e01129..d43ac97 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/FacetResult.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/FacetResult.cs @@ -1,25 +1,30 @@ namespace Dfe.Data.SearchPrototype.SearchForEstablishments.Models; /// -/// The object that encapsulates a single facet result for a facet +/// Encapsulates a single facet result and count for a given fact type. /// public class FacetResult { /// - /// The value of the facet result + /// The value of the facet result. /// public string Value { get; } /// - /// The number of records that belong to this facet value + /// The number of records that belong to this facet value. /// public long? Count { get; } + /// - /// Constructor + /// Establishes an immutable instance via the constructor arguments specified. /// - /// The value of the facet result - /// The number of records that belong to this facet value + /// + /// The values associated with the given facet type. + /// + /// + /// The number of records that belong to this facet value. + /// public FacetResult(string value, long? count) { Value = value; diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/SearchByKeywordCriteria.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/SearchByKeywordCriteria.cs new file mode 100644 index 0000000..efc4e39 --- /dev/null +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/SearchByKeywordCriteria.cs @@ -0,0 +1,20 @@ +using Dfe.Data.SearchPrototype.SearchForEstablishments.ByKeyword.Usecase; + +namespace Dfe.Data.SearchPrototype.SearchForEstablishments.Models; + +/// +/// The search criteria used by the +/// which is set using the configuration settings defined (via IOptions pattern). +/// +public class SearchByKeywordCriteria +{ + /// + /// The collection of fields in the underlying collection to search over. + /// + public IList SearchFields { get; set; } = []; + + /// + /// The collection of facets to apply in the search request. + /// + public IList Facets { get; set; } = []; +} diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/SearchResults.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/SearchResults.cs index e01e35c..86a77dc 100644 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/SearchResults.cs +++ b/Dfe.Data.SearchPrototype/SearchForEstablishments/Models/SearchResults.cs @@ -1,16 +1,22 @@ namespace Dfe.Data.SearchPrototype.SearchForEstablishments.Models; /// -/// The search results +/// Encapsulates the and +/// types that make up the response from the underlying search system. /// public class SearchResults { /// /// The returned from the Establishment search + /// which encapsulates the underlying collection + /// that is built from the underlying search response. /// public EstablishmentResults? Establishments { get; init; } + /// - /// The resturned from the Establishment search + /// The returned from the Establishment search + /// which encapsulates the underlying collection + /// that is built from the underlying search response. /// public EstablishmentFacets? Facets { get; init; } } diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchByKeywordRequest.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchByKeywordRequest.cs deleted file mode 100644 index f91fead..0000000 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchByKeywordRequest.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Dfe.Data.SearchPrototype.SearchForEstablishments; - -/// -/// This is the object used to make requests (send input) through to the -/// T:Dfe.Data.SearchPrototype.SearchForEstablishments.SearchByKeywordUseCase instance. -/// -public sealed class SearchByKeywordRequest -{ - /// - /// The following arguments are passed via the constructor and used to create - /// an immutable instance of the T:Dfe.Data.SearchPrototype.Search.SearchContext. - /// - /// - /// The string keyword used to search the collection specified. - /// - /// - /// The target collection on which to invoke a search. - /// - public SearchByKeywordRequest(string searchKeyword, string targetCollection) - { - Context = SearchContext.Create(searchKeyword, targetCollection); - } - - /// - /// This property exposes the T:Dfe.Data.SearchPrototype.Search.SearchContext object - /// which encapsulates the criteria necessary to perform a valid search. - /// - public SearchContext? Context { get; set; } -} diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchContext.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchContext.cs deleted file mode 100644 index 70e9638..0000000 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchContext.cs +++ /dev/null @@ -1,66 +0,0 @@ -namespace Dfe.Data.SearchPrototype.SearchForEstablishments; - -/// -/// Prescribes the context of the search including the keyword and collection target. -/// -public sealed class SearchContext -{ - /// - /// The search keyword(s) to be applied. - /// - public string SearchKeyword { get; } - - /// - /// The facets to be returned - /// - public IList? Facets { get; } - - /// - /// The target collection on which to apply the search. - /// - public string TargetCollection { get; } - - /// - /// The following arguments are passed via the constructor and are not changeable - /// once an instance is created, this ensures we preserve immutability. - /// - /// - /// The search keyword(s) to be applied. - /// - /// - /// The target collection on which to apply the search. - /// - /// - /// The facets to be returned. - /// - /// - public SearchContext(string searchKeyword, string targetCollection, IList? facets = null) - { - SearchKeyword = - string.IsNullOrWhiteSpace(searchKeyword) ? - throw new ArgumentNullException(nameof(searchKeyword)) : searchKeyword; - - TargetCollection = - string.IsNullOrWhiteSpace(targetCollection) ? - throw new ArgumentNullException(nameof(targetCollection)) : targetCollection; - - Facets = facets; - } - - /// - /// Factory method to allow implicit creation of a T:Dfe.Data.SearchPrototype.Search.SearchContext instance. - /// - /// - /// The keyword string which defines the search. - /// - /// - /// The underlying collection on which to undertake the search. - /// - /// - /// The facets to be returned. - /// - /// - /// A configured T:Dfe.Data.SearchPrototype.Search.SearchContext instance. - /// - public static SearchContext Create(string searchKeyword, string targetCollection, IList? facets = null) => new(searchKeyword, targetCollection, facets); -} diff --git a/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchResponseStatus.cs b/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchResponseStatus.cs deleted file mode 100644 index ad82ae9..0000000 --- a/Dfe.Data.SearchPrototype/SearchForEstablishments/SearchResponseStatus.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Dfe.Data.SearchPrototype.SearchForEstablishments; - -/// -/// The status of the search response -/// -public enum SearchResponseStatus -{ - /// - /// The search request completed successfully - /// - Success, - /// - /// The request was not valid - /// - InvalidRequest, - /// - /// The request was submitted and resulted in an error - /// - SearchServiceError -} diff --git a/Dfe.Data.SearchPrototype/Tests/Dfe.Data.SearchPrototype.Tests.csproj b/Dfe.Data.SearchPrototype/Tests/Dfe.Data.SearchPrototype.Tests.csproj index 825d71f..cc9bc5f 100644 --- a/Dfe.Data.SearchPrototype/Tests/Dfe.Data.SearchPrototype.Tests.csproj +++ b/Dfe.Data.SearchPrototype/Tests/Dfe.Data.SearchPrototype.Tests.csproj @@ -14,6 +14,7 @@ + diff --git a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/SearchByKeywordUseCaseTests.cs b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/SearchByKeywordUseCaseTests.cs similarity index 76% rename from Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/SearchByKeywordUseCaseTests.cs rename to Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/SearchByKeywordUseCaseTests.cs index 0385128..265f18a 100644 --- a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/SearchByKeywordUseCaseTests.cs +++ b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/SearchByKeywordUseCaseTests.cs @@ -1,11 +1,13 @@ -using Dfe.Data.SearchPrototype.SearchForEstablishments; +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 Dfe.Data.SearchPrototype.Tests.SearchForEstablishments.TestDoubles; +using Dfe.Data.SearchPrototype.Tests.SearchForEstablishments.ByKeyword.TestDoubles; using FluentAssertions; using Moq; using Xunit; -namespace Dfe.Data.SearchPrototype.Tests.SearchForEstablishments; +namespace Dfe.Data.SearchPrototype.Tests.SearchForEstablishments.ByKeyword; public sealed class SearchByKeywordUseCaseTests { @@ -20,14 +22,15 @@ public SearchByKeywordUseCaseTests() _searchServiceAdapter = SearchServiceAdapterTestDouble.MockFor(_searchResults); - _useCase = new(_searchServiceAdapter); + var options = SearchByKeywordCriteriaTestDouble.Create(); + _useCase = new(_searchServiceAdapter, IOptionsTestDouble.IOptionsMockFor(options)); } [Fact] public async Task HandleRequest_ValidRequest_ReturnsResponse() { // arrange - SearchByKeywordRequest request = new("searchkeyword", "target collection"); + SearchByKeywordRequest request = new("searchkeyword"); // act SearchByKeywordResponse response = await _useCase.HandleRequest(request); @@ -38,7 +41,7 @@ public async Task HandleRequest_ValidRequest_ReturnsResponse() response.EstablishmentFacetResults!.Facets.Should().Contain(_searchResults.Facets!.Facets); } - [Fact] + [Fact] public async Task HandleRequest_NullSearchByKeywordRequest_ReturnsErrorStatus() { // act @@ -53,9 +56,9 @@ public async Task HandleRequest_NullSearchByKeywordRequest_ReturnsErrorStatus() public async Task HandleRequest_ServiceAdapterThrowsException_ReturnsErrorStatus() { // arrange - SearchByKeywordRequest request = new("searchkeyword", "target collection"); + SearchByKeywordRequest request = new("searchkeyword"); Mock.Get(_searchServiceAdapter) - .Setup(adapter => adapter.SearchAsync(It.IsAny())) + .Setup(adapter => adapter.SearchAsync(It.IsAny())) .ThrowsAsync(new ApplicationException()); // act @@ -70,9 +73,9 @@ public async Task HandleRequest_ServiceAdapterThrowsException_ReturnsErrorStatus public async Task HandleRequest_NoResults_ReturnsSuccess() { // arrange - SearchByKeywordRequest request = new("searchkeyword", "target collection"); + SearchByKeywordRequest request = new("searchkeyword"); Mock.Get(_searchServiceAdapter) - .Setup(adapter => adapter.SearchAsync(It.IsAny())) + .Setup(adapter => adapter.SearchAsync(It.IsAny())) .ReturnsAsync(SearchResultsTestDouble.CreateWithNoResults); // act diff --git a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/EstablishmentFacetsTestDouble.cs b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/TestDoubles/EstablishmentFacetsTestDouble.cs similarity index 95% rename from Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/EstablishmentFacetsTestDouble.cs rename to Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/TestDoubles/EstablishmentFacetsTestDouble.cs index c0a4bb0..435328b 100644 --- a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/EstablishmentFacetsTestDouble.cs +++ b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/TestDoubles/EstablishmentFacetsTestDouble.cs @@ -1,6 +1,6 @@ using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; -namespace Dfe.Data.SearchPrototype.Tests.SearchForEstablishments.TestDoubles; +namespace Dfe.Data.SearchPrototype.Tests.SearchForEstablishments.ByKeyword.TestDoubles; public static class EstablishmentFacetsTestDouble { diff --git a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/EstablishmentResultsTestDouble.cs b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/TestDoubles/EstablishmentResultsTestDouble.cs similarity index 96% rename from Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/EstablishmentResultsTestDouble.cs rename to Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/TestDoubles/EstablishmentResultsTestDouble.cs index a609df6..a9ec567 100644 --- a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/EstablishmentResultsTestDouble.cs +++ b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/TestDoubles/EstablishmentResultsTestDouble.cs @@ -1,6 +1,6 @@ using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; -namespace Dfe.Data.SearchPrototype.Tests.SearchForEstablishments.TestDoubles; +namespace Dfe.Data.SearchPrototype.Tests.SearchForEstablishments.ByKeyword.TestDoubles; public static class EstablishmentResultsTestDouble { diff --git a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/EstablishmentTestDouble.cs b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/TestDoubles/EstablishmentTestDouble.cs similarity index 95% rename from Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/EstablishmentTestDouble.cs rename to Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/TestDoubles/EstablishmentTestDouble.cs index b8d39a7..bbbed7c 100644 --- a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/EstablishmentTestDouble.cs +++ b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/TestDoubles/EstablishmentTestDouble.cs @@ -1,13 +1,12 @@ using Bogus; using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; -using System.IO; -namespace Dfe.Data.SearchPrototype.Tests.SearchForEstablishments.TestDoubles; +namespace Dfe.Data.SearchPrototype.Tests.SearchForEstablishments.ByKeyword.TestDoubles; public class EstablishmentTestDouble { private static string GetEstablishmentNameFake() => - new Faker().Company.CompanyName(); + new Faker().Company.CompanyName(); private static string GetEstablishmentIdentifierFake() => new Faker().Random.Int(100000, 999999).ToString(); diff --git a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/TestDoubles/SearchByKeywordOptions.cs b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/TestDoubles/SearchByKeywordOptions.cs new file mode 100644 index 0000000..a96c022 --- /dev/null +++ b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/TestDoubles/SearchByKeywordOptions.cs @@ -0,0 +1,15 @@ +using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; + +namespace Dfe.Data.SearchPrototype.Tests.SearchForEstablishments.ByKeyword.TestDoubles; + +public static class SearchByKeywordCriteriaTestDouble +{ + public static SearchByKeywordCriteria Create() + { + return new SearchByKeywordCriteria() + { + Facets = ["FIELD1", "FIELD2", "FIELD3"], + SearchFields = ["FACET1", "FACET2", "FACET3"] + }; + } +} diff --git a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/SearchResultsTestDouble.cs b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/TestDoubles/SearchResultsTestDouble.cs similarity index 93% rename from Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/SearchResultsTestDouble.cs rename to Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/TestDoubles/SearchResultsTestDouble.cs index ca70edc..cba9625 100644 --- a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/SearchResultsTestDouble.cs +++ b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/TestDoubles/SearchResultsTestDouble.cs @@ -1,6 +1,6 @@ using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; -namespace Dfe.Data.SearchPrototype.Tests.SearchForEstablishments.TestDoubles; +namespace Dfe.Data.SearchPrototype.Tests.SearchForEstablishments.ByKeyword.TestDoubles; public static class SearchResultsTestDouble { @@ -10,7 +10,7 @@ public static SearchResults Create() { Facets = EstablishmentFacetsTestDouble.Create(), Establishments = EstablishmentResultsTestDouble.Create() - }; + }; } public static SearchResults CreateWithNoResults() diff --git a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/SearchServiceAdapterTestDouble.cs b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/TestDoubles/SearchServiceAdapterTestDouble.cs similarity index 79% rename from Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/SearchServiceAdapterTestDouble.cs rename to Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/TestDoubles/SearchServiceAdapterTestDouble.cs index 571b6a1..92d6114 100644 --- a/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/TestDoubles/SearchServiceAdapterTestDouble.cs +++ b/Dfe.Data.SearchPrototype/Tests/SearchForEstablishments/ByKeyword/TestDoubles/SearchServiceAdapterTestDouble.cs @@ -1,8 +1,8 @@ -using Dfe.Data.SearchPrototype.SearchForEstablishments; +using Dfe.Data.SearchPrototype.SearchForEstablishments.ByKeyword.ServiceAdapters; using Dfe.Data.SearchPrototype.SearchForEstablishments.Models; using Moq; -namespace Dfe.Data.SearchPrototype.Tests.SearchForEstablishments.TestDoubles; +namespace Dfe.Data.SearchPrototype.Tests.SearchForEstablishments.ByKeyword.TestDoubles; public static class SearchServiceAdapterTestDouble { @@ -11,7 +11,7 @@ public static ISearchServiceAdapter MockFor(SearchResults searchResults) Mock searchServiceAdapter = new(); searchServiceAdapter - .Setup(adapter => adapter.SearchAsync(It.IsAny())) + .Setup(adapter => adapter.SearchAsync(It.IsAny())) .ReturnsAsync(searchResults); return searchServiceAdapter.Object;