diff --git a/.husky/pre-commit b/.husky/pre-commit old mode 100644 new mode 100755 diff --git a/README.md b/README.md index 3ad66f6f..7f9760f6 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,15 @@ A flexible cache management system for Salesforce Apex developers. Built to be s Learn more about the history & implementation of this repo in [the Joys of Apex article 'Iteratively Building a Flexible Caching System for Apex'](https://www.jamessimone.net/blog/joys-of-apex/iteratively-building-a-flexible-caching-system/) -## Unlocked Package - `Nebula` Namespace - v1.0.0 +## Unlocked Package - `Nebula` Namespace - v1.0.1 -[![Install Unlocked Package (Nebula namespace) in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015n2AQAQ) -[![Install Unlocked Package (Nebula namespace) in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015n2AQAQ) +[![Install Unlocked Package (Nebula namespace) in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015n6rQAA) +[![Install Unlocked Package (Nebula namespace) in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015n6rQAA) -## Unlocked Package - No Namespace - v1.0.0 +## Unlocked Package - No Namespace - v1.0.1 -[![Install Unlocked Package (no namespace) in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015n25QAA) -[![Install Unlocked Package (no namespace) in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015n25QAA) +[![Install Unlocked Package (no namespace) in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015n6hQAA) +[![Install Unlocked Package (no namespace) in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015n6hQAA) --- diff --git a/nebula-cache-manager/core/classes/CacheManager.cls b/nebula-cache-manager/core/classes/CacheManager.cls index 131f0e10..8dfabe6c 100644 --- a/nebula-cache-manager/core/classes/CacheManager.cls +++ b/nebula-cache-manager/core/classes/CacheManager.cls @@ -41,6 +41,7 @@ public without sharing class CacheManager { Map contains(Set keys); Boolean containsAll(Set keys); Object get(String key); + Object get(String key, System.Type cacheBuilderClass); Map get(Set keys); Map getAll(); Set getKeys(); @@ -86,6 +87,7 @@ public without sharing class CacheManager { cacheTypeToMockPartitionProxy.put(cacheType, mockPartitionProxy); } + @TestVisible private static void validateKey(String key) { Matcher regexMatcher = ALPHANUMERIC_REGEX_PATTERN.matcher(key); if (regexMatcher.matches() == false) { @@ -161,7 +163,7 @@ public without sharing class CacheManager { public Boolean containsAll(Set keys) { Map keyToContainsResult = this.contains(keys); - if (keyToContainsResult == null || keyToContainsResult.isEmpty() == true) { + if (keyToContainsResult.isEmpty() == true) { return false; } @@ -188,6 +190,19 @@ public without sharing class CacheManager { } } + public Object get(String key, System.Type cacheBuilderClass) { + if (this.fallbackTransactionCache.contains(key)) { + return this.fallbackTransactionCache.get(key); + } else if (this.cachePartitionProxy.isAvailable() == false) { + return this.fallbackTransactionCache.get(key, cacheBuilderClass); + } else { + // Cache.CacheBuilder.doLoad method can return null + Object value = this.cachePartitionProxy.get(key, cacheBuilderClass); + this.fallbackTransactionCache.put(key, value); + return value; + } + } + public Map get(Set keys) { Map keyToValue = this.cachePartitionProxy.get(keys); if (keyToValue == null) { @@ -315,6 +330,10 @@ public without sharing class CacheManager { return this.platformCachePartition?.get(key); } + public virtual Object get(String key, System.Type cacheBuilderClass) { + return this.platformCachePartition?.get(cacheBuilderClass, key); + } + public virtual Map get(Set keys) { return this.platformCachePartition?.get(keys); } @@ -367,6 +386,15 @@ public without sharing class CacheManager { return this.keyToValue.get(key); } + public Object get(String key, System.Type cacheBuilderClass) { + if (this.contains(key) == false) { + Cache.CacheBuilder cacheBuilder = (Cache.CacheBuilder) cacheBuilderClass.newInstance(); + Object value = cacheBuilder.doLoad(key); + this.put(key, value); + } + return this.get(key); + } + public Map get(Set keys) { Map matchingKeyToValue = new Map(); for (String key : keys) { diff --git a/nebula-cache-manager/core/classes/CacheManager_Tests.cls b/nebula-cache-manager/core/classes/CacheManager_Tests.cls index 061a95fd..cde518b3 100644 --- a/nebula-cache-manager/core/classes/CacheManager_Tests.cls +++ b/nebula-cache-manager/core/classes/CacheManager_Tests.cls @@ -19,6 +19,40 @@ private class CacheManager_Tests { CacheManager.CONFIGURED_CACHE_VALUES.clear(); } + @IsTest + static void it_does_not_put_key_in_organisation_cache_when_configuration_is_disabled() { + String mockKey = 'SomeKey'; + CacheConfiguration__mdt disabledConfiguration = new CacheConfiguration__mdt( + IsEnabled__c = false, + IsImmutable__c = true, + PlatformCachePartitionName__c = 'Mock' + ); + User mockValue = new User(Id = System.UserInfo.getUserId(), ProfileId = System.UserInfo.getProfileId()); + CacheManager.Cacheable cache = CacheManager.getOrganizationCache(disabledConfiguration); + System.Assert.isFalse(cache.contains(mockKey)); + + cache.put(mockKey, mockValue); + + System.Assert.isFalse(cache.contains(mockKey)); + } + + @IsTest + static void it_does_not_put_key_in_session_cache_when_configuration_is_disabled() { + String mockKey = 'SomeKey'; + CacheConfiguration__mdt disabledConfiguration = new CacheConfiguration__mdt( + IsEnabled__c = false, + IsImmutable__c = true, + PlatformCachePartitionName__c = 'Mock' + ); + User mockValue = new User(Id = System.UserInfo.getUserId(), ProfileId = System.UserInfo.getProfileId()); + CacheManager.Cacheable cache = CacheManager.getSessionCache(disabledConfiguration); + System.Assert.isFalse(cache.contains(mockKey)); + + cache.put(mockKey, mockValue); + + System.Assert.isFalse(cache.contains(mockKey)); + } + @IsTest static void it_adds_new_key_to_transaction_cache() { String mockKey = 'SomeKey'; @@ -43,6 +77,23 @@ private class CacheManager_Tests { System.Assert.areEqual(mockValue, CacheManager.getTransactionCache().get(mockKey)); } + @IsTest + static void it_adds_new_key_with_cachebuilder_to_transaction_cache_when_get_key() { + String mockKey = 'SomeKey'; + System.Type cacheBuilderStubType = CacheBuilderStub.class; + System.Assert.isFalse(CacheManager.getTransactionCache().contains(mockKey)); + + Object generatedValue = CacheManager.getTransactionCache().get(mockKey, cacheBuilderStubType); + + System.Assert.isNotNull(generatedValue); + System.Assert.isTrue(CacheManager.getTransactionCache().contains(mockKey)); + + Object cachedValue = CacheManager.getTransactionCache().get(mockKey, cacheBuilderStubType); + + System.Assert.isNotNull(cachedValue); + System.Assert.areEqual(generatedValue, cachedValue); + } + @IsTest static void it_adds_configured_key_and_value_to_transaction_cache() { String mockKey = 'SomeKey'; @@ -96,6 +147,7 @@ private class CacheManager_Tests { @IsTest static void it_supports_bulk_operations_in_transaction_cache() { + System.Assert.isTrue(CacheManager.getTransactionCache().containsAll(new Set())); System.Assert.isTrue(CacheManager.getTransactionCache().isAvailable()); Map keyToValue = new Map{ 'SomeDate' => Date.newInstance(1999, 9, 9), @@ -121,7 +173,7 @@ private class CacheManager_Tests { } @IsTest - static void it_remove_alls_keys_in_tranaction_cache_when_remove_all_method_is_called() { + static void it_remove_alls_keys_in_transaction_cache_when_remove_all_method_is_called() { System.Assert.isTrue(CacheManager.getTransactionCache().isAvailable()); Map keyToValue = new Map{ 'SomeDate' => Date.newInstance(1999, 9, 9), @@ -157,6 +209,84 @@ private class CacheManager_Tests { System.Assert.areEqual(mockValue, CacheManager.getOrganizationCache().get(mockKey)); } + @IsTest + static void it_gets_value_from_organization_cache_when_transaction_cache_miss_and_organization_platform_cache_is_available() { + String mockKey = 'SomeKey'; + User mockValue = new User(Id = System.UserInfo.getUserId(), ProfileId = System.UserInfo.getProfileId()); + MockPlatformCachePartitionProxy mockOrganizationPartitionProxy = new MockPlatformCachePartitionProxy(true); + mockOrganizationPartitionProxy.put(mockKey, mockValue, 3600, Cache.Visibility.ALL, false); + System.Assert.isTrue(mockOrganizationPartitionProxy.isAvailable()); + CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.ORGANIZATION, mockOrganizationPartitionProxy); + System.Assert.areEqual(0, mockOrganizationPartitionProxy.containsMethodCallCount); + System.Assert.isTrue(CacheManager.getOrganizationCache().contains(mockKey)); + System.Assert.areEqual(1, mockOrganizationPartitionProxy.containsMethodCallCount); + System.Assert.areEqual(0, mockOrganizationPartitionProxy.getMethodCallCount); + + Object actualResult = CacheManager.getOrganizationCache().get(mockKey); + + System.Assert.areEqual(1, mockOrganizationPartitionProxy.getMethodCallCount); + System.Assert.areEqual(mockValue, actualResult); + System.Assert.isTrue(CacheManager.getOrganizationCache().contains(mockKey)); + } + + @IsTest + static void it_gets_value_from_session_cache_when_transaction_cache_miss_and_session_platform_cache_is_available() { + String mockKey = 'SomeKey'; + User mockValue = new User(Id = System.UserInfo.getUserId(), ProfileId = System.UserInfo.getProfileId()); + MockPlatformCachePartitionProxy mockSessionPartitionProxy = new MockPlatformCachePartitionProxy(true); + mockSessionPartitionProxy.put(mockKey, mockValue, 3600, Cache.Visibility.ALL, false); + System.Assert.isTrue(mockSessionPartitionProxy.isAvailable()); + CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.SESSION, mockSessionPartitionProxy); + System.Assert.areEqual(0, mockSessionPartitionProxy.containsMethodCallCount); + System.Assert.isTrue(CacheManager.getSessionCache().contains(mockKey)); + System.Assert.areEqual(1, mockSessionPartitionProxy.containsMethodCallCount); + System.Assert.areEqual(0, mockSessionPartitionProxy.getMethodCallCount); + + Object actualResult = CacheManager.getSessionCache().get(mockKey); + + System.Assert.areEqual(1, mockSessionPartitionProxy.getMethodCallCount); + System.Assert.areEqual(mockValue, actualResult); + System.Assert.isTrue(CacheManager.getSessionCache().contains(mockKey)); + } + + @IsTest + static void it_gets_null_from_organization_cache_when_transaction_cache_miss_and_organization_platform_cache_is_available() { + String mockKey = 'SomeKey'; + MockPlatformCachePartitionProxy mockOrganizationPartitionProxy = new MockPlatformCachePartitionProxy(true); + mockOrganizationPartitionProxy.put(mockKey, CacheManager.PLATFORM_CACHE_NULL_VALUE, 3600, Cache.Visibility.ALL, false); + System.Assert.isTrue(mockOrganizationPartitionProxy.isAvailable()); + CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.ORGANIZATION, mockOrganizationPartitionProxy); + System.Assert.areEqual(0, mockOrganizationPartitionProxy.containsMethodCallCount); + System.Assert.isTrue(CacheManager.getOrganizationCache().contains(mockKey)); + System.Assert.areEqual(1, mockOrganizationPartitionProxy.containsMethodCallCount); + System.Assert.areEqual(0, mockOrganizationPartitionProxy.getMethodCallCount); + + Object actualResult = CacheManager.getOrganizationCache().get(mockKey); + + System.Assert.areEqual(1, mockOrganizationPartitionProxy.getMethodCallCount); + System.Assert.areEqual(null, actualResult); + System.Assert.isTrue(CacheManager.getOrganizationCache().contains(mockKey)); + } + + @IsTest + static void it_gets_null_from_session_cache_when_transaction_cache_miss_and_session_platform_cache_is_available() { + String mockKey = 'SomeKey'; + MockPlatformCachePartitionProxy mockSessionPartitionProxy = new MockPlatformCachePartitionProxy(true); + mockSessionPartitionProxy.put(mockKey, CacheManager.PLATFORM_CACHE_NULL_VALUE, 3600, Cache.Visibility.ALL, false); + System.Assert.isTrue(mockSessionPartitionProxy.isAvailable()); + CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.SESSION, mockSessionPartitionProxy); + System.Assert.areEqual(0, mockSessionPartitionProxy.containsMethodCallCount); + System.Assert.isTrue(CacheManager.getSessionCache().contains(mockKey)); + System.Assert.areEqual(1, mockSessionPartitionProxy.containsMethodCallCount); + System.Assert.areEqual(0, mockSessionPartitionProxy.getMethodCallCount); + + Object actualResult = CacheManager.getSessionCache().get(mockKey); + + System.Assert.areEqual(1, mockSessionPartitionProxy.getMethodCallCount); + System.Assert.areEqual(null, actualResult); + System.Assert.isTrue(CacheManager.getSessionCache().contains(mockKey)); + } + @IsTest static void it_adds_new_key_to_fallback_transaction_cache_when_organization_platform_cache_is_not_available() { String mockKey = 'SomeKey'; @@ -178,6 +308,64 @@ private class CacheManager_Tests { System.Assert.areEqual(mockValue, CacheManager.getOrganizationCache().get(mockKey)); } + @IsTest + static void it_adds_new_key_to_organization_cache_with_cachebuilder_when_organization_platform_cache_is_available() { + String mockKey = 'SomeKey'; + System.Type cacheBuilderStubType = CacheBuilderStub.class; + MockPlatformCachePartitionProxy mockOrganizationPartitionProxy = new MockPlatformCachePartitionProxy(true); + System.Assert.isTrue(mockOrganizationPartitionProxy.isAvailable()); + CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.ORGANIZATION, mockOrganizationPartitionProxy); + System.Assert.areEqual(0, mockOrganizationPartitionProxy.containsMethodCallCount); + System.Assert.isFalse(CacheManager.getOrganizationCache().contains(mockKey)); + System.Assert.areEqual(1, mockOrganizationPartitionProxy.containsMethodCallCount); + System.Assert.areEqual(0, mockOrganizationPartitionProxy.getCacheBuilderMethodCallCount); + + Object generatedValue = CacheManager.getOrganizationCache().get(mockKey, cacheBuilderStubType); + + System.Assert.areEqual(1, mockOrganizationPartitionProxy.getCacheBuilderMethodCallCount); + System.Assert.isTrue(mockOrganizationPartitionProxy.contains(mockKey)); + System.Assert.isNotNull(generatedValue); + + Object cachedValue = CacheManager.getOrganizationCache().get(mockKey, cacheBuilderStubType); + + System.Assert.isNotNull(cachedValue); + System.Assert.areEqual(generatedValue, cachedValue); + System.Assert.areEqual(1, mockOrganizationPartitionProxy.getCacheBuilderMethodCallCount); + System.Assert.areEqual(cachedValue, mockOrganizationPartitionProxy.get(mockKey)); + System.Assert.isTrue(mockOrganizationPartitionProxy.contains(mockKey)); + System.Assert.isTrue(CacheManager.getOrganizationCache().contains(mockKey)); + System.Assert.areEqual(cachedValue, CacheManager.getOrganizationCache().get(mockKey)); + } + + @IsTest + static void it_adds_new_key_to_fallback_transaction_cache_with_cachebuilder_when_organization_platform_cache_is_not_available() { + String mockKey = 'SomeKey'; + System.Type cacheBuilderStubType = CacheBuilderStub.class; + MockPlatformCachePartitionProxy mockOrganizationPartitionProxy = new MockPlatformCachePartitionProxy(false); + System.Assert.isFalse(mockOrganizationPartitionProxy.isAvailable()); + CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.ORGANIZATION, mockOrganizationPartitionProxy); + System.Assert.areEqual(0, mockOrganizationPartitionProxy.containsMethodCallCount); + System.Assert.isFalse(CacheManager.getOrganizationCache().contains(mockKey)); + System.Assert.areEqual(0, mockOrganizationPartitionProxy.containsMethodCallCount); + System.Assert.areEqual(0, mockOrganizationPartitionProxy.getCacheBuilderMethodCallCount); + + Object generatedValue = CacheManager.getOrganizationCache().get(mockKey, cacheBuilderStubType); + + System.Assert.areEqual(0, mockOrganizationPartitionProxy.getCacheBuilderMethodCallCount); + System.Assert.isFalse(mockOrganizationPartitionProxy.contains(mockKey)); + System.Assert.isNotNull(generatedValue); + + Object cachedValue = CacheManager.getOrganizationCache().get(mockKey, cacheBuilderStubType); + + System.Assert.isNotNull(cachedValue); + System.Assert.areEqual(generatedValue, cachedValue); + System.Assert.areEqual(0, mockOrganizationPartitionProxy.getCacheBuilderMethodCallCount); + System.Assert.isNull(mockOrganizationPartitionProxy.get(mockKey)); + System.Assert.isFalse(mockOrganizationPartitionProxy.contains(mockKey)); + System.Assert.isTrue(CacheManager.getOrganizationCache().contains(mockKey)); + System.Assert.areEqual(cachedValue, CacheManager.getOrganizationCache().get(mockKey)); + } + @IsTest static void it_adds_configured_key_and_value_to_organization_cache_when_organization_platform_cache_is_available() { String mockKey = 'SomeKey'; @@ -204,6 +392,29 @@ private class CacheManager_Tests { System.Assert.areEqual(mockValue, CacheManager.getOrganizationCache().get(mockKey)); } + @IsTest + static void it_adds_configured_key_with_null_value_to_organization_cache_when_organization_platform_cache_is_available() { + String mockKey = 'SomeKey'; + CacheValue__mdt configuredCacheValue = new CacheValue__mdt( + Cache__c = CacheManager.ORGANIZATION_CACHE_CONFIGURATION.Id, + DataType__c = String.class.getName(), + IsEnabled__c = true, + Key__c = mockKey, + Value__c = null + ); + CacheManager.CONFIGURED_CACHE_VALUES.add(configuredCacheValue); + MockPlatformCachePartitionProxy mockOrganizationPartitionProxy = new MockPlatformCachePartitionProxy(true); + System.Assert.isTrue(mockOrganizationPartitionProxy.isAvailable()); + CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.ORGANIZATION, mockOrganizationPartitionProxy); + + Object returnedValue = CacheManager.getOrganizationCache().get(mockKey); + + System.Assert.areEqual(null, returnedValue); + System.Assert.areEqual(CacheManager.PLATFORM_CACHE_NULL_VALUE, mockOrganizationPartitionProxy.get(mockKey)); + System.Assert.isTrue(CacheManager.getOrganizationCache().contains(mockKey)); + System.Assert.areEqual(null, CacheManager.getOrganizationCache().get(mockKey)); + } + @IsTest static void it_adds_new_key_with_null_value_to_organization_cache_when_organization_platform_cache_is_available() { String mockKey = 'SomeKey'; @@ -330,11 +541,13 @@ private class CacheManager_Tests { 'SomeString' => 'hello, world', 'SomeSObject' => new User(Id = System.UserInfo.getUserId()) }; + System.Assert.isFalse(CacheManager.getOrganizationCache().containsAll(new Set())); System.Assert.isFalse(CacheManager.getOrganizationCache().containsAll(keyToValue.keySet())); CacheManager.getOrganizationCache().put(keyToValue); for (String key : keyToValue.keySet()) { System.Assert.isTrue(CacheManager.getOrganizationCache().contains(key)); } + System.Assert.isFalse(CacheManager.getOrganizationCache().containsAll(new Set())); Map keyToContainsResult = CacheManager.getOrganizationCache().contains(keyToValue.keySet()); for (String key : keyToContainsResult.keySet()) { Boolean containsResult = keyToContainsResult.get(key); @@ -367,6 +580,64 @@ private class CacheManager_Tests { System.Assert.isTrue(CacheManager.getOrganizationCache().getKeys().isEmpty()); } + @IsTest + static void it_adds_new_key_to_session_cache_with_cachebuilder_when_session_platform_cache_is_available() { + String mockKey = 'SomeKey'; + System.Type cacheBuilderStubType = CacheBuilderStub.class; + MockPlatformCachePartitionProxy mockSessionPartitionProxy = new MockPlatformCachePartitionProxy(true); + System.Assert.isTrue(mockSessionPartitionProxy.isAvailable()); + CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.SESSION, mockSessionPartitionProxy); + System.Assert.areEqual(0, mockSessionPartitionProxy.containsMethodCallCount); + System.Assert.isFalse(CacheManager.getSessionCache().contains(mockKey)); + System.Assert.areEqual(1, mockSessionPartitionProxy.containsMethodCallCount); + System.Assert.areEqual(0, mockSessionPartitionProxy.getCacheBuilderMethodCallCount); + + Object generatedValue = CacheManager.getSessionCache().get(mockKey, cacheBuilderStubType); + + System.Assert.areEqual(1, mockSessionPartitionProxy.getCacheBuilderMethodCallCount); + System.Assert.isTrue(mockSessionPartitionProxy.contains(mockKey)); + System.Assert.isNotNull(generatedValue); + + Object cachedValue = CacheManager.getSessionCache().get(mockKey, cacheBuilderStubType); + + System.Assert.isNotNull(cachedValue); + System.Assert.areEqual(generatedValue, cachedValue); + System.Assert.areEqual(1, mockSessionPartitionProxy.getCacheBuilderMethodCallCount); + System.Assert.areEqual(cachedValue, mockSessionPartitionProxy.get(mockKey)); + System.Assert.isTrue(mockSessionPartitionProxy.contains(mockKey)); + System.Assert.isTrue(CacheManager.getSessionCache().contains(mockKey)); + System.Assert.areEqual(cachedValue, CacheManager.getSessionCache().get(mockKey)); + } + + @IsTest + static void it_adds_new_key_to_fallback_transaction_cache_with_cachebuilder_when_session_platform_cache_is_not_available() { + String mockKey = 'SomeKey'; + System.Type cacheBuilderStubType = CacheBuilderStub.class; + MockPlatformCachePartitionProxy mockSessionPartitionProxy = new MockPlatformCachePartitionProxy(false); + System.Assert.isFalse(mockSessionPartitionProxy.isAvailable()); + CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.SESSION, mockSessionPartitionProxy); + System.Assert.areEqual(0, mockSessionPartitionProxy.containsMethodCallCount); + System.Assert.isFalse(CacheManager.getSessionCache().contains(mockKey)); + System.Assert.areEqual(0, mockSessionPartitionProxy.containsMethodCallCount); + System.Assert.areEqual(0, mockSessionPartitionProxy.getCacheBuilderMethodCallCount); + + Object generatedValue = CacheManager.getSessionCache().get(mockKey, cacheBuilderStubType); + + System.Assert.areEqual(0, mockSessionPartitionProxy.getCacheBuilderMethodCallCount); + System.Assert.isFalse(mockSessionPartitionProxy.contains(mockKey)); + System.Assert.isNotNull(generatedValue); + + Object cachedValue = CacheManager.getSessionCache().get(mockKey, cacheBuilderStubType); + + System.Assert.isNotNull(cachedValue); + System.Assert.areEqual(generatedValue, cachedValue); + System.Assert.areEqual(0, mockSessionPartitionProxy.getCacheBuilderMethodCallCount); + System.Assert.isNull(mockSessionPartitionProxy.get(mockKey)); + System.Assert.isFalse(mockSessionPartitionProxy.contains(mockKey)); + System.Assert.isTrue(CacheManager.getSessionCache().contains(mockKey)); + System.Assert.areEqual(cachedValue, CacheManager.getSessionCache().get(mockKey)); + } + @IsTest static void it_adds_new_key_to_session_cache_when_session_platform_cache_is_available() { String mockKey = 'SomeKey'; @@ -435,6 +706,30 @@ private class CacheManager_Tests { System.Assert.areEqual(mockValue, CacheManager.getSessionCache().get(mockKey)); } + @IsTest + static void it_adds_configured_key_with_null_value_to_session_cache_when_session_platform_cache_is_available() { + String mockKey = 'SomeKey'; + User mockValue = new User(Id = System.UserInfo.getUserId(), ProfileId = System.UserInfo.getProfileId()); + CacheValue__mdt configuredCacheValue = new CacheValue__mdt( + Cache__c = CacheManager.SESSION_CACHE_CONFIGURATION.Id, + DataType__c = String.class.getName(), + IsEnabled__c = true, + Key__c = mockKey, + Value__c = null + ); + CacheManager.CONFIGURED_CACHE_VALUES.add(configuredCacheValue); + MockPlatformCachePartitionProxy mockSessionPartitionProxy = new MockPlatformCachePartitionProxy(true); + System.Assert.isTrue(mockSessionPartitionProxy.isAvailable()); + CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.SESSION, mockSessionPartitionProxy); + + Object returnedValue = CacheManager.getSessionCache().get(mockKey); + + System.Assert.areEqual(null, returnedValue); + System.Assert.areEqual(CacheManager.PLATFORM_CACHE_NULL_VALUE, mockSessionPartitionProxy.get(mockKey)); + System.Assert.isTrue(CacheManager.getSessionCache().contains(mockKey)); + System.Assert.areEqual(null, CacheManager.getSessionCache().get(mockKey)); + } + @IsTest static void it_adds_new_key_with_null_value_to_session_cache_when_session_platform_cache_is_available() { String mockKey = 'SomeKey'; @@ -561,11 +856,13 @@ private class CacheManager_Tests { 'SomeString' => 'hello, world', 'SomeSObject' => new User(Id = System.UserInfo.getUserId()) }; + System.Assert.isFalse(CacheManager.getSessionCache().containsAll(new Set())); System.Assert.isFalse(CacheManager.getSessionCache().containsAll(keyToValue.keySet())); CacheManager.getSessionCache().put(keyToValue); for (String key : keyToValue.keySet()) { System.Assert.isTrue(CacheManager.getSessionCache().contains(key)); } + System.Assert.isFalse(CacheManager.getSessionCache().containsAll(new Set())); Map keyToContainsResult = CacheManager.getSessionCache().contains(keyToValue.keySet()); for (String key : keyToContainsResult.keySet()) { Boolean containsResult = keyToContainsResult.get(key); @@ -646,6 +943,59 @@ private class CacheManager_Tests { System.Assert.isFalse(CacheManager.getSessionCache().contains(key)); } + @IsTest + static void validateKey_does_not_throw_when_called_with_number() { + try { + for (Integer i = 0; i < 50; ++i) { + CacheManager.validateKey('' + i); + } + System.Assert.isTrue(true); + } catch (IllegalArgumentException ex) { + System.Assert.fail(); + } + } + + @IsTest + static void validateKey_does_not_throw_when_called_with_letters() { + String alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + try { + for (String key : alphabet.split('')) { + CacheManager.validateKey(key); + } + System.Assert.isTrue(true); + } catch (IllegalArgumentException ex) { + System.Assert.fail(); + } + } + + @IsTest + static void validateKey_throws_when_called_with_not_letters_nor_numbers() { + String otherChars = 'ù%`$€"\'(§!)-&@"#éèàç*¨ %£+/=.?,\\'; + + for (String key : otherChars.split('')) { + try { + CacheManager.validateKey(key); + System.Assert.fail(); + } catch (Exception ex) { + System.Assert.isInstanceOfType(ex, IllegalArgumentException.class); + } + } + } + + @IsTest + static void validateKey_throws_when_called_with_not_letters_nor_numbers_and_letter_numbers() { + List phrase = new List{ 'I throw !!', 'I throw 2 times for sure!!', 'No, you throw' }; + + for (String key : phrase) { + try { + CacheManager.validateKey(key); + System.Assert.fail(); + } catch (Exception ex) { + System.Assert.isInstanceOfType(ex, IllegalArgumentException.class); + } + } + } + // Since the class `Cache.Partition` can't have be mocked & can't have its methods overridden, // the `CacheManager` class internally uses a proxy to help abstract out the usage of the partition, // which lets us mock the proxy within (true) unit tests. @@ -657,6 +1007,7 @@ private class CacheManager_Tests { public Integer isAvailableMethodCallCount = 0; public Integer containsMethodCallCount = 0; public Integer getMethodCallCount = 0; + public Integer getCacheBuilderMethodCallCount = 0; public Integer putMethodCallCount = 0; public Integer removeMethodCallCount = 0; @@ -680,6 +1031,16 @@ private class CacheManager_Tests { return this.keyToValue.get(key); } + public override Object get(String key, System.Type cacheBuilderClass) { + this.getCacheBuilderMethodCallCount++; + if (this.keyToValue.containsKey(key) == false) { + Cache.CacheBuilder cacheBuilder = (Cache.CacheBuilder) cacheBuilderClass.newInstance(); + Object value = cacheBuilder.doLoad(key); + this.keyToValue.put(key, value); + } + return this.keyToValue.get(key); + } + @SuppressWarnings('PMD.ExcessiveParameterList') public override void put(String key, Object value, Integer cacheTtlSeconds, Cache.Visibility cacheVisiblity, Boolean isCacheImmutable) { this.putMethodCallCount++; @@ -691,4 +1052,11 @@ private class CacheManager_Tests { this.keyToValue.remove(key); } } + + private class CacheBuilderStub implements Cache.CacheBuilder { + public Object doLoad(String key) { + User mockValue = new User(Id = System.UserInfo.getUserId(), ProfileId = System.UserInfo.getProfileId()); + return mockValue; + } + } } diff --git a/package-lock.json b/package-lock.json index 6ceed063..1781eff8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25562,12 +25562,6 @@ "node": ">=4" } }, - "node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, "node_modules/type-fest": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", diff --git a/sfdx-project.json b/sfdx-project.json index 92fbccbf..5e25d193 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -9,9 +9,9 @@ "package": "Nebula Cache Manager (no namespace)", "path": "./nebula-cache-manager/core/", "definitionFile": "./config/scratch-orgs/base-scratch-def.json", - "versionNumber": "1.0.0.0", - "versionName": "Initial Release", - "versionDescription": "Initial release of new package", + "versionNumber": "1.0.1.0", + "versionName": "Support for Cache.CacheBuilder", + "versionDescription": "Adds a new overload, Cacheable.get(String, System.Type) to provide support for using the interface Support for Cache.CacheBuilder", "releaseNotesUrl": "https://github.com/jongpie/NebulaCacheManager/releases", "default": false }, @@ -19,9 +19,9 @@ "package": "Nebula Cache Manager (Nebula namespace)", "path": "./nebula-cache-manager/core/", "definitionFile": "./config/scratch-orgs/base-scratch-def.json", - "versionNumber": "1.0.0.0", - "versionName": "Initial Release", - "versionDescription": "Initial release of new package", + "versionNumber": "1.0.1.0", + "versionName": "Support for Cache.CacheBuilder", + "versionDescription": "Adds a new overload, Cacheable.get(String, System.Type) to provide support for using the interface Support for Cache.CacheBuilder", "releaseNotesUrl": "https://github.com/jongpie/NebulaCacheManager/releases", "default": false }, @@ -38,7 +38,9 @@ "packageAliases": { "Nebula Cache Manager (Nebula namespace)": "0Ho5Y000000fxvlSAA", "Nebula Cache Manager (Nebula namespace)@1.0.0": "04t5Y0000015n2AQAQ", + "Nebula Cache Manager (Nebula namespace)@1.0.1": "04t5Y0000015n6rQAA", "Nebula Cache Manager (no namespace)": "0Ho5Y000000fxvqSAA", - "Nebula Cache Manager (no namespace)@1.0.0": "04t5Y0000015n25QAA" + "Nebula Cache Manager (no namespace)@1.0.0": "04t5Y0000015n25QAA", + "Nebula Cache Manager (no namespace)@1.0.1": "04t5Y0000015n6hQAA" } }