diff --git a/src/applications/Nebula.app b/src/applications/Nebula.app new file mode 100644 index 0000000..ff89ffb --- /dev/null +++ b/src/applications/Nebula.app @@ -0,0 +1,7 @@ + + + NebulaLog__c + Large + + NebulaLog__c + diff --git a/src/classes/DML.cls b/src/classes/DML.cls new file mode 100644 index 0000000..3f88a83 --- /dev/null +++ b/src/classes/DML.cls @@ -0,0 +1,84 @@ +/************************************************************************************************* +* This file is part of the Nebula Framework project, released under the MIT License. * +* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * +*************************************************************************************************/ +public abstract class DML extends NebulaCore implements IDML { + + private Schema.SObjectType sobjectType; + + public DML(Schema.SObjectType sobjectType) { + this.sobjectType = sobjectType; + } + + public virtual void insertRecords(SObject record) { + this.insertRecords(new List{record}); + } + + public virtual void insertRecords(List records) { + Database.insert(records); + } + + public virtual void updateRecords(SObject record) { + this.updateRecords(new List{record}); + } + + public virtual void updateRecords(List records) { + Database.update(records); + } + + public virtual void upsertRecords(SObject record) { + this.upsertRecords(this.castRecords(record)); + } + + public virtual void upsertRecords(List records) { + Database.upsert(records); + } + + public virtual void undeleteRecords(SObject record) { + this.undeleteRecords(new List{record}); + } + + public virtual void undeleteRecords(List records) { + Database.undelete(records); + } + + public virtual void deleteRecords(SObject record) { + this.deleteRecords(new List{record}); + } + + public virtual void deleteRecords(List records) { + Database.delete(records); + } + + public virtual void hardDeleteRecords(SObject record) { + this.hardDeleteRecords(new List{record}); + } + + public virtual void hardDeleteRecords(List records) { + this.deleteRecords(records); + if(!records.isEmpty()) Database.emptyRecycleBin(records); + } + + // Not all objects will have external ID fields, so these methods are protected (instead of public) + // Any object that needs an upsert by external ID can expose these methods in their repos + protected virtual void upsertRecords(SObject record, Schema.SObjectField externalIdField) { + this.upsertRecords(this.castRecords(record), externalIdField); + } + + protected virtual void upsertRecords(List records, Schema.SObjectField externalIdField) { + Database.upsert(records, externalIdField); + } + + private List castRecords(SObject record) { + // Salesforce will only allow upsert calls for SObjects if a declared-type list is passed in. + // This is fine for the bulk method, where we can assume the caller is passing in an explicit list, but for a single record, + // the only way to successfully perform the upsert is to dynamically spin up a list of the SObject's type + + String listType = 'List<' + this.sobjectType + '>'; + List castRecords = (List)Type.forName(listType).newInstance(); + castRecords.add(record); + + return castRecords; + } + +} \ No newline at end of file diff --git a/src/classes/DML.cls-meta.xml b/src/classes/DML.cls-meta.xml new file mode 100644 index 0000000..8b061c8 --- /dev/null +++ b/src/classes/DML.cls-meta.xml @@ -0,0 +1,5 @@ + + + 39.0 + Active + diff --git a/src/classes/DMLMock.cls b/src/classes/DMLMock.cls new file mode 100644 index 0000000..677a376 --- /dev/null +++ b/src/classes/DMLMock.cls @@ -0,0 +1,62 @@ +/************************************************************************************************* +* This file is part of the Nebula Framework project, released under the MIT License. * +* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * +*************************************************************************************************/ +@isTest +public class DMLMock { + + public virtual class Base implements IDML { + + public void insertRecords(SObject record) { + this.insertRecords(new List{record}); + } + + public void insertRecords(List recordList) { + TestingUtils.generateIds(recordList); + TestingUtils.insertedRecords.addAll(recordList); + } + + public void updateRecords(SObject record) { + this.updateRecords(new List{record}); + } + + public void updateRecords(List recordList) { + TestingUtils.updatedRecords.addAll(recordList); + } + + public void upsertRecords(SObject record) { + this.upsertRecords(new List{record}); + } + + public void upsertRecords(List recordList) { + TestingUtils.generateIds(recordList); + TestingUtils.upsertedRecords.addAll(recordList); + } + + public void undeleteRecords(SObject record) { + this.undeleteRecords(new List{record}); + } + + public void undeleteRecords(List recordList) { + TestingUtils.undeletedRecords.addAll(recordList); + } + + public void deleteRecords(SObject record) { + this.deleteRecords(new List{record}); + } + + public void deleteRecords(List recordList) { + if(recordList != null) TestingUtils.deletedRecords.addAll(recordList); + } + + public void hardDeleteRecords(SObject record) { + this.hardDeleteRecords(new List{record}); + } + + public void hardDeleteRecords(List recordList) { + this.deleteRecords(recordList); + } + + } + +} \ No newline at end of file diff --git a/src/classes/DMLMock.cls-meta.xml b/src/classes/DMLMock.cls-meta.xml new file mode 100644 index 0000000..8b061c8 --- /dev/null +++ b/src/classes/DMLMock.cls-meta.xml @@ -0,0 +1,5 @@ + + + 39.0 + Active + diff --git a/src/classes/DMLMock_Tests.cls b/src/classes/DMLMock_Tests.cls new file mode 100644 index 0000000..d6e0e71 --- /dev/null +++ b/src/classes/DMLMock_Tests.cls @@ -0,0 +1,89 @@ +/************************************************************************************************* +* This file is part of the Nebula Framework project, released under the MIT License. * +* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * +*************************************************************************************************/ +@isTest +private class DMLMock_Tests { + + static IDML dmlRepo = new DMLMock.Base(); + static Schema.Contact con = createContact(); + + @isTest + static void it_should_fake_dml_insert() { + Test.startTest(); + dmlRepo.insertRecords(con); + Test.stopTest(); + + System.assert(TestingUtils.insertedRecords.size() > 0); + } + + @isTest + static void it_should_fake_dml_update() { + Test.startTest(); + dmlRepo.updateRecords(con); + Test.stopTest(); + + System.assert(!TestingUtils.updatedRecords.isEmpty()); + } + + @isTest + static void it_should_fake_dml_upsert() { + Test.startTest(); + dmlRepo.upsertRecords(con); + Test.stopTest(); + + System.assert(!TestingUtils.upsertedRecords.isEmpty()); + } + + @isTest + static void it_should_fake_dml_delete() { + Test.startTest(); + dmlRepo.deleteRecords(con); + Test.stopTest(); + + System.assert(!TestingUtils.deletedRecords.isEmpty()); + } + + @isTest + static void it_should_fake_dml_hard_delete() { + Test.startTest(); + dmlRepo.hardDeleteRecords(con); + Test.stopTest(); + + System.assert(!TestingUtils.deletedRecords.isEmpty()); + } + + @isTest + static void it_should_fake_dml_undelete() { + Test.startTest(); + dmlRepo.undeleteRecords(con); + Test.stopTest(); + + System.assert(!TestingUtils.undeletedRecords.isEmpty()); + } + + @isTest + static void it_should_mock_updating_read_only_fields_when_updating_data() { + Schema.Lead l = new Schema.Lead(); + l = (Lead)TestingUtils.setReadOnlyField(l, Schema.Lead.IsConverted, true); + + Test.startTest(); + dmlRepo.updateRecords(l); + Test.stoptest(); + + SObject record = TestingUtils.updatedRecords[0]; + System.assert(record instanceof Schema.Lead); + System.assert(record.get('IsConverted') != null); + } + + private static Contact createContact() { + con = new Contact(); + con.Email = 'rightHandMan@hamilton.com'; + con.FirstName = 'George'; + con.LastName = 'Washington'; + con.LeadSource = 'Web'; + + return con; + } + +} \ No newline at end of file diff --git a/src/classes/DMLMock_Tests.cls-meta.xml b/src/classes/DMLMock_Tests.cls-meta.xml new file mode 100644 index 0000000..8b061c8 --- /dev/null +++ b/src/classes/DMLMock_Tests.cls-meta.xml @@ -0,0 +1,5 @@ + + + 39.0 + Active + diff --git a/src/classes/Environment.cls-meta.xml b/src/classes/Environment.cls-meta.xml index cbddff8..8b061c8 100644 --- a/src/classes/Environment.cls-meta.xml +++ b/src/classes/Environment.cls-meta.xml @@ -1,5 +1,5 @@ - 38.0 + 39.0 Active diff --git a/src/classes/Environment_Tests.cls-meta.xml b/src/classes/Environment_Tests.cls-meta.xml index cbddff8..8b061c8 100644 --- a/src/classes/Environment_Tests.cls-meta.xml +++ b/src/classes/Environment_Tests.cls-meta.xml @@ -1,5 +1,5 @@ - 38.0 + 39.0 Active diff --git a/src/classes/Exceptions.cls b/src/classes/Exceptions.cls new file mode 100644 index 0000000..70b180e --- /dev/null +++ b/src/classes/Exceptions.cls @@ -0,0 +1,11 @@ +/************************************************************************************************* +* This file is part of the Nebula Framework project, released under the MIT License. * +* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * +*************************************************************************************************/ +public without sharing class Exceptions { + + public class InvalidOperationException extends Exception {} + public class RecordTypeException extends Exception {} + public class SObjectTriggerHandlerException extends Exception {} + +} \ No newline at end of file diff --git a/src/classes/Exceptions.cls-meta.xml b/src/classes/Exceptions.cls-meta.xml new file mode 100644 index 0000000..8b061c8 --- /dev/null +++ b/src/classes/Exceptions.cls-meta.xml @@ -0,0 +1,5 @@ + + + 39.0 + Active + diff --git a/src/classes/IDML.cls b/src/classes/IDML.cls new file mode 100644 index 0000000..4e53798 --- /dev/null +++ b/src/classes/IDML.cls @@ -0,0 +1,20 @@ +/************************************************************************************************* +* This file is part of the Nebula Framework project, released under the MIT License. * +* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * +*************************************************************************************************/ +public interface IDML { + + void insertRecords(SObject record); + void insertRecords(List recordList); + void updateRecords(SObject record); + void updateRecords(List recordList); + void upsertRecords(SObject record); + void upsertRecords(List recordList); + void undeleteRecords(SObject record); + void undeleteRecords(List recordList); + void deleteRecords(SObject record); + void deleteRecords(List recordList); + void hardDeleteRecords(SObject record); + void hardDeleteRecords(List recordList); + +} \ No newline at end of file diff --git a/src/classes/IDML.cls-meta.xml b/src/classes/IDML.cls-meta.xml new file mode 100644 index 0000000..8b061c8 --- /dev/null +++ b/src/classes/IDML.cls-meta.xml @@ -0,0 +1,5 @@ + + + 39.0 + Active + diff --git a/src/classes/INebulaCore.cls-meta.xml b/src/classes/INebulaCore.cls-meta.xml index cbddff8..8b061c8 100644 --- a/src/classes/INebulaCore.cls-meta.xml +++ b/src/classes/INebulaCore.cls-meta.xml @@ -1,5 +1,5 @@ - 38.0 + 39.0 Active diff --git a/src/classes/IQueryArgumentFormatter.cls b/src/classes/IQueryArgumentFormatter.cls new file mode 100644 index 0000000..bf321e2 --- /dev/null +++ b/src/classes/IQueryArgumentFormatter.cls @@ -0,0 +1,9 @@ +/************************************************************************************************* +* This file is part of the Nebula Framework project, released under the MIT License. * +* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * +*************************************************************************************************/ +public interface IQueryArgumentFormatter { + + String getValue(); + +} \ No newline at end of file diff --git a/src/classes/IQueryArgumentFormatter.cls-meta.xml b/src/classes/IQueryArgumentFormatter.cls-meta.xml new file mode 100644 index 0000000..8b061c8 --- /dev/null +++ b/src/classes/IQueryArgumentFormatter.cls-meta.xml @@ -0,0 +1,5 @@ + + + 39.0 + Active + diff --git a/src/classes/IQueryBuilder.cls b/src/classes/IQueryBuilder.cls index 2fb161c..5d5ec29 100644 --- a/src/classes/IQueryBuilder.cls +++ b/src/classes/IQueryBuilder.cls @@ -4,12 +4,16 @@ *************************************************************************************************/ public interface IQueryBuilder { - // SOQL buildermethods - IQueryBuilder whereField(Schema.SObjectField field, QueryOperator operator, Object value); + // Query builder methods + IQueryBuilder filterBy(IQueryFilter queryFilter); + IQueryBuilder filterBy(List queryFilters); + + IQueryBuilder includeChildrenRecords(Schema.SObjectField childToParentRelationshipField, ISObjectRepository childSObjectRepository); + IQueryBuilder includeChildrenRecords(Map childFieldToChildSObjectRepositoryrMap); IQueryBuilder orderBy(Schema.SObjectField orderByField); IQueryBuilder orderBy(Schema.SObjectField orderByField, QuerySortOrder sortOrder); - IQueryBuilder orderBy(Schema.SObjectField orderByField, QuerySortOrder sortOrder, QueryNullSortOrder nullSortOrder); + IQueryBuilder orderBy(Schema.SObjectField orderByField, QuerySortOrder sortOrder, QueryNullSortOrder nullsSortOrder); IQueryBuilder limitCount(Integer limitCount); IQueryBuilder setAsUpdate(); @@ -20,4 +24,8 @@ public interface IQueryBuilder { List getQueryResults(); List getSearchResults(String searchTerm, QuerySearchGroup searchGroup); + // Get the dyanmic query strings + String getQuery(); + String getQuery(Schema.SObjectField sobjectField); + } \ No newline at end of file diff --git a/src/classes/IQueryBuilder.cls-meta.xml b/src/classes/IQueryBuilder.cls-meta.xml index cbddff8..8b061c8 100644 --- a/src/classes/IQueryBuilder.cls-meta.xml +++ b/src/classes/IQueryBuilder.cls-meta.xml @@ -1,5 +1,5 @@ - 38.0 + 39.0 Active diff --git a/src/classes/IQueryFilter.cls b/src/classes/IQueryFilter.cls new file mode 100644 index 0000000..6fd64e1 --- /dev/null +++ b/src/classes/IQueryFilter.cls @@ -0,0 +1,11 @@ +/************************************************************************************************* +* This file is part of the Nebula Framework project, released under the MIT License. * +* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * +*************************************************************************************************/ +public interface IQueryFilter { + + Object getProvidedValue(); + Schema.SObjectField getSObjectField(); + String getValue(); + +} \ No newline at end of file diff --git a/src/classes/IQueryFilter.cls-meta.xml b/src/classes/IQueryFilter.cls-meta.xml new file mode 100644 index 0000000..8b061c8 --- /dev/null +++ b/src/classes/IQueryFilter.cls-meta.xml @@ -0,0 +1,5 @@ + + + 39.0 + Active + diff --git a/src/classes/ISObjectRepository.cls b/src/classes/ISObjectRepository.cls index 975f7c4..95caf88 100644 --- a/src/classes/ISObjectRepository.cls +++ b/src/classes/ISObjectRepository.cls @@ -7,23 +7,17 @@ public interface ISObjectRepository { // SOQL SObject getById(Id recordId); List getById(List recordIdList); + List get(IQueryFilter queryFilter); + List get(List queryFilters); + List getByIdAndQueryFilters(Set idSet, List queryFilters); + List getByIdAndQueryFilters(List idList, List queryFilters); // SOSL - List searchInAllFields(String searchTerm); - List searchInFieldsBySearchGroup(String searchTerm, QuerySearchGroup searchGroup); + List getSearchResults(String searchTerm); + List getSearchResults(String searchTerm, QuerySearchGroup searchGroup); + List getSearchResults(String searchTerm, QuerySearchGroup searchGroup, List queryFilters); - // DML - void doInsert(SObject record); - void doInsert(List recordList); - void doUpdate(SObject record); - void doUpdate(List recordList); - void doUpsert(SObject record); - void doUpsert(List recordList); - void doDelete(SObject record); - void doDelete(List recordList); - void doHardDelete(SObject record); - void doHardDelete(List recordList); - void doUndelete(SObject record); - void doUndelete(List recordList); + // Additional methods + IQueryBuilder getQueryBuilder(); } \ No newline at end of file diff --git a/src/classes/ISObjectRepository.cls-meta.xml b/src/classes/ISObjectRepository.cls-meta.xml index cbddff8..8b061c8 100644 --- a/src/classes/ISObjectRepository.cls-meta.xml +++ b/src/classes/ISObjectRepository.cls-meta.xml @@ -1,5 +1,5 @@ - 38.0 + 39.0 Active diff --git a/src/classes/ISObjectTriggerHandler.cls-meta.xml b/src/classes/ISObjectTriggerHandler.cls-meta.xml index cbddff8..8b061c8 100644 --- a/src/classes/ISObjectTriggerHandler.cls-meta.xml +++ b/src/classes/ISObjectTriggerHandler.cls-meta.xml @@ -1,5 +1,5 @@ - 38.0 + 39.0 Active diff --git a/src/classes/Logger.cls b/src/classes/Logger.cls index b973375..716b71f 100644 --- a/src/classes/Logger.cls +++ b/src/classes/Logger.cls @@ -6,23 +6,19 @@ public without sharing class Logger { private static Id logId; private static Attachment logAttachment; - private static List logMessages; + @testVisible private static List logMessages; static { Logger.logMessages = Logger.logMessages == null ? new List() : Logger.logMessages; Logger.logAttachment = Logger.logAttachment == null ? createLogAttachment() : Logger.logAttachment; } - public static void addEntry(INebulaCore moduleClass, String message) { - addEntry(moduleClass, message, null); - } - public static void addEntry(String message) { - addEntry(null, message, null); + addEntry(null, message); } - public static void addEntry(INebulaCore moduleClass, String message, Exception ex) { - Logger.Message logMessage = new Logger.Message(moduleClass, message, ex); + public static void addEntry(INebulaCore moduleClass, String message) { + Logger.Message logMessage = new Logger.Message(moduleClass, message); Logger.logMessages.add(logMessage); } @@ -38,7 +34,6 @@ public without sharing class Logger { NebulaLog__c newLog = new NebulaLog__c( InitialClass__c = NebulaCore.INITIAL_CLASS, - InitialModule__c = NebulaCore.INITIAL_MODULE == null ? null : NebulaCore.INITIAL_MODULE.name(), TransactionId__c = NebulaCore.TRANSACTION_ID ); insert newLog; @@ -55,10 +50,10 @@ public without sharing class Logger { parsedMessageString = parsedMessageString + divider - + '\nCurrent Module: ' + logMessage.classModule - + '\nCurrent Class: ' + logMessage.className - + '\nTimestamp: ' + logMessage.timestamp.format('yyyy-MM-dd_hh:mm:ss.SS') - + '\n\n' + logMessage.message; + + '\nCurrent Module: ' + logMessage.ClassModule + + '\nCurrent Class: ' + logMessage.ClassName + + '\nTimestamp: ' + logMessage.Timestamp.format('yyyy-MM-dd_hh:mm:ss.SS') + + '\n\n' + logMessage.Message; } if(Logger.logAttachment.Id == null) { @@ -82,18 +77,16 @@ public without sharing class Logger { } private class Message { - private NebulaCore.Module classModule; - private String className; - private Exception ex; // TODO need to add exception info to log - private String message; - private Datetime timestamp; - - public Message(INebulaCore moduleClass, String message, Exception ex) { - this.classModule = moduleClass == null ? null : moduleClass.getClassModule(); - this.className = moduleClass == null ? null : moduleClass.getClassName(); - this.ex = ex; - this.message = message; - this.timestamp = System.now(); + public NebulaCore.Module ClassModule {get; private set;} + public String ClassName {get; private set;} + public String Message {get; private set;} + public Datetime Timestamp {get; private set;} + + public Message(INebulaCore moduleClass, String message) { + this.ClassModule = moduleClass == null ? null : moduleClass.getClassModule(); + this.ClassName = moduleClass == null ? null : moduleClass.getClassName(); + this.Message = message; + this.Timestamp = System.now(); } } diff --git a/src/classes/Logger.cls-meta.xml b/src/classes/Logger.cls-meta.xml index cbddff8..8b061c8 100644 --- a/src/classes/Logger.cls-meta.xml +++ b/src/classes/Logger.cls-meta.xml @@ -1,5 +1,5 @@ - 38.0 + 39.0 Active diff --git a/src/classes/Logger_Tests.cls b/src/classes/Logger_Tests.cls new file mode 100644 index 0000000..36ae070 --- /dev/null +++ b/src/classes/Logger_Tests.cls @@ -0,0 +1,111 @@ +/************************************************************************************************* +* This file is part of the Nebula Framework project, released under the MIT License. * +* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * +*************************************************************************************************/ +@isTest +private class Logger_Tests { + + private class FakeNebulaClass extends NebulaCore {} + + @isTest + static void it_should_add_entry_for_a_message_string() { + String message = 'my test message'; + System.assert(Logger.logMessages.isEmpty()); + + Test.startTest(); + Logger.addEntry(message); + Test.stopTest(); + + System.assertEquals(null, Logger.logMessages[0].ClassModule); + System.assertEquals(null, Logger.logMessages[0].ClassName); + System.assertEquals(message, Logger.logMessages[0].Message); + } + + @isTest + static void it_should_add_entry_for_a_module_class_and_a_message_string() { + FakeNebulaClass moduleClass = new FakeNebulaClass(); + String message = 'my test message'; + System.assert(Logger.logMessages.isEmpty()); + + Test.startTest(); + Logger.addEntry(moduleClass, message); + Test.stopTest(); + + System.assertEquals(moduleClass.getClassModule(), Logger.logMessages[0].ClassModule); + System.assertEquals(moduleClass.getClassName(), Logger.logMessages[0].ClassName); + System.assertEquals(message, Logger.logMessages[0].Message); + } + + @isTest + static void it_should_add_entry_for_a_module_class_and_an_exception_and_a_message_string() { + FakeNebulaClass moduleClass = new FakeNebulaClass(); + String message = 'my test message'; + System.assert(Logger.logMessages.isEmpty()); + + Test.startTest(); + Logger.addEntry(moduleClass, message); + Test.stopTest(); + + System.assertEquals(moduleClass.getClassModule(), Logger.logMessages[0].ClassModule); + System.assertEquals(moduleClass.getClassName(), Logger.logMessages[0].ClassName); + System.assertEquals(message, Logger.logMessages[0].Message); + } + + @isTest + static void it_should_save_logs_when_logging_is_enabled() { + FakeNebulaClass moduleClass = new FakeNebulaClass(); + String message = 'my test message'; + System.assert(Logger.logMessages.isEmpty()); + List existingLogs = [SELECT Id FROM NebulaLog__c]; + System.assert(existingLogs.isEmpty()); + + // Enable logging + NebulaLoggerSettings__c nebulaLoggerSettings = NebulaLoggerSettings__c.getInstance(); + nebulaLoggerSettings.EnableLogging__c = true; + upsert nebulaLoggerSettings; + + Logger.addEntry(moduleClass, message); + + Test.startTest(); + String transactionId = NebulaCore.TRANSACTION_ID; + Logger.saveLogs(); + Test.stopTest(); + + List logs = [SELECT Id, TransactionId__c FROM NebulaLog__c]; + System.assertEquals(1, logs.size()); + System.assertEquals(transactionId, logs[0].TransactionId__c); + + List logAttachments = [SELECT Id, Name, ParentId, ContentType, IsPrivate, Body FROM Attachment WHERE ParentId = :logs[0].Id]; + System.assertEquals(1, logAttachments.size()); + System.assertEquals(transactionId, logAttachments[0].Name); + System.assertEquals('text/plain', logAttachments[0].ContentType); + System.assertEquals(false, logAttachments[0].IsPrivate); + String attachmentBody = logAttachments[0].Body.toString(); + System.assert(attachmentBody.contains(message), attachmentBody); + } + + @isTest + static void it_should_not_save_logs_when_logging_is_disabled() { + FakeNebulaClass moduleClass = new FakeNebulaClass(); + String message = 'my test message'; + System.assert(Logger.logMessages.isEmpty()); + List existingLogs = [SELECT Id FROM NebulaLog__c]; + System.assert(existingLogs.isEmpty()); + + // Disable logging + NebulaLoggerSettings__c nebulaLoggerSettings = NebulaLoggerSettings__c.getInstance(); + nebulaLoggerSettings.EnableLogging__c = false; + upsert nebulaLoggerSettings; + + Logger.addEntry(moduleClass, message); + + Test.startTest(); + String transactionId = NebulaCore.TRANSACTION_ID; + Logger.saveLogs(); + Test.stopTest(); + + List logs = [SELECT Id, TransactionId__c FROM NebulaLog__c]; + System.assertEquals(0, logs.size()); + } + +} \ No newline at end of file diff --git a/src/classes/Logger_Tests.cls-meta.xml b/src/classes/Logger_Tests.cls-meta.xml new file mode 100644 index 0000000..8b061c8 --- /dev/null +++ b/src/classes/Logger_Tests.cls-meta.xml @@ -0,0 +1,5 @@ + + + 39.0 + Active + diff --git a/src/classes/NebulaCore.cls b/src/classes/NebulaCore.cls index 1113ebd..89591cf 100644 --- a/src/classes/NebulaCore.cls +++ b/src/classes/NebulaCore.cls @@ -5,8 +5,7 @@ public abstract class NebulaCore implements INebulaCore { public static final String TRANSACTION_ID; - public static String INITIAL_CLASS {public get; private set;} - public static Module INITIAL_MODULE {public get; private set;} + public static String INITIAL_CLASS {get; private set;} static { NebulaCore.TRANSACTION_ID = new UUID().getValue(); @@ -19,7 +18,6 @@ public abstract class NebulaCore implements INebulaCore { protected NebulaCore() { if(NebulaCore.INITIAL_CLASS == null) NebulaCore.INITIAL_CLASS = this.getClassName(); - if(NebulaCore.INITIAL_MODULE == null) NebulaCore.INITIAL_MODULE = this.getClassModule(); } public String getClassName() { @@ -27,10 +25,6 @@ public abstract class NebulaCore implements INebulaCore { } public NebulaCore.Module getClassModule() { - return this.getModule(); - } - - public NebulaCore.Module getModule() { return this.currentModule; } diff --git a/src/classes/NebulaCore.cls-meta.xml b/src/classes/NebulaCore.cls-meta.xml index cbddff8..8b061c8 100644 --- a/src/classes/NebulaCore.cls-meta.xml +++ b/src/classes/NebulaCore.cls-meta.xml @@ -1,5 +1,5 @@ - 38.0 + 39.0 Active diff --git a/src/classes/NebulaSettings.cls b/src/classes/NebulaSettings.cls index a907772..6fc734a 100644 --- a/src/classes/NebulaSettings.cls +++ b/src/classes/NebulaSettings.cls @@ -4,10 +4,10 @@ *************************************************************************************************/ public without sharing class NebulaSettings { - public static NebulaLoggerSettings__c loggerSettings; - public static NebulaRecordTypesSettings__c recordTypesSettings; - public static NebulaRepositorySettings__c repositorySettings; - public static NebulaTriggerHandlerSettings__c triggerHandlerSettings; + public static NebulaLoggerSettings__c LoggerSettings {get; private set;} + public static NebulaRecordTypesSettings__c RecordTypesSettings {get; private set;} + public static NebulaRepositorySettings__c RepositorySettings {get; private set;} + public static NebulaTriggerHandlerSettings__c TriggerHandlerSettings {get; private set;} static { loadCustomSettings(); diff --git a/src/classes/NebulaSettings.cls-meta.xml b/src/classes/NebulaSettings.cls-meta.xml index cbddff8..8b061c8 100644 --- a/src/classes/NebulaSettings.cls-meta.xml +++ b/src/classes/NebulaSettings.cls-meta.xml @@ -1,5 +1,5 @@ - 38.0 + 39.0 Active diff --git a/src/classes/NebulaSettings_Tests.cls b/src/classes/NebulaSettings_Tests.cls index c7c4bc5..7fb6181 100644 --- a/src/classes/NebulaSettings_Tests.cls +++ b/src/classes/NebulaSettings_Tests.cls @@ -6,42 +6,42 @@ private class NebulaSettings_Tests { @isTest - static void it_should_return_recordTypesSettings() { + static void it_should_return_record_types_settings() { List existingSettings = [SELECT Id FROM NebulaRecordTypesSettings__c]; System.assert(existingSettings.isEmpty()); Test.startTest(); - System.assertNotEquals(null, NebulaSettings.recordTypesSettings); + System.assertNotEquals(null, NebulaSettings.RecordTypesSettings); Test.stopTest(); } @isTest - static void it_should_return_loggerSettings() { + static void it_should_return_logger_settings() { List existingSettings = [SELECT Id FROM NebulaLoggerSettings__c]; System.assert(existingSettings.isEmpty()); Test.startTest(); - System.assertNotEquals(null, NebulaSettings.loggerSettings); + System.assertNotEquals(null, NebulaSettings.LoggerSettings); Test.stopTest(); } @isTest - static void it_should_return_repositorySettings() { + static void it_should_return_repository_settings() { List existingSettings = [SELECT Id FROM NebulaRepositorySettings__c]; System.assert(existingSettings.isEmpty()); Test.startTest(); - System.assertNotEquals(null, NebulaSettings.repositorySettings); + System.assertNotEquals(null, NebulaSettings.RepositorySettings); Test.stopTest(); } @isTest - static void it_should_return_triggerHandlerSettings() { + static void it_should_return_trigger_handler_settings() { List existingSettings = [SELECT Id FROM NebulaTriggerHandlerSettings__c]; System.assert(existingSettings.isEmpty()); Test.startTest(); - System.assertNotEquals(null, NebulaSettings.triggerHandlerSettings); + System.assertNotEquals(null, NebulaSettings.TriggerHandlerSettings); Test.stopTest(); } diff --git a/src/classes/NebulaSettings_Tests.cls-meta.xml b/src/classes/NebulaSettings_Tests.cls-meta.xml index cbddff8..8b061c8 100644 --- a/src/classes/NebulaSettings_Tests.cls-meta.xml +++ b/src/classes/NebulaSettings_Tests.cls-meta.xml @@ -1,5 +1,5 @@ - 38.0 + 39.0 Active diff --git a/src/classes/QueryArgumentFormatter.cls b/src/classes/QueryArgumentFormatter.cls index 6e96f84..0efce6a 100644 --- a/src/classes/QueryArgumentFormatter.cls +++ b/src/classes/QueryArgumentFormatter.cls @@ -2,19 +2,21 @@ * This file is part of the Nebula Framework project, released under the MIT License. * * See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * *************************************************************************************************/ -public without sharing class QueryArgumentFormatter { +public virtual class QueryArgumentFormatter extends NebulaCore implements IQueryArgumentFormatter { private Object argumentValue; public QueryArgumentFormatter(Object argumentValue) { + this.currentModule = NebulaCore.Module.REPOSITORY; + this.argumentValue = argumentValue; } - public String getValue() { + public virtual String getValue() { return this.objectToQueryString(this.argumentValue); } - private String objectToQueryString(Object valueToFormat) { + protected virtual String objectToQueryString(Object valueToFormat) { if(valueToFormat == null) return null; else if(valueToFormat instanceof List) return this.listToQueryString((List)valueToFormat); else if(valueToFormat instanceof QueryDateLiteral) { @@ -35,17 +37,21 @@ public without sharing class QueryArgumentFormatter { SObject record = (SObject)valueToFormat; return wrapInSingleQuotes(record.Id); } + else if(valueToFormat instanceof Schema.SObjectType) { + Schema.SObjectType sobjectType = (Schema.SObjectType)valueToFormat; + return wrapInSingleQuotes(sobjectType.getDescribe().getName()); + } else if(valueToFormat instanceof String) return wrapInSingleQuotes((String)valueToFormat); else return String.valueOf(valueToFormat); } - private String listToQueryString(List valueList) { + protected virtual String listToQueryString(List valueList) { List parsedValueList = new List(); for(Object value : valueList) parsedValueList.add(this.objectToQueryString(value)); return '(' + String.join(parsedValueList, ',') + ')'; } - private String wrapInSingleQuotes(String input) { + protected virtual String wrapInSingleQuotes(String input) { if(input.left(1) != '\'') input = '\'' + input; if(input.right(1) != '\'') input = input + '\''; return input; diff --git a/src/classes/QueryArgumentFormatter.cls-meta.xml b/src/classes/QueryArgumentFormatter.cls-meta.xml index cbddff8..8b061c8 100644 --- a/src/classes/QueryArgumentFormatter.cls-meta.xml +++ b/src/classes/QueryArgumentFormatter.cls-meta.xml @@ -1,5 +1,5 @@ - 38.0 + 39.0 Active diff --git a/src/classes/QueryArgumentFormatter_Tests.cls b/src/classes/QueryArgumentFormatter_Tests.cls index 8cc19e1..ec30f23 100644 --- a/src/classes/QueryArgumentFormatter_Tests.cls +++ b/src/classes/QueryArgumentFormatter_Tests.cls @@ -31,7 +31,7 @@ private class QueryArgumentFormatter_Tests { @isTest static void it_should_return_query_string_for_query_date_literal() { - QueryDateLiteral providedValue = new QueryDateLiteral().YESTERDAY; + QueryDateLiteral providedValue = QueryDateLiteral.YESTERDAY; String expectedString = providedValue.getValue(); Test.startTest(); @@ -141,6 +141,18 @@ private class QueryArgumentFormatter_Tests { System.assertEquals(expectedString, returnedValue); } + @isTest + static void it_should_return_query_string_for_sobject_type() { + Schema.SObjectType providedValue = Schema.Lead.SObjectType; + String expectedString = '\'' + providedValue.getDescribe().getName() + '\''; + + Test.startTest(); + String returnedValue = new QueryArgumentFormatter(providedValue).getValue(); + Test.stopTest(); + + System.assertEquals(expectedString, returnedValue); + } + @isTest static void it_should_return_query_string_for_string() { String providedValue = 'test'; @@ -154,7 +166,7 @@ private class QueryArgumentFormatter_Tests { } @isTest - static void it_should_return_query_string_for_unsported_type() { + static void it_should_return_query_string_for_unsupported_type() { UUID providedValue = new UUID(); String expectedString = String.valueOf(providedValue); diff --git a/src/classes/QueryArgumentFormatter_Tests.cls-meta.xml b/src/classes/QueryArgumentFormatter_Tests.cls-meta.xml index cbddff8..8b061c8 100644 --- a/src/classes/QueryArgumentFormatter_Tests.cls-meta.xml +++ b/src/classes/QueryArgumentFormatter_Tests.cls-meta.xml @@ -1,5 +1,5 @@ - 38.0 + 39.0 Active diff --git a/src/classes/QueryBuilder.cls b/src/classes/QueryBuilder.cls index d118a1c..edfbbaa 100644 --- a/src/classes/QueryBuilder.cls +++ b/src/classes/QueryBuilder.cls @@ -2,41 +2,63 @@ * This file is part of the Nebula Framework project, released under the MIT License. * * See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * *************************************************************************************************/ -public without sharing class QueryBuilder extends NebulaCore implements IQueryBuilder { +public class QueryBuilder extends NebulaCore implements IQueryBuilder { - private SObjectType sobjectType; - private Map sobjectTypeFieldMap; + private String query; private Set queryFields; - private Schema.FieldSet fieldSet; - private List sobjectFieldList; + private List childRelationshipQueries; private List whereClauseList; - private List orderByList; - private Integer limitCount; - private Boolean forUpdate; + @testVisible private List orderByList; private QueryFilterScope filterScope; + @testVisible private Boolean forUpdate; + private Integer limitCount; + + private final Schema.FieldSet fieldSet; + private final List sobjectFieldList; + private final SObjectType sobjectType; + private final Map sobjectTypeFieldMap; public QueryBuilder(Schema.SObjectType sobjectType, Schema.FieldSet fieldSet, List sobjectFieldList) { this.currentModule = NebulaCore.Module.REPOSITORY; - this.sobjectType = sobjectType; - this.fieldSet = fieldSet; - this.sobjectFieldList = sobjectFieldList; - + this.sobjectType = sobjectType; + this.sobjectFieldList = sobjectFieldList; + this.fieldSet = fieldSet; this.sobjectTypeFieldMap = this.sobjectType.getDescribe().fields.getMap(); - this.queryFields = new Set(); - this.whereClauseList = new List(); - this.orderByList = new List(); - this.forUpdate = false; + + this.queryFields = new Set(); + this.childRelationshipQueries = new List(); + this.whereClauseList = new List(); + this.orderByList = new List(); + this.forUpdate = false; this.addCommonQueryFields(); this.addFieldSetMembers(); this.addSObjectFields(); + this.addSObjectTypeFields(); + } + + public IQueryBuilder filterBy(IQueryFilter queryFilter) { + return this.filterBy(new List{queryFilter}); } - public IQueryBuilder whereField(Schema.SObjectField field, QueryOperator operator, Object value) { - String parsedValue = new QueryArgumentFormatter(value).getValue(); + public IQueryBuilder filterBy(List queryFilters) { + if(queryFilters == null) return this; - this.whereClauseList.add(field + ' ' + operator.getValue() + ' ' + parsedValue); + for(IQueryFilter queryFilter : queryFilters) this.whereClauseList.add(queryFilter.getValue()); + return this; + } + + public IQueryBuilder includeChildrenRecords(Schema.SObjectField childToParentRelationshipField, ISObjectRepository childSObjectRepository) { + return this.includeChildrenRecords(new Map{childToParentRelationshipField => childSObjectRepository}); + } + + public IQueryBuilder includeChildrenRecords(Map childFieldToChildSObjectRepositoryMap) { + for(Schema.SObjectField sobjectField : childFieldToChildSObjectRepositoryMap.keySet()) { + IQueryBuilder childQueryBuilder = childFieldToChildSObjectRepositoryMap.get(sobjectField).getQueryBuilder(); + + this.childRelationshipQueries.add(childQueryBuilder.getQuery(sobjectField)); + } return this; } @@ -45,15 +67,15 @@ public without sharing class QueryBuilder extends NebulaCore implements IQueryBu } public IQueryBuilder orderBy(Schema.SObjectField orderByField, QuerySortOrder sortOrder) { - return orderBy(orderByField, sortOrder, null); + return this.orderBy(orderByField, sortOrder, null); } - public IQueryBuilder orderBy(Schema.SObjectField orderByField, QuerySortOrder sortOrder, QueryNullSortOrder nullSortOrder) { + public IQueryBuilder orderBy(Schema.SObjectField orderByField, QuerySortOrder sortOrder, QueryNullSortOrder nullsSortOrder) { String sortOrderSoql = ''; if(sortOrder == QuerySortOrder.ASCENDING) sortOrderSoql = ' ASC'; else if(sortOrder == QuerySortOrder.DESCENDING) sortOrderSoql = ' DESC'; - if(nullSortOrder != null) sortOrderSoql += ' NULLS ' + nullSortOrder.name(); + if(nullsSortOrder != null) sortOrderSoql += ' NULLS ' + nullsSortOrder; this.orderByList.add(orderByField.getDescribe().getName() + sortOrderSoql); @@ -75,84 +97,55 @@ public without sharing class QueryBuilder extends NebulaCore implements IQueryBu return this; } - // Query execution methods + // Query execution methods public SObject getFirstQueryResult() { return this.getQueryResults()[0]; } public List getQueryResults() { - String query = this.getQueryString(); - List results = Database.query(query); + List results = Database.query(this.getQuery()); - String logEntry = 'Query:\n' + query + '\n\nResults:\n' + results; - Logger.addEntry(this, logEntry); + this.logEntry(results); return results; } - public List getSearchResults(String searchTerm, QuerySearchGroup searchGroup) { - String query = this.getSearchQueryString(searchTerm, searchGroup); - List results = Search.query(query)[0]; + public virtual List getSearchResults(String searchTerm, QuerySearchGroup searchGroup) { + List results = Search.query(this.getSearchQuery(searchTerm, searchGroup))[0]; - String logEntry = 'Query:\n' + query + '\n\nResults:\n' + results; - Logger.addEntry(this, logEntry); + this.logEntry(results); return results; } - private String getQueryFieldString() { - List queryFieldList = new List(this.queryFields); - if(NebulaSettings.repositorySettings.SortQueryFields__c) queryFieldList.sort(); - return String.join(queryFieldList, ','); + // Query string methods + public String getQuery() { + return this.getQuery(String.valueOf(this.sobjectType)); } - private String getWhereClauseString() { - String whereClauseString = ''; - if(!this.whereClauseList.isEmpty()) whereClauseString = '\nWHERE ' + String.join(this.whereClauseList, '\nAND '); - return whereClauseString; + public String getQuery(Schema.SObjectField childRelationshipSObjectField) { + Schema.SObjectType parentSObjectType = new SObjectFieldDescriber(childRelationshipSObjectField).getParentSObjectType(); + String childRelationshipName = new SObjectTypeDescriber(parentSObjectType).getChildRelationshipName(childRelationshipSObjectField); + return this.getQuery(childRelationshipName); } - private String getOrderByString() { - String orderByString = ''; - if(!this.orderByList.isEmpty()) orderByString = '\nORDER BY ' + String.join(new List(orderByList), ', '); - return orderByString; - } - - private String getLimitCountString() { - String limitString = ''; - if(this.limitCount != null) limitString = '\nLIMIT '+ this.limitCount; - return limitString; - } - - private String getForUpdateString() { - String forUpdateString = ''; - if(this.orderByList.isEmpty() && this.forUpdate) forUpdateString = '\nFOR UPDATE'; - return forUpdateString; - } + private String getQuery(String sobjectQueryName) { + this.query = 'SELECT ' + this.getQueryFieldString(); - private String getUsingScopeString() { - String usingScopeString = ''; - if(this.filterScope != null) usingScopeString = '\nUSING SCOPE ' + this.filterScope.name(); - return usingScopeString; -// TODO figure out what to do for SOSL https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_sosl_syntax.htm - } + // Only 1 level of child relationships is allowed, so don't include them if the SObject name isn't the current SObject Type + if(sobjectQueryName == String.valueOf(this.sobjectType)) this.query += this.getChildRelationshipsQueryString(); - private String getQueryString() { - String query = 'SELECT ' + this.getQueryFieldString() - + '\nFROM ' + this.sobjectType - + this.getUsingScopeString() + this.query +='\nFROM ' + sobjectQueryName + this.getWhereClauseString() + this.getOrderByString() + this.getLimitCountString() + this.getForUpdateString(); - Logger.addEntry(this, query); - - return query; + return this.query; } - private String getSearchQueryString(String searchTerm, QuerySearchGroup searchGroup) { - String query = 'FIND ' + new QueryArgumentFormatter(searchTerm).getValue() + private String getSearchQuery(String searchTerm, QuerySearchGroup searchGroup) { + this.query = 'FIND ' + new QueryArgumentFormatter(searchTerm.toLowerCase()).getValue() + '\nIN ' + searchGroup.name().replace('_', ' ') + '\nRETURNING ' + this.sobjectType + '(' + this.getQueryFieldString() @@ -164,25 +157,49 @@ public without sharing class QueryBuilder extends NebulaCore implements IQueryBu if(this.forUpdate) Logger.addEntry(this, 'SOSL Search Query method flagged as FOR UPDATE. SOSL cannot use FOR UPDATE, ignoring'); if(this.filterScope != null) Logger.addEntry(this, 'SOSL Search Query method flagged as USING SCOPE ' + this.filterScope + '. SOSL cannot use USING SCOPE, ignoring'); - Logger.addEntry(this, query); + return this.query; + } - return query; + private String getQueryFieldString() { + List queryFieldList = new List(this.queryFields); + queryFieldList.sort(); + return String.join(queryFieldList, ', '); } - private void addCommonQueryFields() { - if(!NebulaSettings.repositorySettings.IncludeCommonFields__c) return; + private String getChildRelationshipsQueryString() { + if(this.childRelationshipQueries.isEmpty()) return ''; + + return ',\n(' + String.join(childRelationshipQueries, '), (') + ')'; + } + + private String getWhereClauseString() { + return !this.whereClauseList.isEmpty() ? '\nWHERE ' + String.join(this.whereClauseList, '\nAND ') : ''; + } + + private String getOrderByString() { + return !this.orderByList.isEmpty() ? '\nORDER BY ' + String.join(new List(orderByList), ', ') : ''; + } + private String getLimitCountString() { + return this.limitCount != null ? '\nLIMIT '+ this.limitCount : ''; + } + + private String getForUpdateString() { + return this.orderByList.isEmpty() && this.forUpdate ? '\nFOR UPDATE' : ''; + } + + private void addCommonQueryFields() { + if(!NebulaSettings.RepositorySettings.IncludeCommonFields__c) return; // Auto-add the common fields that are available for the SObject Type List commonFieldNameList = new List{ 'Id', 'CaseNumber', 'CreatedById', 'CreatedDate', 'IsClosed', 'LastModifiedById', 'LastModifiedDate', - 'Name', 'OwnerId', 'Subject', 'RecordTypeId', 'SystemModStamp' + 'Name', 'OwnerId', 'ParentId', 'Subject', 'RecordTypeId', 'SystemModStamp' }; for(String commonFieldName : commonFieldNameList) { if(!this.sobjectTypeFieldMap.containsKey(commonFieldName)) continue; this.queryFields.add(commonFieldName.toLowerCase()); } - Logger.addEntry(this, 'this.queryFields=' + this.queryFields); } private void addFieldSetMembers() { @@ -197,4 +214,17 @@ public without sharing class QueryBuilder extends NebulaCore implements IQueryBu for(Schema.SObjectField field : this.sobjectFieldList) this.queryFields.add(field.getDescribe().getName().toLowerCase()); } + private void addSObjectTypeFields() { + if(this.fieldSet != null || this.sobjectFieldList != null) return; + + // If no field set or list of fields is provided, we enter 'lazy mode' and all fields for the SObject Type are added + // This is effectively 'select * from '. Performance issues can occur with large objects, so use carefully. + for(Schema.SObjectField field : this.sobjectTypeFieldMap.values()) this.queryFields.add(field.getDescribe().getName().toLowerCase()); + } + + private void logEntry(List results) { + String logEntry = 'Query:\n' + this.query + '\n\nResults:\n' + results; + Logger.addEntry(this, logEntry); + } + } \ No newline at end of file diff --git a/src/classes/QueryBuilder.cls-meta.xml b/src/classes/QueryBuilder.cls-meta.xml index cbddff8..8b061c8 100644 --- a/src/classes/QueryBuilder.cls-meta.xml +++ b/src/classes/QueryBuilder.cls-meta.xml @@ -1,5 +1,5 @@ - 38.0 + 39.0 Active diff --git a/src/classes/QueryBuilder_Tests.cls b/src/classes/QueryBuilder_Tests.cls new file mode 100644 index 0000000..d6d50b6 --- /dev/null +++ b/src/classes/QueryBuilder_Tests.cls @@ -0,0 +1,47 @@ +/************************************************************************************************* +* This file is part of the Nebula Framework project, released under the MIT License. * +* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * +*************************************************************************************************/ +@isTest +public class QueryBuilder_Tests { + + @isTest + static void it_should_call_sort_order_properly_ascending() { + Schema.SObjectType objType = Contact.SObjectType; + List fields = new List{Contact.CreatedDate}; + + Test.startTest(); + QueryBuilder query = new QueryBuilder(objType, null, fields); + query.orderBy(Contact.CreatedDate,QuerySortOrder.ASCENDING); + Test.stopTest(); + + System.assert(query.orderByList[0].contains(Contact.CreatedDate.getDescribe().getName())); + } + + @isTest + static void it_should_call_sort_order_properly_descending() { + Schema.SObjectType objType = Contact.SObjectType; + List fields = new List{Contact.CreatedDate}; + + Test.startTest(); + QueryBuilder query = new QueryBuilder(objType, null, fields); + query.orderBy(Contact.CreatedDate,QuerySortOrder.DESCENDING); + Test.stopTest(); + + System.assert(query.orderByList[0].contains(Contact.CreatedDate.getDescribe().getName())); + } + + @isTest + static void it_should_set_for_update() { + Schema.SObjectType objType = Contact.SObjectType; + List fields = new List{Contact.CreatedDate}; + + Test.startTest(); + QueryBuilder query = new QueryBuilder(objType, null, fields); + query.setAsUpdate(); + Test.stopTest(); + + System.assert(query.forUpdate); + } + +} \ No newline at end of file diff --git a/src/classes/QueryBuilder_Tests.cls-meta.xml b/src/classes/QueryBuilder_Tests.cls-meta.xml new file mode 100644 index 0000000..8b061c8 --- /dev/null +++ b/src/classes/QueryBuilder_Tests.cls-meta.xml @@ -0,0 +1,5 @@ + + + 39.0 + Active + diff --git a/src/classes/QueryDateLiteral.cls b/src/classes/QueryDateLiteral.cls index 5f11406..cd179ec 100644 --- a/src/classes/QueryDateLiteral.cls +++ b/src/classes/QueryDateLiteral.cls @@ -4,16 +4,17 @@ *************************************************************************************************/ public without sharing class QueryDateLiteral { - private final String LAST_N = 'LAST_N_{0}: {1}'; - private final String NEXT_N = 'NEXT_N_{0}: {1}'; - - private final String DAYS = 'DAYS'; - private final String WEEKS = 'WEEKS'; - private final String MONTHS = 'MONTHS'; - private final String QUARTERS = 'QUARTERS'; - private final String FISCAL_QUARTERS = 'FISCAL_QUARTERS'; - private final String YEARS = 'YEARS'; - private final String FISCAL_YEARS = 'FISCAL_YEARS'; + private final static String LAST_N = 'LAST_N_{0}: {1}'; + private final static String NEXT_N = 'NEXT_N_{0}: {1}'; + private final static String N_DAYS_AGO = 'N_DAYS_AGO: {0}'; + + private final static String DAYS = 'DAYS'; + private final static String WEEKS = 'WEEKS'; + private final static String MONTHS = 'MONTHS'; + private final static String QUARTERS = 'QUARTERS'; + private final static String FISCAL_QUARTERS = 'FISCAL_QUARTERS'; + private final static String YEARS = 'YEARS'; + private final static String FISCAL_YEARS = 'FISCAL_YEARS'; private String value; @@ -21,168 +22,172 @@ public without sharing class QueryDateLiteral { return this.value; } + private QueryDateLiteral(String value) { + this.value = value; + } + //Actual constant literals - public QueryDateLiteral YESTERDAY { - get {return this.setValue('YESTERDAY');} + public static QueryDateLiteral YESTERDAY { + get { return new QueryDateLiteral('YESTERDAY'); } } - public QueryDateLiteral TODAY { - get {return this.setValue('TODAY');} + public static QueryDateLiteral TODAY { + get { return new QueryDateLiteral('TODAY'); } } - public QueryDateLiteral TOMORROW { - get {return this.setValue('TOMORROW');} + public static QueryDateLiteral TOMORROW { + get { return new QueryDateLiteral('TOMORROW'); } } - public QueryDateLiteral LAST_WEEK { - get {return this.setValue('LAST_WEEK');} + public static QueryDateLiteral LAST_WEEK { + get { return new QueryDateLiteral('LAST_WEEK'); } } - public QueryDateLiteral THIS_WEEK { - get {return this.setValue('THIS_WEEK');} + public static QueryDateLiteral THIS_WEEK { + get { return new QueryDateLiteral('THIS_WEEK'); } } - public QueryDateLiteral NEXT_WEEK { - get {return this.setValue('NEXT_WEEK');} + public static QueryDateLiteral NEXT_WEEK { + get { return new QueryDateLiteral('NEXT_WEEK'); } } - public QueryDateLiteral LAST_MONTH { - get {return this.setValue('LAST_MONTH');} + public static QueryDateLiteral LAST_MONTH { + get { return new QueryDateLiteral('LAST_MONTH'); } } - public QueryDateLiteral THIS_MONTH { - get {return this.setValue('THIS_MONTH');} + public static QueryDateLiteral THIS_MONTH { + get { return new QueryDateLiteral('THIS_MONTH'); } } - public QueryDateLiteral NEXT_MONTH { - get {return this.setValue('NEXT_MONTH');} + public static QueryDateLiteral NEXT_MONTH { + get { return new QueryDateLiteral('NEXT_MONTH'); } } - public QueryDateLiteral LAST_90_DAYS { - get {return this.setValue('LAST_90_DAYS');} + public static QueryDateLiteral LAST_90_DAYS { + get { return new QueryDateLiteral('LAST_90_DAYS'); } } - public QueryDateLiteral NEXT_90_DAYS { - get {return this.setValue('NEXT_90_DAYS');} + public static QueryDateLiteral NEXT_90_DAYS { + get { return new QueryDateLiteral('NEXT_90_DAYS'); } } - public QueryDateLiteral THIS_QUARTER { - get {return this.setValue('THIS_QUARTER');} + public static QueryDateLiteral THIS_QUARTER { + get { return new QueryDateLiteral('THIS_QUARTER'); } } - public QueryDateLiteral THIS_FISCAL_QUARTER { - get {return this.setValue('THIS_FISCAL_QUARTER');} + public static QueryDateLiteral THIS_FISCAL_QUARTER { + get { return new QueryDateLiteral('THIS_FISCAL_QUARTER'); } } - public QueryDateLiteral LAST_QUARTER { - get {return this.setValue('LAST_QUARTER');} + public static QueryDateLiteral LAST_QUARTER { + get { return new QueryDateLiteral('LAST_QUARTER'); } } - public QueryDateLiteral LAST_FISCAL_QUARTER { - get {return this.setValue('LAST_FISCAL_QUARTER');} + public static QueryDateLiteral LAST_FISCAL_QUARTER { + get { return new QueryDateLiteral('LAST_FISCAL_QUARTER'); } } - public QueryDateLiteral NEXT_QUARTER { - get {return this.setValue('NEXT_QUARTER');} + public static QueryDateLiteral NEXT_QUARTER { + get { return new QueryDateLiteral('NEXT_QUARTER'); } } - public QueryDateLiteral NEXT_FISCAL_QUARTER { - get {return this.setValue('NEXT_FISCAL_QUARTER');} + public static QueryDateLiteral NEXT_FISCAL_QUARTER { + get { return new QueryDateLiteral('NEXT_FISCAL_QUARTER'); } } - public QueryDateLiteral THIS_YEAR { - get {return this.setValue('THIS_YEAR');} + public static QueryDateLiteral THIS_YEAR { + get { return new QueryDateLiteral('THIS_YEAR'); } } - public QueryDateLiteral THIS_FISCAL_YEAR { - get {return this.setValue('THIS_FISCAL_YEAR');} + public static QueryDateLiteral THIS_FISCAL_YEAR { + get { return new QueryDateLiteral('THIS_FISCAL_YEAR'); } } - public QueryDateLiteral LAST_YEAR { - get {return this.setValue('LAST_YEAR');} + public static QueryDateLiteral LAST_YEAR { + get { return new QueryDateLiteral('LAST_YEAR'); } } - public QueryDateLiteral LAST_FISCAL_YEAR { - get {return this.setValue('LAST_FISCAL_YEAR');} + public static QueryDateLiteral LAST_FISCAL_YEAR { + get { return new QueryDateLiteral('LAST_FISCAL_YEAR'); } } - public QueryDateLiteral NEXT_YEAR { - get {return this.setValue('NEXT_YEAR');} + public static QueryDateLiteral NEXT_YEAR { + get { return new QueryDateLiteral('NEXT_YEAR'); } } - public QueryDateLiteral NEXT_FISCAL_YEAR { - get {return this.setValue('NEXT_FISCAL_YEAR');} + public static QueryDateLiteral NEXT_FISCAL_YEAR { + get { return new QueryDateLiteral('NEXT_FISCAL_YEAR'); } } ////Buildable literals - public QueryDateLiteral LAST_N_DAYS(Integer num) { + public static QueryDateLiteral N_DAYS_AGO(Integer num) { + String parsedValue = String.format(N_DAYS_AGO, new List{String.valueOf(num)}); + return new QueryDateLiteral(parsedValue); + } + + public static QueryDateLiteral LAST_N_DAYS(Integer num) { String parsedValue = String.format(LAST_N, new List{DAYS,String.valueOf(num)}); - return this.setValue(parsedValue); + return new QueryDateLiteral(parsedValue); } - public QueryDateLiteral LAST_N_WEEKS(Integer num) { + public static QueryDateLiteral LAST_N_WEEKS(Integer num) { String parsedValue = String.format(LAST_N, new List{WEEKS, String.valueof(num)}); - return this.setValue(parsedValue); + return new QueryDateLiteral(parsedValue); } - public QueryDateLiteral LAST_N_MONTHS(Integer num) { + public static QueryDateLiteral LAST_N_MONTHS(Integer num) { String parsedValue = String.format(LAST_N, new List{MONTHS, String.valueOf(num)}); - return this.setValue(parsedValue); + return new QueryDateLiteral(parsedValue); } - public QueryDateLiteral LAST_N_QUARTERS(Integer num) { + public static QueryDateLiteral LAST_N_QUARTERS(Integer num) { String parsedValue = String.format(LAST_N, new List{QUARTERS, String.valueOf(num)}); - return this.setValue(parsedValue); + return new QueryDateLiteral(parsedValue); } - public QueryDateLiteral LAST_N_YEARS(Integer num) { + public static QueryDateLiteral LAST_N_YEARS(Integer num) { String parsedValue = String.format(LAST_N, new List{YEARS, String.valueOf(num)}); - return this.setValue(parsedValue); + return new QueryDateLiteral(parsedValue); } - public QueryDateLiteral LAST_N_FISCAL_YEARS(Integer num) { + public static QueryDateLiteral LAST_N_FISCAL_YEARS(Integer num) { String parsedValue = String.format(LAST_N, new List{FISCAL_YEARS, String.valueOf(num)}); - return this.setValue(parsedValue); + return new QueryDateLiteral(parsedValue); } - public QueryDateLiteral NEXT_N_DAYS(Integer num) { + public static QueryDateLiteral NEXT_N_DAYS(Integer num) { String parsedValue = String.format(NEXT_N, new List{DAYS, String.valueOf(num)}); - return this.setValue(parsedValue); + return new QueryDateLiteral(parsedValue); } - public QueryDateLiteral NEXT_N_WEEKS(Integer num) { + public static QueryDateLiteral NEXT_N_WEEKS(Integer num) { String parsedValue = String.format(NEXT_N, new List{WEEKS, String.valueOf(num)}); - return this.setValue(parsedValue); + return new QueryDateLiteral(parsedValue); } - public QueryDateLiteral NEXT_N_MONTHS(Integer num) { + public static QueryDateLiteral NEXT_N_MONTHS(Integer num) { String parsedValue = String.format(NEXT_N, new List{MONTHS, String.valueOf(num)}); - return this.setValue(parsedValue); + return new QueryDateLiteral(parsedValue); } - public QueryDateLiteral NEXT_N_QUARTERS(Integer num) { + public static QueryDateLiteral NEXT_N_QUARTERS(Integer num) { String parsedValue = String.format(NEXT_N, new List{QUARTERS, String.valueOf(num)}); - return this.setValue(parsedValue); + return new QueryDateLiteral(parsedValue); } - public QueryDateLiteral NEXT_N_FISCAL_QUARTERS(Integer num) { + public static QueryDateLiteral NEXT_N_FISCAL_QUARTERS(Integer num) { String parsedValue = String.format(NEXT_N, new List{FISCAL_QUARTERS, String.valueOf(num)}); - return this.setValue(parsedValue); + return new QueryDateLiteral(parsedValue); } - public QueryDateLiteral NEXT_N_YEARS(Integer num) { + public static QueryDateLiteral NEXT_N_YEARS(Integer num) { String parsedValue = String.format(NEXT_N, new List{YEARS, String.valueOf(num)}); - return this.setValue(parsedValue); + return new QueryDateLiteral(parsedValue); } - public QueryDateLiteral NEXT_N_FISCAL_YEARS(Integer num) { + public static QueryDateLiteral NEXT_N_FISCAL_YEARS(Integer num) { String parsedValue = String.format(NEXT_N, new List{FISCAL_YEARS, String.valueOf(num)}); - return this.setValue(parsedValue); - } - - private QueryDateLiteral setValue(String value) { - this.value = value; - return this; + return new QueryDateLiteral(parsedValue); } } \ No newline at end of file diff --git a/src/classes/QueryDateLiteral.cls-meta.xml b/src/classes/QueryDateLiteral.cls-meta.xml index cbddff8..8b061c8 100644 --- a/src/classes/QueryDateLiteral.cls-meta.xml +++ b/src/classes/QueryDateLiteral.cls-meta.xml @@ -1,5 +1,5 @@ - 38.0 + 39.0 Active diff --git a/src/classes/QueryDateLiteral_Tests.cls b/src/classes/QueryDateLiteral_Tests.cls index b372fac..244db7e 100644 --- a/src/classes/QueryDateLiteral_Tests.cls +++ b/src/classes/QueryDateLiteral_Tests.cls @@ -5,221 +5,227 @@ @isTest public class QueryDateLiteral_Tests { - private static integer n_number = 5; + private static Integer n_number = 5; @isTest static void it_should_return_yesterday_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().YESTERDAY; + QueryDateLiteral dateLiteral = QueryDateLiteral.YESTERDAY; System.assertEquals('YESTERDAY', dateLiteral.getValue()); } @isTest static void it_should_return_today_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().TODAY; + QueryDateLiteral dateLiteral = QueryDateLiteral.TODAY; System.assertEquals('TODAY', dateLiteral.getValue()); } @isTest static void it_should_return_tomorrow_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().TOMORROW; + QueryDateLiteral dateLiteral = QueryDateLiteral.TOMORROW; System.assertEquals('TOMORROW', dateLiteral.getValue()); } @isTest static void it_should_return_last_week_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().LAST_WEEK; + QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_WEEK; System.assertEquals('LAST_WEEK', dateLiteral.getValue()); } @isTest static void it_should_return_this_week_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().THIS_WEEK; + QueryDateLiteral dateLiteral = QueryDateLiteral.THIS_WEEK; System.assertEquals('THIS_WEEK', dateLiteral.getValue()); } @isTest static void it_should_return_next_week_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().NEXT_WEEK; + QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_WEEK; System.assertEquals('NEXT_WEEK', dateLiteral.getValue()); } @isTest static void it_should_return_last_month_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().LAST_MONTH; + QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_MONTH; System.assertEquals('LAST_MONTH', dateLiteral.getValue()); } @isTest static void it_should_return_this_month_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().THIS_MONTH; + QueryDateLiteral dateLiteral = QueryDateLiteral.THIS_MONTH; System.assertEquals('THIS_MONTH', dateLiteral.getValue()); } @isTest static void it_should_return_next_month_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().NEXT_MONTH; + QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_MONTH; System.assertEquals('NEXT_MONTH', dateLiteral.getValue()); } @isTest static void it_should_return_last_ninety_days_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().LAST_90_DAYS; + QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_90_DAYS; System.assertEquals('LAST_90_DAYS', dateLiteral.getValue()); } @isTest static void it_should_return_next_ninety_days_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().NEXT_90_DAYS; + QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_90_DAYS; System.assertEquals('NEXT_90_DAYS', dateLiteral.getValue()); } @isTest static void it_should_return_this_quarter_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().THIS_QUARTER; + QueryDateLiteral dateLiteral = QueryDateLiteral.THIS_QUARTER; System.assertEquals('THIS_QUARTER', dateLiteral.getValue()); } @isTest static void it_should_return_last_quarter_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().LAST_QUARTER; + QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_QUARTER; System.assertEquals('LAST_QUARTER', dateLiteral.getValue()); } @isTest static void it_should_return_last_fiscal_quarter_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().LAST_FISCAL_QUARTER; + QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_FISCAL_QUARTER; System.assertEquals('LAST_FISCAL_QUARTER', dateLiteral.getValue()); } @isTest static void it_should_return_this_fiscal_quarter_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().THIS_FISCAL_QUARTER; + QueryDateLiteral dateLiteral = QueryDateLiteral.THIS_FISCAL_QUARTER; System.assertEquals('THIS_FISCAL_QUARTER', dateLiteral.getValue()); } @isTest static void it_should_return_next_quarter_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().NEXT_QUARTER; + QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_QUARTER; System.assertEquals('NEXT_QUARTER', dateLiteral.getValue()); } @isTest static void it_should_return_next_fiscal_quarter_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().NEXT_FISCAL_QUARTER; + QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_FISCAL_QUARTER; System.assertEquals('NEXT_FISCAL_QUARTER', dateLiteral.getValue()); } @isTest static void it_should_return_this_year_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().THIS_YEAR; + QueryDateLiteral dateLiteral = QueryDateLiteral.THIS_YEAR; System.assertEquals('THIS_YEAR', dateLiteral.getValue()); } @isTest static void it_should_return_this_fiscal_year_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().THIS_FISCAL_YEAR; + QueryDateLiteral dateLiteral = QueryDateLiteral.THIS_FISCAL_YEAR; System.assertEquals('THIS_FISCAL_YEAR', dateLiteral.getValue()); } @isTest static void it_should_return_last_year_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().LAST_YEAR; + QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_YEAR; System.assertEquals('LAST_YEAR', dateLiteral.getValue()); } @isTest static void it_should_return_last_fiscal_year_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().LAST_FISCAL_YEAR; + QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_FISCAL_YEAR; System.assertEquals('LAST_FISCAL_YEAR', dateLiteral.getValue()); } @isTest static void it_should_return_next_year_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().NEXT_YEAR; + QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_YEAR; System.assertEquals('NEXT_YEAR', dateLiteral.getValue()); } @isTest static void it_should_return_next_fiscal_year_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().NEXT_FISCAL_YEAR; + QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_FISCAL_YEAR; System.assertEquals('NEXT_FISCAL_YEAR', dateLiteral.getValue()); } + @isTest + static void it_should_return_n_days_ago_string() { + QueryDateLiteral dateLiteral = QueryDateLiteral.N_DAYS_AGO(n_number); + System.assertEquals('N_DAYS_AGO: ' + n_number, dateLiteral.getValue()); + } + @isTest static void it_should_return_last_n_days_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().LAST_N_DAYS(n_number); + QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_N_DAYS(n_number); System.assertEquals('LAST_N_DAYS: ' + n_number, dateLiteral.getValue()); } @isTest static void it_should_return_last_n_weeks_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().LAST_N_WEEKS(n_number); + QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_N_WEEKS(n_number); System.assertEquals('LAST_N_WEEKS: ' + n_number, dateLiteral.getValue()); } @isTest static void it_should_return_last_n_months_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().LAST_N_MONTHS(n_number); + QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_N_MONTHS(n_number); System.assertEquals('LAST_N_MONTHS: ' + n_number, dateLiteral.getValue()); } @isTest static void it_should_return_last_n_quarters_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().LAST_N_QUARTERS(n_number); + QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_N_QUARTERS(n_number); System.assertEquals('LAST_N_QUARTERS: ' + n_number, dateLiteral.getValue()); } @isTest static void it_should_return_last_n_years_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().LAST_N_YEARS(n_number); + QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_N_YEARS(n_number); System.assertEquals('LAST_N_YEARS: ' + n_number, dateLiteral.getValue()); } @isTest static void it_should_return_last_n_fiscal_years_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().LAST_N_FISCAL_YEARS(n_number); + QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_N_FISCAL_YEARS(n_number); System.assertEquals('LAST_N_FISCAL_YEARS: ' + n_number, dateLiteral.getValue()); } @isTest static void it_should_return_next_n_days_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().NEXT_N_DAYS(n_number); + QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_N_DAYS(n_number); System.assertEquals('NEXT_N_DAYS: ' + n_number, dateLiteral.getValue()); } @isTest static void it_should_return_next_n_weeks_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().NEXT_N_WEEKS(n_number); + QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_N_WEEKS(n_number); System.assertEquals('NEXT_N_WEEKS: ' + n_number, dateLiteral.getValue()); } @isTest static void it_should_return_next_n_months_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().NEXT_N_MONTHS(n_number); + QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_N_MONTHS(n_number); System.assertEquals('NEXT_N_MONTHS: ' + n_number, dateLiteral.getValue()); } @isTest static void it_should_return_next_n_quarters_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().NEXT_N_QUARTERS(n_number); + QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_N_QUARTERS(n_number); System.assertEquals('NEXT_N_QUARTERS: ' + n_number, dateLiteral.getValue()); } @isTest static void it_should_return_next_n_fiscal_quarters_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().NEXT_N_FISCAL_QUARTERS(n_number); + QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_N_FISCAL_QUARTERS(n_number); System.assertEquals('NEXT_N_FISCAL_QUARTERS: ' + n_number, dateLiteral.getValue()); } @isTest static void it_should_return_next_n_years_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().NEXT_N_YEARS(n_number); + QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_N_YEARS(n_number); System.assertEquals('NEXT_N_YEARS: ' + n_number, dateLiteral.getValue()); } @isTest static void it_should_return_next_n_fiscal_years_string() { - QueryDateLiteral dateLiteral = new QueryDateLiteral().NEXT_N_FISCAL_YEARS(n_number); + QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_N_FISCAL_YEARS(n_number); System.assertEquals('NEXT_N_FISCAL_YEARS: ' + n_number, dateLiteral.getValue()); } diff --git a/src/classes/QueryDateLiteral_Tests.cls-meta.xml b/src/classes/QueryDateLiteral_Tests.cls-meta.xml index cbddff8..8b061c8 100644 --- a/src/classes/QueryDateLiteral_Tests.cls-meta.xml +++ b/src/classes/QueryDateLiteral_Tests.cls-meta.xml @@ -1,5 +1,5 @@ - 38.0 + 39.0 Active diff --git a/src/classes/QueryFilter.cls b/src/classes/QueryFilter.cls new file mode 100644 index 0000000..d4f4393 --- /dev/null +++ b/src/classes/QueryFilter.cls @@ -0,0 +1,54 @@ +/************************************************************************************************* +* This file is part of the Nebula Framework project, released under the MIT License. * +* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * +*************************************************************************************************/ +public class QueryFilter implements IQueryFilter { + + private List parentRelationshipFields; + private Schema.SObjectField sobjectField; + private Schema.SObjectType sobjectType; + private QueryOperator operator; + private Object providedValue; + @testVisible private String queryFieldName; + + public QueryFilter(Schema.SObjectField fieldToFilter, QueryOperator operator, Object providedValue) { + this(new List(), fieldToFilter, operator, providedValue); + } + + public QueryFilter(Schema.SObjectField parentRelationshipField, Schema.SObjectField fieldToFilter, QueryOperator operator, Object providedValue) { + this(new List{parentRelationshipField}, fieldToFilter, operator, providedValue); + } + + public QueryFilter(List sortedParentRelationshipFields, Schema.SObjectField fieldToFilter, QueryOperator operator, Object providedValue) { + this.parentRelationshipFields = sortedParentRelationshipFields; + this.sobjectField = fieldToFilter; + this.sobjectType = new SObjectFieldDescriber(fieldToFilter).SObjectType; + this.operator = operator; + this.providedValue = providedValue; + + this.setQueryFieldName(); + } + + public Object getProvidedValue() { + return this.providedValue; + } + + public Schema.SObjectField getSObjectField() { + return this.sobjectField; + } + + public String getValue() { + return this.queryFieldName + ' ' + this.operator.getValue() + ' ' + new QueryArgumentFormatter(this.providedValue).getValue(); + } + + private void setQueryFieldName() { + this.queryFieldName = ''; + SObjectType currentSObjectType = this.SObjectType; + for(Schema.SObjectField parentRelationshipField : this.parentRelationshipFields) { + this.queryFieldName += parentRelationshipField.getDescribe().getRelationshipName() + '.'; + currentSObjectType = new SObjectFieldDescriber(parentRelationshipField).SObjectType; + } + this.queryFieldName += this.sobjectField; + } + +} \ No newline at end of file diff --git a/src/classes/QueryFilter.cls-meta.xml b/src/classes/QueryFilter.cls-meta.xml new file mode 100644 index 0000000..8b061c8 --- /dev/null +++ b/src/classes/QueryFilter.cls-meta.xml @@ -0,0 +1,5 @@ + + + 39.0 + Active + diff --git a/src/classes/QueryFilterScope.cls-meta.xml b/src/classes/QueryFilterScope.cls-meta.xml index cbddff8..8b061c8 100644 --- a/src/classes/QueryFilterScope.cls-meta.xml +++ b/src/classes/QueryFilterScope.cls-meta.xml @@ -1,5 +1,5 @@ - 38.0 + 39.0 Active diff --git a/src/classes/QueryFilter_Tests.cls b/src/classes/QueryFilter_Tests.cls new file mode 100644 index 0000000..59b5e9d --- /dev/null +++ b/src/classes/QueryFilter_Tests.cls @@ -0,0 +1,64 @@ +/************************************************************************************************* +* This file is part of the Nebula Framework project, released under the MIT License. * +* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * +*************************************************************************************************/ +@isTest +private class QueryFilter_Tests { + + @isTest + static void it_should_return_the_query_filter_for_a_field() { + Schema.SObjectField sobjectField = Schema.User.CompanyName; + QueryOperator operator = QueryOperator.IS_IN; + List values = new List{'derp'}; + + Test.startTest(); + QueryFilter queryFilter = new QueryFilter(sobjectField, operator, values); + Test.stopTest(); + + String expectedQueryFieldName = 'CompanyName'; + System.assertEquals(expectedQueryFieldName, queryFilter.queryFieldName); + + String expectedQueryFilter = 'CompanyName ' + operator.getValue() + ' (\'' + String.join(values, '\',\'') + '\')'; + System.assertEquals(expectedQueryFilter, queryFilter.getValue()); + } + + @isTest + static void it_should_return_the_query_filter_for_a_parent_field() { + Schema.SObjectField parentRelationshipSObjectField = Schema.Lead.CreatedById; + Schema.SObjectField sobjectField = Schema.User.Email; + QueryOperator operator = QueryOperator.EQUALS; + String value = 'derp@test.com'; + + Test.startTest(); + QueryFilter queryFilter = new QueryFilter(parentRelationshipSObjectField, sobjectField, operator, value); + Test.stopTest(); + + String expectedQueryFieldName = 'CreatedBy.Email'; + System.assertEquals(expectedQueryFieldName, queryFilter.queryFieldName); + + String expectedQueryFilter = 'CreatedBy.Email ' + operator.getValue() + ' \'' + value + '\''; + System.assertEquals(expectedQueryFilter, queryFilter.getValue()); + } + + @isTest + static void it_should_return_the_query_filter_for_a_grandparent_field() { + List grandparentFields = new List{ + Schema.Lead.OwnerId, Schema.User.ManagerId, Schema.User.ProfileId + }; + + Schema.SObjectField sobjectField = Schema.Profile.Name; + QueryOperator operator = QueryOperator.EQUALS; + String value = 'derp'; + + Test.startTest(); + QueryFilter queryFilter = new QueryFilter(grandparentFields, sobjectField, operator, value); + Test.stopTest(); + + String expectedQueryFieldName = 'Owner.Manager.Profile.Name'; + System.assertEquals(expectedQueryFieldName, queryFilter.queryFieldName); + + String expectedQueryFilter = 'Owner.Manager.Profile.Name ' + operator.getValue() + ' \'' + value + '\''; + System.assertEquals(expectedQueryFilter, queryFilter.getValue()); + } + +} \ No newline at end of file diff --git a/src/classes/QueryFilter_Tests.cls-meta.xml b/src/classes/QueryFilter_Tests.cls-meta.xml new file mode 100644 index 0000000..8b061c8 --- /dev/null +++ b/src/classes/QueryFilter_Tests.cls-meta.xml @@ -0,0 +1,5 @@ + + + 39.0 + Active + diff --git a/src/classes/QueryNullSortOrder.cls-meta.xml b/src/classes/QueryNullSortOrder.cls-meta.xml index cbddff8..8b061c8 100644 --- a/src/classes/QueryNullSortOrder.cls-meta.xml +++ b/src/classes/QueryNullSortOrder.cls-meta.xml @@ -1,5 +1,5 @@ - 38.0 + 39.0 Active diff --git a/src/classes/QueryOperator.cls b/src/classes/QueryOperator.cls index efc658f..12e5101 100644 --- a/src/classes/QueryOperator.cls +++ b/src/classes/QueryOperator.cls @@ -6,62 +6,60 @@ public without sharing class QueryOperator { private String value; - public String getValue() { - return this.value; + private QueryOperator(String value) { + this.value = value; } - //Actual constant literals - public QueryOperator EQUALS { - get {return this.setValue('=');} + public String getValue() { + return this.value; } - public QueryOperator NOT_EQUAL_TO { - get {return this.setValue('!=');} + public static QueryOperator EQUALS { + get {return new QueryOperator('=');} } - public QueryOperator GREATER_THAN { - get {return this.setValue('>');} + public static QueryOperator NOT_EQUAL_TO { + get {return new QueryOperator('!=');} } - public QueryOperator GREATER_THAN_OR_EQUAL_TO { - get {return this.setValue('>=');} + public static QueryOperator GREATER_THAN { + get {return new QueryOperator('>');} } - public QueryOperator LESS_THAN { - get {return this.setValue('<');} + public static QueryOperator GREATER_THAN_OR_EQUAL_TO { + get {return new QueryOperator('>=');} } - public QueryOperator LESS_THAN_OR_EQUAL_TO { - get {return this.setValue('<=');} + public static QueryOperator LESS_THAN { + get {return new QueryOperator('<');} } - public QueryOperator IS_IN { - get {return this.setValue('IN');} + public static QueryOperator LESS_THAN_OR_EQUAL_TO { + get {return new QueryOperator('<=');} } - public QueryOperator IS_NOT_IN { - get {return this.setValue('NOT IN');} + public static QueryOperator IS_IN { + get {return new QueryOperator('IN');} } - public QueryOperator INCLUDES { - get {return this.setValue('INCLUDES');} + public static QueryOperator IS_NOT_IN { + get {return new QueryOperator('NOT IN');} } - public QueryOperator EXCLUDES { - get {return this.setValue('EXCLUDES');} + public static QueryOperator INCLUDES { + get {return new QueryOperator('INCLUDES');} } - public QueryOperator IS_LIKE { - get {return this.setValue('LIKE');} + public static QueryOperator EXCLUDES { + get {return new QueryOperator('EXCLUDES');} } - public QueryOperator IS_NOT_LIKE { - get {return this.setValue('NOT LIKE');} + public static QueryOperator IS_LIKE { + get {return new QueryOperator('LIKE');} } - private QueryOperator setValue(String value) { - this.value = value; - return this; + public static QueryOperator IS_NOT_LIKE { + get {return new QueryOperator('NOT LIKE');} } } \ No newline at end of file diff --git a/src/classes/QueryOperator.cls-meta.xml b/src/classes/QueryOperator.cls-meta.xml index cbddff8..8b061c8 100644 --- a/src/classes/QueryOperator.cls-meta.xml +++ b/src/classes/QueryOperator.cls-meta.xml @@ -1,5 +1,5 @@ - 38.0 + 39.0 Active diff --git a/src/classes/QueryOperator_Tests.cls b/src/classes/QueryOperator_Tests.cls index 00f8a75..1645beb 100644 --- a/src/classes/QueryOperator_Tests.cls +++ b/src/classes/QueryOperator_Tests.cls @@ -7,62 +7,62 @@ private class QueryOperator_Tests { @isTest static void it_should_return_EQUALS_string() { - System.assertEquals('=', new QueryOperator().EQUALS.getValue()); + System.assertEquals('=', QueryOperator.EQUALS.getValue()); } @isTest static void it_should_return_NOT_EQUAL_TO_string() { - System.assertEquals('!=', new QueryOperator().NOT_EQUAL_TO.getValue()); + System.assertEquals('!=', QueryOperator.NOT_EQUAL_TO.getValue()); } @isTest static void it_should_return_GREATER_THAN_string() { - System.assertEquals('>', new QueryOperator().GREATER_THAN.getValue()); + System.assertEquals('>', QueryOperator.GREATER_THAN.getValue()); } @isTest static void it_should_return_GREATER_THAN_OR_EQUAL_TO_string() { - System.assertEquals('>=', new QueryOperator().GREATER_THAN_OR_EQUAL_TO.getValue()); + System.assertEquals('>=', QueryOperator.GREATER_THAN_OR_EQUAL_TO.getValue()); } @isTest static void it_should_return_LESS_THAN_string() { - System.assertEquals('<', new QueryOperator().LESS_THAN.getValue()); + System.assertEquals('<', QueryOperator.LESS_THAN.getValue()); } @isTest static void it_should_return_LESS_THAN_OR_EQUAL_TO_string() { - System.assertEquals('<=', new QueryOperator().LESS_THAN_OR_EQUAL_TO.getValue()); + System.assertEquals('<=', QueryOperator.LESS_THAN_OR_EQUAL_TO.getValue()); } @isTest static void it_should_return_IS_IN_string() { - System.assertEquals('IN', new QueryOperator().IS_IN.getValue()); + System.assertEquals('IN', QueryOperator.IS_IN.getValue()); } @isTest static void it_should_return_IS_NOT_IN_string() { - System.assertEquals('NOT IN', new QueryOperator().IS_NOT_IN.getValue()); + System.assertEquals('NOT IN', QueryOperator.IS_NOT_IN.getValue()); } @isTest static void it_should_return_INCLUDES_string() { - System.assertEquals('INCLUDES', new QueryOperator().INCLUDES.getValue()); + System.assertEquals('INCLUDES', QueryOperator.INCLUDES.getValue()); } @isTest static void it_should_return_EXCLUDES_string() { - System.assertEquals('EXCLUDES', new QueryOperator().EXCLUDES.getValue()); + System.assertEquals('EXCLUDES', QueryOperator.EXCLUDES.getValue()); } @isTest static void it_should_return_IS_LIKE_string() { - System.assertEquals('LIKE', new QueryOperator().IS_LIKE.getValue()); + System.assertEquals('LIKE', QueryOperator.IS_LIKE.getValue()); } @isTest static void it_should_return_IS_NOT_LIKE_string() { - System.assertEquals('NOT LIKE', new QueryOperator().IS_NOT_LIKE.getValue()); + System.assertEquals('NOT LIKE', QueryOperator.IS_NOT_LIKE.getValue()); } } \ No newline at end of file diff --git a/src/classes/QueryOperator_Tests.cls-meta.xml b/src/classes/QueryOperator_Tests.cls-meta.xml index cbddff8..8b061c8 100644 --- a/src/classes/QueryOperator_Tests.cls-meta.xml +++ b/src/classes/QueryOperator_Tests.cls-meta.xml @@ -1,5 +1,5 @@ - 38.0 + 39.0 Active diff --git a/src/classes/QuerySearchGroup.cls-meta.xml b/src/classes/QuerySearchGroup.cls-meta.xml index cbddff8..8b061c8 100644 --- a/src/classes/QuerySearchGroup.cls-meta.xml +++ b/src/classes/QuerySearchGroup.cls-meta.xml @@ -1,5 +1,5 @@ - 38.0 + 39.0 Active diff --git a/src/classes/QuerySortOrder.cls-meta.xml b/src/classes/QuerySortOrder.cls-meta.xml index cbddff8..8b061c8 100644 --- a/src/classes/QuerySortOrder.cls-meta.xml +++ b/src/classes/QuerySortOrder.cls-meta.xml @@ -1,5 +1,5 @@ - 38.0 + 39.0 Active diff --git a/src/classes/SObjectFieldDescriber.cls b/src/classes/SObjectFieldDescriber.cls new file mode 100644 index 0000000..d7b6e66 --- /dev/null +++ b/src/classes/SObjectFieldDescriber.cls @@ -0,0 +1,72 @@ +/************************************************************************************************* +* This file is part of the Nebula Framework project, released under the MIT License. * +* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * +*************************************************************************************************/ +public without sharing class SObjectFieldDescriber { + // We would love for Schema.SObjectField to have a way to return the SObject Type + // Sadly, it doesn't, so we have this class to fill the void. + // If a proper method is ever added to Apex to get the field's SObject Type, + // then we should consider removing this class. + + private static Map sobjectFieldHashCodeToSObjectTypeMap; + + public Schema.SObjectField SObjectField {get; private set;} + public Schema.SObjectType SObjectType {get; private set;} + + public SObjectFieldDescriber(Schema.SObjectField sobjectField) { + this.cacheSObjectTypeFieldHashCodes(); + + this.SObjectField = sobjectField; + this.setSObjectType(); + } + + public String getFieldName() { + return this.SObjectField.getDescribe().getName(); + } + + public String getFullFieldName() { + return this.SObjectType + '.' + this.getFieldName(); + } + + public Schema.SObjectType getParentSObjectType() { + Schema.DescribeFieldResult fieldDescribe = this.SObjectField.getDescribe(); + + Schema.SObjectType parentSObjectType; + if(!fieldDescribe.isNamePointing() && !fieldDescribe.getReferenceTo().isEmpty()) parentSObjectType = fieldDescribe.getReferenceTo()[0]; + + return parentSObjectType; + } + + public Boolean validateSObjectType(Schema.SObjectType expectedSObjectType) { + return this.SObjectType == expectedSObjectType; + } + + private void setSObjectType() { + Integer sobjectFieldHashCode = this.getHashCode(this.sobjectField); + this.SObjectType = sobjectFieldHashCodeToSObjectTypeMap.get(sobjectFieldHashCode); + } + + private void cacheSObjectTypeFieldHashCodes() { + // Describe calls are "free" but still add CPU time, so let's cache them + if(sobjectFieldHashCodeToSObjectTypeMap != null) return; + + sobjectFieldHashCodeToSObjectTypeMap = new Map(); + + // Build a map of hash codes for each fieldDescribe taken from Schema Global Describe + Map globalDescribe = Schema.getGlobalDescribe(); + for(String sobjTypeName : globalDescribe.keySet()) { + SObjectType sobjType = globalDescribe.get(sobjTypeName); + + for(Schema.SObjectField sobjField : sobjType.getDescribe().fields.getMap().values()) { + Integer sobjFieldHashCode = getHashCode(sobjField); + + sobjectFieldHashCodeToSObjectTypeMap.put(sobjFieldHashCode, sobjType); + } + } + } + + private Integer getHashCode(Schema.SObjectField sobjField) { + return ((Object)sobjField).hashCode(); + } + +} \ No newline at end of file diff --git a/src/classes/SObjectFieldDescriber.cls-meta.xml b/src/classes/SObjectFieldDescriber.cls-meta.xml new file mode 100644 index 0000000..8b061c8 --- /dev/null +++ b/src/classes/SObjectFieldDescriber.cls-meta.xml @@ -0,0 +1,5 @@ + + + 39.0 + Active + diff --git a/src/classes/SObjectFieldDescriber_Tests.cls b/src/classes/SObjectFieldDescriber_Tests.cls new file mode 100644 index 0000000..d8f47df --- /dev/null +++ b/src/classes/SObjectFieldDescriber_Tests.cls @@ -0,0 +1,77 @@ +/************************************************************************************************* +* This file is part of the Nebula Framework project, released under the MIT License. * +* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * +*************************************************************************************************/ +@isTest +private class SObjectFieldDescriber_Tests { + + @isTest + static void it_should_return_the_sobject_type() { + Schema.SObjectType expectedSObjectType = Schema.Lead.SObjectType; + Schema.SObjectField sobjectField = Schema.Lead.Id; + + Test.startTest(); + + SObjectFieldDescriber sobjectFieldDescriber = new SObjectFieldDescriber(sobjectField); + System.assertEquals(expectedSObjectType, sobjectFieldDescriber.SObjectType); + + Test.stopTest(); + } + + @isTest + static void it_should_return_the_sobject_field() { + Schema.SObjectField expectedSObjectField = Schema.Lead.Id; + Integer expectedSObjectFieldHash = ((Object)expectedSObjectField).hashCode(); + + Test.startTest(); + + SObjectFieldDescriber sobjectFieldDescriber = new SObjectFieldDescriber(expectedSObjectField); + Schema.SObjectField returnedSObjectField = sobjectFieldDescriber.sobjectField; + Integer returnedSObjectFieldHash = ((Object)returnedSObjectField).hashCode(); + + System.assertEquals(expectedSObjectFieldHash, returnedSObjectFieldHash); + + Test.stopTest(); + } + + @isTest + static void it_should_validate_the_expected_sobject_type() { + Schema.SObjectType expectedSObjectType = Schema.Lead.SObjectType; + Schema.SObjectField sobjectField = Schema.Lead.Id; + + Test.startTest(); + + SObjectFieldDescriber sobjectFieldDescriber = new SObjectFieldDescriber(sobjectField); + System.assert(sobjectFieldDescriber.validateSObjectType(expectedSObjectType)); + + Test.stopTest(); + } + + @isTest + static void it_should_return_the_full_field_name() { + Schema.SObjectType expectedSObjectType = Schema.Lead.SObjectType; + Schema.SObjectField sobjectField = Schema.Lead.Id; + String expectedFullFieldName = String.valueOf(expectedSObjectType) + '.' + String.valueOf(sobjectField); + + Test.startTest(); + + SObjectFieldDescriber sobjectFieldDescriber = new SObjectFieldDescriber(sobjectField); + System.assertEquals(expectedFullFieldName, sobjectFieldDescriber.getFullFieldName()); + + Test.stopTest(); + } + + @isTest + static void it_should_return_the_parent_field_sobject_type() { + Schema.SObjectField sobjectField = Schema.Lead.ConvertedAccountId; + Schema.SObjectType expectedParentSObjectType = Schema.Account.SObjectType; + + Test.startTest(); + + SObjectFieldDescriber sobjectFieldDescriber = new SObjectFieldDescriber(sobjectField); + System.assertEquals(expectedParentSObjectType, sobjectFieldDescriber.getParentSObjectType()); + + Test.stopTest(); + } + +} \ No newline at end of file diff --git a/src/classes/SObjectFieldDescriber_Tests.cls-meta.xml b/src/classes/SObjectFieldDescriber_Tests.cls-meta.xml new file mode 100644 index 0000000..8b061c8 --- /dev/null +++ b/src/classes/SObjectFieldDescriber_Tests.cls-meta.xml @@ -0,0 +1,5 @@ + + + 39.0 + Active + diff --git a/src/classes/SObjectRecordTypes.cls b/src/classes/SObjectRecordTypes.cls index 5ee84d2..cfbe80c 100644 --- a/src/classes/SObjectRecordTypes.cls +++ b/src/classes/SObjectRecordTypes.cls @@ -9,12 +9,14 @@ public abstract class SObjectRecordTypes extends NebulaCore { public final Map ALL_RECORD_TYPES_BY_ID; public final Map ALL_RECORD_TYPES_BY_DEVELOPER_NAME; + private Schema.SObjectType sobjectType; private String sobjectName; - public SObjectRecordTypes(Schema.DescribeSObjectResult describeSObjectResult) { + public SObjectRecordTypes(Schema.SObjectType sobjectType) { this.currentModule = NebulaCore.Module.RECORD_TYPES; - this.sobjectName = describeSObjectResult.getName(); + this.sobjectType = sobjectType; + this.sobjectName = this.sobjectType.getDescribe().getName(); Logger.addEntry(this, 'Getting record types for ' + this.sobjectName); @@ -29,27 +31,29 @@ public abstract class SObjectRecordTypes extends NebulaCore { private void populateCache() { if(cachedRecordTypesBySObjectMap.containsKey(this.sobjectName)) return; - // If we don't have the SObject cached, then we need to query - List fieldList = new List(Schema.SObjectType.RecordType.fields.getMap().keySet()); - - String query = 'SELECT ' + String.join(fieldList, ', ') + ' FROM RecordType'; + Schema.SObjectType recordTypeSObjectType = Schema.getGlobalDescribe().get('RecordType'); + Schema.FieldSet nullFieldSet; + List nullSObjectFieldList; - List whereClauseList = new List(); + // If we don't have the SObject cached, then we need to query + QueryBuilder query = new QueryBuilder(recordTypeSObjectType, nullFieldSet, nullSObjectFieldList); - if(NebulaSettings.recordTypesSettings.LazyLoad__c){ - whereClauseList.add('SObjectType = \'' + this.sobjectName + '\''); - Logger.addEntry(this, 'NebulaSettings.recordTypesSettings.LazyLoad__c=' + NebulaSettings.recordTypesSettings.LazyLoad__c); + if(NebulaSettings.RecordTypesSettings.LazyLoad__c) { + query.filterBy(new QueryFilter(Schema.RecordType.SObjectType, QueryOperator.EQUALS, this.sobjectName)); + Logger.addEntry(this, 'NebulaSettings.RecordTypesSettings.LazyLoad__c=' + NebulaSettings.RecordTypesSettings.LazyLoad__c); Logger.addEntry(this, 'this.sobjectName=' + this.sobjectName); } - if(NebulaSettings.recordTypesSettings.ExcludeManagedRecordTypes__c) whereClauseList.add('NamespacePrefix = null'); - if(!whereClauseList.isEmpty()) query += ' WHERE ' + String.join(whereClauseList, ' AND '); - query += ' ORDER BY DeveloperName'; + if(NebulaSettings.RecordTypesSettings.ExcludeManagedRecordTypes__c) { + query.filterBy(new QueryFilter(Schema.RecordType.NamespacePrefix, QueryOperator.EQUALS, null)); + } + + query.orderBy(Schema.RecordType.DeveloperName); - Logger.addEntry(this, 'SObjectRecordTypes query=' + query); + Logger.addEntry(this, 'SObjectRecordTypes query=' + query.getQuery()); if(!cachedRecordTypesBySObjectMap.containsKey(this.sobjectName)) cachedRecordTypesBySObjectMap.put(this.sobjectName, new List()); - List recordTypeList = (List)Database.query(query); + List recordTypeList = (List)query.getQueryResults(); for(RecordType recordType : recordTypeList) cachedRecordTypesBySObjectMap.get(this.sobjectName).add(recordType); Logger.addEntry(this, 'cachedRecordTypesBySObjectMap=' + cachedRecordTypesBySObjectMap); @@ -70,6 +74,4 @@ public abstract class SObjectRecordTypes extends NebulaCore { return allRecordTypesByDeveloperName; } - private class RecordTypeException extends Exception {} - } \ No newline at end of file diff --git a/src/classes/SObjectRecordTypes.cls-meta.xml b/src/classes/SObjectRecordTypes.cls-meta.xml index cbddff8..8b061c8 100644 --- a/src/classes/SObjectRecordTypes.cls-meta.xml +++ b/src/classes/SObjectRecordTypes.cls-meta.xml @@ -1,5 +1,5 @@ - 38.0 + 39.0 Active diff --git a/src/classes/SObjectRecordTypes_Tests.cls b/src/classes/SObjectRecordTypes_Tests.cls index b45d92c..a077f60 100644 --- a/src/classes/SObjectRecordTypes_Tests.cls +++ b/src/classes/SObjectRecordTypes_Tests.cls @@ -8,12 +8,12 @@ private class SObjectRecordTypes_Tests { private class LeadRecordTypes extends SObjectRecordTypes { // Test subclass that extends SObjectRecordTypes public LeadRecordTypes() { - super(Schema.SObjectType.Lead); + super(Schema.Lead.SObjectType); } } @isTest - static void allRecordTypesById() { + static void it_should_return_a_map_of_all_record_types_by_id() { List expectedRecordTypeList = [SELECT Id, DeveloperName FROM RecordType WHERE SObjectType = 'Lead']; Test.startTest(); @@ -27,7 +27,7 @@ private class SObjectRecordTypes_Tests { } @isTest - static void allRecordTypesByDeveloperName() { + static void it_should_return_a_map_of_all_record_types_by_developer_name() { List expectedRecordTypeList = [SELECT Id, DeveloperName FROM RecordType WHERE SObjectType = 'Lead']; Test.startTest(); @@ -40,8 +40,30 @@ private class SObjectRecordTypes_Tests { Test.stopTest(); } + + @isTest + static void it_should_return_a_map_of_all_record_types_by_id_excluding_managed_record_types() { + List expectedRecordTypeList = [SELECT Id, DeveloperName, NamespacePrefix FROM RecordType WHERE SObjectType = 'Lead' AND NamespacePrefix = null]; + + NebulaRecordTypesSettings__c recordTypesSettings = NebulaRecordTypesSettings__c.getInstance(); + recordTypesSettings.ExcludeManagedRecordTypes__c = true; + upsert recordTypesSettings; + + Test.startTest(); + + System.assertEquals(expectedRecordTypeList.size(), new SObjectRecordTypes_Tests.LeadRecordTypes().ALL_RECORD_TYPES_BY_ID.size()); + for(RecordType recordType : expectedRecordTypeList) { + System.assert(new SObjectRecordTypes_Tests.LeadRecordTypes().ALL_RECORD_TYPES_BY_ID.containsKey(recordType.Id)); + } + for(RecordType recordType : new SObjectRecordTypes_Tests.LeadRecordTypes().ALL_RECORD_TYPES_BY_ID.values()) { + System.assertEquals(null, recordType.NamespacePrefix); + } + + Test.stopTest(); + } + @isTest - static void queryIsCached_when_lazyLoadEnabled() { + static void it_should_cache_the_query_results_when_lazy_load_is_enabled() { NebulaRecordTypesSettings__c recordTypesSettings = NebulaRecordTypesSettings__c.getInstance(); recordTypesSettings.LazyLoad__c = true; upsert recordTypesSettings; @@ -59,7 +81,7 @@ private class SObjectRecordTypes_Tests { } @isTest - static void queryIsCached_when_lazyLoadDisabled() { + static void it_should_cache_the_query_results_when_lazy_load_is_disabled() { NebulaRecordTypesSettings__c recordTypesSettings = NebulaRecordTypesSettings__c.getInstance(); recordTypesSettings.LazyLoad__c = false; upsert recordTypesSettings; diff --git a/src/classes/SObjectRecordTypes_Tests.cls-meta.xml b/src/classes/SObjectRecordTypes_Tests.cls-meta.xml index cbddff8..8b061c8 100644 --- a/src/classes/SObjectRecordTypes_Tests.cls-meta.xml +++ b/src/classes/SObjectRecordTypes_Tests.cls-meta.xml @@ -1,5 +1,5 @@ - 38.0 + 39.0 Active diff --git a/src/classes/SObjectRepository.cls b/src/classes/SObjectRepository.cls index eb20b4f..c4f9202 100644 --- a/src/classes/SObjectRepository.cls +++ b/src/classes/SObjectRepository.cls @@ -2,40 +2,97 @@ * This file is part of the Nebula Framework project, released under the MIT License. * * See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * *************************************************************************************************/ -public abstract class SObjectRepository extends NebulaCore implements ISObjectRepository { +public virtual class SObjectRepository extends DML implements ISObjectRepository, IDML { - protected IQueryBuilder queryBuilder; + protected IQueryBuilder Query { + get { return new QueryBuilder(this.sobjectType, this.fieldSet, this.sobjectFieldList); } + } + + private final Schema.SObjectType sobjectType; + private final Schema.FieldSet fieldSet; + private final List sobjectFieldList; + private final Map sobjectFieldMap; + private final Schema.SObjectField idField; + + public SObjectRepository(Schema.SObjectType sobjectType) { + this(sobjectType, null, null); + } - protected SObjectRepository(Schema.SObjectType sobjectType, Schema.FieldSet fieldSet) { + public SObjectRepository(Schema.SObjectType sobjectType, Schema.FieldSet fieldSet) { this(sobjectType, fieldSet, null); } - protected SObjectRepository(Schema.SObjectType sobjectType, List sobjectFieldList) { + public SObjectRepository(Schema.SObjectType sobjectType, List sobjectFieldList) { this(sobjectType, null, sobjectFieldList); } private SObjectRepository(Schema.SObjectType sobjectType, Schema.FieldSet fieldSet, List sobjectFieldList) { + super(sobjectType); + this.currentModule = NebulaCore.Module.REPOSITORY; - this.queryBuilder = new QueryBuilder(sobjectType, fieldSet, sobjectFieldList); + this.sobjectType = sobjectType; + this.fieldSet = fieldSet; + this.sobjectFieldList = sobjectFieldList; + this.sobjectFieldMap = sobjectType.getDescribe().fields.getMap(); + this.idField = this.getField('Id'); + } + + // SOQL + public virtual SObject getById(Id recordId) { + return this.Query + .filterBy(new QueryFilter(this.idField, QueryOperator.EQUALS, recordId)) + .getFirstQueryResult(); + } + + public virtual List getById(List recordIds) { + return this.Query + .filterBy(new QueryFilter(this.idField, QueryOperator.IS_IN, recordIds)) + .getQueryResults(); + } + + public virtual List get(IQueryFilter queryFilter) { + return this.get(new List{queryFilter}); + } + + public virtual List get(List queryFilters) { + return this.Query + .filterBy(queryFilters) + .getQueryResults(); + } + + public virtual List getByIdAndQueryFilters(Set idSet, List queryFilters) { + return this.getByIdAndQueryFilters(new List(idSet), queryFilters); + } + + public virtual List getByIdAndQueryFilters(List idList, List queryFilters) { + return this.Query + .filterBy(new QueryFilter(this.idField, QueryOperator.IS_IN, idList)) + .filterBy(queryFilters) + .getQueryResults(); + } + + // SOSL + public virtual List getSearchResults(String searchTerm) { + return this.getSearchResults(searchTerm, QuerySearchGroup.ALL_FIELDS, null); + } + + public virtual List getSearchResults(String searchTerm, QuerySearchGroup searchGroup) { + return this.getSearchResults(searchTerm, searchGroup, null); } - public virtual void doInsert(SObject record) {this.doInsert(new List{record});} - public virtual void doInsert(List recordList) {Database.insert(recordList);} - public virtual void doUpdate(SObject record) {this.doUpdate(new List{record});} - public virtual void doUpdate(List recordList) {Database.update(recordList);} - // Apex doesn't allow upserting on generic SObject, so we'll delegate the implementation to each concrete class - public abstract void doUpsert(SObject record); - public abstract void doUpsert(List recordList); + public virtual List getSearchResults(String searchTerm, QuerySearchGroup searchGroup, List queryFilters) { + return this.Query + .filterBy(queryFilters) + .getSearchResults(searchTerm, searchGroup); + } + + public virtual IQueryBuilder getQueryBuilder() { + return this.Query; + } - public virtual void doDelete(SObject record) {this.doDelete(new List{record});} - public virtual void doDelete(List recordList) {Database.delete(recordList);} - public virtual void doHardDelete(SObject record) {this.doHardDelete(new List{record});} - public virtual void doHardDelete(List recordList) { - Database.delete(recordList); - if(!recordList.isEmpty()) Database.emptyRecycleBin(recordList); + private Schema.SObjectField getField(String fieldName) { + return this.sobjectFieldMap.get(fieldName); } - public virtual void doUndelete(SObject record) {this.doUndelete(new List{record});} - public virtual void doUndelete(List recordList) {Database.undelete(recordList);} } \ No newline at end of file diff --git a/src/classes/SObjectRepository.cls-meta.xml b/src/classes/SObjectRepository.cls-meta.xml index cbddff8..8b061c8 100644 --- a/src/classes/SObjectRepository.cls-meta.xml +++ b/src/classes/SObjectRepository.cls-meta.xml @@ -1,5 +1,5 @@ - 38.0 + 39.0 Active diff --git a/src/classes/SObjectRepositoryMocks.cls b/src/classes/SObjectRepositoryMocks.cls new file mode 100644 index 0000000..0b48984 --- /dev/null +++ b/src/classes/SObjectRepositoryMocks.cls @@ -0,0 +1,122 @@ +/************************************************************************************************* +* This file is part of the Nebula Framework project, released under the MIT License. * +* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * +*************************************************************************************************/ +@isTest +public class SObjectRepositoryMocks { + + public virtual class Base extends DMLMock.Base implements ISObjectRepository { + + protected List records; + protected List recordIdList; + protected List queryFilters; + + public Base() { + this.records = new List(); + } + + public Base(List records) { + this.records = records; + } + + public Base with(List records) { + this.records = records; + return this; + } + + public virtual ISObjectRepository filterBy(IQueryFilter queryFilter) { + return this.filterBy(new List{queryFilter}); + } + + public virtual ISObjectRepository filterBy(List queryFilters) { + this.queryFilters = queryFilters; + return this; + } + + // SOQL + public virtual SObject getById(Id recordId) { + return this.getById(new List{recordId})[0]; + } + + public virtual List getById(List recordIdList) { + this.recordIdList = recordIdList; + + if(this.records == null) this.createMockObjects(); + + return this.records; + } + + public virtual List get(List queryFilters) { + this.queryFilters = queryFilters; + return this.records; + } + + public List get(IQueryFilter queryFilter) { + return this.get(new List{queryFilter}); + } + + public List get(IQueryFilter queryFilterOne, IQueryFilter queryFilterTwo) { + return this.get(new List{queryFilterOne, queryFilterTwo}); + } + + public List get(IQueryFilter queryFilterOne, IQueryFilter queryFilterTwo, IQueryFilter queryFilterThree) { + return this.get(new List{queryFilterOne, queryFilterTwo, queryFilterThree}); + } + + public virtual List getQueryFilters() { + return queryFilters; + } + + public virtual List getByIdAndQueryFilters(Set idList, List queryFilters) { + return this.getByIdAndQueryFilters(new List(idList), queryFilters); + } + + public virtual List getByIdAndQueryFilters(List idList, List queryFilters) { + List records = this.getById(idList); + for(SObject record : records) { + for(IQueryFilter queryFilter : queryFilters) { + record.put(queryFilter.getSObjectField(), queryFilter.getProvidedValue()); + } + } + + return records; + } + + // SOSL + public virtual List getSearchResults(String searchTerm) { + return this.returnListOfSObjects(); + } + + public virtual List getSearchResults(String searchTerm, QuerySearchGroup searchGroup) { + return this.returnListOfSObjects(); + } + + public virtual List getSearchResults(String searchTerm, QuerySearchGroup searchGroup, List queryFilters) { + return this.returnListOfSObjects(); + } + + public virtual IQueryBuilder getQueryBuilder() { + return new QueryBuilder(this.records.getSObjectType(), null, null); + } + + private void createMockObjects() { + // We would expect that for the Ids passed in, there will be a corresponding number of records returned of the exact same + // SObjectType as their Ids. + this.records = new List(); + for(Id recordId : this.recordIdList) { + SObjectType objType = recordId.getSObjectType(); + SObject record = (SObject)Type.forName(String.valueOf(objType)).newInstance(); + record.put('Id', recordId); + + this.records.add(record); + } + } + + private List returnListOfSObjects() { + if(this.records == null) return new List(); + + return this.records; + } + } + +} \ No newline at end of file diff --git a/src/classes/SObjectRepositoryMocks.cls-meta.xml b/src/classes/SObjectRepositoryMocks.cls-meta.xml new file mode 100644 index 0000000..8b061c8 --- /dev/null +++ b/src/classes/SObjectRepositoryMocks.cls-meta.xml @@ -0,0 +1,5 @@ + + + 39.0 + Active + diff --git a/src/classes/SObjectRepositoryMocks_Tests.cls b/src/classes/SObjectRepositoryMocks_Tests.cls new file mode 100644 index 0000000..2a0c1ac --- /dev/null +++ b/src/classes/SObjectRepositoryMocks_Tests.cls @@ -0,0 +1,53 @@ +/************************************************************************************************* +* This file is part of the Nebula Framework project, released under the MIT License. * +* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * +*************************************************************************************************/ +@isTest +private class SObjectRepositoryMocks_Tests { + + @isTest + static void it_should_fake_returning_by_id() { + Id testId = getTestId(); + + SObject returnedObj = new SObjectRepositoryMocks.Base(null).getById(testId); + + System.assert(returnedObj.Id == testId); + } + + @isTest + static void it_should_fake_returning_by_field_and_value() { + Id testId = getTestId(); + SObjectField field = getField(); + + List queryFilters = new List{new QueryFilter(field, QueryOperator.EQUALS, getFieldValue())}; + SObject returnedObj = new SObjectRepositoryMocks.Base(null).getByIdAndQueryFilters(new Set{testId}, queryFilters)[0]; + + System.assertEquals(getFieldValue(), returnedObj.get(String.valueOf(field))); + } + + @isTest + static void it_should_return_list_of_sobjects_when_mocking_sosl_search() { + System.assert(new SObjectRepositoryMocks.Base().getSearchResults(getFieldValue(),QuerySearchGroup.ALL_FIELDS) instanceof List); + } + + @isTest + static void it_should_return_list_of_sobjects_when_mocking_sosl_search_with_passed_objects() { + Contact con = new Contact(); + ISObjectRepository base = new SObjectRepositoryMocks.Base().with(new List{con}); + + System.assert(base.getSearchResults(getFieldValue(),QuerySearchGroup.ALL_FIELDS) instanceof List); + } + + static SObjectField getField() { + return Schema.Lead.LeadSource; + } + + static String getFieldValue() { + return 'Web'; + } + + static Id getTestId() { + return TestingUtils.generateId(Schema.Lead.SObjectType); + } + +} \ No newline at end of file diff --git a/src/classes/SObjectRepositoryMocks_Tests.cls-meta.xml b/src/classes/SObjectRepositoryMocks_Tests.cls-meta.xml new file mode 100644 index 0000000..8b061c8 --- /dev/null +++ b/src/classes/SObjectRepositoryMocks_Tests.cls-meta.xml @@ -0,0 +1,5 @@ + + + 39.0 + Active + diff --git a/src/classes/SObjectRepository_Tests.cls b/src/classes/SObjectRepository_Tests.cls index 2f885e1..96afa07 100644 --- a/src/classes/SObjectRepository_Tests.cls +++ b/src/classes/SObjectRepository_Tests.cls @@ -5,84 +5,73 @@ @isTest private class SObjectRepository_Tests { - private without sharing class LeadRepository extends SObjectRepository { + private class AccountRepository extends SObjectRepository { // Test subclass that extends SObjectRepository - public LeadRepository() { - super(Schema.Lead.SObjectType, new List{Schema.Lead.Status}); + public AccountRepository() { + super(Schema.Account.SObjectType, new List{Schema.Account.Id, Schema.Account.Name}); } - public LeadRepository(Schema.FieldSet fieldSet) { - super(Schema.Lead.SObjectType, fieldSet); + public AccountRepository(Schema.FieldSet fieldSet) { + super(Schema.Account.SObjectType, fieldSet); } - public Lead getById(Id leadId) { - return (Lead)this.queryBuilder - .whereField(Schema.Lead.Id, new QueryOperator().EQUALS, leadId) - .setAsUpdate() - .getFirstQueryResult(); - } - - public List getById(List leadIdList) { - return (List)this.queryBuilder - .whereField(Schema.Lead.Id, new QueryOperator().IS_IN, leadIdList) - .setAsUpdate() - .orderBy(Schema.Lead.LastActivityDate, QuerySortOrder.DESCENDING, QueryNullSortOrder.LAST) - .getQueryResults(); - } - - public List getMyRecentLeads(Integer limitCount) { - return (List)this.queryBuilder + public List getMyRecentAccounts(Integer limitCount) { + return (List)this.Query .usingScope(QueryFilterScope.MINE) .limitCount(limitCount) - .orderBy(Schema.Lead.LastActivityDate) + .orderBy(Schema.Account.LastActivityDate) .getQueryResults(); } - public List getNewThisWeekById(List leadIdList) { - return (List)this.queryBuilder - .whereField(Schema.Lead.Id, new QueryOperator().IS_IN, leadIdList) - .whereField(Schema.Lead.CreatedDate, new QueryOperator().EQUALS, new QueryDateLiteral().THIS_WEEK) - .setAsUpdate() - .getQueryResults(); + public Account getAccountAndContactsWithEmails(Id accountId) { + return (Account)this.Query + .filterBy(new QueryFilter(Schema.Account.Id, QueryOperator.EQUALS, accountId)) + .includeChildrenRecords( + Schema.Contact.AccountId, + new ContactRepository() + ) + .getFirstQueryResult(); } + } - public List searchInAllFields(String searchTerm) { - return this.searchInFieldsBySearchGroup(searchTerm, QuerySearchGroup.ALL_FIELDS); + private class ContactRepository extends SObjectRepository { + public ContactRepository() { + super(Schema.Contact.SObjectType, new List{Schema.Contact.Id, Schema.Contact.AccountId, Schema.Contact.Email, Schema.Contact.LastName}); } + } - public List searchInFieldsBySearchGroup(String searchTerm, QuerySearchGroup searchGroup) { - return (List)this.queryBuilder - .orderBy(Schema.Lead.CreatedDate, QuerySortOrder.DESCENDING) - .setAsUpdate() // SOSL cannot use FOR UPDATE. This will execute, but a warning debug statement will indicate that it is ignored - .getSearchResults(searchTerm, searchGroup); + private class UserRepository extends SObjectRepository { + public UserRepository() { + super(Schema.User.SObjectType); } - public override void doUpsert(SObject record) { - Lead lead = (Lead)record; - upsert lead; + public override void upsertRecords(SObject record, Schema.SObjectField externalIdField) { + super.upsertRecords(record, externalIdField); } + } - public override void doUpsert(List recordList) { - List leadList = (List)recordList; - upsert leadList; - } + static Account createAccount() { + Account account = new Account( + Name = 'My Test Company' + ); + return account; } - static Lead createLead() { - Lead lead = new Lead( - Company = 'My Test Company', - LastName = 'Gillespie' + static Contact createContact(Id accountId) { + Contact contact = new Contact( + AccountId = accountId, + LastName = 'Gillespie' ); - return lead; + return contact; } @testSetup static void setupData() { - List leadList = new List(); + List accountList = new List(); for(Integer i = 0; i < 5; i++) { - leadList.add(createLead()); + accountList.add(createAccount()); } - insert leadList; + insert accountList; } @isTest @@ -92,151 +81,303 @@ private class SObjectRepository_Tests { Schema.FieldSet fieldSet; Test.startTest(); - SObjectRepository_Tests.LeadRepository leadRepo = new SObjectRepository_Tests.LeadRepository(fieldSet); - System.assertNotEquals(null, leadRepo); + SObjectRepository_Tests.AccountRepository accountRepo = new SObjectRepository_Tests.AccountRepository(fieldSet); + System.assertNotEquals(null, accountRepo); Test.stopTest(); } @isTest static void it_should_get_by_id_when_id() { - Lead expectedLead = [SELECT Id FROM Lead LIMIT 1]; + Account expectedAccount = [SELECT Id FROM Account LIMIT 1]; + + Test.startTest(); + Account returnedAccount = (Account)new SObjectRepository_Tests.AccountRepository().getById(expectedAccount.Id); + Test.stopTest(); + + System.assertEquals(expectedAccount.Id, returnedAccount.Id); + } + + @isTest + static void it_should_get_by_id_using_query_filter() { + Account expectedAccount = [SELECT Id FROM Account LIMIT 1]; + QueryFilter queryFilter = new QueryFilter(Schema.Account.Id, QueryOperator.EQUALS, expectedAccount.Id); + + Test.startTest(); + Account returnedAccount = ((List)new SObjectRepository_Tests.AccountRepository().get(queryFilter))[0]; + Test.stopTest(); + + System.assertEquals(expectedAccount.Id, returnedAccount.Id); + } + + @isTest + static void it_should_get_by_id_using_list_of_query_filters() { + Account expectedAccount = [SELECT Id FROM Account LIMIT 1]; + QueryFilter queryFilter = new QueryFilter(Schema.Account.Id, QueryOperator.EQUALS, expectedAccount.Id); Test.startTest(); - Lead returnedLead = new SObjectRepository_Tests.LeadRepository().getById(expectedLead.Id); + Account returnedAccount = ((List)new SObjectRepository_Tests.AccountRepository().get(new List{queryFilter}))[0]; Test.stopTest(); - System.assertEquals(expectedLead.Id, returnedLead.Id); + System.assertEquals(expectedAccount.Id, returnedAccount.Id); } @isTest static void it_should_get_by_id_when_list_of_id() { - Map expectedLeadList = new Map([SELECT Id FROM Lead]); + Map expectedAccountList = new Map([SELECT Id FROM Account]); + + Test.startTest(); + List returnedAccountList = (List)new SObjectRepository_Tests.AccountRepository().getById(new List(expectedAccountList.keySet())); + Test.stopTest(); + + System.assertNotEquals(null, expectedAccountList); + System.assert(!expectedAccountList.isEmpty()); + System.assertEquals(expectedAccountList.size(), returnedAccountList.size()); + } + + @isTest + static void it_should_return_my_recent_accounts() { + List expectedAccountList = [SELECT Id FROM Account LIMIT 2]; + + Test.startTest(); + List returnedAccountList = new SObjectRepository_Tests.AccountRepository().getMyRecentAccounts(expectedAccountList.size()); + Test.stopTest(); + + System.assertNotEquals(null, expectedAccountList); + System.assert(!expectedAccountList.isEmpty()); + System.assertEquals(expectedAccountList.size(), returnedAccountList.size()); + } + + @isTest + static void it_should_return_for_ids_in_set_and_query_filters() { + Map expectedAccountMap = new Map([SELECT Id FROM Account]); + List accountFields = new List{Schema.Account.Id, Schema.Account.Name}; + ISObjectRepository repo = new SObjectRepository(Schema.Account.SObjectType, accountFields); + + Test.startTest(); + QueryFilter queryFilter = new QueryFilter(Schema.Account.Name, QueryOperator.EQUALS, 'My Test Company'); + List queryFilters = new List{queryFilter}; + List returnedAccountList = (List)repo.getByIdAndQueryFilters(expectedAccountMap.keyset(), queryFilters); + Test.stopTest(); + + System.assertNotEquals(null, expectedAccountMap); + System.assert(!expectedAccountMap.isEmpty()); + System.assertEquals(expectedAccountMap.size(), returnedAccountList.size()); + } + + @isTest + static void it_should_return_by_field_for_ids_in_list() { + Map expectedAccountMap = new Map([SELECT Id FROM Account]); + List accountFields = new List{Schema.Account.Id, Schema.Account.Name}; + ISObjectRepository repo = new SObjectRepository(Schema.Account.SObjectType, accountFields); Test.startTest(); - List returnedLeadList = new SObjectRepository_Tests.LeadRepository().getById(new List(expectedLeadList.keySet())); + QueryFilter queryFilter = new QueryFilter(Schema.Account.Name, QueryOperator.EQUALS, 'My Test Company'); + List queryFilters = new List{queryFilter}; + List returnedAccountList = (List)repo.getByIdAndQueryFilters(new List(expectedAccountMap.keyset()), queryFilters); Test.stopTest(); - System.assertNotEquals(null, expectedLeadList); - System.assert(!expectedLeadList.isEmpty()); - System.assertEquals(expectedLeadList.size(), returnedLeadList.size()); + System.assertNotEquals(null, expectedAccountMap); + System.assert(!expectedAccountMap.isEmpty()); + System.assertEquals(expectedAccountMap.size(), returnedAccountList.size()); } @isTest - static void it_should_return_my_recent_leads() { - List expectedLeadList = [SELECT Id FROM Lead LIMIT 2]; + static void it_should_return_account_and_contacts_as_children_records() { + Account account = [SELECT Id FROM Account LIMIT 1]; + + Contact contact = createContact(account.Id); + insert contact; + contact = [SELECT Id, AccountId FROM Contact WHERE Id = :contact.Id]; + System.assertEquals(account.Id, contact.AccountId); + + Account expectedAccount = [ + SELECT Id, (SELECT Id, AccountId FROM Contacts) + FROM Account + WHERE Id = :account.Id + ]; + System.assertEquals(1, expectedAccount.Contacts.size()); Test.startTest(); - List returnedLeadList = new SObjectRepository_Tests.LeadRepository().getMyRecentLeads(expectedLeadList.size()); + // Make sure that we have 0 queries used so far + System.assertEquals(0, Limits.getQueries()); + + account = new SObjectRepository_Tests.AccountRepository().getAccountAndContactsWithEmails(account.Id); + + // Make sure that the account & contact query only counts as a singlequery + System.assertEquals(1, Limits.getQueries()); Test.stopTest(); - System.assertNotEquals(null, expectedLeadList); - System.assert(!expectedLeadList.isEmpty()); - System.assertEquals(expectedLeadList.size(), returnedLeadList.size()); + System.assertEquals(expectedAccount.Contacts.size(), account.Contacts.size()); + Contact firstContact = account.Contacts[0]; + System.assertEquals(contact.Id, firstContact.Id); } @isTest - static void it_should_return_new_this_week_by_id() { - Map expectedLeadMap = new Map([SELECT Id FROM Lead]); + static void it_should_search_for_search_term() { + String searchTerm = 'Gillespie'; + Map soslAccountMap = new Map([SELECT Id, Name, OwnerId, Owner.Name FROM Account]); + Test.setFixedSearchResults(new List(soslAccountMap.keySet())); + + List expectedAccountList = [FIND :searchTerm IN ALL FIELDS RETURNING Account(Id, Name)][0]; + System.assertNotEquals(0, expectedAccountList.size()); + + Test.startTest(); + List returnedAccountList = (List)new SObjectRepository_Tests.AccountRepository().getSearchResults(searchTerm); + Test.stopTest(); + + System.assertNotEquals(null, expectedAccountList); + System.assert(!expectedAccountList.isEmpty()); + System.assertEquals(expectedAccountList.size(), returnedAccountList.size()); + } + + @isTest + static void it_should_search_for_search_term_and_search_group() { + String searchTerm = 'Gillespie'; + Map soslAccountMap = new Map([SELECT Id, Name, OwnerId, Owner.Name FROM Account]); + Test.setFixedSearchResults(new List(soslAccountMap.keySet())); + + List expectedAccountList = [FIND :searchTerm IN ALL FIELDS RETURNING Account(Id, Name)][0]; + System.assertNotEquals(0, expectedAccountList.size()); Test.startTest(); - List returnedLeadList = new SObjectRepository_Tests.LeadRepository().getNewThisWeekById(new List(expectedLeadMap.keySet())); + List returnedAccountList = (List)new SObjectRepository_Tests.AccountRepository().getSearchResults(searchTerm, QuerySearchGroup.NAME_FIELDS); Test.stopTest(); - System.assertNotEquals(null, expectedLeadMap); - System.assert(!expectedLeadMap.isEmpty()); - System.assertEquals(expectedLeadMap.size(), returnedLeadList.size()); + System.assertNotEquals(null, expectedAccountList); + System.assert(!expectedAccountList.isEmpty()); + System.assertEquals(expectedAccountList.size(), returnedAccountList.size()); } @isTest - static void it_should_search_in_all_fields() { + static void it_should_search_for_search_term_and_search_group_and_query_filters() { String searchTerm = 'Gillespie'; - Map soslLeadMap = new Map([SELECT Id, Name FROM Lead]); - Test.setFixedSearchResults(new List(soslLeadMap.keySet())); + Map soslAccountMap = new Map([SELECT Id, Name, OwnerId, Owner.Name FROM Account]); + Test.setFixedSearchResults(new List(soslAccountMap.keySet())); - List expectedLeadList = [FIND :searchTerm IN ALL FIELDS RETURNING Lead(Id, Name)][0]; - System.assertNotEquals(0, expectedLeadList.size()); + List expectedAccountList = [FIND :searchTerm IN ALL FIELDS RETURNING Account(Id, Name)][0]; + System.assertNotEquals(0, expectedAccountList.size()); Test.startTest(); - List returnedLeadList = new SObjectRepository_Tests.LeadRepository().searchInAllFields(searchTerm); + QueryFilter queryFilter = new QueryFilter(Schema.Account.CreatedDate, QueryOperator.EQUALS, QueryDateLiteral.TODAY); + List queryFilters = new List{queryFilter}; + List returnedAccountList = (List)new SObjectRepository_Tests.AccountRepository().getSearchResults(searchTerm, QuerySearchGroup.NAME_FIELDS, queryFilters); Test.stopTest(); - System.assertNotEquals(null, expectedLeadList); - System.assert(!expectedLeadList.isEmpty()); - System.assertEquals(expectedLeadList.size(), returnedLeadList.size()); + System.assertNotEquals(null, expectedAccountList); + System.assert(!expectedAccountList.isEmpty()); + System.assertEquals(expectedAccountList.size(), returnedAccountList.size()); } @isTest static void it_should_insert_a_single_record() { - Lead lead = createLead(); - System.assertEquals(null, lead.Id); + Account account = createAccount(); + System.assertEquals(null, account.Id); Test.startTest(); - new SObjectRepository_Tests.LeadRepository().doInsert(lead); + new SObjectRepository_Tests.AccountRepository().insertRecords(account); Test.stopTest(); - System.assertNotEquals(null, lead.Id); + System.assertNotEquals(null, account.Id); - List queriedLeadList = [SELECT Id FROM Lead WHERE Id = :lead.Id]; - System.assertEquals(1, queriedLeadList.size()); - System.assertEquals(queriedLeadList[0].Id, lead.Id); + List queriedAccountList = [SELECT Id FROM Account WHERE Id = :account.Id]; + System.assertEquals(1, queriedAccountList.size()); + System.assertEquals(queriedAccountList[0].Id, account.Id); } @isTest static void it_should_update_a_single_record() { - Lead existingLead = [SELECT Id, LastModifiedDate FROM Lead LIMIT 1]; - Datetime originalLastModifiedDate = existingLead.LastModifiedDate; + Account existingAccount = [SELECT Id, LastModifiedDate FROM Account LIMIT 1]; + Datetime originalLastModifiedDate = existingAccount.LastModifiedDate; + + Test.startTest(); + new SObjectRepository_Tests.AccountRepository().updateRecords(existingAccount); + Test.stopTest(); + + existingAccount = [SELECT Id, LastModifiedDate FROM Account LIMIT 1]; + System.assert(existingAccount.LastModifiedDate > originalLastModifiedDate, existingAccount); + } + + @isTest + static void it_should_upsert_a_single_new_record() { + Account newAccount = createAccount(); + System.assertEquals(null, newAccount.Id); + + Test.startTest(); + new SObjectRepository_Tests.AccountRepository().upsertRecords(newAccount); + Test.stopTest(); + + System.assertNotEquals(null, newAccount.Id); + } + + @isTest + static void it_should_upsert_a_single_existing_record() { + Account existingAccount = [SELECT Id, LastModifiedDate FROM Account LIMIT 1]; + Datetime originalLastModifiedDate = existingAccount.LastModifiedDate; + + Test.startTest(); + new SObjectRepository_Tests.AccountRepository().upsertRecords(existingAccount); + Test.stopTest(); + + existingAccount = [SELECT Id, LastModifiedDate FROM Account LIMIT 1]; + System.assert(existingAccount.LastModifiedDate > originalLastModifiedDate, existingAccount); + } + + @isTest + static void it_should_upsert_a_single_existing_record_with_external_id() { + User existingUser = [SELECT Id, LastModifiedDate, Username FROM User WHERE Id = :UserInfo.getUserId()]; + Datetime originalLastModifiedDate = existingUser.LastModifiedDate; + System.assertNotEquals(null, existingUser.Username); Test.startTest(); - new SObjectRepository_Tests.LeadRepository().doUpdate(existingLead); + new SObjectRepository_Tests.UserRepository().upsertRecords(existingUser, Schema.User.Username); Test.stopTest(); - existingLead = [SELECT Id, LastModifiedDate FROM Lead WHERE Id = :existingLead.Id]; - System.assert(existingLead.LastModifiedDate > originalLastModifiedDate); + existingUser = [SELECT Id, LastModifiedDate FROM User LIMIT 1]; + System.assert(existingUser.LastModifiedDate > originalLastModifiedDate, existingUser); } @isTest static void it_should_delete_a_single_record() { - Lead existingLead = [SELECT Id, IsDeleted FROM Lead LIMIT 1]; - System.assertEquals(false, existingLead.IsDeleted); + Account existingAccount = [SELECT Id, IsDeleted FROM Account LIMIT 1]; + System.assertEquals(false, existingAccount.IsDeleted); Test.startTest(); - new SObjectRepository_Tests.LeadRepository().doDelete(existingLead); + new SObjectRepository_Tests.AccountRepository().deleteRecords(existingAccount); Test.stopTest(); - existingLead = [SELECT Id, IsDeleted FROM Lead WHERE Id = :existingLead.Id ALL ROWS]; - System.assertEquals(true, existingLead.IsDeleted); + existingAccount = [SELECT Id, IsDeleted FROM Account WHERE Id = :existingAccount.Id ALL ROWS]; + System.assertEquals(true, existingAccount.IsDeleted); } @isTest static void it_should_hard_delete_a_single_record() { - Lead existingLead = [SELECT Id, IsDeleted FROM Lead LIMIT 1]; - System.assertEquals(false, existingLead.IsDeleted); + Account existingAccount = [SELECT Id, IsDeleted FROM Account LIMIT 1]; + System.assertEquals(false, existingAccount.IsDeleted); Test.startTest(); - new SObjectRepository_Tests.LeadRepository().doHardDelete(existingLead); + new SObjectRepository_Tests.AccountRepository().hardDeleteRecords(existingAccount); Test.stopTest(); - List existingLeadList = [SELECT Id, IsDeleted FROM Lead WHERE Id = :existingLead.Id ALL ROWS]; - //System.assertEquals(0, existingLeadList.size()); - // TODO figure out why the hard delete doesn't work in unit tests + List existingAccountList = [SELECT Id, IsDeleted FROM Account WHERE Id = :existingAccount.Id ALL ROWS]; + System.assert(existingAccountList[0].IsDeleted); } @isTest static void it_should_undelete_a_single_record() { - Lead existingLead = [SELECT Id, IsDeleted FROM Lead LIMIT 1]; - System.assertEquals(false, existingLead.IsDeleted); + Account existingAccount = [SELECT Id, IsDeleted FROM Account LIMIT 1]; + System.assertEquals(false, existingAccount.IsDeleted); - delete existingLead; + delete existingAccount; - existingLead = [SELECT Id, IsDeleted FROM Lead WHERE Id = :existingLead.Id ALL ROWS]; - System.assertEquals(true, existingLead.IsDeleted); + existingAccount = [SELECT Id, IsDeleted FROM Account WHERE Id = :existingAccount.Id ALL ROWS]; + System.assertEquals(true, existingAccount.IsDeleted); Test.startTest(); - new SObjectRepository_Tests.LeadRepository().doUndelete(existingLead); + new SObjectRepository_Tests.AccountRepository().undeleteRecords(existingAccount); Test.stopTest(); - existingLead = [SELECT Id, IsDeleted FROM Lead WHERE Id = :existingLead.Id ALL ROWS]; - System.assertEquals(false, existingLead.IsDeleted); + existingAccount = [SELECT Id, IsDeleted FROM Account WHERE Id = :existingAccount.Id ALL ROWS]; + System.assertEquals(false, existingAccount.IsDeleted); } } \ No newline at end of file diff --git a/src/classes/SObjectRepository_Tests.cls-meta.xml b/src/classes/SObjectRepository_Tests.cls-meta.xml index cbddff8..8b061c8 100644 --- a/src/classes/SObjectRepository_Tests.cls-meta.xml +++ b/src/classes/SObjectRepository_Tests.cls-meta.xml @@ -1,5 +1,5 @@ - 38.0 + 39.0 Active diff --git a/src/classes/SObjectTriggerHandler.cls b/src/classes/SObjectTriggerHandler.cls index b229cb0..32a2c4a 100644 --- a/src/classes/SObjectTriggerHandler.cls +++ b/src/classes/SObjectTriggerHandler.cls @@ -6,28 +6,24 @@ public abstract class SObjectTriggerHandler extends NebulaCore implements ISObje private static Map> hashCodesForProcessedRecords = new Map>(); - @testVisible - private enum TriggerContext { + @testVisible private enum TriggerContext { BEFORE_INSERT, BEFORE_UPDATE, BEFORE_DELETE, AFTER_INSERT, AFTER_UPDATE, AFTER_DELETE, AFTER_UNDELETE } - @testVisible - private TriggerContext context; // The current context of the trigger + @testVisible private TriggerContext currentTriggerContext; // The current context of the trigger private Integer hashCode; // The hash code for the current records private Boolean isTestMode; private Boolean isTriggerExecuting; // Checks if the code was called by a trigger - @testVisible - private List recordList, oldRecordList; - @testVisible - private Map recordMap, oldRecordMap; + @testVisible private List recordList, oldRecordList; + @testVisible private Map recordMap, oldRecordMap; public SObjectTriggerHandler() { this(false); } - private SObjectTriggerHandler(Boolean isTestMode) { + protected SObjectTriggerHandler(Boolean isTestMode) { this.currentModule = NebulaCore.Module.TRIGGER_HANDLER; this.isTestMode = isTestMode; @@ -66,7 +62,7 @@ public abstract class SObjectTriggerHandler extends NebulaCore implements ISObje Logger.addEntry(this, 'Starting execute method for: ' + sobjectType); Logger.addEntry(this, 'Hash codes already processed: ' + SObjectTriggerHandler.hashCodesForProcessedRecords); Logger.addEntry(this, 'Hash code for current records: ' + this.hashCode); - Logger.addEntry(this, 'Trigger context for current records: ' + this.context); + Logger.addEntry(this, 'Trigger context for current records: ' + this.currentTriggerContext); Logger.addEntry(this, 'Number of current records: ' + Trigger.size); if(this.haveRecordsAlreadyBeenProcessed()) { @@ -74,13 +70,13 @@ public abstract class SObjectTriggerHandler extends NebulaCore implements ISObje return; } else Logger.addEntry(this, 'Records have not been processed for this context, continuing'); - if(this.context == TriggerContext.BEFORE_INSERT) this.executeBeforeInsert(this.recordList); - else if(this.context == TriggerContext.BEFORE_UPDATE) this.executeBeforeUpdate(this.recordList, this.recordMap, this.oldRecordList, this.oldRecordMap); - else if(this.context == TriggerContext.BEFORE_DELETE) this.executeBeforeDelete(this.oldRecordList, this.oldRecordMap); - else if(this.context == TriggerContext.AFTER_INSERT) this.executeAfterInsert(this.recordList, this.recordMap); - else if(this.context == TriggerContext.AFTER_UPDATE) this.executeAfterUpdate(this.recordList, this.recordMap, this.oldRecordList, this.oldRecordMap); - else if(this.context == TriggerContext.AFTER_DELETE) this.executeAfterDelete(this.oldRecordList, this.oldRecordMap); - else if(this.context == TriggerContext.AFTER_UNDELETE) this.executeAfterUndelete(this.recordList, this.recordMap); + if(this.currentTriggerContext == TriggerContext.BEFORE_INSERT) this.executeBeforeInsert(this.recordList); + else if(this.currentTriggerContext == TriggerContext.BEFORE_UPDATE) this.executeBeforeUpdate(this.recordList, this.recordMap, this.oldRecordList, this.oldRecordMap); + else if(this.currentTriggerContext == TriggerContext.BEFORE_DELETE) this.executeBeforeDelete(this.oldRecordList, this.oldRecordMap); + else if(this.currentTriggerContext == TriggerContext.AFTER_INSERT) this.executeAfterInsert(this.recordList, this.recordMap); + else if(this.currentTriggerContext == TriggerContext.AFTER_UPDATE) this.executeAfterUpdate(this.recordList, this.recordMap, this.oldRecordList, this.oldRecordMap); + else if(this.currentTriggerContext == TriggerContext.AFTER_DELETE) this.executeAfterDelete(this.oldRecordList, this.oldRecordMap); + else if(this.currentTriggerContext == TriggerContext.AFTER_UNDELETE) this.executeAfterUndelete(this.recordList, this.recordMap); if(Trigger.isAfter) Logger.saveLogs(); } @@ -96,20 +92,20 @@ public abstract class SObjectTriggerHandler extends NebulaCore implements ISObje private void setTriggerContext() { if(this.isTestMode || !this.isTriggerExecuting) return; - if(Trigger.isBefore && Trigger.isInsert) this.context = TriggerContext.BEFORE_INSERT; - else if(Trigger.isBefore && Trigger.isUpdate) this.context = TriggerContext.BEFORE_UPDATE; - else if(Trigger.isBefore && Trigger.isDelete) this.context = TriggerContext.BEFORE_DELETE; - else if(Trigger.isAfter && Trigger.isInsert) this.context = TriggerContext.AFTER_INSERT; - else if(Trigger.isAfter && Trigger.isUpdate) this.context = TriggerContext.AFTER_UPDATE; - else if(Trigger.isAfter && Trigger.isDelete) this.context = TriggerContext.AFTER_DELETE; - else if(Trigger.isAfter && Trigger.isUndelete) this.context = TriggerContext.AFTER_UNDELETE; + if(Trigger.isBefore && Trigger.isInsert) this.currentTriggerContext = TriggerContext.BEFORE_INSERT; + else if(Trigger.isBefore && Trigger.isUpdate) this.currentTriggerContext = TriggerContext.BEFORE_UPDATE; + else if(Trigger.isBefore && Trigger.isDelete) this.currentTriggerContext = TriggerContext.BEFORE_DELETE; + else if(Trigger.isAfter && Trigger.isInsert) this.currentTriggerContext = TriggerContext.AFTER_INSERT; + else if(Trigger.isAfter && Trigger.isUpdate) this.currentTriggerContext = TriggerContext.AFTER_UPDATE; + else if(Trigger.isAfter && Trigger.isDelete) this.currentTriggerContext = TriggerContext.AFTER_DELETE; + else if(Trigger.isAfter && Trigger.isUndelete) this.currentTriggerContext = TriggerContext.AFTER_UNDELETE; } private void validateTriggerContext() { if(this.isTestMode) return; String errorMessage = 'Trigger handler called outside of trigger execution ' + this.isTriggerExecuting; - if(!this.isTriggerExecuting || this.context == null) throw new SObjectTriggerHandlerException(errorMessage); + if(!this.isTriggerExecuting || this.currentTriggerContext == null) throw new Exceptions.SObjectTriggerHandlerException(errorMessage); } private void setTriggerRecords() { @@ -166,21 +162,18 @@ public abstract class SObjectTriggerHandler extends NebulaCore implements ISObje // This method is a safeguard that checks to see if we have recursion problems and stops if we do // It allows each context to occur once for a given hash code - if(this.context == TriggerContext.BEFORE_INSERT) { + if(this.currentTriggerContext == TriggerContext.BEFORE_INSERT) { // BEFORE_INSERT doesn't have record IDs yet, so the hash here will never match the other hashes // Since Salesforce makes it impossible to recursively run "insert record", we can let the platform handle it return false; } else if(!SObjectTriggerHandler.hashCodesForProcessedRecords.containsKey(this.hashCode)) { - SObjectTriggerHandler.hashCodesForProcessedRecords.put(this.hashCode, new Set{this.context}); + SObjectTriggerHandler.hashCodesForProcessedRecords.put(this.hashCode, new Set{this.currentTriggerContext}); return false; - } else if(!SObjectTriggerHandler.hashCodesForProcessedRecords.get(this.hashCode).contains(this.context)) { - SObjectTriggerHandler.hashCodesForProcessedRecords.get(this.hashCode).add(this.context); + } else if(!SObjectTriggerHandler.hashCodesForProcessedRecords.get(this.hashCode).contains(this.currentTriggerContext)) { + SObjectTriggerHandler.hashCodesForProcessedRecords.get(this.hashCode).add(this.currentTriggerContext); return false; } else { return true; } } - - private class SObjectTriggerHandlerException extends Exception {} - } \ No newline at end of file diff --git a/src/classes/SObjectTriggerHandler.cls-meta.xml b/src/classes/SObjectTriggerHandler.cls-meta.xml index cbddff8..8b061c8 100644 --- a/src/classes/SObjectTriggerHandler.cls-meta.xml +++ b/src/classes/SObjectTriggerHandler.cls-meta.xml @@ -1,5 +1,5 @@ - 38.0 + 39.0 Active diff --git a/src/classes/SObjectTriggerHandler_Tests.cls b/src/classes/SObjectTriggerHandler_Tests.cls index cc32225..f43858f 100644 --- a/src/classes/SObjectTriggerHandler_Tests.cls +++ b/src/classes/SObjectTriggerHandler_Tests.cls @@ -1,12 +1,16 @@ +/************************************************************************************************* +* This file is part of the Nebula Framework project, released under the MIT License. * +* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * +*************************************************************************************************/ @isTest private class SObjectTriggerHandler_Tests { -/* - private class LeadTriggerHandler extends SObjectTriggerHandler { - public LeadTriggerHandler() { + + private class LeadTriggerHandlerTest extends SObjectTriggerHandler { + public LeadTriggerHandlerTest() { super(true); } public override void executeBeforeInsert(List newRecordList) { - List newLeadList = (List)newRecordList; + List newLeadList = (List)this.recordList; for(Lead newLead : newLeadList) { this.setStatus(newLead); @@ -19,13 +23,14 @@ private class SObjectTriggerHandler_Tests { } } - @testSetup + static List leadList = new List(); + static void setupData() { - List leadList = new List(); for(Integer i = 0; i < 5; i++) { Lead lead = new Lead( Company = 'My Test Company', - LastName = 'Gillespie' + LastName = 'Gillespie', + Status = '' ); leadList.add(lead); } @@ -34,75 +39,64 @@ private class SObjectTriggerHandler_Tests { @isTest static void shouldNotExecuteTriggers() { - TriggerHandlerSettings__c triggerHandlerSettings = TriggerHandlerSettings__c.getInstance(); + NebulaTriggerHandlerSettings__c triggerHandlerSettings = NebulaTriggerHandlerSettings__c.getInstance(); triggerHandlerSettings.ExecuteTriggers__c = false; upsert triggerHandlerSettings; - List leadList = [SELECT Id, Status FROM Lead]; - - LeadTriggerHandler leadTriggerHandler = new LeadTriggerHandler(); - leadTriggerHandler.context = SObjectTriggerHandler.TriggerContext.BEFORE_INSERT; - leadTriggerHandler.recordList = leadList; - - Test.startTest(); - - leadTriggerHandler.execute(); - - Test.stopTest(); - } - - @isTest - static void executeAllContexts() { - List leadList = [SELECT Id, Status FROM Lead]; - - LeadTriggerHandler leadTriggerHandler = new LeadTriggerHandler(); + LeadTriggerHandlerTest leadTriggerHandler = new LeadTriggerHandlerTest(); + leadTriggerHandler.currentTriggerContext = SObjectTriggerHandler.TriggerContext.BEFORE_INSERT; leadTriggerHandler.recordList = leadList; Test.startTest(); - for(SObjectTriggerHandler.TriggerContext context : SObjectTriggerHandler.TriggerContext.values()) { - leadTriggerHandler.context = context; - leadTriggerHandler.execute(); - } - - Test.stopTest(); - } - - @isTest - static void executeBeforeInsert() { - List leadList = [SELECT Id, Status FROM Lead]; - - LeadTriggerHandler leadTriggerHandler = new LeadTriggerHandler(); - // Set the variables for the relevant context - leadTriggerHandler.context = SObjectTriggerHandler.TriggerContext.BEFORE_INSERT; - leadTriggerHandler.recordList = leadList; - - Test.startTest(); - - leadTriggerHandler.execute(); - - Test.stopTest(); - } - - @isTest - static void executeBeforeUpdate() { - List leadList = [SELECT Id, Status FROM Lead]; - - LeadTriggerHandler leadTriggerHandler = new LeadTriggerHandler(); - // Set the variables for the relevant context - leadTriggerHandler.context = SObjectTriggerHandler.TriggerContext.BEFORE_UPDATE; - leadTriggerHandler.recordList = leadList; - leadTriggerHandler.recordMap = new Map(leadList); - leadTriggerHandler.oldRecordList = leadList; - leadTriggerHandler.oldRecordMap = new Map(leadList); - - Test.startTest(); - leadTriggerHandler.execute(); Test.stopTest(); } - */ + // @isTest + // static void executeAllContexts() { + // setupData(); + // LeadTriggerHandlerTest leadTriggerHandler = new LeadTriggerHandlerTest(); + // leadTriggerHandler.recordList = leadList; + + // Test.startTest(); + // for(SObjectTriggerHandler.TriggerContext context : SObjectTriggerHandler.TriggerContext.values()) { + // leadTriggerHandler.currentTriggerContext = context; + // leadTriggerHandler.execute(); + // } + // Test.stopTest(); + // } + + // @isTest + // static void executeBeforeInsert() { + // LeadTriggerHandlerTest leadTriggerHandler = new LeadTriggerHandlerTest(); + // // Set the variables for the relevant context + // leadTriggerHandler.currentTriggerContext = SObjectTriggerHandler.TriggerContext.BEFORE_INSERT; + // leadTriggerHandler.recordList = leadList; + + // Test.startTest(); + + // leadTriggerHandler.execute(); + + // Test.stopTest(); + // } + + // @isTest + // static void executeBeforeUpdate() { + // LeadTriggerHandlerTest leadTriggerHandler = new LeadTriggerHandlerTest(); + // // Set the variables for the relevant context + // leadTriggerHandler.currentTriggerContext = SObjectTriggerHandler.TriggerContext.BEFORE_UPDATE; + // leadTriggerHandler.recordList = leadList; + // leadTriggerHandler.recordMap = new Map(leadList); + // leadTriggerHandler.oldRecordList = leadList; + // leadTriggerHandler.oldRecordMap = new Map(leadList); + + // Test.startTest(); + + // leadTriggerHandler.execute(); + + // Test.stopTest(); + // } } \ No newline at end of file diff --git a/src/classes/SObjectTriggerHandler_Tests.cls-meta.xml b/src/classes/SObjectTriggerHandler_Tests.cls-meta.xml index cbddff8..8b061c8 100644 --- a/src/classes/SObjectTriggerHandler_Tests.cls-meta.xml +++ b/src/classes/SObjectTriggerHandler_Tests.cls-meta.xml @@ -1,5 +1,5 @@ - 38.0 + 39.0 Active diff --git a/src/classes/SObjectTypeDescriber.cls b/src/classes/SObjectTypeDescriber.cls new file mode 100644 index 0000000..51672cf --- /dev/null +++ b/src/classes/SObjectTypeDescriber.cls @@ -0,0 +1,50 @@ +/************************************************************************************************* +* This file is part of the Nebula Framework project, released under the MIT License. * +* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * +*************************************************************************************************/ +public without sharing class SObjectTypeDescriber { + + public Schema.SObjectType SObjectType {get; private set;} + + public SObjectTypeDescriber(Id recordId) { + this(recordId.getSObjectType()); + } + + public SObjectTypeDescriber(SObject record) { + this(record.getSObjectType()); + } + + public SObjectTypeDescriber(Schema.SObjectType sobjectType) { + this.SObjectType = sobjectType; + } + + public String getChildRelationshipName(Schema.SObjectField childSObjectField) { + Schema.SObjectType parentSObjectType = new SObjectFieldDescriber(childSObjectField).getParentSObjectType(); + Schema.ChildRelationship childRelationship = this.getSObjectTypeChildRelationshipMap(parentSObjectType).get(childSObjectField); + + return childRelationship.getRelationshipName(); + } + + public Boolean validateSObjectFieldExists(Schema.SObjectField expectedSObjectField) { + SObjectFieldDescriber sobjectFieldDescriber = new SObjectFieldDescriber(expectedSObjectField); + Map fieldMap = this.SObjectType.getDescribe().fields.getMap(); + + Boolean sobjectTypesMatch = sobjectFieldDescriber.validateSObjectType(this.SObjectType); + Boolean sobjectTypesHasMatchingField = fieldMap.containsKey(sobjectFieldDescriber.getFieldName()); + + return sobjectTypesMatch && sobjectTypesHasMatchingField; + } + + private Map getSObjectTypeChildRelationshipMap(Schema.SObjectType parentSObjectType) { + Map childRelationshipMap = new Map(); + + for(Schema.ChildRelationship childRelationship : parentSObjectType.getDescribe().getChildRelationships()) { + if(childRelationship.getRelationshipName() == null) continue; + + childRelationshipMap.put(childRelationship.getField(), childRelationship); + } + + return childRelationshipMap; + } + +} \ No newline at end of file diff --git a/src/classes/SObjectTypeDescriber.cls-meta.xml b/src/classes/SObjectTypeDescriber.cls-meta.xml new file mode 100644 index 0000000..8b061c8 --- /dev/null +++ b/src/classes/SObjectTypeDescriber.cls-meta.xml @@ -0,0 +1,5 @@ + + + 39.0 + Active + diff --git a/src/classes/SObjectTypeDescriber_Tests.cls b/src/classes/SObjectTypeDescriber_Tests.cls new file mode 100644 index 0000000..afc814a --- /dev/null +++ b/src/classes/SObjectTypeDescriber_Tests.cls @@ -0,0 +1,72 @@ +/************************************************************************************************* +* This file is part of the Nebula Framework project, released under the MIT License. * +* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * +*************************************************************************************************/ +@isTest +private class SObjectTypeDescriber_Tests { + + @testSetup + static void setupData() { + Lead lead = new Lead( + Company = 'My Test Company', + LastName = 'Gillespie' + ); + + insert lead; + } + + @isTest + static void it_should_return_the_sobject_type_for_id() { + Id leadId = [SELECT Id FROM Lead LIMIT 1].Id; + Schema.SObjectType expectedSObjectType = leadId.getSobjectType(); + + Test.startTest(); + + SObjectTypeDescriber sobjectTypeDescriber = new SObjectTypeDescriber(leadId); + System.assertEquals(expectedSObjectType, sobjectTypeDescriber.SObjectType); + + Test.stopTest(); + } + + @isTest + static void it_should_return_the_sobject_type_for_record() { + Lead lead = [SELECT Id FROM Lead LIMIT 1]; + Schema.SObjectType expectedSObjectType = lead.getSobjectType(); + + Test.startTest(); + + SObjectTypeDescriber sobjectTypeDescriber = new SObjectTypeDescriber(lead); + System.assertEquals(expectedSObjectType, sobjectTypeDescriber.SObjectType); + + Test.stopTest(); + } + + @isTest + static void it_should_return_the_sobject_type_for_sobject_type() { + Schema.SObjectType expectedSObjectType = Schema.Lead.SObjectType; + + Test.startTest(); + SObjectTypeDescriber sobjectTypeDescriber = new SObjectTypeDescriber(expectedSObjectType); + System.assertEquals(expectedSObjectType, sobjectTypeDescriber.SObjectType); + Test.stopTest(); + } + + @isTest + static void it_should_validate_sobject_field_exists() { + SObjectTypeDescriber sobjectTypeDescriber = new SObjectTypeDescriber(Schema.Lead.SObjectType); + + Test.startTest(); + System.assertEquals(true, sobjectTypeDescriber.validateSObjectFieldExists(Schema.Lead.Id)); + Test.stopTest(); + } + + @isTest + static void it_should_validate_sobject_field_does_not_exist() { + SObjectTypeDescriber sobjectTypeDescriber = new SObjectTypeDescriber(Schema.Lead.SObjectType); + + Test.startTest(); + System.assertEquals(false, sobjectTypeDescriber.validateSObjectFieldExists(Schema.Task.Id)); + Test.stopTest(); + } + +} \ No newline at end of file diff --git a/src/classes/SObjectTypeDescriber_Tests.cls-meta.xml b/src/classes/SObjectTypeDescriber_Tests.cls-meta.xml new file mode 100644 index 0000000..8b061c8 --- /dev/null +++ b/src/classes/SObjectTypeDescriber_Tests.cls-meta.xml @@ -0,0 +1,5 @@ + + + 39.0 + Active + diff --git a/src/classes/TestingUtils.cls b/src/classes/TestingUtils.cls new file mode 100644 index 0000000..f3a552f --- /dev/null +++ b/src/classes/TestingUtils.cls @@ -0,0 +1,44 @@ +/************************************************************************************************* +* This file is part of the Nebula Framework project, released under the MIT License. * +* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. * +*************************************************************************************************/ +@isTest +public class TestingUtils { + + public static List insertedRecords = new List(); + public static List upsertedRecords = new List(); + public static List updatedRecords = new List(); + public static List deletedRecords = new List(); + public static List undeletedRecords = new List(); + + private static Integer startingNumber = 1; + + public static String generateId(Schema.SObjectType sobjectType) { + String result = String.valueOf(startingNumber++); + return sobjectType.getDescribe().getKeyPrefix() + '0'.repeat(12-result.length()) + result; + } + + public static void generateIds(List records) { + for(SObject record : records) record.Id = record.Id != null ? record.Id : generateId(record.getSObjectType()); + } + + public static SObject setReadOnlyField(SObject sobj, Schema.SObjectField fieldName, Object value) { + return setReadOnlyField(sobj, new Map{fieldName => value}); + } + + public static SObject setReadOnlyField(SObject sobj, Map changesToFields) { + String serializedRecord = JSON.serialize(sobj); + Map deserializedRecordMap = (Map)JSON.deserializeUntyped(serializedRecord); + + // Loop through the deserialized record map and put the field & value + // Since it's a map, if the field already exists on the SObject, it's updated (or added if it wasn't there already) + for(Schema.SObjectField sobjectField : changesToFields.keySet()) { + String fieldName = sobjectField.getDescribe().getName(); + deserializedRecordMap.put(fieldName, changesToFields.get(sobjectField)); + } + + serializedRecord = JSON.serialize(deserializedRecordMap); + return (SObject)JSON.deserialize(serializedRecord, SObject.class); + } + +} \ No newline at end of file diff --git a/src/classes/TestingUtils.cls-meta.xml b/src/classes/TestingUtils.cls-meta.xml new file mode 100644 index 0000000..8b061c8 --- /dev/null +++ b/src/classes/TestingUtils.cls-meta.xml @@ -0,0 +1,5 @@ + + + 39.0 + Active + diff --git a/src/classes/UUID.cls-meta.xml b/src/classes/UUID.cls-meta.xml index cbddff8..8b061c8 100644 --- a/src/classes/UUID.cls-meta.xml +++ b/src/classes/UUID.cls-meta.xml @@ -1,5 +1,5 @@ - 38.0 + 39.0 Active diff --git a/src/classes/UUID_Tests.cls-meta.xml b/src/classes/UUID_Tests.cls-meta.xml index cbddff8..8b061c8 100644 --- a/src/classes/UUID_Tests.cls-meta.xml +++ b/src/classes/UUID_Tests.cls-meta.xml @@ -1,5 +1,5 @@ - 38.0 + 39.0 Active diff --git a/src/layouts/NebulaLog__c-Log Layout.layout b/src/layouts/NebulaLog__c-Log Layout.layout index 61b3277..c3229bf 100644 --- a/src/layouts/NebulaLog__c-Log Layout.layout +++ b/src/layouts/NebulaLog__c-Log Layout.layout @@ -15,10 +15,6 @@ Required TransactionId__c - - Edit - InitialModule__c - @@ -33,25 +29,12 @@ true true - - - Edit - RepositorySettings__c - - - Edit - TriggerHandlerSettings__c - - - Edit - RecordTypesSettings__c - - + false - false + true true diff --git a/src/objects/NebulaLog__c.object b/src/objects/NebulaLog__c.object index 6f39b9e..0239720 100644 --- a/src/objects/NebulaLog__c.object +++ b/src/objects/NebulaLog__c.object @@ -63,37 +63,6 @@ Text false - - InitialModule__c - false - - 255 - false - false - false - Text - false - - - RecordTypesSettings__c - false - - 32768 - false - false - LongTextArea - 3 - - - RepositorySettings__c - false - - 32768 - false - false - LongTextArea - 3 - TransactionId__c false @@ -106,16 +75,6 @@ Text true - - TriggerHandlerSettings__c - false - - 32768 - false - false - LongTextArea - 5 - All_Logs @@ -147,6 +106,12 @@ AutoNumber Nebula Logs - + + InitialClass__c + TransactionId__c + CREATEDBY_USER + CREATED_DATE + ReadWrite + Public diff --git a/src/objects/NebulaLoggerSettings__c.object b/src/objects/NebulaLoggerSettings__c.object index 169385f..5163279 100644 --- a/src/objects/NebulaLoggerSettings__c.object +++ b/src/objects/NebulaLoggerSettings__c.object @@ -1,10 +1,11 @@ Hierarchy + Controls the behavior of the class Logger.cls false EnableLogging__c - false + true false false diff --git a/src/objects/NebulaRepositorySettings__c.object b/src/objects/NebulaRepositorySettings__c.object index 2aaf9b7..efca705 100644 --- a/src/objects/NebulaRepositorySettings__c.object +++ b/src/objects/NebulaRepositorySettings__c.object @@ -17,6 +17,7 @@ LastModifiedById LastModifiedDate Name OwnerId +ParentId Subject RecordTypeId SystemModStamp @@ -32,6 +33,7 @@ LastModifiedById LastModifiedDate Name OwnerId +ParentId Subject RecordTypeId SystemModStamp @@ -39,22 +41,6 @@ SystemModStamp false Checkbox - - SortQueryFields__c - true - When enabled, the list of query fields in the dynamic query are sorted when the query is built. For example: -'SELECT Status, Id, Name FROM Lead' -becomes -'SELECT Id, Name, Status FROM Lead' - false - When enabled, the list of query fields in the dynamic query are sorted when the query is built. For example: -'SELECT Status, Id, Name FROM Lead' -becomes -'SELECT Id, Name, Status FROM Lead' - - false - Checkbox - Public diff --git a/src/package.xml b/src/package.xml index 7e70bd0..190e200 100644 --- a/src/package.xml +++ b/src/package.xml @@ -4,6 +4,10 @@ * ApexClass + + Nebula + CustomApplication + NebulaLog__c NebulaLoggerSettings__c @@ -12,9 +16,13 @@ NebulaTriggerHandlerSettings__c CustomObject + + NebulaLog__c + CustomTab + NebulaLog__c-Log Layout Layout - 38.0 + 39.0 diff --git a/src/tabs/NebulaLog__c.tab b/src/tabs/NebulaLog__c.tab new file mode 100644 index 0000000..9d91fd8 --- /dev/null +++ b/src/tabs/NebulaLog__c.tab @@ -0,0 +1,6 @@ + + + true + false + Custom83: Pencil +