Skip to content

Commit 2bb34a7

Browse files
authored
Remove cache eviction on init and add a method to allow consumers to clear the cache (#37)
* Remove the cache clearing at initialisation, add method to manually clear the cache, and add unit tests to cover the expected cache behaviour. * Improvements to the negative caching test
1 parent 797c387 commit 2bb34a7

File tree

3 files changed

+92
-3
lines changed

3 files changed

+92
-3
lines changed

Diff for: FlagsmithClient/src/main/java/com/flagsmith/Flagsmith.kt

+4
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ class Flagsmith constructor(
150150
retrofit.getIdentityFlagsAndTraits(identity).enqueueWithResult(defaults = null, result = result)
151151
.also { lastUsedIdentity = identity }
152152

153+
fun clearCache() {
154+
cache?.evictAll()
155+
}
156+
153157
private fun getFeatureFlag(
154158
featureId: String,
155159
identity: String?,

Diff for: FlagsmithClient/src/main/java/com/flagsmith/internal/FlagsmithRetrofitService.kt

-3
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,6 @@ interface FlagsmithRetrofitService {
9696
.cache(cache)
9797
.build()
9898

99-
// Make sure that we start with a clean cache
100-
client.cache?.evictAll()
101-
10299
val retrofit = Retrofit.Builder()
103100
.baseUrl(baseUrl)
104101
.addConverterFactory(GsonConverterFactory.create())

Diff for: FlagsmithClient/src/test/java/com/flagsmith/FeatureFlagCachingTests.kt

+88
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import com.flagsmith.mockResponses.mockResponseFor
1313
import org.awaitility.Awaitility
1414
import org.awaitility.kotlin.await
1515
import org.awaitility.kotlin.untilNotNull
16+
import org.awaitility.kotlin.untilTrue
1617
import org.junit.After
1718
import org.junit.Assert
1819
import org.junit.Before
@@ -25,6 +26,7 @@ import org.mockito.MockitoAnnotations
2526
import org.mockserver.integration.ClientAndServer
2627
import java.io.File
2728
import java.time.Duration
29+
import java.util.concurrent.atomic.AtomicBoolean
2830

2931

3032
class FeatureFlagCachingTests {
@@ -291,4 +293,90 @@ class FeatureFlagCachingTests {
291293
Assert.assertNotNull(foundFromServer)
292294
Assert.assertEquals(7.0, foundFromServer?.featureStateValue)
293295
}
296+
297+
@Test
298+
fun testGetFeatureFlagsWithNewCachedFlagsmithGetsCachedValue() {
299+
mockServer.mockResponseFor(MockEndpoint.GET_IDENTITIES)
300+
mockServer.mockFailureFor(MockEndpoint.GET_IDENTITIES)
301+
302+
// First time around we should be successful and cache the response
303+
var foundFromServer: Flag? = null
304+
flagsmithWithCache.clearCache()
305+
flagsmithWithCache.getFeatureFlags(identity = "person") { result ->
306+
Assert.assertTrue(result.isSuccess)
307+
308+
foundFromServer =
309+
result.getOrThrow().find { flag -> flag.feature.name == "with-value" }
310+
}
311+
312+
await untilNotNull { foundFromServer }
313+
Assert.assertNotNull(foundFromServer)
314+
Assert.assertEquals(756.0, foundFromServer?.featureStateValue)
315+
316+
// Now get a new Flagsmith instance with the same cache and expect the cached response to be returned
317+
val newFlagsmithWithCache = Flagsmith(
318+
environmentKey = "",
319+
baseUrl = "http://localhost:${mockServer.localPort}",
320+
enableAnalytics = false,
321+
context = mockApplicationContext,
322+
cacheConfig = FlagsmithCacheConfig(enableCache = true)
323+
)
324+
325+
// Now we mock the failure and expect the cached response to be returned from the new flagsmith instance
326+
var foundFromCache: Flag? = null
327+
newFlagsmithWithCache.getFeatureFlags(identity = "person") { result ->
328+
Assert.assertTrue("The request will fail but we should be successful as we fall back on the cache", result.isSuccess)
329+
330+
foundFromCache =
331+
result.getOrThrow().find { flag -> flag.feature.name == "with-value" }
332+
}
333+
334+
await untilNotNull { foundFromCache }
335+
Assert.assertNotNull(foundFromCache)
336+
Assert.assertEquals(756.0, foundFromCache?.featureStateValue)
337+
}
338+
339+
@Test
340+
fun testGetFeatureFlagsWithNewCachedFlagsmithDoesntGetCachedValueWhenWeClearTheCache() {
341+
mockServer.mockResponseFor(MockEndpoint.GET_IDENTITIES)
342+
mockServer.mockFailureFor(MockEndpoint.GET_IDENTITIES)
343+
344+
// First time around we should be successful and cache the response
345+
var foundFromServer: Flag? = null
346+
flagsmithWithCache.clearCache()
347+
flagsmithWithCache.getFeatureFlags(identity = "person") { result ->
348+
Assert.assertTrue(result.isSuccess)
349+
350+
foundFromServer =
351+
result.getOrThrow().find { flag -> flag.feature.name == "with-value" }
352+
}
353+
354+
await untilNotNull { foundFromServer }
355+
Assert.assertNotNull(foundFromServer)
356+
Assert.assertEquals(756.0, foundFromServer?.featureStateValue)
357+
358+
// Now get a new Flagsmith instance with the same cache and evict the cache straight away
359+
val newFlagsmithWithClearedCache = Flagsmith(
360+
environmentKey = "",
361+
baseUrl = "http://localhost:${mockServer.localPort}",
362+
enableAnalytics = false,
363+
context = mockApplicationContext,
364+
cacheConfig = FlagsmithCacheConfig(enableCache = true)
365+
)
366+
newFlagsmithWithClearedCache.clearCache()
367+
368+
// Now we mock the failure and expect the get to fail as we don't have the cache to fall back on
369+
var foundFromCache: Flag? = null
370+
val hasFinishedGetRequest = AtomicBoolean(false)
371+
newFlagsmithWithClearedCache.getFeatureFlags(identity = "person") { result ->
372+
Assert.assertFalse("This un-cached response should fail", result.isSuccess)
373+
374+
foundFromCache =
375+
result.getOrNull()?.find { flag -> flag.feature.name == "with-value" }
376+
hasFinishedGetRequest.set(true)
377+
}
378+
379+
await untilTrue(hasFinishedGetRequest)
380+
Assert.assertNull("Shouldn't get any data back as we don't have a cache", foundFromCache)
381+
}
294382
}

0 commit comments

Comments
 (0)