diff --git a/framework/default/ortoo-core/default/classes/search/SearchResults.cls b/framework/default/ortoo-core/default/classes/search/SearchResults.cls index 915747ba527..3cdb5160d44 100644 --- a/framework/default/ortoo-core/default/classes/search/SearchResults.cls +++ b/framework/default/ortoo-core/default/classes/search/SearchResults.cls @@ -9,24 +9,34 @@ */ public inherited sharing class SearchResults { - @AuraEnabled public Integer totalNumberOfRecords; - @AuraEnabled public List records; + @AuraEnabled public Integer totalNumberOfRecords; + @AuraEnabled public List records; - /** + /** * Constructor, defining the dataset * * @param Integer The total nmumber of records of which these records are a subset - * @param List The subset of records + * @param List The subset of records */ - public SearchResults( Integer totalNumberOfRecords, List records ) - { - Contract.requires( totalNumberOfRecords != null, 'constructor called with a null totalNumberOfRecords' ); - Contract.requires( totalNumberOfRecords >= 0, 'constructor called with a negative totalNumberOfRecords' ); - Contract.requires( records != null, 'constructor called with a null records' ); + public SearchResults( Integer totalNumberOfRecords, List records ) + { + Contract.requires( totalNumberOfRecords != null, 'constructor called with a null totalNumberOfRecords' ); + Contract.requires( totalNumberOfRecords >= 0, 'constructor called with a negative totalNumberOfRecords' ); + Contract.requires( records != null, 'constructor called with a null records' ); - Contract.requires( totalNumberOfRecords >= records.size(), 'constructor called with a totalNumberOfRecords that is lower than the size of records' ); + Contract.requires( totalNumberOfRecords >= records.size(), 'constructor called with a totalNumberOfRecords that is lower than the size of records' ); - this.totalNumberOfRecords = totalNumberOfRecords; - this.records = records; - } + this.totalNumberOfRecords = totalNumberOfRecords; + this.records = records; + } + + /** + * States if there are currently records in this result set + * + * @return Boolean States if there are currently records in this result set + */ + public Boolean hasRecords() + { + return !records.isEmpty(); + } } diff --git a/framework/default/ortoo-core/default/classes/search/tests/SearchResultsTest.cls b/framework/default/ortoo-core/default/classes/search/tests/SearchResultsTest.cls index f574e8d3104..37ccd0c9678 100644 --- a/framework/default/ortoo-core/default/classes/search/tests/SearchResultsTest.cls +++ b/framework/default/ortoo-core/default/classes/search/tests/SearchResultsTest.cls @@ -100,4 +100,24 @@ public inherited sharing class SearchResultsTest Amoss_Asserts.assertContains( 'constructor called with a totalNumberOfRecords that is lower than the size of records', exceptionMessage, 'constructor, when passed a total number of records that is lower than the number of records, will throw an exception' ); } + + @isTest + private static void hasRecords_whenResultsHaveBeenSetToAnEmptyList_returnsFalse() // NOPMD: Test method name format + { + Test.startTest(); + Boolean got = new SearchResults( 15, new List() ).hasRecords(); + Test.stopTest(); + + System.assertEquals( false, got, 'hasRecords, when results have been set to an empty list, will return false' ); + } + + @isTest + private static void hasRecords_whenResultsHaveBeenSetWithRecords_returnsTrue() // NOPMD: Test method name format + { + Test.startTest(); + Boolean got = new SearchResults( 15, new List{ 1, 2 } ).hasRecords(); + Test.stopTest(); + + System.assertEquals( true, got, 'hasRecords, when results have been set to a list with records, will return true' ); + } } \ No newline at end of file diff --git a/framework/default/ortoo-core/default/classes/services/search-service/SearchServiceImpl.cls b/framework/default/ortoo-core/default/classes/services/search-service/SearchServiceImpl.cls index 36f7582c982..7a154a6fa3c 100644 --- a/framework/default/ortoo-core/default/classes/services/search-service/SearchServiceImpl.cls +++ b/framework/default/ortoo-core/default/classes/services/search-service/SearchServiceImpl.cls @@ -31,9 +31,11 @@ public with sharing class SearchServiceImpl implements ISearchService SearchResults searchResults = ((ISearchSelector)Application.SELECTOR.newInstance( sObjectType ) ) .selectBySearchCriteria( searchConfiguration, criteria, window, orderBy ); - ISearchResultBuilder searchResultsBuilder = ((ISearchResultBuilder)Application.DOMAIN.newInstance( (List)searchResults.records ) ); - - searchResults.records = searchResultsBuilder.buildSearchResults( searchConfiguration ); + if ( searchResults.hasRecords() ) + { + ISearchResultBuilder searchResultsBuilder = ((ISearchResultBuilder)Application.DOMAIN.newInstance( (List)searchResults.records ) ); + searchResults.records = searchResultsBuilder.buildSearchResults( searchConfiguration ); + } Contract.ensures( searchResults != null, 'search attempted to return with a null searchResults' ); Contract.ensures( searchResults.records != null, 'search attempted to return with a null searchResults.records' ); diff --git a/framework/default/ortoo-core/default/classes/services/search-service/tests/SearchServiceImplTest.cls b/framework/default/ortoo-core/default/classes/services/search-service/tests/SearchServiceImplTest.cls index 91a3fab0911..73b670258f9 100644 --- a/framework/default/ortoo-core/default/classes/services/search-service/tests/SearchServiceImplTest.cls +++ b/framework/default/ortoo-core/default/classes/services/search-service/tests/SearchServiceImplTest.cls @@ -18,7 +18,9 @@ public inherited sharing class SearchServiceImplTest Amoss_Instance selectorController = ApplicationMockRegistrar.registerMockSelector( searchSobjectType, SearchServiceMockSearchSelector.class ); // the config will say this is the SobjectType we're searching for Amoss_Instance domainController = ApplicationMockRegistrar.registerMockDomain( searchSobjectType, SearchServiceMockDomain.class ); - SearchResults interimSearchResults = new SearchResults( 1, new List{ new Contact( FirstName = 'Interim') } ); + // it looks like it shouldn't, but selectBySearchCriteria does appear to return a list based on the actual SObject type, if there are records + // Therefore we return a SearchResults built with List, and not List, as you might expect. + SearchResults interimSearchResults = new SearchResults( 1, new List{ new Contact( Id = TestIdUtils.generateId( Contact.sobjectType ), FirstName = 'Interim') } ); List adjustedObjects = new List{ finalResult }; // configure the expected behaviours @@ -53,6 +55,46 @@ public inherited sharing class SearchServiceImplTest System.assertEquals( adjustedObjects, results.records, 'search, when called with a config, criteria, window and order by, will construct a domain object based on the cofig, with the results from the selector and then ask it to buildSearchResults from them' ); } + @isTest + private static void search_whenNoRecordsAreReturnedByTheSearch_doesNotTryToCreateTheDomain() // NOPMD: Test method name format + { + SobjectType searchSobjectType = Contact.sobjectType; + Type searchConfigurationType = ISearchConfiguration.class; + + // Define all the mocks and register them where appropriate + ISearchCriteria criteria = (ISearchCriteria)new Amoss_Instance( ISearchCriteria.class ).generateDouble(); + SearchWindow window = (SearchWindow)new Amoss_Instance( SearchWindow.class ).generateDouble(); + SearchOrderBy orderBy = (SearchOrderBy)new Amoss_Instance( SearchOrderBy.class ).generateDouble(); + + ISearchResult finalResult = (ISearchResult)new Amoss_Instance( ISearchResult.class ).generateDouble(); + + Amoss_Instance searchConfigController = ApplicationMockRegistrar.registerMockAppLogic( searchConfigurationType ); + Amoss_Instance selectorController = ApplicationMockRegistrar.registerMockSelector( searchSobjectType, SearchServiceMockSearchSelector.class ); // the config will say this is the SobjectType we're searching for + + SearchResults interimSearchResults = new SearchResults( 0, new List() ); + + // configure the expected behaviours + searchConfigController + .expects( 'getBaseSobjectType' ) + .returns( searchSobjectType ); + + selectorController + .when( 'selectBySearchCriteria' ) + .returns( interimSearchResults ); + + Test.startTest(); + + SearchResults results = new SearchServiceImpl().search( searchConfigurationType, criteria, window, orderBy ); + + Test.stopTest(); + + searchConfigController.verify(); + selectorController.verify(); + + System.assertEquals( 0, results.totalNumberOfRecords, 'search, when the search matches no records, will give a total number of records of zero' ); + System.assertEquals( 0, results.records.size(), 'search, when the search matches no records, will return an empty search results' ); + } + @isTest private static void search_whenPassedANullSearchConfigurationType_throwsAnException() // NOPMD: Test method name format {