Skip to content

Commit

Permalink
Added tests for OrgCache ageing and improved behaviours
Browse files Browse the repository at this point in the history
  • Loading branch information
rob-baillie-ortoo committed Apr 26, 2022
1 parent 3a26bd2 commit 14fc597
Show file tree
Hide file tree
Showing 8 changed files with 361 additions and 33 deletions.
10 changes: 10 additions & 0 deletions TODO.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
TODO:
* Combine the DateTimeUtils and DateLiterals where appropriate
* Document TestDateTimeUtils and how to set now and today
* Test ageing using dates on the OrgCache
* Implement ageing into the SessionCache
* Add the aged get definitions into ICacheAdaptor
* Add 'maximum age' into CachedSoqlExecutor



* Move all the Jest LWc tests over to create the component in the before

Look at the use of 'MockDatabase' in fflib
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ public inherited sharing class CacheEntry
Long createdOnEpoch;
Object value;

private Long age
private Long ageInSeconds
{
get
{
return DateTimeUtils.getEpochTime() - this.createdOnEpoch ;
return DateTimeUtils.getEpochTime() - this.createdOnEpoch;
}
}

Expand All @@ -19,10 +19,11 @@ public inherited sharing class CacheEntry
this.createdOnEpoch = DateTimeUtils.getEpochTime();
}

public Boolean isYoungerThan( Integer compareAgeInSeconds )
public Boolean isYoungerThanOrEqualTo( Long compareAgeInSeconds )
{
Contract.requires( compareAgeInSeconds != null, 'isOlderThan called with a null compareAgeInSeconds' );
return age < compareAgeInSeconds;
Contract.requires( compareAgeInSeconds != null, 'isYoungerThanOrEqualTo called with a null compareAgeInSeconds' );

return ageInSeconds <= compareAgeInSeconds;
}

public Object getValue()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,37 +57,39 @@ public inherited sharing class OrgCache implements ICacheAdaptor
*/
public Object get( String key )
{
Contract.requires( String.isNotBlank( key ), 'get called with a blank key' );

if ( ! hasAccessToCache )
{
throw new AccessViolationException( Label.ortoo_core_org_cache_access_violation )
.setErrorCode( FrameworkErrorCodes.CACHE_ACCESS_VIOLATION )
.addContext( 'method', 'get' )
.addContext( 'key', key );
}

Object rawCacheValue = Cache.Org.get( createFullyQualifiedKey( key ) );
Long maximumAgeInSeconds = null;
return get( key, maximumAgeInSeconds );
}

if ( rawCacheValue instanceOf CacheEntry )
/**
* Retrieve the cache entry with the given key, but only if it was created after the stated date time
*
* If the user does not have access to the cache, will throw an AccessViolationException.
* If the entry does not exist in the cache (I.E. is a "cache miss"), will return null
*
* @param String The key for the object to retrieve
* @return Object The cached object, if it exists
*/
public Object get( String key, DateTime minimumDateTime )
{
Long maximumAgeInSeconds;
if ( minimumDateTime != null )
{
return ((CacheEntry)rawCacheValue).getValue();
maximumAgeInSeconds = DateTimeUtils.getEpochTime() - DateTimeUtils.getEpochTime( minimumDateTime );
}
return rawCacheValue;
return get( key, maximumAgeInSeconds );
}

// TODO: document
// TODO: test
/**
* Retrieve the cache entry with the given key.
* Retrieve the cache entry with the given key, but only if it is younger than the stated number of seconds
*
* If the user does not have access to the cache, will throw an AccessViolationException.
* If the entry does not exist in the cache (I.E. is a "cache miss"), will return null
*
* @param String The key for the object to retrieve
* @return Object The cached object, if it exists
*/
public Object get( String key, Integer maximumAgeInSeconds )
public Object get( String key, Long maximumAgeInSeconds )
{
Contract.requires( String.isNotBlank( key ), 'get called with a blank key' );

Expand All @@ -105,11 +107,17 @@ public inherited sharing class OrgCache implements ICacheAdaptor
if ( rawCacheValue instanceOf CacheEntry )
{
CacheEntry cacheValue = (CacheEntry)rawCacheValue;
if ( cacheValue.isYoungerThan( maximumAgeInSeconds ) )
if ( maximumAgeInSeconds == null || cacheValue.isYoungerThanOrEqualTo( maximumAgeInSeconds ) )
{
return cacheValue.getValue();
}
}

if ( maximumAgeInSeconds == null ) // if we don't care about the age, then return it
{
return rawCacheValue;
}

return null; // if there is no age, or it is too old, regard it as a cache miss
}

Expand Down Expand Up @@ -232,6 +240,7 @@ public inherited sharing class OrgCache implements ICacheAdaptor
return Cache.OrgPartition.createFullyQualifiedPartition( PackageUtils.NAMESPACE_PREFIX, PARTITION_NAME );
}

