Skip to content

Commit

Permalink
Cl/refactor prep for facets (#42)
Browse files Browse the repository at this point in the history
* refactor mappers
* FacetResult count is long not int
  • Loading branch information
CathLass authored Sep 3, 2024
1 parent 8bbb74d commit 3b40da1
Show file tree
Hide file tree
Showing 14 changed files with 66 additions and 147 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public sealed class CognitiveSearchServiceAdapter<TSearchResult> : ISearchServic
{
private readonly ISearchByKeywordService _searchByKeywordService;
private readonly ISearchOptionsFactory _searchOptionsFactory;
private readonly IMapper<Response<SearchResults<TSearchResult>>, EstablishmentResults> _searchResponseMapper;
private readonly IMapper<Pageable<SearchResult<TSearchResult>>, EstablishmentResults> _searchResponseMapper;

/// <summary>
/// The following dependencies include the core cognitive search service definition,
Expand All @@ -35,7 +35,7 @@ public sealed class CognitiveSearchServiceAdapter<TSearchResult> : ISearchServic
public CognitiveSearchServiceAdapter(
ISearchByKeywordService searchByKeywordService,
ISearchOptionsFactory searchOptionsFactory,
IMapper<Response<SearchResults<TSearchResult>>, EstablishmentResults> searchResponseMapper)
IMapper<Pageable<SearchResult<TSearchResult>>, EstablishmentResults> searchResponseMapper)
{
_searchOptionsFactory = searchOptionsFactory;
_searchByKeywordService = searchByKeywordService;
Expand Down Expand Up @@ -79,6 +79,6 @@ await _searchByKeywordService.SearchAsync<TSearchResult>(
throw new ApplicationException(
$"Unable to derive search results based on input {searchContext.SearchKeyword}.");

return _searchResponseMapper.MapFrom(searchResults);
return _searchResponseMapper.MapFrom(searchResults.Value.GetResults());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ 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.
/// </summary>
public sealed class AzureSearchResponseToEstablishmentResultMapper : IMapper<Response<SearchResults<Establishment>>, EstablishmentResults>
public sealed class PageableSearchResultsToEstablishmentResultsMapper : IMapper<Pageable<SearchResult<Establishment>>, EstablishmentResults>
{
private readonly IMapper<Establishment, SearchForEstablishments.Models.Establishment> _azureSearchResultToEstablishmentMapper;

Expand All @@ -21,7 +21,7 @@ public sealed class AzureSearchResponseToEstablishmentResultMapper : IMapper<Res
/// <param name="azureSearchResultToEstablishmentMapper">
/// Mapper used to map from the raw Azure search result to a T:Dfe.Data.SearchPrototype.Search.Establishment instance.
/// </param>
public AzureSearchResponseToEstablishmentResultMapper(IMapper<Establishment, SearchForEstablishments.Models.Establishment> azureSearchResultToEstablishmentMapper)
public PageableSearchResultsToEstablishmentResultsMapper(IMapper<Establishment, SearchForEstablishments.Models.Establishment> azureSearchResultToEstablishmentMapper)
{
_azureSearchResultToEstablishmentMapper = azureSearchResultToEstablishmentMapper;
}
Expand All @@ -43,15 +43,13 @@ public AzureSearchResponseToEstablishmentResultMapper(IMapper<Establishment, Sea
/// <exception cref="ArgumentException">
/// Exception thrown if the data cannot be mapped
/// </exception>
public EstablishmentResults MapFrom(Response<SearchResults<Establishment>> input)
public EstablishmentResults MapFrom(Pageable<SearchResult<Establishment>> input)
{
ArgumentNullException.ThrowIfNull(input);

var results = input.Value.GetResults();

if (results.Any())
if (input.Any())
{
var mappedResults = results.Select(result =>
var mappedResults = input.Select(result =>
result.Document != null
? _azureSearchResultToEstablishmentMapper.MapFrom(result.Document)
: throw new InvalidOperationException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public sealed class CognitiveSearchServiceAdapterTests
private static CognitiveSearchServiceAdapter<Establishment> CreateServiceAdapterWith(
ISearchByKeywordService searchByKeywordService,
ISearchOptionsFactory searchOptionsFactory,
IMapper<Response<SearchResults<Establishment>>, EstablishmentResults> searchResponseMapper
IMapper<Pageable<SearchResult<Establishment>>, EstablishmentResults> searchResponseMapper
) =>
new(searchByKeywordService, searchOptionsFactory, searchResponseMapper);

Expand All @@ -27,7 +27,7 @@ public async Task Search_WithValidSearchContext_ReturnsConfiguredResults()
// arrange
var mockService = SearchServiceTestDouble.MockSearchService("SearchKeyword", "TargetCollection");
var mockSearchOptionsFactory = SearchOptionsFactoryTestDouble.MockSearchOptionsFactory();
var mockMapper = AzureSearchResponseToSearchResultsMapperTestDouble.MockFor(new EstablishmentResults());
var mockMapper = PageableSearchResultsToEstablishmentResultsMapperTestDouble.MockFor(new EstablishmentResults());

ISearchServiceAdapter cognitiveSearchServiceAdapter =
CreateServiceAdapterWith(
Expand All @@ -46,15 +46,15 @@ await cognitiveSearchServiceAdapter.SearchAsync(
response.Establishments.Should().NotBeNull();
Mock.Get(mockService).Verify(SearchServiceTestDouble.SearchRequest("SearchKeyword", "TargetCollection"),Times.Once());
Mock.Get(mockSearchOptionsFactory).Verify(SearchOptionsFactoryTestDouble.SearchOption(), Times.Once());
Mock.Get(mockMapper).Verify(AzureSearchResponseToSearchResultsMapperTestDouble.MapFrom(), Times.Once());
Mock.Get(mockMapper).Verify(PageableSearchResultsToEstablishmentResultsMapperTestDouble.MapFrom(), Times.Once());
}

[Fact]
public Task Search_WithNoSearchOptions_ThrowsApplicationException()
{
var mockService = SearchServiceTestDouble.MockSearchService("SearchKeyword", "TargetCollection");
var mockSearchOptionsFactory = SearchOptionsFactoryTestDouble.MockForNoOptions();
var mockMapper = AzureSearchResponseToSearchResultsMapperTestDouble.MockFor(new EstablishmentResults());
var mockMapper = PageableSearchResultsToEstablishmentResultsMapperTestDouble.MockFor(new EstablishmentResults());

// arrange
ISearchServiceAdapter cognitiveSearchServiceAdapter =
Expand All @@ -81,7 +81,7 @@ public async Task Search_WithNoResultsReturned_ReturnsEmptyResults()
// arrange
var mockService = SearchServiceTestDouble.MockSearchService("SearchKeyword", "TargetCollection");
var mockSearchOptionsFactory = SearchOptionsFactoryTestDouble.MockSearchOptionsFactory();
var mockMapper = AzureSearchResponseToSearchResultsMapperTestDouble.MockFor(new EstablishmentResults());
var mockMapper = PageableSearchResultsToEstablishmentResultsMapperTestDouble.MockFor(new EstablishmentResults());

ISearchServiceAdapter cognitiveSearchServiceAdapter =
CreateServiceAdapterWith(
Expand All @@ -104,7 +104,7 @@ public Task Search_MapperThrowsException_ExceptionPassesThrough()
// arrange
var mockService = SearchServiceTestDouble.MockSearchService("SearchKeyword", "TargetCollection");
var mockSearchOptionsFactory = SearchOptionsFactoryTestDouble.MockSearchOptionsFactory();
var mockMapper = AzureSearchResponseToSearchResultsMapperTestDouble.MockMapperThrowingArgumentException();
var mockMapper = PageableSearchResultsToEstablishmentResultsMapperTestDouble.MockMapperThrowingArgumentException();

ISearchServiceAdapter cognitiveSearchServiceAdapter =
CreateServiceAdapterWith(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,19 @@

namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.Mappers;

public sealed class AzureSearchResponseToEstablishmentResultMapperTests
public sealed class PageableSearchResultsToEstablishmentResultsMapperTests
{
IMapper<Establishment, SearchForEstablishments.Models.Establishment> _searchResultToEstablishmentMapper;
IMapper<Response<SearchResults<Establishment>>, EstablishmentResults> _searchResponseMapper;
IMapper<Pageable<SearchResult<Establishment>>, EstablishmentResults> _searchResultsMapper;
IMapper<Establishment, Address> _searchResultToAddressMapper;

public AzureSearchResponseToEstablishmentResultMapperTests()
public PageableSearchResultsToEstablishmentResultsMapperTests()
{
_searchResultToAddressMapper = new AzureSearchResultToAddressMapper();
_searchResultToEstablishmentMapper =
new AzureSearchResultToEstablishmentMapper(_searchResultToAddressMapper);
_searchResponseMapper =
new AzureSearchResponseToEstablishmentResultMapper(_searchResultToEstablishmentMapper);
_searchResultsMapper =
new PageableSearchResultsToEstablishmentResultsMapper(_searchResultToEstablishmentMapper);
}

[Fact]
Expand All @@ -31,11 +31,10 @@ public void MapFrom_WithValidSearchResults_ReturnsConfiguredEstablishments()
// arrange
List<SearchResult<Establishment>> searchResultDocuments =
SearchResultFake.SearchResults();
Response<SearchResults<Establishment>> searchResponseFake =
ResponseFake.WithSearchResults(searchResultDocuments);
var pageableSearchResults = PageableTestDouble.FromResults(searchResultDocuments);

// act
EstablishmentResults? mappedResult = _searchResponseMapper.MapFrom(searchResponseFake);
EstablishmentResults? mappedResult = _searchResultsMapper.MapFrom(pageableSearchResults);

// assert
mappedResult.Should().NotBeNull();
Expand All @@ -49,12 +48,8 @@ public void MapFrom_WithValidSearchResults_ReturnsConfiguredEstablishments()
[Fact]
public void MapFrom_WithEmptySearchResults_ReturnsEmptyList()
{
// arrange
Response<SearchResults<Establishment>> searchResponseFake =
ResponseFake.WithSearchResults(SearchResultFake.EmptySearchResult());

// act
EstablishmentResults? result = _searchResponseMapper.MapFrom(searchResponseFake);
EstablishmentResults? result = _searchResultsMapper.MapFrom(PageableTestDouble.FromResults(SearchResultFake.EmptySearchResult()));

// assert
result.Should().NotBeNull();
Expand All @@ -64,13 +59,10 @@ public void MapFrom_WithEmptySearchResults_ReturnsEmptyList()
[Fact]
public void MapFrom_WithNullSearchResults_ThrowsArgumentNullException()
{
// arrange
Response<SearchResults<Establishment>>? searchResponseFake = null;

// act.
_searchResponseMapper
_searchResultsMapper
.Invoking(mapper =>
mapper.MapFrom(searchResponseFake!))
mapper.MapFrom(null!))
.Should()
.Throw<ArgumentNullException>()
.WithMessage("Value cannot be null. (Parameter 'input')");
Expand All @@ -83,13 +75,11 @@ public void MapFrom_WithANullSearchResult_ThrowsInvalidOperationException()
var searchResultDocuments =
SearchResultFake.SearchResults();
searchResultDocuments.Add(SearchResultFake.SearchResultWithDocument(null));
Response<SearchResults<Establishment>> searchResponseFake =
ResponseFake.WithSearchResults(searchResultDocuments);

// act.
_searchResponseMapper
_searchResultsMapper
.Invoking(mapper =>
mapper.MapFrom(searchResponseFake))
mapper.MapFrom(PageableTestDouble.FromResults(searchResultDocuments)))
.Should()
.Throw<InvalidOperationException>()
.WithMessage("Search result document object cannot be null.");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using Dfe.Data.SearchPrototype.SearchForEstablishments.Models;

namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles;
namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles;

public static class EstablishmentTestDouble
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,26 @@

namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles;

internal static class AzureSearchResponseToSearchResultsMapperTestDouble
internal static class PageableSearchResultsToEstablishmentResultsMapperTestDouble
{
public static IMapper<Response<SearchResults<Establishment>>, EstablishmentResults> DefaultMock() =>
Mock.Of<IMapper<Response<SearchResults<Establishment>>, EstablishmentResults>>();
public static Expression<Func<IMapper<Response<SearchResults<Establishment>>, EstablishmentResults>, EstablishmentResults>> MapFrom() =>
mapper => mapper.MapFrom(It.IsAny<Response<SearchResults<Establishment>>>());
public static IMapper<Pageable<SearchResult<Establishment>>, EstablishmentResults> DefaultMock() =>
Mock.Of<IMapper<Pageable<SearchResult<Establishment>>, EstablishmentResults>>();

public static IMapper<Response<SearchResults<Establishment>>, EstablishmentResults> MockFor(EstablishmentResults establishments)
public static Expression<Func<IMapper<Pageable<SearchResult<Establishment>>, EstablishmentResults>, EstablishmentResults>> MapFrom() =>
mapper => mapper.MapFrom(It.IsAny<Pageable<SearchResult<Establishment>>>());

public static IMapper<Pageable<SearchResult<Establishment>>, EstablishmentResults> MockFor(EstablishmentResults establishments)
{
var mapperMock = new Mock<IMapper<Response<SearchResults<Establishment>>, EstablishmentResults>>();
var mapperMock = new Mock<IMapper<Pageable<SearchResult<Establishment>>, EstablishmentResults>>();

mapperMock.Setup(MapFrom()).Returns(establishments);

return mapperMock.Object;
}

public static IMapper<Response<SearchResults<Establishment>>, EstablishmentResults> MockMapperThrowingArgumentException()
public static IMapper<Pageable<SearchResult<Establishment>>, EstablishmentResults> MockMapperThrowingArgumentException()
{
var mapperMock = new Mock<IMapper<Response<SearchResults<Establishment>>, EstablishmentResults>>();
var mapperMock = new Mock<IMapper<Pageable<SearchResult<Establishment>>, EstablishmentResults>>();

mapperMock.Setup(MapFrom()).Throws(new ArgumentException());

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Azure;
using Azure.Search.Documents.Models;
using Moq;

namespace Dfe.Data.SearchPrototype.Infrastructure.Tests.TestDoubles;

public static class PageableTestDouble
{
public static Pageable<SearchResult<Establishment>> FromResults(List<SearchResult<Establishment>> results)
{
var page = Page<SearchResult<Establishment>>.FromValues(results, continuationToken: null, new Mock<Response>().Object);

return Pageable<SearchResult<Establishment>>.FromPages(new List<Page<SearchResult<Establishment>>>() { page});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ public class FacetResult
/// <summary>
/// The number of records that belong to this facet value
/// </summary>
public int? Count { get; }
public long? Count { get; }

/// <summary>
/// Constructor
/// </summary>
/// <param name="value">The value of the facet result</param>
/// <param name="count">The number of records that belong to this facet value</param>
public FacetResult(string value, int? count)
public FacetResult(string value, long? count)
{
Value = value;
Count = count;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@

namespace Dfe.Data.SearchPrototype.SearchForEstablishments;




/// <summary>
/// This is the object that carries the response (output) back from the
/// T:Dfe.Data.SearchPrototype.SearchForEstablishments.SearchByKeywordUseCase instance.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Dfe.Data.SearchPrototype.Common.Mappers;
using Dfe.Data.SearchPrototype.Common.CleanArchitecture.Application.UseCase;
using Dfe.Data.SearchPrototype.Common.CleanArchitecture.Application.UseCase;
using Dfe.Data.SearchPrototype.SearchForEstablishments.Models;

namespace Dfe.Data.SearchPrototype.SearchForEstablishments;
Expand All @@ -13,7 +12,6 @@ namespace Dfe.Data.SearchPrototype.SearchForEstablishments;
public sealed class SearchByKeywordUseCase : IUseCase<SearchByKeywordRequest, SearchByKeywordResponse>
{
private readonly ISearchServiceAdapter _searchServiceAdapter;
private readonly IMapper<EstablishmentResults, SearchByKeywordResponse> _resultsToResponseMapper;

/// <summary>
/// The following dependencies include the core cognitive search service definition,
Expand All @@ -23,16 +21,10 @@ public sealed class SearchByKeywordUseCase : IUseCase<SearchByKeywordRequest, Se
/// The concrete implementation of the T:Dfe.Data.SearchPrototype.Search.ISearchServiceAdapter
/// defined within, and injected by the IOC container.
/// </param>
/// <param name="resultsToResponseMapper">
/// The concrete implementation of the T:Dfe.Data.SearchPrototype.Common.IMapper
/// defined within, and injected by the IOC container.
/// </param>
public SearchByKeywordUseCase(
ISearchServiceAdapter searchServiceAdapter,
IMapper<EstablishmentResults, SearchByKeywordResponse> resultsToResponseMapper)
ISearchServiceAdapter searchServiceAdapter)
{
_searchServiceAdapter = searchServiceAdapter;
_resultsToResponseMapper = resultsToResponseMapper;
}

/// <summary>
Expand All @@ -58,11 +50,16 @@ public async Task<SearchByKeywordResponse> HandleRequest(SearchByKeywordRequest
try
{
EstablishmentResults establishmentResults = await _searchServiceAdapter.SearchAsync(request.Context);
return _resultsToResponseMapper.MapFrom(establishmentResults);

return establishmentResults switch
{
null => new() { Status = SearchResponseStatus.SearchServiceError },
_ => new(establishmentResults.Establishments) { Status = SearchResponseStatus.Success }
};
}
catch (Exception) // something went wrong in the infrastructure
catch (Exception) // something went wrong in the infrastructure tier
{
return new SearchByKeywordResponse()
return new()
{
Status = SearchResponseStatus.SearchServiceError
};
Expand Down
Loading

0 comments on commit 3b40da1

Please sign in to comment.