diff --git a/src/main/java/com/truelayer/java/TrueLayerClientBuilder.java b/src/main/java/com/truelayer/java/TrueLayerClientBuilder.java index e895d663..a54c4620 100644 --- a/src/main/java/com/truelayer/java/TrueLayerClientBuilder.java +++ b/src/main/java/com/truelayer/java/TrueLayerClientBuilder.java @@ -196,6 +196,7 @@ public TrueLayerClientBuilder withCredentialsCaching() { /** * Utility to enable a custom cache for Oauth credentials. + * @param credentialsCache the custom cache implementation * @return the instance of the client builder used */ public TrueLayerClientBuilder withCredentialsCaching(ICredentialsCache credentialsCache) { @@ -223,7 +224,7 @@ public TrueLayerClient build() { throw new TrueLayerException("client credentials must be set"); } - if (inMemoryCredentialsCachingEnabled && isNotEmpty(customCredentialsCachingImplementation)){ + if (inMemoryCredentialsCachingEnabled && isNotEmpty(customCredentialsCachingImplementation)) { throw new TrueLayerException("Invalid caching configuration"); } @@ -254,7 +255,10 @@ public TrueLayerClient build() { // We're building a client which has the authentication handler and the options to cache the token. // this one represents the baseline for the client used for Signup+ and Payments OkHttpClient authenticatedApiClient = httpClientFactory.buildAuthenticatedApiClient( - authServerApiHttpClient, authenticationHandler, getCredentialsCacheImplementation()); + clientCredentials.clientId, + authServerApiHttpClient, + authenticationHandler, + getCredentialsCacheImplementation()); ISignupPlusApi signupPlusApi = RetrofitFactory.build(authenticatedApiClient, environment.getPaymentsApiUri()) .create(ISignupPlusApi.class); SignupPlusHandler.SignupPlusHandlerBuilder signupPlusHandlerBuilder = @@ -347,7 +351,7 @@ private boolean customScopesPresent() { private ICredentialsCache getCredentialsCacheImplementation() { if (this.inMemoryCredentialsCachingEnabled) { - return new InMemoryCredentialsCache(this.clientCredentials.clientId(), Clock.systemUTC()); + return new InMemoryCredentialsCache(Clock.systemUTC()); } if (isNotEmpty(this.customCredentialsCachingImplementation)) { diff --git a/src/main/java/com/truelayer/java/http/OkHttpClientFactory.java b/src/main/java/com/truelayer/java/http/OkHttpClientFactory.java index 21e43826..50221805 100644 --- a/src/main/java/com/truelayer/java/http/OkHttpClientFactory.java +++ b/src/main/java/com/truelayer/java/http/OkHttpClientFactory.java @@ -108,6 +108,7 @@ public OkHttpClient buildAuthServerApiClient(OkHttpClient baseHttpClient, Client } public OkHttpClient buildAuthenticatedApiClient( + String clientId, OkHttpClient authServerApiClient, IAuthenticationHandler authenticationHandler, ICredentialsCache credentialsCache) { @@ -118,7 +119,7 @@ public OkHttpClient buildAuthenticatedApiClient( OkHttpClient.Builder authenticatedApiClientBuilder = authServerApiClient.newBuilder(); AccessTokenManager.AccessTokenManagerBuilder accessTokenManagerBuilder = - AccessTokenManager.builder().authenticationHandler(authenticationHandler); + AccessTokenManager.builder().clientId(clientId).authenticationHandler(authenticationHandler); // setup credentials caching if required if (isNotEmpty(credentialsCache)) { diff --git a/src/main/java/com/truelayer/java/http/auth/AccessTokenManager.java b/src/main/java/com/truelayer/java/http/auth/AccessTokenManager.java index 337ad351..4bff4942 100644 --- a/src/main/java/com/truelayer/java/http/auth/AccessTokenManager.java +++ b/src/main/java/com/truelayer/java/http/auth/AccessTokenManager.java @@ -4,6 +4,7 @@ import com.truelayer.java.auth.IAuthenticationHandler; import com.truelayer.java.auth.entities.AccessToken; import com.truelayer.java.entities.RequestScopes; +import com.truelayer.java.http.auth.cache.CredentialsCacheHelper; import com.truelayer.java.http.auth.cache.ICredentialsCache; import com.truelayer.java.http.entities.ApiResponse; import java.util.Optional; @@ -13,6 +14,8 @@ @Builder public class AccessTokenManager implements IAccessTokenManager { + private final String clientId; + private final IAuthenticationHandler authenticationHandler; private final ICredentialsCache credentialsCache; @@ -23,10 +26,12 @@ private Optional getCredentialsCache() { @Override public AccessToken getToken(RequestScopes scopes) { + String cacheKey = CredentialsCacheHelper.buildKey(clientId, scopes); + if (getCredentialsCache().isPresent()) { - return getCredentialsCache().get().getToken(scopes).orElseGet(() -> { + return getCredentialsCache().get().getToken(cacheKey).orElseGet(() -> { AccessToken token = tryGetToken(scopes); - credentialsCache.storeToken(scopes, token); + credentialsCache.storeToken(cacheKey, token); return token; }); } @@ -37,7 +42,8 @@ public AccessToken getToken(RequestScopes scopes) { @Override @Synchronized public void invalidateToken(RequestScopes scopes) { - getCredentialsCache().ifPresent(iCredentialsCache -> iCredentialsCache.clearToken(scopes)); + String cacheKey = CredentialsCacheHelper.buildKey(clientId, scopes); + getCredentialsCache().ifPresent(iCredentialsCache -> iCredentialsCache.clearToken(cacheKey)); } private AccessToken tryGetToken(RequestScopes scopes) { diff --git a/src/main/java/com/truelayer/java/http/auth/cache/CredentialsCacheHelper.java b/src/main/java/com/truelayer/java/http/auth/cache/CredentialsCacheHelper.java new file mode 100644 index 00000000..82f4cb49 --- /dev/null +++ b/src/main/java/com/truelayer/java/http/auth/cache/CredentialsCacheHelper.java @@ -0,0 +1,17 @@ +package com.truelayer.java.http.auth.cache; + +import com.truelayer.java.entities.RequestScopes; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class CredentialsCacheHelper { + private static final String CACHE_KEY_PREFIX = "tl-auth-token"; + + public static String buildKey(String clientId, RequestScopes requestScopes) { + List scopes = new ArrayList<>(requestScopes.getScopes()); + Collections.sort(scopes); + return MessageFormat.format("{0}:{1}:{2}", CACHE_KEY_PREFIX, clientId, scopes.hashCode()); + } +} diff --git a/src/main/java/com/truelayer/java/http/auth/cache/ICredentialsCache.java b/src/main/java/com/truelayer/java/http/auth/cache/ICredentialsCache.java index b6ef9130..489c10cc 100644 --- a/src/main/java/com/truelayer/java/http/auth/cache/ICredentialsCache.java +++ b/src/main/java/com/truelayer/java/http/auth/cache/ICredentialsCache.java @@ -1,28 +1,13 @@ package com.truelayer.java.http.auth.cache; import com.truelayer.java.auth.entities.AccessToken; -import com.truelayer.java.entities.RequestScopes; import java.util.Optional; public interface ICredentialsCache { - /** - * Gets the cached access token for the given request scopes. - * @param scopes the requested scopes - * @return an optional access token. If the token is expired an empty optional is returned - */ - Optional getToken(RequestScopes scopes); + Optional getToken(String key); - /** - * Stores an access token in cache for the given request scopes. - * @param token the new token to store - * @param scopes the requested scopes - */ - void storeToken(RequestScopes scopes, AccessToken token); + void storeToken(String key, AccessToken token); - /** - * Remove the entry in the cache for the given request scopes. - * @param scopes the requested scopes - */ - void clearToken(RequestScopes scopes); + void clearToken(String key); } diff --git a/src/main/java/com/truelayer/java/http/auth/cache/InMemoryCredentialsCache.java b/src/main/java/com/truelayer/java/http/auth/cache/InMemoryCredentialsCache.java index d6604b43..bcbe7193 100644 --- a/src/main/java/com/truelayer/java/http/auth/cache/InMemoryCredentialsCache.java +++ b/src/main/java/com/truelayer/java/http/auth/cache/InMemoryCredentialsCache.java @@ -1,45 +1,25 @@ package com.truelayer.java.http.auth.cache; import com.truelayer.java.auth.entities.AccessToken; -import com.truelayer.java.entities.RequestScopes; -import java.text.MessageFormat; import java.time.Clock; import java.time.LocalDateTime; -import java.util.*; -import lombok.Getter; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; import lombok.RequiredArgsConstructor; -/** - * Default in-memory cache implementation. - * Class constructor accepts a Clock instance for improved testing purposes. Please note that this is not a monotonic implementation. - * @see this article for more context, - */ +@RequiredArgsConstructor public class InMemoryCredentialsCache implements ICredentialsCache { - /** - * internal state - */ - private final Map tokenRecords = new HashMap<>(); - private final Clock clock; - private final String clientId; - - public static final String CACHE_KEY_PREFIX = "tl-auth-token"; - /** - * Constructor for this class. - * @param clock clock instance + * internal state */ - public InMemoryCredentialsCache(String clientId, Clock clock) { - this.clientId = clientId; - this.clock = clock; - } + private final Map tokenRecords = new ConcurrentHashMap<>(); @Override - public Optional getToken(RequestScopes scopes) { - String key = computeCacheKey(scopes); - + public Optional getToken(String key) { AccessTokenRecord tokenRecord = tokenRecords.get(key); if (tokenRecord == null || !LocalDateTime.now(clock).isBefore(tokenRecord.expiresAt)) { return Optional.empty(); @@ -49,28 +29,21 @@ public Optional getToken(RequestScopes scopes) { } @Override - public void storeToken(RequestScopes scopes, AccessToken token) { + public void storeToken(String key, AccessToken token) { AccessTokenRecord tokenRecord = new AccessTokenRecord(token, LocalDateTime.now(clock).plusSeconds(token.getExpiresIn())); - tokenRecords.put(computeCacheKey(scopes), tokenRecord); + tokenRecords.put(key, tokenRecord); } @Override - public void clearToken(RequestScopes scopes) { - tokenRecords.remove(computeCacheKey(scopes)); + public void clearToken(String key) { + tokenRecords.remove(key); } - @Getter @RequiredArgsConstructor public static class AccessTokenRecord { private final AccessToken token; private final LocalDateTime expiresAt; } - - private String computeCacheKey(RequestScopes scopes) { - List sortedScopes = new ArrayList<>(scopes.getScopes()); - Collections.sort(sortedScopes); - return MessageFormat.format("{0}:{1}:{2}", CACHE_KEY_PREFIX, this.clientId, sortedScopes.hashCode()); - } } diff --git a/src/test/java/com/truelayer/java/TrueLayerClientBuilderTests.java b/src/test/java/com/truelayer/java/TrueLayerClientBuilderTests.java index 7a365c73..379fa8fd 100644 --- a/src/test/java/com/truelayer/java/TrueLayerClientBuilderTests.java +++ b/src/test/java/com/truelayer/java/TrueLayerClientBuilderTests.java @@ -39,9 +39,7 @@ public void itShouldBuildAClientWithCustomLoggingAndCaching() { .clientCredentials(getClientCredentials()) .signingOptions(getSigningOptions()) .withHttpLogs(System.out::println) - .withCredentialsCaching(mock(ICredentialsCache.class)) - .withCredentialsCaching(); - + .withCredentialsCaching(mock(ICredentialsCache.class)); assertDoesNotThrow(sut::build); } diff --git a/src/test/java/com/truelayer/java/http/OkHttpClientFactoryTests.java b/src/test/java/com/truelayer/java/http/OkHttpClientFactoryTests.java index d84c1b1f..ee6b3148 100644 --- a/src/test/java/com/truelayer/java/http/OkHttpClientFactoryTests.java +++ b/src/test/java/com/truelayer/java/http/OkHttpClientFactoryTests.java @@ -21,6 +21,7 @@ import java.net.Proxy; import java.net.URI; import java.time.Duration; +import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.function.Consumer; @@ -208,9 +209,10 @@ public void shouldCreateAnAuthenticatedApiClient() { .clientCredentials(getClientCredentials()) .httpClient(RetrofitFactory.build(baseHttpClient, URI.create("http://localhost"))) .build(); + String clientId = UUID.randomUUID().toString(); - OkHttpClient authenticatedApiClient = - getOkHttpClientFactory().buildAuthenticatedApiClient(authServerApiClient, authenticationHandler, null); + OkHttpClient authenticatedApiClient = getOkHttpClientFactory() + .buildAuthenticatedApiClient(clientId, authServerApiClient, authenticationHandler, null); assertNotNull(authenticatedApiClient); assertTrue( @@ -236,10 +238,11 @@ public void shouldCreateAPaymentsApiClient() { .clientCredentials(getClientCredentials()) .httpClient(RetrofitFactory.build(baseHttpClient, URI.create("http://localhost"))) .build(); + String clientId = UUID.randomUUID().toString(); OkHttpClient authApiClient = getOkHttpClientFactory().buildAuthServerApiClient(baseHttpClient, getClientCredentials()); - OkHttpClient authenticatedApiClient = - getOkHttpClientFactory().buildAuthenticatedApiClient(authApiClient, authenticationHandler, null); + OkHttpClient authenticatedApiClient = getOkHttpClientFactory() + .buildAuthenticatedApiClient(clientId, authApiClient, authenticationHandler, null); OkHttpClient paymentClient = getOkHttpClientFactory().buildPaymentsApiClient(authenticatedApiClient, TestUtils.getSigningOptions()); diff --git a/src/test/java/com/truelayer/java/http/auth/AccessTokenManagerTests.java b/src/test/java/com/truelayer/java/http/auth/AccessTokenManagerTests.java index a6a8078f..32101d5f 100644 --- a/src/test/java/com/truelayer/java/http/auth/AccessTokenManagerTests.java +++ b/src/test/java/com/truelayer/java/http/auth/AccessTokenManagerTests.java @@ -10,8 +10,7 @@ import com.truelayer.java.auth.IAuthenticationHandler; import com.truelayer.java.auth.entities.AccessToken; import com.truelayer.java.entities.RequestScopes; -import com.truelayer.java.http.auth.cache.ICredentialsCache; -import com.truelayer.java.http.auth.cache.InMemoryCredentialsCache; +import com.truelayer.java.http.auth.cache.*; import com.truelayer.java.http.entities.ApiResponse; import java.util.*; import java.util.concurrent.CompletableFuture; @@ -23,79 +22,88 @@ class AccessTokenManagerTests { @Test @DisplayName("It should get a cached token") public void itShouldGetACachedToken() { + String clientId = UUID.randomUUID().toString(); AccessToken expectedToken = buildAccessToken().getData(); ICredentialsCache cache = mock(InMemoryCredentialsCache.class); RequestScopes scopes = RequestScopes.builder().scope(PAYMENTS).build(); - when(cache.getToken(scopes)).thenReturn(Optional.of(expectedToken)); + String cacheKey = CredentialsCacheHelper.buildKey(clientId, scopes); + when(cache.getToken(cacheKey)).thenReturn(Optional.of(expectedToken)); IAuthenticationHandler authenticationHandler = mock(AuthenticationHandler.class); - AccessTokenManager sut = new AccessTokenManager(authenticationHandler, cache); + AccessTokenManager sut = new AccessTokenManager(clientId, authenticationHandler, cache); AccessToken actualToken = sut.getToken(scopes); assertEquals(expectedToken, actualToken); - verify(cache, times(1)).getToken(scopes); + verify(cache, times(1)).getToken(cacheKey); verify(authenticationHandler, never()).getOauthToken(eq(scopes.getScopes())); } @Test @DisplayName("It should get a new token and store it in cache when requested scopes are not cached") public void itShouldGetAFreshTokenWhenRequestedScopesAreNotCached() { + String clientId = UUID.randomUUID().toString(); AccessToken expectedToken = buildAccessToken().getData(); ICredentialsCache cache = mock(InMemoryCredentialsCache.class); RequestScopes scopes = RequestScopes.builder().scope(PAYMENTS).build(); - when(cache.getToken(scopes)).thenReturn(Optional.empty()); + String cacheKey = CredentialsCacheHelper.buildKey(clientId, scopes); + when(cache.getToken(cacheKey)).thenReturn(Optional.empty()); IAuthenticationHandler authenticationHandler = mock(AuthenticationHandler.class); when(authenticationHandler.getOauthToken(eq(scopes.getScopes()))) .thenReturn(CompletableFuture.completedFuture( ApiResponse.builder().data(expectedToken).build())); - AccessTokenManager sut = new AccessTokenManager(authenticationHandler, cache); + AccessTokenManager sut = new AccessTokenManager(clientId, authenticationHandler, cache); AccessToken actualToken = sut.getToken(scopes); assertEquals(expectedToken, actualToken); - verify(cache, times(1)).getToken(scopes); + verify(cache, times(1)).getToken(cacheKey); verify(authenticationHandler, times(1)).getOauthToken(eq(scopes.getScopes())); - verify(cache, times(1)).storeToken(eq(scopes), eq(expectedToken)); + verify(cache, times(1)).storeToken(eq(cacheKey), eq(expectedToken)); } @Test @DisplayName("It should get a new token and store it in cache when different requested scopes are cached") public void itShouldGetAFreshTokenWhenDifferentRequestedScopesAreCached() { + String clientId = UUID.randomUUID().toString(); AccessToken expectedToken = buildAccessToken().getData(); AccessToken storedPaymentsToken = buildAccessToken().getData(); ICredentialsCache cache = mock(InMemoryCredentialsCache.class); RequestScopes paymentsScopes = RequestScopes.builder().scope(PAYMENTS).build(); RequestScopes vrpScopes = RequestScopes.builder().scope(RECURRING_PAYMENTS_SWEEPING).build(); - when(cache.getToken(paymentsScopes)).thenReturn(Optional.of(storedPaymentsToken)); - when(cache.getToken(vrpScopes)).thenReturn(Optional.empty()); + String paymentsCacheKey = CredentialsCacheHelper.buildKey(clientId, paymentsScopes); + String vrpCacheKey = CredentialsCacheHelper.buildKey(clientId, vrpScopes); + when(cache.getToken(paymentsCacheKey)).thenReturn(Optional.of(storedPaymentsToken)); + when(cache.getToken(vrpCacheKey)).thenReturn(Optional.empty()); IAuthenticationHandler authenticationHandler = mock(AuthenticationHandler.class); when(authenticationHandler.getOauthToken(eq(vrpScopes.getScopes()))) .thenReturn(CompletableFuture.completedFuture( ApiResponse.builder().data(expectedToken).build())); - AccessTokenManager sut = new AccessTokenManager(authenticationHandler, cache); + AccessTokenManager sut = new AccessTokenManager(clientId, authenticationHandler, cache); AccessToken actualToken = sut.getToken(vrpScopes); assertEquals(expectedToken, actualToken); - verify(cache, times(1)).getToken(vrpScopes); + verify(cache, times(1)).getToken(vrpCacheKey); verify(authenticationHandler, times(1)).getOauthToken(eq(vrpScopes.getScopes())); - verify(cache, times(1)).storeToken(eq(vrpScopes), eq(expectedToken)); + verify(cache, times(1)).storeToken(eq(vrpCacheKey), eq(expectedToken)); } @Test @DisplayName("It should invalidate an existing token") public void itShouldInvalidateExistingToken() { + String clientId = UUID.randomUUID().toString(); ICredentialsCache cache = mock(InMemoryCredentialsCache.class); IAuthenticationHandler authenticationHandler = mock(AuthenticationHandler.class); - AccessTokenManager sut = new AccessTokenManager(authenticationHandler, cache); + AccessTokenManager sut = new AccessTokenManager(clientId, authenticationHandler, cache); RequestScopes scopes = RequestScopes.builder().scope(PAYMENTS).build(); + String cacheKey = CredentialsCacheHelper.buildKey(clientId, scopes); AccessToken accessToken = buildAccessToken().getData(); - cache.storeToken(scopes, accessToken); + cache.storeToken(cacheKey, accessToken); sut.invalidateToken(scopes); - verify(cache, times(1)).clearToken(scopes); + verify(cache, times(1)).clearToken(cacheKey); } } diff --git a/src/test/java/com/truelayer/java/http/auth/cache/SimpleCredentialsCacheTests.java b/src/test/java/com/truelayer/java/http/auth/cache/InMemoryCredentialsCacheTests.java similarity index 60% rename from src/test/java/com/truelayer/java/http/auth/cache/SimpleCredentialsCacheTests.java rename to src/test/java/com/truelayer/java/http/auth/cache/InMemoryCredentialsCacheTests.java index d73bc1fb..8a0aef55 100644 --- a/src/test/java/com/truelayer/java/http/auth/cache/SimpleCredentialsCacheTests.java +++ b/src/test/java/com/truelayer/java/http/auth/cache/InMemoryCredentialsCacheTests.java @@ -13,24 +13,26 @@ import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; import java.util.Arrays; +import java.util.UUID; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.Mockito; -class SimpleCredentialsCacheTests { +class InMemoryCredentialsCacheTests { static final RequestScopes scopes = RequestScopes.builder().scope(PAYMENTS).build(); @Test @DisplayName("It should store a token record") public void shouldStoreATokenRecord() { - AccessToken expectedToken = buildAccessToken().getData(); InMemoryCredentialsCache sut = new InMemoryCredentialsCache(Clock.systemUTC()); + String clientId = UUID.randomUUID().toString(); + String cacheKey = CredentialsCacheHelper.buildKey(clientId, scopes); - sut.storeToken(scopes, expectedToken); + sut.storeToken(cacheKey, expectedToken); - assertEquals(expectedToken, sut.getToken(scopes).get()); + assertEquals(expectedToken, sut.getToken(cacheKey).get()); } @Test @@ -38,24 +40,31 @@ public void shouldStoreATokenRecord() { public void itShouldClearTheExistingToken() { InMemoryCredentialsCache sut = new InMemoryCredentialsCache(Clock.systemUTC()); AccessToken accessToken = buildAccessToken().getData(); - sut.storeToken(scopes, accessToken); + String clientId = UUID.randomUUID().toString(); + String cacheKey = CredentialsCacheHelper.buildKey(clientId, scopes); + + sut.storeToken(cacheKey, accessToken); - sut.clearToken(scopes); + sut.clearToken(cacheKey); - assertFalse(sut.getToken(scopes).isPresent()); + assertFalse(sut.getToken(cacheKey).isPresent()); } @Test @DisplayName("It should yield an empty optional if there are no cached tokens") public void itShouldYieldAnEmptyOptionalIfNoToken() { InMemoryCredentialsCache sut = new InMemoryCredentialsCache(Clock.systemUTC()); + String clientId = UUID.randomUUID().toString(); + String cacheKey = CredentialsCacheHelper.buildKey(clientId, scopes); - assertFalse(sut.getToken(scopes).isPresent()); + assertFalse(sut.getToken(cacheKey).isPresent()); } @Test @DisplayName("It should yield an empty optional if token is expired") public void itShouldYieldAnEmptyOptionalIfTokenExpired() { + String clientId = UUID.randomUUID().toString(); + String cacheKey = CredentialsCacheHelper.buildKey(clientId, scopes); AccessToken accessToken = buildAccessToken().getData(); // now() will return an instant which is behind the current time of (accessToken.expiresIn + 5 seconds) Instant aPastInstant = Clock.systemUTC().instant().minus(accessToken.getExpiresIn() + 5, ChronoUnit.SECONDS); @@ -67,43 +76,51 @@ public void itShouldYieldAnEmptyOptionalIfTokenExpired() { .thenReturn(Clock.systemUTC().instant()); when(fakeClock.getZone()).thenReturn(ZoneOffset.UTC); InMemoryCredentialsCache sut = new InMemoryCredentialsCache(fakeClock); - sut.storeToken(scopes, accessToken); + sut.storeToken(cacheKey, accessToken); - assertFalse(sut.getToken(scopes).isPresent()); + assertFalse(sut.getToken(cacheKey).isPresent()); } @Test @DisplayName("It should yield an token if token is not expired") public void itShouldYieldAnToken() { InMemoryCredentialsCache sut = new InMemoryCredentialsCache(Clock.systemUTC()); - sut.storeToken(scopes, buildAccessToken().getData()); + String clientId = UUID.randomUUID().toString(); + String cacheKey = CredentialsCacheHelper.buildKey(clientId, scopes); + + sut.storeToken(cacheKey, buildAccessToken().getData()); - assertTrue(sut.getToken(scopes).isPresent()); + assertTrue(sut.getToken(cacheKey).isPresent()); } @Test @DisplayName("It should yield different tokens with different scopes") public void itShouldYieldDifferentTokensWithDifferentScopes() { + String clientId = UUID.randomUUID().toString(); InMemoryCredentialsCache sut = new InMemoryCredentialsCache(Clock.systemUTC()); RequestScopes paymentsScopes = RequestScopes.builder().scope(PAYMENTS).build(); RequestScopes vrpScopes = RequestScopes.builder().scope(RECURRING_PAYMENTS_SWEEPING).build(); + String paymentsCacheKey = CredentialsCacheHelper.buildKey(clientId, paymentsScopes); + String vrpCacheKey = CredentialsCacheHelper.buildKey(clientId, vrpScopes); + AccessToken paymentsAccessToken = buildAccessToken().getData(); AccessToken vrpAccessToken = buildAccessToken().getData(); - sut.storeToken(paymentsScopes, paymentsAccessToken); - sut.storeToken(vrpScopes, vrpAccessToken); + sut.storeToken(paymentsCacheKey, paymentsAccessToken); + sut.storeToken(vrpCacheKey, vrpAccessToken); - assertTrue(sut.getToken(paymentsScopes).isPresent()); - assertEquals(paymentsAccessToken, sut.getToken(paymentsScopes).get()); - assertTrue(sut.getToken(vrpScopes).isPresent()); - assertEquals(vrpAccessToken, sut.getToken(vrpScopes).get()); + assertTrue(sut.getToken(paymentsCacheKey).isPresent()); + assertEquals(paymentsAccessToken, sut.getToken(paymentsCacheKey).get()); + assertTrue(sut.getToken(vrpCacheKey).isPresent()); + assertEquals(vrpAccessToken, sut.getToken(vrpCacheKey).get()); } @Test @DisplayName("It should yield the token with same scopes in different order") public void itShouldYieldTokenWithSameScopesInDifferentOrder() { + String clientId = UUID.randomUUID().toString(); InMemoryCredentialsCache sut = new InMemoryCredentialsCache(Clock.systemUTC()); RequestScopes storingScopes = RequestScopes.builder() .scopes(Arrays.asList(PAYMENTS, RECURRING_PAYMENTS_SWEEPING)) @@ -111,18 +128,21 @@ public void itShouldYieldTokenWithSameScopesInDifferentOrder() { RequestScopes requestingScopes = RequestScopes.builder() .scopes(Arrays.asList(RECURRING_PAYMENTS_SWEEPING, PAYMENTS)) .build(); + String storingScopesCacheKey = CredentialsCacheHelper.buildKey(clientId, storingScopes); + String requestingScopesCacheKey = CredentialsCacheHelper.buildKey(clientId, requestingScopes); AccessToken accessToken = buildAccessToken().getData(); - sut.storeToken(storingScopes, accessToken); + sut.storeToken(storingScopesCacheKey, accessToken); - assertTrue(sut.getToken(requestingScopes).isPresent()); - assertEquals(accessToken, sut.getToken(requestingScopes).get()); + assertTrue(sut.getToken(requestingScopesCacheKey).isPresent()); + assertEquals(accessToken, sut.getToken(requestingScopesCacheKey).get()); } @Test @DisplayName("It should replace the token with same scopes in different order") public void itShouldReplaceTheTokenWithSameScopesInDifferentOrder() { + String clientId = UUID.randomUUID().toString(); InMemoryCredentialsCache sut = new InMemoryCredentialsCache(Clock.systemUTC()); RequestScopes orderedStoringScopes = RequestScopes.builder() .scopes(Arrays.asList(PAYMENTS, RECURRING_PAYMENTS_SWEEPING)) @@ -131,39 +151,48 @@ public void itShouldReplaceTheTokenWithSameScopesInDifferentOrder() { .scopes(Arrays.asList(RECURRING_PAYMENTS_SWEEPING, PAYMENTS)) .build(); + String orderedStoringScopesCacheKey = CredentialsCacheHelper.buildKey(clientId, orderedStoringScopes); + String reverseOrderedStoringScopesCacheKey = + CredentialsCacheHelper.buildKey(clientId, reverseOrderedStoringScopes); + AccessToken anAccessToken = buildAccessToken().getData(); AccessToken anotherAccessToken = buildAccessToken().getData(); - sut.storeToken(orderedStoringScopes, anAccessToken); - sut.storeToken(reverseOrderedStoringScopes, anotherAccessToken); + sut.storeToken(orderedStoringScopesCacheKey, anAccessToken); + sut.storeToken(reverseOrderedStoringScopesCacheKey, anotherAccessToken); - assertTrue(sut.getToken(orderedStoringScopes).isPresent()); - assertEquals(anotherAccessToken, sut.getToken(orderedStoringScopes).get()); + assertTrue(sut.getToken(orderedStoringScopesCacheKey).isPresent()); + assertEquals( + anotherAccessToken, sut.getToken(orderedStoringScopesCacheKey).get()); } @Test @DisplayName("It should clear the cache only for the required scopes") public void itShouldClearTheCacheOnlyForRequiredScopes() { + String clientId = UUID.randomUUID().toString(); InMemoryCredentialsCache sut = new InMemoryCredentialsCache(Clock.systemUTC()); RequestScopes paymentsScopes = RequestScopes.builder().scope(PAYMENTS).build(); RequestScopes vrpScopes = RequestScopes.builder().scope(RECURRING_PAYMENTS_SWEEPING).build(); + String paymentsCacheKey = CredentialsCacheHelper.buildKey(clientId, paymentsScopes); + String vrpCacheKey = CredentialsCacheHelper.buildKey(clientId, vrpScopes); AccessToken paymentsAccessToken = buildAccessToken().getData(); AccessToken vrpAccessToken = buildAccessToken().getData(); - sut.storeToken(paymentsScopes, paymentsAccessToken); - sut.storeToken(vrpScopes, vrpAccessToken); + sut.storeToken(paymentsCacheKey, paymentsAccessToken); + sut.storeToken(vrpCacheKey, vrpAccessToken); - sut.clearToken(paymentsScopes); + sut.clearToken(paymentsCacheKey); - assertFalse(sut.getToken(paymentsScopes).isPresent()); - assertTrue(sut.getToken(vrpScopes).isPresent()); + assertFalse(sut.getToken(paymentsCacheKey).isPresent()); + assertTrue(sut.getToken(vrpCacheKey).isPresent()); } @Test @DisplayName("It should clear the cache with same scopes in different order") public void itShouldClearTheCacheWithSameScopesInDifferentOrder() { + String clientId = UUID.randomUUID().toString(); InMemoryCredentialsCache sut = new InMemoryCredentialsCache(Clock.systemUTC()); RequestScopes storingScopes = RequestScopes.builder() .scopes(Arrays.asList(PAYMENTS, RECURRING_PAYMENTS_SWEEPING)) @@ -172,11 +201,14 @@ public void itShouldClearTheCacheWithSameScopesInDifferentOrder() { .scopes(Arrays.asList(RECURRING_PAYMENTS_SWEEPING, PAYMENTS)) .build(); + String storingScopesCacheKey = CredentialsCacheHelper.buildKey(clientId, storingScopes); + String clearingScopesCacheKey = CredentialsCacheHelper.buildKey(clientId, clearCacheScopes); + AccessToken accessToken = buildAccessToken().getData(); - sut.storeToken(storingScopes, accessToken); - sut.clearToken(clearCacheScopes); + sut.storeToken(storingScopesCacheKey, accessToken); + sut.clearToken(clearingScopesCacheKey); - assertFalse(sut.getToken(storingScopes).isPresent()); + assertFalse(sut.getToken(storingScopesCacheKey).isPresent()); } }