diff --git a/TODO.txt b/TODO.txt index 6378f3d8d58..d84ff1c7ca3 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,7 +1,5 @@ TODO: * Document TestDateTimeUtils and how to set now and today - * Add 'maximum age' into CachedSoqlExecutor - * Add 'maximum age' into DynamicSelector * Consider a superclass for OrgCache and SessionCache? * Move all the Jest LWc tests over to create the component in the before diff --git a/framework/default/ortoo-core/default/classes/fflib-extension/ortoo_DynamicSobjectSelector.cls b/framework/default/ortoo-core/default/classes/fflib-extension/ortoo_DynamicSobjectSelector.cls index fb424b01068..3a2a87d5155 100644 --- a/framework/default/ortoo-core/default/classes/fflib-extension/ortoo_DynamicSobjectSelector.cls +++ b/framework/default/ortoo-core/default/classes/fflib-extension/ortoo_DynamicSobjectSelector.cls @@ -71,11 +71,11 @@ public inherited sharing class ortoo_DynamicSobjectSelector extends ortoo_Sobjec } /** - * Retrieve the records that match the passed criteria, optionally via the Org Level SOQL Cache + * Retrieve the records that match the passed criteria, utilising the specified level of cache. * - * @param ortoo_Criteria The criteria that should be used to derive the records to return - * @param Boolean Should the query go via the Org Level SOQL Cache? - * @return List The result of the Selection + * @param ortoo_Criteria The criteria that should be used to derive the records to return + * @param CachedSoqlExecutor.CacheScope The scope of the cache that should be used + * @return List The result of the Selection */ public List selectByCriteria( ortoo_Criteria criteria, CachedSoqlExecutor.CacheScope cacheScope ) { @@ -90,6 +90,52 @@ public inherited sharing class ortoo_DynamicSobjectSelector extends ortoo_Sobjec .query( soql ); } + /** + * Retrieve the records that match the passed criteria, utilising the specified level of cache, + * only allowing data younger than the age specified to be retrieved from the cache. + * + * @param ortoo_Criteria The criteria that should be used to derive the records to return + * @param CachedSoqlExecutor.CacheScope The scope of the cache that should be used + * @param Long The maximum age, in seconds, that a cache entry is allowed to be before being discarded + * @return List The result of the Selection + */ + public List selectByCriteria( ortoo_Criteria criteria, CachedSoqlExecutor.CacheScope cacheScope, Long maximumAgeInSeconds ) + { + Contract.requires( criteria != null, 'selectByCriteria called with a null criteria' ); + Contract.requires( cacheScope != null, 'selectByCriteria called with a null cacheScope' ); + Contract.requires( maximumAgeInSeconds != null, 'selectByCriteria called with a null maximumAgeInSeconds' ); + Contract.assert( sobjectType != null, 'selectByCriteria called when sobjectType has not been set' ); + + String soql = generateSoqlByCriteria( criteria ); + + return ((CachedSoqlExecutor)Application.APP_LOGIC.newInstance( CachedSoqlExecutor.class )) + .setScope( cacheScope ) + .query( soql, maximumAgeInSeconds ); + } + + /** + * Retrieve the records that match the passed criteria, utilising the specified level of cache, + * only allowing data younger than the age specified to be retrieved from the cache. + * + * @param ortoo_Criteria The criteria that should be used to derive the records to return + * @param CachedSoqlExecutor.CacheScope The scope of the cache that should be used + * @param DateTime The minimum datetime (how recent) that a cache entry must have been created on to be used + * @return List The result of the Selection + */ + public List selectByCriteria( ortoo_Criteria criteria, CachedSoqlExecutor.CacheScope cacheScope, DateTime minimumDateTimeAdded ) + { + Contract.requires( criteria != null, 'selectByCriteria called with a null criteria' ); + Contract.requires( cacheScope != null, 'selectByCriteria called with a null cacheScope' ); + Contract.requires( minimumDateTimeAdded != null, 'selectByCriteria called with a null minimumDateTimeAdded' ); + Contract.assert( sobjectType != null, 'selectByCriteria called when sobjectType has not been set' ); + + String soql = generateSoqlByCriteria( criteria ); + + return ((CachedSoqlExecutor)Application.APP_LOGIC.newInstance( CachedSoqlExecutor.class )) + .setScope( cacheScope ) + .query( soql, minimumDateTimeAdded ); + } + /** * Request that the cached results for the given criteria be cleared. * Assumes that the fields are set up correctly. diff --git a/framework/default/ortoo-core/default/classes/fflib-extension/tests/ortoo_DynamicSobjectSelectorTest.cls b/framework/default/ortoo-core/default/classes/fflib-extension/tests/ortoo_DynamicSobjectSelectorTest.cls index a23b7c31433..90ee2acf240 100644 --- a/framework/default/ortoo-core/default/classes/fflib-extension/tests/ortoo_DynamicSobjectSelectorTest.cls +++ b/framework/default/ortoo-core/default/classes/fflib-extension/tests/ortoo_DynamicSobjectSelectorTest.cls @@ -123,6 +123,62 @@ private without sharing class ortoo_DynamicSobjectSelectorTest System.assertEquals( expects, returnedSobjects, 'selectByCriteria, when not told about cache, will tell the CachedSoqlExecutor to use no cache' ); } + @isTest + private static void selectByCriteria_whenGivenAMaximumAge_willPassThatIntoTheQueryCall() // NOPMD: Test method name format + { + Long maximumAge = 100; + + ortoo_DynamicSobjectSelector selector = new ortoo_DynamicSobjectSelector() + .setSobjectType( Contact.sobjectType ); + + List expects = new List{ new Contact() }; + Amoss_Instance cacheController = ApplicationMockRegistrar.registerMockAppLogic( CachedSoqlExecutor.class ); + cacheController + .allows( 'setScope' ) + .returning( cacheController.getDouble() ) + .also() + .expects( 'query' ) + .withParameter().containing( 'SELECT Id FROM Contact' ) + .thenParameter( maximumAge ) + .returning( expects ); + + Test.startTest(); + List returnedSobjects = selector.selectByCriteria( new ortoo_Criteria(), CachedSoqlExecutor.CacheScope.ORG, maximumAge ); + Test.stopTest(); + + cacheController.verify(); + + System.assertEquals( expects, returnedSobjects, 'selectByCriteria, when given a maximum age, will pass that into the query call' ); + } + + @isTest + private static void selectByCriteria_whenGivenAMinimumDate_willPassThatIntoTheQueryCall() // NOPMD: Test method name format + { + DateTime minimumDate = DateTime.newInstance( 2020, 01, 02 ); + + ortoo_DynamicSobjectSelector selector = new ortoo_DynamicSobjectSelector() + .setSobjectType( Contact.sobjectType ); + + List expects = new List{ new Contact() }; + Amoss_Instance cacheController = ApplicationMockRegistrar.registerMockAppLogic( CachedSoqlExecutor.class ); + cacheController + .allows( 'setScope' ) + .returning( cacheController.getDouble() ) + .also() + .expects( 'query' ) + .withParameter().containing( 'SELECT Id FROM Contact' ) + .thenParameter( minimumDate ) + .returning( expects ); + + Test.startTest(); + List returnedSobjects = selector.selectByCriteria( new ortoo_Criteria(), CachedSoqlExecutor.CacheScope.ORG, minimumDate ); + Test.stopTest(); + + cacheController.verify(); + + System.assertEquals( expects, returnedSobjects, 'selectByCriteria, when given a minimum date, will pass that into the query call' ); + } + @isTest private static void selectByCriteria_whenTheSobjectTypeHasNotBeenSet_willThrowAnException() // NOPMD: Test method name format { @@ -164,6 +220,115 @@ private without sharing class ortoo_DynamicSobjectSelectorTest ortoo_Asserts.assertContains( 'selectByCriteria called with a null criteria', exceptionMessage, 'selectByCriteria, when given a null criteria, will throw an exception' ); } + @isTest + private static void selectByCriteria_whenPassedANullScope_throwsAnException() // NOPMD: Test method name format + { + ortoo_DynamicSobjectSelector selector = new ortoo_DynamicSobjectSelector() + .setSobjectType( Contact.sobjectType ); + + Test.startTest(); + String exceptionMessage; + try + { + selector.selectByCriteria( new ortoo_Criteria(), null ); + } + catch ( Contract.RequiresException e ) + { + exceptionMessage = e.getMessage(); + } + Test.stopTest(); + + ortoo_Asserts.assertContains( 'selectByCriteria called with a null cacheScope', exceptionMessage, 'selectByCriteria, when passed a null cacheScope, will throw an exception' ); + } + + @isTest + private static void selectByCriteria_whenPassedANullScopeAnMaxAge_throwsAnException() // NOPMD: Test method name format + { + ortoo_DynamicSobjectSelector selector = new ortoo_DynamicSobjectSelector() + .setSobjectType( Contact.sobjectType ); + + Test.startTest(); + String exceptionMessage; + try + { + selector.selectByCriteria( new ortoo_Criteria(), null, 12 ); + } + catch ( Contract.RequiresException e ) + { + exceptionMessage = e.getMessage(); + } + Test.stopTest(); + + ortoo_Asserts.assertContains( 'selectByCriteria called with a null cacheScope', exceptionMessage, 'selectByCriteria, when passed a null cacheScope with a max age, will throw an exception' ); + } + + @isTest + private static void selectByCriteria_whenPassedANullScopeAnMinDate_throwsAnException() // NOPMD: Test method name format + { + ortoo_DynamicSobjectSelector selector = new ortoo_DynamicSobjectSelector() + .setSobjectType( Contact.sobjectType ); + + Test.startTest(); + String exceptionMessage; + try + { + selector.selectByCriteria( new ortoo_Criteria(), null, DateTime.newInstance( 2020, 2, 1 ) ); + } + catch ( Contract.RequiresException e ) + { + exceptionMessage = e.getMessage(); + } + Test.stopTest(); + + ortoo_Asserts.assertContains( 'selectByCriteria called with a null cacheScope', exceptionMessage, 'selectByCriteria, when passed a null cacheScope with a min date, will throw an exception' ); + } + + @isTest + private static void selectByCriteria_whenPassedANullMaxAge_throwsAnException() // NOPMD: Test method name format + { + ortoo_DynamicSobjectSelector selector = new ortoo_DynamicSobjectSelector() + .setSobjectType( Contact.sobjectType ); + + Long nullAge; + + Test.startTest(); + String exceptionMessage; + try + { + selector.selectByCriteria( new ortoo_Criteria(), CachedSoqlExecutor.CacheScope.ORG, nullAge ); + } + catch ( Contract.RequiresException e ) + { + exceptionMessage = e.getMessage(); + } + Test.stopTest(); + + ortoo_Asserts.assertContains( 'selectByCriteria called with a null maximumAgeInSeconds', exceptionMessage, 'selectByCriteria, when passed a null max age, will throw an exception' ); + } + + @isTest + private static void selectByCriteria_whenPassedANullMinDate_throwsAnException() // NOPMD: Test method name format + { + ortoo_DynamicSobjectSelector selector = new ortoo_DynamicSobjectSelector() + .setSobjectType( Contact.sobjectType ); + + DateTime nullDateTime; + + Test.startTest(); + String exceptionMessage; + try + { + selector.selectByCriteria( new ortoo_Criteria(), CachedSoqlExecutor.CacheScope.ORG, nullDateTime ); + } + catch ( Contract.RequiresException e ) + { + exceptionMessage = e.getMessage(); + } + Test.stopTest(); + + ortoo_Asserts.assertContains( 'selectByCriteria called with a null minimumDateTimeAdded', exceptionMessage, 'selectByCriteria, when passed a null min date, will throw an exception' ); + } + @isTest private static void addField_whenGivenAString_willAddThatFieldToTheSelection() // NOPMD: Test method name format {