Last active
December 20, 2023 19:29
-
-
Save SpenceDiNicolantonio/c0fd1af9b2edbac3fdf3799b36a8bff7 to your computer and use it in GitHub Desktop.
[Salesforce ID generator] Generates 15-character Salesforce IDs for testing #salesforce #apex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* A collection of static utility methods for unit testing. | |
*/ | |
@IsTest | |
public class TestUtil { | |
// ID generator configuration | |
private static final Integer ID_RESERVED_CHARACTERS = 7; | |
private static final Integer ID_INCREMENTER_PREFIX_LENGTH = 1; | |
private static final String BASE62_ALPHABET = '0123456789abcdefghijklmnopqrstuvwqyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; | |
// Count of generated IDs, used as an incrementer in generateId() | |
private static Integer idCount = 1; | |
/** | |
* Generates a Base62 string that can be used to simulate a Salesforce ID. | |
* @return A 15-character Base62 string | |
*/ | |
public static Id generateId() { | |
// Salesforce IDs are 15-character Base62 strings. The first 7 characters will be the same for all IDs in the | |
// same environment, while the last 8 characters will be an incremented identifier | |
// (https://salesforce.stackexchange.com/questions/1653/what-are-salesforce-ids-composed-of) | |
// | |
// We'll use the current user's ID to determine the appropriate environment-specific characters. We'll set a | |
// number of characters following the 7th to 'Z', to make collisions with system-generated IDs unlikely. We'll | |
// then use a simple incrementing pattern to generate the remaining characters. | |
// Get environment-specific ID prefix from user ID | |
String envPrefix = UserInfo.getUserId().substring(0, ID_RESERVED_CHARACTERS); | |
String incPrefix = ''.rightPad(ID_INCREMENTER_PREFIX_LENGTH, BASE62_ALPHABET.substring(BASE62_ALPHABET.length() - 1)); | |
// Generate incremented value | |
Integer incrementLength = (15 - ID_RESERVED_CHARACTERS - ID_INCREMENTER_PREFIX_LENGTH); | |
String increment = convertBase(idCount, 62, BASE62_ALPHABET).leftPad(incrementLength, '0'); | |
// Increment counter and build ID | |
idCount++; | |
return envPrefix + incPrefix + increment; | |
} | |
/** | |
* Converts an integer value to a different numerical base, using a provided alphabet for representation. | |
* @param value Value to convert | |
* @param radix Numerical base to convert to | |
* @param alphabet Sequential set of characters defining the alphabet used to represent the resulting value | |
* @return The converted value | |
*/ | |
private static String convertBase(Integer value, Integer radix, String alphabet) { | |
if (radix > alphabet.length()) { | |
throw new AlphabetLengthException('Length of provided alphabet must be greater or equal to radix'); | |
} | |
// Short circuit in case of 0 | |
if (value == 0) { | |
return '0'; | |
} | |
// Convert input number to given base by repeatedly dividing it by base and taking remainder | |
String output = ''; | |
while (value > 0) { | |
Integer charIndex = Math.mod(value, radix); | |
output = alphabet.substring(charIndex, charIndex + 1) + output; | |
System.debug('output: ' + output); | |
value = value / radix; | |
} | |
return output; | |
} | |
//================================================================================================================== | |
// Exceptions | |
//================================================================================================================== | |
public class AlphabetLengthException extends Exception {} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@IsTest | |
public class TestUtilTest { | |
@IsTest | |
private static void shouldGenerateIds() { | |
Test.startTest(); | |
List<Id> ids = new List<Id>(); | |
for (Integer i = 0; i < 400; i++) { | |
Id idValue = TestUtil.generateId(); | |
ids.add(idValue); | |
} | |
Test.stopTest(); | |
String userIdPrefix = UserInfo.getUserId().substring(0, TestUtil.RESERVED_ID_CHARACTERS); | |
String incrementPrefix = ''.rightPad(TestUtil.ID_INCREMENT_PREFIX_CHARACTERS, 'Z'); | |
for (Integer i = 0; i < ids.size(); i++) { | |
String idString = String.valueOf(ids[i]); | |
// Prefix should match user ID | |
System.assert( | |
idString.startsWith(userIdPrefix), | |
'Expected first ' + TestUtil.RESERVED_ID_CHARACTERS + ' characters of ID to match user ID: ' + idString | |
); | |
// Incrementing value should start with prefix | |
System.assert( | |
idString.substring(TestUtil.RESERVED_ID_CHARACTERS).startsWith(incrementPrefix), | |
'Expected ID to contain \'' + incrementPrefix + '\': ' + idString | |
); | |
// last characters should increment in Base62 | |
Integer char1Index = Math.Mod(i, 62); | |
String char1 = TestUtil.BASE62_ALPHABET.substring(char1Index, char1Index + 1); | |
Integer char2Index = Math.Mod(i/62, 62); | |
String char2 = TestUtil.BASE62_ALPHABET.substring(char2Index, char2Index + 1); | |
Integer char3Index = Math.Mod(i/62/62, 62); | |
String char3 = TestUtil.BASE62_ALPHABET.substring(char3Index, char3Index + 1); | |
System.assert( | |
idString.substring(0, 15).endsWith(char3 + char2 + char1), | |
'Expected ID to end with \'' + char3 + char2 + char1 + '\': ' + idString | |
); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
private static final String BASE62_ALPHABET = '0123456789abcdefghijklmnopqrstuvwqyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
The BASE62_ALPHABET string has an extra lower case 'q' instead of an 'x'.