diff --git a/framework/default/ortoo-core/default/classes/fflib-extension/OrgCachedSoqlExecutor.cls b/framework/default/ortoo-core/default/classes/fflib-extension/OrgCachedSoqlExecutor.cls index f123c9f34f2..cb5781f0596 100644 --- a/framework/default/ortoo-core/default/classes/fflib-extension/OrgCachedSoqlExecutor.cls +++ b/framework/default/ortoo-core/default/classes/fflib-extension/OrgCachedSoqlExecutor.cls @@ -42,6 +42,8 @@ public inherited sharing class OrgCachedSoqlExecutor */ public List query( String soql ) { + Contract.requires( soql != null, 'query called with a null soql' ); + String key = generateKey( soql ); List returnValues = null; @@ -95,6 +97,8 @@ public inherited sharing class OrgCachedSoqlExecutor */ public void clearCacheFor( String soql ) { + Contract.requires( soql != null, 'clearCacheFor called with a null soql' ); + if ( ! hasAccessToCache ) { throw new OrgCacheAccessViolationException( Label.ortoo_core_soql_cache_access_violation ) 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 26e224adddc..be23636b7ae 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 @@ -67,11 +67,44 @@ public inherited sharing class ortoo_DynamicSobjectSelector extends ortoo_Sobjec */ public List selectByCriteria( ortoo_Criteria criteria ) { - Contract.requires( criteria != null, 'selectByCriteria called with a null criteria' ); + return selectByCriteria( criteria, false ); + } + /** + * Retrieve the records that match the passed criteria, optionally via the Org Level SOQL 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 + */ + public List selectByCriteria( ortoo_Criteria criteria, Boolean cached ) + { + Contract.requires( criteria != null, 'selectByCriteria called with a null criteria' ); + Contract.requires( cached != null, 'selectByCriteria called with a null cached' ); Contract.assert( sobjectType != null, 'selectByCriteria called when sobjectType has not been set' ); - return Database.query( generateSoqlByCriteria( criteria ) ); + String soql = generateSoqlByCriteria( criteria ); + if ( cached ) + { + return ((OrgCachedSoqlExecutor)Application.APP_LOGIC.newInstance( OrgCachedSoqlExecutor.class )) + .query( soql ); + } + return Database.query( soql ); + } + + /** + * Request that the cached results for the given criteria be cleared. + * Assumes that the fields are set up correctly. + * + * @param ortoo_Criteria The criteria that should be used to derive the SOQL results to clear + */ + public ortoo_DynamicSobjectSelector clearCacheFor( ortoo_Criteria criteria ) + { + Contract.requires( criteria != null, 'clearCacheFor called with a null criteria' ); + + ((OrgCachedSoqlExecutor)Application.APP_LOGIC.newInstance( OrgCachedSoqlExecutor.class )) + .clearCacheFor( generateSoqlByCriteria( criteria ) ); + return this; } /** 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 50c3a379649..ddf2748d99e 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 @@ -1,182 +1,238 @@ @isTest private without sharing class ortoo_DynamicSobjectSelectorTest { - @isTest - private static void selectByCriteria_whenTheSobjectHasBeenSetByName_willReturnAListOfSobjects() // NOPMD: Test method name format - { - ortoo_DynamicSobjectSelector selector = new ortoo_DynamicSobjectSelector() - .setSobjectType( 'Contact' ); - - List returnedSobjects = selector.selectByCriteria( new ortoo_Criteria() ); - - System.assertEquals( new List(), returnedSobjects, 'selectByCriteria, when the SObject Type has been set by name, will return a list of SObjects' ); - } - - @isTest - private static void selectByCriteria_whenTheSobjectHasBeenSetByType_willReturnAListOfSobjects() // NOPMD: Test method name format - { - ortoo_DynamicSobjectSelector selector = new ortoo_DynamicSobjectSelector() - .setSobjectType( Contact.sobjectType ); - - List returnedSobjects = selector.selectByCriteria( new ortoo_Criteria() ); - - System.assertEquals( new List(), returnedSobjects, 'selectByCriteria, when the SObject Type has been set by type, will return a list of SObjects' ); - } - - @isTest - private static void selectByCriteria_whenTheSobjectTypeHasNotBeenSet_willThrowAnException() // NOPMD: Test method name format - { - ortoo_DynamicSobjectSelector selector = new ortoo_DynamicSobjectSelector(); - - Test.startTest(); - String exceptionMessage; - try - { - selector.selectByCriteria( new ortoo_Criteria() ); - } - catch ( Contract.AssertException e ) - { - exceptionMessage = e.getMessage(); - } - Test.stopTest(); - - ortoo_Asserts.assertContains( 'selectByCriteria called when sobjectType has not been set', exceptionMessage, 'selectByCriteria, when the sobject type has not been set, will throw an exception' ); - } - - @isTest - private static void selectByCriteria_whenGivenANullCriteria_willThrowAnException() // NOPMD: Test method name format - { - ortoo_DynamicSobjectSelector selector = new ortoo_DynamicSobjectSelector() - .setSobjectType( Contact.sobjectType ); - - Test.startTest(); - String exceptionMessage; - try - { - selector.selectByCriteria( null ); - } - catch ( Contract.RequiresException e ) - { - exceptionMessage = e.getMessage(); - } - Test.stopTest(); - - ortoo_Asserts.assertContains( 'selectByCriteria called with a null criteria', exceptionMessage, 'selectByCriteria, when given a null criteria, will throw an exception' ); - } - - @isTest - private static void addField_whenGivenAString_willAddThatFieldToTheSelection() // NOPMD: Test method name format - { - ortoo_DynamicSobjectSelector selector = new ortoo_DynamicSobjectSelector() - .setSobjectType( Contact.sobjectType ); - - Test.startTest(); - selector.addField( 'Name' ); - String generatedSoql = selector.generateSoqlByCriteria( new ortoo_Criteria() ); - Test.stopTest(); - - Amoss_asserts.assertStartsWith( 'SELECT Name FROM Contact', generatedSoql, 'addField, when given a string that represents a valid field, will add that field to the selection' ); - } - - @isTest - private static void addField_whenGivenMultipleStrings_willAddThoseFieldsToTheSelection() // NOPMD: Test method name format - { - ortoo_DynamicSobjectSelector selector = new ortoo_DynamicSobjectSelector() - .setSobjectType( Contact.sobjectType ); - - Test.startTest(); - selector.addField( 'Name' ) - .addField( 'FirstName' ) - .addField( 'LastName' ); - String generatedSoql = selector.generateSoqlByCriteria( new ortoo_Criteria() ); - Test.stopTest(); - - Amoss_asserts.assertStartsWith( 'SELECT FirstName, LastName, Name FROM Contact', generatedSoql, 'addField, when given multiple strings that represent valid fields, will add them to the selection' ); - } - - @isTest - private static void addField_whenPassedANullString_willThrowAnException() // NOPMD: Test method name format - { - ortoo_DynamicSobjectSelector selector = new ortoo_DynamicSobjectSelector() - .setSobjectType( Contact.sobjectType ); - - Test.startTest(); - String exceptionMessage; - try - { - selector.addField( null ); - } - catch ( Contract.RequiresException e ) - { - exceptionMessage = e.getMessage(); - } - Test.stopTest(); - - ortoo_Asserts.assertContains( 'addField called with a null fieldToAdd', exceptionMessage, 'addField, when passed a null field name, will throw an exception' ); - } - - @isTest - private static void setSobjectType_whenPassedANullString_willThrowAnException() // NOPMD: Test method name format - { - String nullString; - - ortoo_DynamicSobjectSelector selector = new ortoo_DynamicSobjectSelector(); - - Test.startTest(); - String exceptionMessage; - try - { - selector.setSobjectType( nullString ); - } - catch ( Contract.RequiresException e ) - { - exceptionMessage = e.getMessage(); - } - Test.stopTest(); - - ortoo_Asserts.assertContains( 'setSobjectType called with a null sobjectTypeName', exceptionMessage, 'setSobjectType, when passed a null string, will throw an exception' ); - } - - @isTest - private static void setSobjectType_whenPassedAnInvalidString_willThrowAnException() // NOPMD: Test method name format - { - ortoo_DynamicSobjectSelector selector = new ortoo_DynamicSobjectSelector(); - - Test.startTest(); - String exceptionMessage; - try - { - selector.setSobjectType( 'NotAnSObject' ); - } - catch ( Contract.RequiresException e ) - { - exceptionMessage = e.getMessage(); - } - Test.stopTest(); - - ortoo_Asserts.assertContains( 'setSobjectType called with an sobjectTypeName that does not represent a valid SObject Type', exceptionMessage, 'setSobjectType, when passed a string that does not represent an sobject, will throw an exception' ); - } - - @isTest - private static void setSobjectType_whenPassedANullSobjectType_willThrowAnException() // NOPMD: Test method name format - { - SobjectType nullSobjectType; - - ortoo_DynamicSobjectSelector selector = new ortoo_DynamicSobjectSelector(); - - Test.startTest(); - String exceptionMessage; - try - { - selector.setSobjectType( nullSobjectType ); - } - catch ( Contract.RequiresException e ) - { - exceptionMessage = e.getMessage(); - } - Test.stopTest(); - - ortoo_Asserts.assertContains( 'setSobjectType called with a null sobjectType', exceptionMessage, 'setSobjectType, when passed a null sobject type, will throw an exception' ); - } - + @isTest + private static void selectByCriteria_whenTheSobjectHasBeenSetByName_willReturnAListOfSobjects() // NOPMD: Test method name format + { + ortoo_DynamicSobjectSelector selector = new ortoo_DynamicSobjectSelector() + .setSobjectType( 'Contact' ); + + List returnedSobjects = selector.selectByCriteria( new ortoo_Criteria() ); + + System.assertEquals( new List(), returnedSobjects, 'selectByCriteria, when the SObject Type has been set by name, will return a list of SObjects' ); + } + + @isTest + private static void selectByCriteria_whenTheSobjectHasBeenSetByType_willReturnAListOfSobjects() // NOPMD: Test method name format + { + ortoo_DynamicSobjectSelector selector = new ortoo_DynamicSobjectSelector() + .setSobjectType( Contact.sobjectType ); + + List returnedSobjects = selector.selectByCriteria( new ortoo_Criteria() ); + + System.assertEquals( new List(), returnedSobjects, 'selectByCriteria, when the SObject Type has been set by type, will return a list of SObjects' ); + } + + @isTest + private static void selectByCriteria_whenToldToUseTheCache_willUseTheOrgSoqlCache() // NOPMD: Test method name format + { + ortoo_DynamicSobjectSelector selector = new ortoo_DynamicSobjectSelector() + .setSobjectType( Contact.sobjectType ); + + List expects = new List{ new Contact() }; + Amoss_Instance cacheController = ApplicationMockRegistrar.registerMockAppLogic( OrgCachedSoqlExecutor.class ); + cacheController + .expects( 'query' ) + .withParameter().containing( 'SELECT Id FROM Contact' ) + .returning( expects ); + + Test.startTest(); + List returnedSobjects = selector.selectByCriteria( new ortoo_Criteria(), true ); + Test.stopTest(); + + cacheController.verify(); + + System.assertEquals( expects, returnedSobjects, 'selectByCriteria, when told to use the cache, will direct the query via the OrgCachedSoqlExecutor' ); + } + + @isTest + private static void selectByCriteria_whenToldNotToUseTheCache_willNotUseTheOrgSoqlCache() // NOPMD: Test method name format + { + ortoo_DynamicSobjectSelector selector = new ortoo_DynamicSobjectSelector() + .setSobjectType( Contact.sobjectType ); + + Amoss_Instance cacheController = ApplicationMockRegistrar.registerMockAppLogic( OrgCachedSoqlExecutor.class ); + cacheController + .expectsNoCalls(); + + Test.startTest(); + List returnedSobjects = selector.selectByCriteria( new ortoo_Criteria(), false ); + Test.stopTest(); + + System.assertEquals( new List(), returnedSobjects, 'selectByCriteria, when told to not use the cache, will not direct the query via the OrgCachedSoqlExecutor' ); + } + + @isTest + private static void selectByCriteria_whenTheSobjectTypeHasNotBeenSet_willThrowAnException() // NOPMD: Test method name format + { + ortoo_DynamicSobjectSelector selector = new ortoo_DynamicSobjectSelector(); + + Test.startTest(); + String exceptionMessage; + try + { + selector.selectByCriteria( new ortoo_Criteria() ); + } + catch ( Contract.AssertException e ) + { + exceptionMessage = e.getMessage(); + } + Test.stopTest(); + + ortoo_Asserts.assertContains( 'selectByCriteria called when sobjectType has not been set', exceptionMessage, 'selectByCriteria, when the sobject type has not been set, will throw an exception' ); + } + + @isTest + private static void selectByCriteria_whenGivenANullCriteria_willThrowAnException() // NOPMD: Test method name format + { + ortoo_DynamicSobjectSelector selector = new ortoo_DynamicSobjectSelector() + .setSobjectType( Contact.sobjectType ); + + Test.startTest(); + String exceptionMessage; + try + { + selector.selectByCriteria( null ); + } + catch ( Contract.RequiresException e ) + { + exceptionMessage = e.getMessage(); + } + Test.stopTest(); + + ortoo_Asserts.assertContains( 'selectByCriteria called with a null criteria', exceptionMessage, 'selectByCriteria, when given a null criteria, will throw an exception' ); + } + + @isTest + private static void addField_whenGivenAString_willAddThatFieldToTheSelection() // NOPMD: Test method name format + { + ortoo_DynamicSobjectSelector selector = new ortoo_DynamicSobjectSelector() + .setSobjectType( Contact.sobjectType ); + + Test.startTest(); + selector.addField( 'Name' ); + String generatedSoql = selector.generateSoqlByCriteria( new ortoo_Criteria() ); + Test.stopTest(); + + Amoss_asserts.assertStartsWith( 'SELECT Name FROM Contact', generatedSoql, 'addField, when given a string that represents a valid field, will add that field to the selection' ); + } + + @isTest + private static void addField_whenGivenMultipleStrings_willAddThoseFieldsToTheSelection() // NOPMD: Test method name format + { + ortoo_DynamicSobjectSelector selector = new ortoo_DynamicSobjectSelector() + .setSobjectType( Contact.sobjectType ); + + Test.startTest(); + selector.addField( 'Name' ) + .addField( 'FirstName' ) + .addField( 'LastName' ); + String generatedSoql = selector.generateSoqlByCriteria( new ortoo_Criteria() ); + Test.stopTest(); + + Amoss_asserts.assertStartsWith( 'SELECT FirstName, LastName, Name FROM Contact', generatedSoql, 'addField, when given multiple strings that represent valid fields, will add them to the selection' ); + } + + @isTest + private static void addField_whenPassedANullString_willThrowAnException() // NOPMD: Test method name format + { + ortoo_DynamicSobjectSelector selector = new ortoo_DynamicSobjectSelector() + .setSobjectType( Contact.sobjectType ); + + Test.startTest(); + String exceptionMessage; + try + { + selector.addField( null ); + } + catch ( Contract.RequiresException e ) + { + exceptionMessage = e.getMessage(); + } + Test.stopTest(); + + ortoo_Asserts.assertContains( 'addField called with a null fieldToAdd', exceptionMessage, 'addField, when passed a null field name, will throw an exception' ); + } + + @isTest + private static void setSobjectType_whenPassedANullString_willThrowAnException() // NOPMD: Test method name format + { + String nullString; + + ortoo_DynamicSobjectSelector selector = new ortoo_DynamicSobjectSelector(); + + Test.startTest(); + String exceptionMessage; + try + { + selector.setSobjectType( nullString ); + } + catch ( Contract.RequiresException e ) + { + exceptionMessage = e.getMessage(); + } + Test.stopTest(); + + ortoo_Asserts.assertContains( 'setSobjectType called with a null sobjectTypeName', exceptionMessage, 'setSobjectType, when passed a null string, will throw an exception' ); + } + + @isTest + private static void setSobjectType_whenPassedAnInvalidString_willThrowAnException() // NOPMD: Test method name format + { + ortoo_DynamicSobjectSelector selector = new ortoo_DynamicSobjectSelector(); + + Test.startTest(); + String exceptionMessage; + try + { + selector.setSobjectType( 'NotAnSObject' ); + } + catch ( Contract.RequiresException e ) + { + exceptionMessage = e.getMessage(); + } + Test.stopTest(); + + ortoo_Asserts.assertContains( 'setSobjectType called with an sobjectTypeName that does not represent a valid SObject Type', exceptionMessage, 'setSobjectType, when passed a string that does not represent an sobject, will throw an exception' ); + } + + @isTest + private static void setSobjectType_whenPassedANullSobjectType_willThrowAnException() // NOPMD: Test method name format + { + SobjectType nullSobjectType; + + ortoo_DynamicSobjectSelector selector = new ortoo_DynamicSobjectSelector(); + + Test.startTest(); + String exceptionMessage; + try + { + selector.setSobjectType( nullSobjectType ); + } + catch ( Contract.RequiresException e ) + { + exceptionMessage = e.getMessage(); + } + Test.stopTest(); + + ortoo_Asserts.assertContains( 'setSobjectType called with a null sobjectType', exceptionMessage, 'setSobjectType, when passed a null sobject type, will throw an exception' ); + } + + @isTest + private static void clearCacheFor_whenCalled_forwardsTheCallToTheOrgCache() // NOPMD: Test method name format + { + ortoo_DynamicSobjectSelector selector = new ortoo_DynamicSobjectSelector() + .setSobjectType( Contact.sobjectType ); + + Amoss_Instance cacheController = ApplicationMockRegistrar.registerMockAppLogic( OrgCachedSoqlExecutor.class ); + cacheController + .expects( 'clearCacheFor' ) + .withParameter().containing( 'SELECT Id FROM Contact' ); + + Test.startTest(); + selector.clearCacheFor( new ortoo_Criteria() ); + Test.stopTest(); + + cacheController.verify(); + } } \ No newline at end of file