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 +