@testVisible
private String createFullyQualifiedKey( String key )
{
return Cache.OrgPartition.createFullyQualifiedKey( PackageUtils.NAMESPACE_PREFIX, PARTITION_NAME, key );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,9 @@
private without sharing class OrgCacheTest
{
private final static Integer DEFAULT_LIFESPAN = 1000;

// if age on retrieve is too old, returns null
// if age on retrieve is exactly the right age, returns value
// if age on retrieve is young enough, returns value
// retrieving does not reset the age
// if object is not a CacheEntry and no age specified, returns value
// if object is not a CacheEntry and an age specified, returns null
private final static DateTime BASE_TIME = DateTime.newInstanceGmt( 1974, 08, 24, 22, 45, 00 );
private final static Long NULL_AGE = null;
private final static DateTime NULL_MAX_DATE_TIME = null;

@isTest
private static void hasAccessToCache_whenCalledByUserWithCacheAccess_returnsTrue() // NOPMD: Test method name format
Expand Down Expand Up @@ -95,6 +91,251 @@ private without sharing class OrgCacheTest
System.assertEquals( expected, got, 'get, when called with a key that is in the cache, will return it' );
}

@isTest
private static void get_whenTheCachedValueHasNoAge_returnsTheValue() // NOPMD: Test method name format
{
setupAccessToSoqlCache( true );
String cachedObject = 'thecachedthing';

OrgCache cacheInstance = new OrgCache();

Cache.Org.put( cacheInstance.createFullyQualifiedKey( 'key' ), cachedObject );

Test.startTest();
Object got = cacheInstance.get( 'key' );
Test.stopTest();

System.assertEquals( cachedObject, got, 'get, when the cached value has no age, will return the value' );
}

@isTest
private static void get_maxAge_whenTheEntryIsTooOld_returnsNull() // NOPMD: Test method name format
{
setupAccessToSoqlCache( true );
String cachedObject = 'thecachedthing';

OrgCache cache = new OrgCache();

TestDateTimeUtils.setCurrentDateTime( BASE_TIME );
cache.put( 'key', cachedObject, DEFAULT_LIFESPAN );

Test.startTest();
TestDateTimeUtils.addToCurrentTime( 100 );
Object got = cache.get( 'key', 50 );
Test.stopTest();

System.assertEquals( null, got, 'get, passing maximumAge, when the entry is older than the stated number of seconds, will return null' );
}

@isTest
private static void get_maxAge_whenTheEntryIsExactlyRight_returnsTheValue() // NOPMD: Test method name format
{
setupAccessToSoqlCache( true );
String cachedObject = 'thecachedthing';

OrgCache cache = new OrgCache();

TestDateTimeUtils.setCurrentDateTime( BASE_TIME );
cache.put( 'key', cachedObject, DEFAULT_LIFESPAN );

Test.startTest();
TestDateTimeUtils.addToCurrentTime( 100 );
Object got = cache.get( 'key', 100 );
Test.stopTest();

System.assertEquals( cachedObject, got, 'get, passing maximumAge, when the entry is exactly the stated number of seconds, will return the cached value' );
}

@isTest
private static void get_maxAge_whenTheEntryIsYounger_returnsTheValue() // NOPMD: Test method name format
{
setupAccessToSoqlCache( true );
String cachedObject = 'thecachedthing';

OrgCache cache = new OrgCache();

TestDateTimeUtils.setCurrentDateTime( BASE_TIME );
cache.put( 'key', cachedObject, DEFAULT_LIFESPAN );

Test.startTest();
TestDateTimeUtils.addToCurrentTime( 50 );
Object got = cache.get( 'key', 100 );
Test.stopTest();

System.assertEquals( cachedObject, got, 'get, passing maximumAge, when the entry is younger than the stated number of seconds, will return the cached value' );
}

@isTest
private static void get_maxAge_doesNotResetTheAge() // NOPMD: Test method name format
{
setupAccessToSoqlCache( true );
String cachedObject = 'thecachedthing';

OrgCache cache = new OrgCache();

TestDateTimeUtils.setCurrentDateTime( BASE_TIME );
cache.put( 'key', cachedObject, DEFAULT_LIFESPAN );

Test.startTest();
TestDateTimeUtils.addToCurrentTime( 50 );
cache.get( 'key', 100 );

TestDateTimeUtils.addToCurrentTime( 50 );
cache.get( 'key', 100 );

TestDateTimeUtils.addToCurrentTime( 50 );
Object got = cache.get( 'key', 100 );
Test.stopTest();

System.assertEquals( null, got, 'get, passing maximumAge, does not reset the age of the cached entity, so when its too old it returns null' );
}

@isTest
private static void get_maxAge_whenNoAgeSpecifiedAndTheCachedValueHasNoAge_returnsTheValue() // NOPMD: Test method name format
{
setupAccessToSoqlCache( true );
String cachedObject = 'thecachedthing';

OrgCache cacheInstance = new OrgCache();

Cache.Org.put( cacheInstance.createFullyQualifiedKey( 'key' ), cachedObject );

Test.startTest();
Object got = cacheInstance.get( 'key', NULL_AGE );
Test.stopTest();

System.assertEquals( cachedObject, got, 'get, passing maximumAge, when no age is specified and the cached value has no age, will return the value' );
}

@isTest
private static void get_maxAge_whenAgeSpecifiedAndTheCachedValueHasNoAge_returnsNull() // NOPMD: Test method name format
{
setupAccessToSoqlCache( true );
String cachedObject = 'thecachedthing';

OrgCache cacheInstance = new OrgCache();

Cache.Org.put( cacheInstance.createFullyQualifiedKey( 'key' ), cachedObject );

Test.startTest();
Object got = cacheInstance.get( 'key', 100 );
Test.stopTest();

System.assertEquals( null, got, 'get, passing maximumAge, when an age is specified and the cached value has no age, will return null' );
}

@isTest
private static void get_minDateTime_whenTheEntryIsTooOld_returnsNull() // NOPMD: Test method name format
{
setupAccessToSoqlCache( true );
String cachedObject = 'thecachedthing';

OrgCache cache = new OrgCache();

TestDateTimeUtils.setCurrentDateTime( BASE_TIME );
cache.put( 'key', cachedObject, DEFAULT_LIFESPAN );

Test.startTest();
Object got = cache.get( 'key', BASE_TIME.addSeconds( 50 ) );
Test.stopTest();

System.assertEquals( null, got, 'get, passing minimumDateTime, when the entry is older than the stated time, will return null' );
}

@isTest
private static void get_minDateTime_whenTheEntryIsExactlyRight_returnsTheValue() // NOPMD: Test method name format
{
setupAccessToSoqlCache( true );
String cachedObject = 'thecachedthing';

OrgCache cache = new OrgCache();

TestDateTimeUtils.setCurrentDateTime( BASE_TIME );
cache.put( 'key', cachedObject, DEFAULT_LIFESPAN );

Test.startTest();
Object got = cache.get( 'key', BASE_TIME );
Test.stopTest();

System.assertEquals( cachedObject, got, 'get, passing minimumDateTime, when the entry is exactly the stated time, will return the cached value' );
}

@isTest
private static void get_minDateTime_whenTheEntryIsYounger_returnsTheValue() // NOPMD: Test method name format
{
setupAccessToSoqlCache( true );
String cachedObject = 'thecachedthing';

OrgCache cache = new OrgCache();

TestDateTimeUtils.setCurrentDateTime( BASE_TIME );
cache.put( 'key', cachedObject, DEFAULT_LIFESPAN );

Test.startTest();
Object got = cache.get( 'key', BASE_TIME.addSeconds( -50 ) );
Test.stopTest();

System.assertEquals( cachedObject, got, 'get, passing minimumDateTime, when the entry is younger than the stated time, will return the cached value' );
}

@isTest
private static void get_minDateTime_doesNotResetTheAge() // NOPMD: Test method name format
{
setupAccessToSoqlCache( true );
String cachedObject = 'thecachedthing';

OrgCache cache = new OrgCache();

TestDateTimeUtils.setCurrentDateTime( BASE_TIME );
cache.put( 'key', cachedObject, DEFAULT_LIFESPAN );

Test.startTest();
TestDateTimeUtils.addToCurrentTime( 50 );
cache.get( 'key', BASE_TIME.addSeconds( -50 ) );

TestDateTimeUtils.addToCurrentTime( 50 );
cache.get( 'key', BASE_TIME.addSeconds( -100 ) );

TestDateTimeUtils.addToCurrentTime( 50 );
Object got = cache.get( 'key', BASE_TIME.addSeconds( 100 ) );
Test.stopTest();

System.assertEquals( null, got, 'get, passing minimumDateTime, does not reset the age of the cached entity, so when its too old it returns null' );
}

@isTest
private static void get_minDateTime_whenNoAgeSpecifiedAndTheCachedValueHasNoAge_returnsTheValue() // NOPMD: Test method name format
{
setupAccessToSoqlCache( true );
String cachedObject = 'thecachedthing';

OrgCache cacheInstance = new OrgCache();

Cache.Org.put( cacheInstance.createFullyQualifiedKey( 'key' ), cachedObject );

Test.startTest();
Object got = cacheInstance.get( 'key', NULL_MAX_DATE_TIME );
Test.stopTest();

System.assertEquals( cachedObject, got, 'get, passing minimumDateTime, when no time is specified and the cached value has no age, will return the value' );
}

@isTest
private static void get_minDateTime_whenAgeSpecifiedAndTheCachedValueHasNoAge_returnsNull() // NOPMD: Test method name format
{
setupAccessToSoqlCache( true );
String cachedObject = 'thecachedthing';

OrgCache cacheInstance = new OrgCache();

Cache.Org.put( cacheInstance.createFullyQualifiedKey( 'key' ), cachedObject );

Test.startTest();
Object got = cacheInstance.get( 'key', BASE_TIME.addSeconds( 100 ) );
Test.stopTest();

System.assertEquals( null, got, 'get, passing minimumDateTime, when a time is specified and the cached value has no age, will return null' );
}

@isTest
private static void put_whenTheUserDoesNotHaveAccessToTheCache_throwsAnException() // NOPMD: Test method name format
Expand Down
Loading

0 comments on commit 14fc597

Please sign in to comment.