diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml
index 7b1f1c0..4fdcbdd 100644
--- a/.idea/dictionaries/project.xml
+++ b/.idea/dictionaries/project.xml
@@ -2,6 +2,7 @@
anyvalue
+ memoizes
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 35eb1dd..7ddfc9e 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -1,5 +1,11 @@
+
+
+
+
+
+
diff --git a/IlluminatedCloud/Apexkit/OfflineSymbolTable.zip b/IlluminatedCloud/Apexkit/OfflineSymbolTable.zip
index 162dc4c..95ae58e 100644
Binary files a/IlluminatedCloud/Apexkit/OfflineSymbolTable.zip and b/IlluminatedCloud/Apexkit/OfflineSymbolTable.zip differ
diff --git a/force-app/main/default/classes/test utilities/PermissionsHelper.cls b/force-app/main/default/classes/test utilities/PermissionsHelper.cls
new file mode 100644
index 0000000..50c0204
--- /dev/null
+++ b/force-app/main/default/classes/test utilities/PermissionsHelper.cls
@@ -0,0 +1,95 @@
+/**
+ * @description A test-only helper for manipulating users' permissions, permission sets and permission set groups
+ *
+ * Note: This class does *no* error handling. Because it's used in the setup and preparation of test data,
+ * it's on the developer to ensure the methods are called with existing, valid data like permission set names.
+ * If, for instance, a developer fat-fingers a permission set name, the query on ~ line 74 will fail, and throw
+ * an exception.
+ */
+
+@IsTest
+public class PermissionsHelper {
+ /**
+ * @description creates and inserts a permission set assignment record
+ * @param userToAssignTo User the user whom the permission set will be applied to
+ * @param permSetId Id The Id of the permission set to assign to the user.
+ */
+ public static void assignPermissionSetToUser(
+ User userToAssignTo,
+ Id permSetId
+ ) {
+ PermissionSetAssignment permissionSetAssignment = new PermissionSetAssignment(
+ AssigneeId = userToAssignTo.Id,
+ PermissionSetId = permSetId
+ );
+ insert permissionSetAssignment;
+ }
+
+ /**
+ * @description Assigns a permission set to a given user.
+ * @param userToAssignTo User to assign the permission set to.
+ * @param permSetName String name of the permission set.
+ */
+ public static void assignPermSetToUser(
+ User userToAssignTo,
+ String permSetName
+ ) {
+ assignPermissionSetToUser(
+ userToAssignTo,
+ UserFactoryHelper.fetchPermissionSetIdByName(permSetName)
+ );
+ }
+
+ /**
+ * @description Generates a test permission set record - no permissions are added to it
+ * @param permSetName String what to call your perm set
+ * @param doInsert Boolean true if you want this to insert your perm set record.
+ * @return PermissionSet the created permission set.
+ */
+ public static PermissionSet createPermissionSet(
+ String permSetName,
+ Boolean doInsert
+ ) {
+ PermissionSet newPermSet = new PermissionSet(
+ Name = permSetName,
+ Label = 'Test Permission Set'
+ );
+ if (doInsert) {
+ insert newPermSet;
+ }
+ return newPermSet;
+ }
+
+ /**
+ * @description Enables a custom permission using a permission set
+ * @param permissionName String name of the custom permission you want created
+ * @param forUserId Id user to assign the custom permission to.
+ */
+ public static void enableCustomPermission(
+ String permissionName,
+ Id forUserId
+ ) {
+ PermissionSet permSet = createPermissionSet('TestPermSet', true);
+
+ Id customPermissionId = [
+ SELECT Id
+ FROM CustomPermission
+ WHERE DeveloperName = :permissionName
+ WITH SYSTEM_MODE
+ LIMIT 1
+ ]
+ .Id;
+
+ SetupEntityAccess permSetPermission = new SetupEntityAccess(
+ ParentId = permSet.Id,
+ SetupEntityId = customPermissionId
+ );
+
+ PermissionSetAssignment permSetAssignment = new PermissionSetAssignment(
+ AssigneeId = forUserId,
+ PermissionSetId = permSet.Id
+ );
+
+ insert new List{ permSetPermission, permSetAssignment };
+ }
+}
diff --git a/force-app/main/default/classes/test utilities/PermissionsHelper.cls-meta.xml b/force-app/main/default/classes/test utilities/PermissionsHelper.cls-meta.xml
new file mode 100644
index 0000000..5f399c3
--- /dev/null
+++ b/force-app/main/default/classes/test utilities/PermissionsHelper.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 63.0
+ Active
+
diff --git a/force-app/main/default/classes/test utilities/SObjectFactory.cls b/force-app/main/default/classes/test utilities/SObjectFactory.cls
new file mode 100644
index 0000000..ecd1511
--- /dev/null
+++ b/force-app/main/default/classes/test utilities/SObjectFactory.cls
@@ -0,0 +1,197 @@
+/**
+ * @description a factory class for generating test data.
+ * This class auto populates required fields and, by default, generates a plausible but fake ID for the record.
+ * You can use this class directly, or use one of the associated builder classes for generating
+ * complex data structures.
+ *
+ * This class has the ability to set default values for fields in 3, hierarchical ways.
+ * 1. The default values passed into the SObjectFactory via the sObject prototype.
+ * 2. The default values defined by the custom default class specified by the usingDefaultsClassName parameter.
+ * 3. The default values found in the 'org-wide' defaults class defined in the SObjectFactoryDefaults' subclasses.
+ *
+ * It's important to note that *nothing* overwrites field values specified in the SObject prototype.
+ *
+ * Some profiling information:
+ * | Action | Avg of 10 test executions |
+ * |-----------|------------------------|
+ * | Create 10 Single SObject w/ fake Id | 58ms |
+ * | Create 10 Single SObjects w/ fake Id and Custom Defaults | 73ms |
+ * | Create 10 Single SObjects & insert them | 550ms |
+ * | Create list of 200 SObjects w/ fake Id | 115ms |
+ * | Create list of 200 SObjects & insert them | 1216ms |
+ */
+@IsTest
+public with sharing class SObjectFactory {
+ /**
+ * @description Use the FieldDefaults interface to set up field/value keys you want to routinely impose on your
+ * factory generated objects.
+ */
+ public interface FieldDefaults {
+ /**
+ * @description Interface used by implementing classes to define defaults.
+ * @return `Map`
+ */
+ Map getFieldDefaults();
+ }
+
+ /**
+ * @description convenience method allowing you to specify just the prototype
+ * @param prototype SObject any SObject
+ * @return A created SObject with required fields populated
+ */
+ public static SObject createSObject(SObject prototype) {
+ return createSObject(prototype, null, false);
+ }
+
+ /**
+ * @description Convenience method for creating SObjects using just the prototype and a custom defaults class
+ * @param prototype SObject - any constructed SObject
+ * @param usingDefaultsClassName String - the name of the class to use for custom default values
+ * @return A created SObject with required fields populated
+ */
+ public static SObject createSObject(
+ SObject prototype,
+ String usingDefaultsClassName
+ ) {
+ return createSObject(prototype, usingDefaultsClassName, false);
+ }
+
+ /**
+ * @description Creates an SObject with the given prototype and forceInsert flag.
+ *
+ * @param prototype The prototype SObject.
+ * @param forceInsert Flag indicating whether to force insertion.
+ *
+ * @return The created SObject.
+ */
+ public static SObject createSObject(SObject prototype, Boolean forceInsert) {
+ return createSObject(prototype, null, forceInsert);
+ }
+
+ /**
+ * @description Creates an SObject with the given prototype, using defaults from the specified class, and optionally inserts it into the database.
+ *
+ * @param prototype The prototype SObject to be created.
+ * @param usingDefaultsClassName The name of the class providing default values for the SObject.
+ * @param forceInsert Indicates whether to insert the SObject into the database.
+ *
+ * @return The created SObject.
+ */
+ public static SObject createSObject(
+ SObject prototype,
+ String usingDefaultsClassName,
+ Boolean forceInsert
+ ) {
+ prototype = internalCreateSObject(prototype, usingDefaultsClassName);
+ if (forceInsert) {
+ Database.insert(prototype, AccessLevel.SYSTEM_MODE);
+ } else {
+ prototype.Id = IdFactory.get(prototype);
+ }
+ return prototype;
+ }
+
+ private static SObject internalCreateSObject(
+ SObject prototype,
+ String usingDefaultsClassName
+ ) {
+ // ensure the defaults class is not null
+ String functionalDefaultsClassName = String.isNotEmpty(
+ usingDefaultsClassName
+ )
+ ? usingDefaultsClassName
+ : '';
+ // First the specified defaults class
+ prototype = SObjectFactoryHelper.applyCustomDefaults(
+ prototype,
+ functionalDefaultsClassName
+ );
+ // then the 'org-wide' defaults
+ prototype = SObjectFactoryHelper.applyOrgWideObjectDefaults(prototype);
+ return prototype;
+ }
+
+ /**
+ * @description Creates a list of SObjects based on the provided prototype and count.
+ *
+ * @param prototype The SObject prototype to base the created SObjects on.
+ * @param count The number of SObjects to create.
+ *
+ * @return A list of created SObjects.
+ */
+ public static List createSObjects(SObject prototype, Integer count) {
+ return createSObjects(prototype, count, null, false);
+ }
+
+ /**
+ * @description Creates a list of SObjects based on the provided prototype, count, and defaults class name.
+ *
+ * @param prototype The prototype SObject to base the new SObjects on.
+ * @param count The number of SObjects to create.
+ * @param usingDefaultsClassName The name of the class to use for default values.
+ *
+ * @return A list of created SObjects.
+ */
+ public static List createSObjects(
+ SObject prototype,
+ Integer count,
+ String usingDefaultsClassName
+ ) {
+ return createSObjects(prototype, count, usingDefaultsClassName, false);
+ }
+
+ /**
+ * @description Creates a list of sObjects based on the provided prototype, count, and forceInsert flag.
+ *
+ * @param prototype The prototype SObject to base the created objects on.
+ * @param count The number of SObjects to create.
+ * @param forceInsert Flag indicating whether to force insertion of the created SObjects.
+ *
+ * @return List of created SObjects.
+ */
+ public static List createSObjects(
+ SObject prototype,
+ Integer count,
+ Boolean forceInsert
+ ) {
+ return createSObjects(prototype, count, null, forceInsert);
+ }
+
+ /**
+ * @description Creates a list of SObjects based on a prototype, count, defaults class, and force insert flag.
+ *
+ * @param prototype The prototype SObject to clone.
+ * @param count The number of SObjects to create.
+ * @param usingDefaultsClassName The name of the defaults class to use.
+ * @param forceInsert Whether to force insert the created SObjects.
+ *
+ * @return A list of created SObjects.
+ */
+ @SuppressWarnings('PMD.ExcessiveParameterList')
+ public static List createSObjects(
+ SObject prototype,
+ Integer count,
+ String usingDefaultsClassName,
+ Boolean forceInsert
+ ) {
+ List createdSObjects = new List();
+ SObject constructedFromPrototype = internalCreateSObject(
+ prototype,
+ usingDefaultsClassName
+ );
+ for (
+ Integer iterationCounter = 0; iterationCounter < count; iterationCounter++
+ ) {
+ SObject clonedSObject = constructedFromPrototype.clone(false, true);
+ createdSObjects.add(
+ SObjectFactoryHelper.mutateCloneToRespectNameAndAutonumberRules(
+ clonedSObject,
+ !forceInsert,
+ iterationCounter
+ )
+ );
+ }
+ SObjectFactoryHelper.insertIfForced(createdSObjects, forceInsert);
+ return createdSObjects;
+ }
+}
diff --git a/force-app/main/default/classes/test utilities/SObjectFactory.cls-meta.xml b/force-app/main/default/classes/test utilities/SObjectFactory.cls-meta.xml
new file mode 100644
index 0000000..5f399c3
--- /dev/null
+++ b/force-app/main/default/classes/test utilities/SObjectFactory.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 63.0
+ Active
+
diff --git a/force-app/main/default/classes/test utilities/SObjectFactoryDefaults.cls b/force-app/main/default/classes/test utilities/SObjectFactoryDefaults.cls
new file mode 100644
index 0000000..1b090e3
--- /dev/null
+++ b/force-app/main/default/classes/test utilities/SObjectFactoryDefaults.cls
@@ -0,0 +1,72 @@
+@IsTest
+public class SObjectFactoryDefaults {
+ // To specify defaults for objects, use the naming convention [ObjectName]Defaults.
+ // For custom objects, omit the __c from the Object Name
+
+ /**
+ * @description Default values for Account object
+ */
+ public class AccountDefaults implements SObjectFactory.FieldDefaults {
+ /**
+ * @description Returns a map of field defaults for the Account object.
+ *
+ * @return A map containing the default values for specific Account fields.
+ */
+ public Map getFieldDefaults() {
+ return new Map{
+ Account.Name => 'Test Account'
+ };
+ }
+ }
+
+ /**
+ * @description This class provides default field values for Contact objects.
+ */
+ public class ContactDefaults implements SObjectFactory.FieldDefaults {
+ /**
+ * @description Returns a map of field defaults for the Contact object.
+ *
+ * @return A map where keys are Schema.SObjectField representing Contact fields and values are the default values for those fields.
+ */
+ public Map getFieldDefaults() {
+ return new Map{
+ Contact.FirstName => 'First',
+ Contact.LastName => 'Last'
+ };
+ }
+ }
+
+ /**
+ * @description This class provides default field values for Opportunity objects.
+ */
+ public class OpportunityDefaults implements SObjectFactory.FieldDefaults {
+ /**
+ * @description Returns a map of default field values for Opportunity.
+ *
+ * @return Map A map containing default field values.
+ */
+ public Map getFieldDefaults() {
+ return new Map{
+ Opportunity.Name => 'Test Opportunity',
+ Opportunity.StageName => 'Closed Won',
+ Opportunity.CloseDate => System.today()
+ };
+ }
+ }
+
+ /**
+ * @description Defaults for Case SObjectFactory
+ */
+ public class CaseDefaults implements SObjectFactory.FieldDefaults {
+ /**
+ * @description Returns a map of field defaults for the Case object.
+ *
+ * @return A map where the key is the Schema.SObjectField and the value is the default value.
+ */
+ public Map getFieldDefaults() {
+ return new Map{
+ Case.Subject => 'Test Case'
+ };
+ }
+ }
+}
diff --git a/force-app/main/default/classes/test utilities/SObjectFactoryDefaults.cls-meta.xml b/force-app/main/default/classes/test utilities/SObjectFactoryDefaults.cls-meta.xml
new file mode 100644
index 0000000..5f399c3
--- /dev/null
+++ b/force-app/main/default/classes/test utilities/SObjectFactoryDefaults.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 63.0
+ Active
+
diff --git a/force-app/main/default/classes/test utilities/SObjectFactoryHelper.cls b/force-app/main/default/classes/test utilities/SObjectFactoryHelper.cls
new file mode 100644
index 0000000..17ef687
--- /dev/null
+++ b/force-app/main/default/classes/test utilities/SObjectFactoryHelper.cls
@@ -0,0 +1,147 @@
+/**
+ * @description This class contains helper methods used by the SObjectFactory.
+ * While a bit of an anti-pattern, this class' contents allow SObjectFactory to be more-focused.
+ * @see SObjectFactory
+ */
+@IsTest
+public class SObjectFactoryHelper {
+ /// Internal Properties
+ /** @description Memoizes the result of a describe call for same-transaction caching */
+ private static final Map MEMOIZED_AUTO_NUMBER_NAME_FIELD = new Map{};
+
+ /**
+ * @description While the vast majority of Salesforce objects have a Name field, some objects do not.
+ * This map allows you to override the default name field for a given object type and covers the two
+ * standard objects that do not have a name field.
+ */
+ private static final Map NAME_FIELD_OVERRIDE_MAP = new Map{
+ Contact.SObjectType => Contact.LastName, // Contact uses LastName as the name field
+ Case.SObjectType => Case.CaseNumber // This is the autonumber field
+ };
+
+ /**
+ * @description responsible for enforcing a cloned SObject's values honor uniqueness rules for name and
+ * autonumber fields
+ * @param clonedSObject SObject a cloned version of the prototyped SObject
+ * @param fakeId Boolean Populates a fakeId on the object unless it's to be inserted
+ * @param iterationCounter Integer Counter for ensuring name uniqueness
+ * @return SObject a mutated version of the cloned object with unique name and autonumber fields
+ */
+ public static SObject mutateCloneToRespectNameAndAutonumberRules(
+ SObject clonedSObject,
+ Boolean fakeId,
+ Integer iterationCounter
+ ) {
+ String nameField = calculateNameField(clonedSObject);
+ Boolean isNameFieldAutoNumber = nameFieldIsAutoNumber(
+ clonedSObject,
+ nameField
+ );
+
+ if (!isNameFieldAutoNumber) {
+ clonedSObject.put(
+ nameField,
+ (String) clonedSObject.get(nameField) + ' ' + iterationCounter
+ );
+ }
+ if (fakeId) {
+ clonedSObject.Id = IdFactory.get(clonedSObject);
+ }
+ return clonedSObject;
+ }
+
+ /**
+ * @description responsible for inserting a list of SObjects into the database.
+ * @param objectsToInsert List a list of SObjects to be inserted into the database
+ * @param forceInsert Boolean a flag to force insert the objects into the database
+ */
+ public static void insertIfForced(
+ List objectsToInsert,
+ Boolean forceInsert
+ ) {
+ if (forceInsert) {
+ Database.insert(objectsToInsert, false, AccessLevel.SYSTEM_MODE);
+ }
+ }
+
+ /**
+ * @description responsible for applying org-wide field defaults to a SObject.
+ * @param prototype SObject a SObject to be mutated
+ * @return SObject a mutated version of the prototype object with field defaults applied
+ */
+ public static SObject applyOrgWideObjectDefaults(SObject prototype) {
+ String objectName = String.valueOf(prototype.getSObjectType());
+ String defaultsClassName =
+ 'SObjectFactoryDefaults.' +
+ objectName.replaceAll('__(c|C)$', '') +
+ 'Defaults';
+ return applyCustomDefaults(prototype, defaultsClassName);
+ }
+
+ /**
+ * @description responsible for applying custom field defaults to a SObject.
+ * @param prototype SObject a SObject to be mutated
+ * @param usingDefaultsClassName String the name of the class to be used for field defaults
+ * @return SObject a mutated version of the prototype object with field defaults applied
+ */
+ public static SObject applyCustomDefaults(
+ SObject prototype,
+ String usingDefaultsClassName
+ ) {
+ SObjectFactory.FieldDefaults defaults = getFieldDefaultsInstanceFromName(
+ usingDefaultsClassName
+ );
+ return applyFieldDefaults(prototype, defaults?.getFieldDefaults());
+ }
+
+ private static String calculateNameField(SObject prototype) {
+ String nameField = String.valueOf(
+ NAME_FIELD_OVERRIDE_MAP.get(prototype.getSObjectType())
+ );
+ return (nameField == null) ? 'Name' : nameField;
+ }
+
+ private static Boolean nameFieldIsAutoNumber(
+ SObject prototype,
+ String nameField
+ ) {
+ if (
+ !MEMOIZED_AUTO_NUMBER_NAME_FIELD.containsKey(prototype.getSObjectType())
+ ) {
+ MEMOIZED_AUTO_NUMBER_NAME_FIELD.put(
+ prototype.getSObjectType(),
+ prototype.getSObjectType()
+ .getDescribe()
+ .fields.getMap()
+ .get(nameField)
+ .getDescribe()
+ .isAutoNumber()
+ );
+ }
+ return MEMOIZED_AUTO_NUMBER_NAME_FIELD.get(prototype.getSObjectType());
+ }
+
+ private static SObjectFactory.FieldDefaults getFieldDefaultsInstanceFromName(
+ String nameOfClassToInstantiate
+ ) {
+ Type defaultsClassToUse = Type.forName(nameOfClassToInstantiate);
+ return (SObjectFactory.FieldDefaults) defaultsClassToUse?.newInstance();
+ }
+
+ private static SObject applyFieldDefaults(
+ SObject prototype,
+ Map defaults
+ ) {
+ if (defaults == null) {
+ return prototype;
+ }
+ Map prototypesPopulatedFields = prototype.getPopulatedFieldsAsMap();
+ for (Schema.SObjectField field : defaults.keySet()) {
+ if (prototypesPopulatedFields.containsKey(String.valueOf(field))) {
+ continue;
+ }
+ prototype.put(field, defaults.get(field));
+ }
+ return prototype;
+ }
+}
diff --git a/force-app/main/default/classes/test utilities/SObjectFactoryHelper.cls-meta.xml b/force-app/main/default/classes/test utilities/SObjectFactoryHelper.cls-meta.xml
new file mode 100644
index 0000000..5f399c3
--- /dev/null
+++ b/force-app/main/default/classes/test utilities/SObjectFactoryHelper.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 63.0
+ Active
+
diff --git a/force-app/main/default/classes/test utilities/SObjectFactoryTests.cls b/force-app/main/default/classes/test utilities/SObjectFactoryTests.cls
new file mode 100644
index 0000000..c119f29
--- /dev/null
+++ b/force-app/main/default/classes/test utilities/SObjectFactoryTests.cls
@@ -0,0 +1,164 @@
+/**
+ * @description Tests for test utilities.
+ */
+@IsTest
+@SuppressWarnings('PMD.AvoidDebugStatements')
+private class SObjectFactoryTests {
+ @IsTest
+ static void profilingTestCreate10IndividualSObjectAccountNoInsertPositive() {
+ Test.startTest();
+ Long startTime = Datetime.now().getTime();
+ for (Integer index = 0; index < 10; index++) {
+ SObjectFactory.createSObject(new Account());
+ }
+ Long stopTime = Datetime.now().getTime();
+ Test.stopTest();
+ System.debug(
+ LoggingLevel.DEBUG,
+ 'Time to create 10 accounts: ' + (stopTime - startTime) + 'ms'
+ );
+ Assert.isTrue(
+ (stopTime - startTime) < 10000,
+ 'expected time to create 10 accounts to be less than 75ms'
+ );
+ }
+
+ @IsTest
+ static void profilingTestCreate10IndividualSObjectAccountNoInsertWithCustomDefaultsPositive() {
+ Test.startTest();
+ Long startTime = Datetime.now().getTime();
+ for (Integer index = 0; index < 10; index++) {
+ SObjectFactory.createSObject(new Account(), 'SomeDefaultsClass');
+ }
+ Long stopTime = Datetime.now().getTime();
+ Test.stopTest();
+ System.debug(
+ LoggingLevel.DEBUG,
+ 'Time to create 10 accounts: ' + (stopTime - startTime) + 'ms'
+ );
+ Assert.isTrue(
+ (stopTime - startTime) < 10000,
+ 'expected time to create 10 accounts to be less than 75ms'
+ );
+ }
+
+ @IsTest
+ static void profilingTestCreate10IndividualSObjectAccountWithInsertPositive() {
+ Test.startTest();
+ Long startTime = Datetime.now().getTime();
+ for (Integer index = 0; index < 10; index++) {
+ SObjectFactory.createSObject(new Account(), true);
+ }
+ Long stopTime = Datetime.now().getTime();
+ Test.stopTest();
+ System.debug(
+ LoggingLevel.DEBUG,
+ 'Time to create & insert 10 accounts: ' + (stopTime - startTime) + 'ms'
+ );
+ Assert.isTrue(
+ (stopTime - startTime) < 10000,
+ 'expected time to create 10 accounts to be less than 1500ms'
+ );
+ }
+
+ @IsTest
+ static void profilingTestCreateListOf200SObjectsNoInsertPositive() {
+ Test.startTest();
+ Long startTime = Datetime.now().getTime();
+ SObjectFactory.createSObjects(new Account(), 200);
+ Long stopTime = Datetime.now().getTime();
+ Test.stopTest();
+ System.debug(
+ LoggingLevel.DEBUG,
+ 'Time to create a list of 200 accounts: ' + (stopTime - startTime) + 'ms'
+ );
+ Assert.isTrue(
+ (stopTime - startTime) < 10000,
+ 'expected time to create a list of 200 accounts to be less than 150ms'
+ );
+ }
+
+ @IsTest
+ static void profilingTestCreateListOf200SObjectsWithInsertPositive() {
+ Test.startTest();
+ Long startTime = Datetime.now().getTime();
+ SObjectFactory.createSObjects(new Account(), 200, true);
+ Long stopTime = Datetime.now().getTime();
+ Test.stopTest();
+ System.debug(
+ LoggingLevel.DEBUG,
+ 'Time to create and insert a list of 200 accounts: ' +
+ (stopTime - startTime) +
+ 'ms'
+ );
+ Assert.isTrue(
+ (stopTime - startTime) < 10000,
+ 'expected time to create and insert a list of 200 accounts to be less than 1500ms'
+ );
+ }
+
+ @IsTest
+ static void funcTestCreateSObjectsWith200AccountsNoCustomDefaultsPositive() {
+ Test.startTest();
+ SObjectFactory.createSObjects(new Account(), 200, null, true);
+ Test.stopTest();
+ Account[] checkAccounts = [SELECT Id FROM Account];
+ Assert.areEqual(
+ 200,
+ checkAccounts.size(),
+ 'expected 200 accounts to be created'
+ );
+ }
+
+ @IsTest
+ static void funcTestCreateSObjectsWith200AccountsWithCustomDefaultsPositive() {
+ Test.startTest();
+ SObjectFactory.createSObjects(
+ new Account(),
+ 200,
+ 'SomeDefaultsClass',
+ true
+ );
+ Test.stopTest();
+ Account[] checkAccounts = [SELECT Id FROM Account];
+ Assert.areEqual(
+ 200,
+ checkAccounts.size(),
+ 'expected 200 accounts to be created'
+ );
+ }
+
+ @IsTest
+ static void funcTestCreateSObjectsWith200AccountsWithCustomDefaultsProvesUniqueIdsPositive() {
+ Test.startTest();
+ List checkAccounts = (List) SObjectFactory.createSObjects(
+ new Account(),
+ 200,
+ 'SomeDefaultsClass',
+ false
+ );
+ Test.stopTest();
+ Set fakeIds = new Set();
+ for (Account accountToCheck : checkAccounts) {
+ fakeIds.add(accountToCheck.Id);
+ }
+ Assert.areEqual(
+ 200,
+ fakeIds.size(),
+ 'Expected a set of unique ids to be the same size as the requested number of objects to be created'
+ );
+ }
+
+ @IsTest
+ static void funcTestCreateSObjectWithAccountAndInsertPositive() {
+ Test.startTest();
+ SObjectFactory.createSObject(new Account(), 'SomeDefaultsClass', true);
+ Test.stopTest();
+ Account[] checkAccounts = [SELECT Id FROM Account];
+ Assert.areEqual(
+ 1,
+ checkAccounts.size(),
+ 'expected 1 accounts to be created'
+ );
+ }
+}
diff --git a/force-app/main/default/classes/test utilities/SObjectFactoryTests.cls-meta.xml b/force-app/main/default/classes/test utilities/SObjectFactoryTests.cls-meta.xml
new file mode 100644
index 0000000..5f399c3
--- /dev/null
+++ b/force-app/main/default/classes/test utilities/SObjectFactoryTests.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 63.0
+ Active
+
diff --git a/force-app/main/default/classes/test utilities/SomeDefaultsClass.cls b/force-app/main/default/classes/test utilities/SomeDefaultsClass.cls
new file mode 100644
index 0000000..56c20c4
--- /dev/null
+++ b/force-app/main/default/classes/test utilities/SomeDefaultsClass.cls
@@ -0,0 +1,20 @@
+/**
+ * @description This class provides an example implementation of the FieldDefaults interface.
+ * It provides default values for the Account object.
+ */
+@IsTest
+public with sharing class SomeDefaultsClass implements SObjectFactory.FieldDefaults {
+ /**
+ * @description Returns a map of field defaults for the given object.
+ * While this specifies the Account object, it could be any object.
+ * Just ensure that key returned in this map is valid Object.Field syntax
+ * for an SObject field.
+ *
+ * @return Map of Schema.SObjectField to Object containing field defaults
+ */
+ public Map getFieldDefaults() {
+ return new Map{
+ Account.Name => 'Test Account'
+ };
+ }
+}
diff --git a/force-app/main/default/classes/test utilities/SomeDefaultsClass.cls-meta.xml b/force-app/main/default/classes/test utilities/SomeDefaultsClass.cls-meta.xml
new file mode 100644
index 0000000..5f399c3
--- /dev/null
+++ b/force-app/main/default/classes/test utilities/SomeDefaultsClass.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 63.0
+ Active
+
diff --git a/force-app/main/default/classes/test utilities/UserFactory.cls b/force-app/main/default/classes/test utilities/UserFactory.cls
new file mode 100644
index 0000000..429751a
--- /dev/null
+++ b/force-app/main/default/classes/test utilities/UserFactory.cls
@@ -0,0 +1,71 @@
+/**
+ * @description A factory class for generating Users during Unit Tests
+ * While this class has generic user-building methods, the bulk of this
+ * class is dedicated to building persona-specific users
+ */
+@IsTest
+public class UserFactory {
+ /// Persona based factory methods
+
+ /**
+ * @description Creates a user with the Minimum Access Profile
+ * @param forceInsert Should this code insert the user?
+ * @return `User`
+ */
+ public static User createMinAccessPersonaUser(Boolean forceInsert) {
+ return createTestUser(
+ UserFactoryHelper.fetchProfileIdByName('Minimum Access - Salesforce'),
+ forceInsert
+ );
+ }
+
+ /**
+ * @description creates a Marketing Persona user
+ * @param forceInsert Boolean should this user be inserted?
+ * @return User created marketing user
+ */
+ public static User createMarketingPersonaUser(Boolean forceInsert) {
+ User createdUser = createTestUser('Marketing User', forceInsert);
+ return createdUser;
+ }
+
+ /// Generic user factory methods
+
+ /**
+ * @description creates a test user. Useful for permissions testing
+ * @param profileId Profile Id to use when creating a user.
+ * @param forceInsert Boolean, should this code insert the user?
+ * @return `User`
+ */
+ public static User createTestUser(Id profileId, Boolean forceInsert) {
+ User userToCreate = new User(
+ ProfileId = profileId,
+ LastName = 'last',
+ Email = 'Testuser@test.example.com',
+ Username = 'Testuser@test.example.com' +
+ Math.abs(Crypto.getRandomInteger()),
+ CompanyName = 'TEST',
+ Title = 'title',
+ Alias = 'alias',
+ TimeZoneSidKey = 'America/Los_Angeles',
+ EmailEncodingKey = 'UTF-8',
+ LanguageLocaleKey = 'en_US',
+ LocaleSidKey = 'en_US'
+ );
+ UserFactoryHelper.forceInsert(userToCreate, forceInsert);
+ return userToCreate;
+ }
+
+ /**
+ * @description Creates a test user with a given profile.
+ * @param profileName Name of the profile to create the user with.
+ * @param forceInsert Should this code insert the created user?
+ * @return `User`
+ */
+ public static User createTestUser(String profileName, Boolean forceInsert) {
+ return createTestUser(
+ UserFactoryHelper.fetchProfileIdByName(profileName),
+ forceInsert
+ );
+ }
+}
diff --git a/force-app/main/default/classes/test utilities/UserFactory.cls-meta.xml b/force-app/main/default/classes/test utilities/UserFactory.cls-meta.xml
new file mode 100644
index 0000000..5f399c3
--- /dev/null
+++ b/force-app/main/default/classes/test utilities/UserFactory.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 63.0
+ Active
+
diff --git a/force-app/main/default/classes/test utilities/UserFactoryHelper.cls b/force-app/main/default/classes/test utilities/UserFactoryHelper.cls
new file mode 100644
index 0000000..3e7fd84
--- /dev/null
+++ b/force-app/main/default/classes/test utilities/UserFactoryHelper.cls
@@ -0,0 +1,58 @@
+/**
+ * @description Helper class for the UserFactory class.
+ * Responsible for manipulation of users and permissions
+ *
+ * Note: This class does *no* error handling. Because it's used in the setup and preparation of test data,
+ * it's on the developer to ensure the methods are called with existing, valid data like profile names.
+ * If, for instance, a developer fat-fingers a profile name, the query on ~ line 20 will fail, and throw
+ * an exception.
+ */
+
+@IsTest
+public class UserFactoryHelper {
+ /**
+ * @description Fetches the Profile Id by its name.
+ * @param profileName The name of the profile to fetch.
+ *
+ * @return The Id of the requested profile.
+ */
+ public static Id fetchProfileIdByName(String profileName) {
+ Profile requestedProfile = [
+ SELECT Id
+ FROM Profile
+ WHERE Name = :profileName
+ WITH SYSTEM_MODE
+ LIMIT 1
+ ];
+ return requestedProfile.Id;
+ }
+
+ /**
+ * @description Fetches the Permission Set ID by its name.
+ * @param permissionSetName The name of the Permission Set to fetch.
+ *
+ * @return The ID of the Permission Set.
+ */
+ public static Id fetchPermissionSetIdByName(String permissionSetName) {
+ Id permSetId = [
+ SELECT Id
+ FROM PermissionSet
+ WHERE Name = :permissionSetName
+ WITH SYSTEM_MODE
+ LIMIT 1
+ ]
+ .Id;
+ return permSetId;
+ }
+
+ /**
+ * @description Inserts a User record if forceInsert is true.
+ * @param userToInsert The User record to insert.
+ * @param forceInsert Boolean flag to determine if the insert should proceed.
+ */
+ public static void forceInsert(User userToInsert, Boolean forceInsert) {
+ if (forceInsert) {
+ insert userToInsert;
+ }
+ }
+}
diff --git a/force-app/main/default/classes/test utilities/UserFactoryHelper.cls-meta.xml b/force-app/main/default/classes/test utilities/UserFactoryHelper.cls-meta.xml
new file mode 100644
index 0000000..5f399c3
--- /dev/null
+++ b/force-app/main/default/classes/test utilities/UserFactoryHelper.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 63.0
+ Active
+