-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Local Data File Option To Read Configuration Locally (#242)
Added the ability to read configuration from a file when in local mode.
- Loading branch information
1 parent
97df78c
commit 1747ec7
Showing
7 changed files
with
200 additions
and
5 deletions.
There are no files selected for viewing
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
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
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
2 changes: 1 addition & 1 deletion
2
...main/kotlin/com/statsig/sdk/IDataStore.kt → ...n/com/statsig/sdk/datastore/IDataStore.kt
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
49 changes: 49 additions & 0 deletions
49
src/main/kotlin/com/statsig/sdk/datastore/LocalFileDataStore.kt
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package com.statsig.sdk.datastore | ||
|
||
import java.io.File | ||
import java.util.* | ||
|
||
/** | ||
* LocalFileDataStore class implements IDataStore interface to provide data storage operations using local files. | ||
*/ | ||
class LocalFileDataStore() : IDataStore() { | ||
|
||
var workingDirectory: String = "/tmp/statsig/" | ||
var filePath: String = "" | ||
|
||
init { | ||
workingDirectory = resolvePath(workingDirectory) | ||
if (!File(workingDirectory).exists()) { | ||
File(workingDirectory).mkdirs() // Ensure the working directory exists | ||
} | ||
} | ||
|
||
override fun get(key: String): String? { | ||
val path = "$workingDirectory${Base64.getEncoder().encodeToString(filePath.toByteArray())}" | ||
return try { | ||
File(path).readText() | ||
} catch (e: Exception) { | ||
null | ||
} | ||
} | ||
|
||
override fun set(key: String, value: String) { | ||
val path = "$workingDirectory${Base64.getEncoder().encodeToString(filePath.toByteArray())}" | ||
File(path).writeText(value) | ||
} | ||
|
||
override fun shutdown() { | ||
// No explicit shutdown operations needed for this class | ||
} | ||
|
||
internal fun resolvePath(path: String): String { | ||
var resolvedPath = path | ||
if (!resolvedPath.endsWith("/")) { | ||
resolvedPath += "/" | ||
} | ||
if (!resolvedPath.startsWith("/")) { | ||
resolvedPath = File("").absolutePath + "/" + resolvedPath | ||
} | ||
return resolvedPath | ||
} | ||
} |
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
116 changes: 116 additions & 0 deletions
116
src/test/java/com/statsig/sdk/LocalFileDataStoreTest.kt
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package com.statsig.sdk | ||
|
||
import com.statsig.sdk.datastore.LocalFileDataStore | ||
import okhttp3.mockwebserver.Dispatcher | ||
import okhttp3.mockwebserver.MockResponse | ||
import okhttp3.mockwebserver.MockWebServer | ||
import okhttp3.mockwebserver.RecordedRequest | ||
import org.junit.After | ||
import org.junit.Assert | ||
import org.junit.Before | ||
import org.junit.Test | ||
import java.io.File | ||
|
||
class LocalFileDataStoreTest { | ||
private lateinit var localDataStore: LocalFileDataStore | ||
private lateinit var downloadConfigSpecsResponse: String | ||
private lateinit var statsigServer: StatsigServer | ||
private lateinit var options: StatsigOptions | ||
private lateinit var mockServer: MockWebServer | ||
private var didCallDownloadConfig = false | ||
private val user = StatsigUser("test-user") | ||
|
||
@Before | ||
fun setUp() { | ||
downloadConfigSpecsResponse = StatsigE2ETest::class.java.getResource("/download_config_specs.json")?.readText() ?: "" | ||
|
||
mockServer = MockWebServer() | ||
mockServer.start(8899) | ||
mockServer.apply { | ||
dispatcher = object : Dispatcher() { | ||
@Throws(InterruptedException::class) | ||
override fun dispatch(request: RecordedRequest): MockResponse { | ||
if (request.path == null) { | ||
return MockResponse().setResponseCode(404) | ||
} | ||
if ("/v1/download_config_specs" in request.path!!) { | ||
didCallDownloadConfig = true | ||
return MockResponse().setResponseCode(200).setBody(downloadConfigSpecsResponse) | ||
} | ||
return MockResponse().setResponseCode(404) | ||
} | ||
} | ||
} | ||
|
||
localDataStore = LocalFileDataStore() | ||
} | ||
|
||
@After | ||
fun tearDown() { | ||
File(localDataStore.workingDirectory).deleteRecursively() // clean up the folder when finished tests | ||
mockServer.shutdown() | ||
} | ||
|
||
@Test | ||
fun testLocalDataStoreIsLoaded() { | ||
options = StatsigOptions( | ||
api = mockServer.url("/v1").toString(), | ||
dataStore = localDataStore, | ||
disableDiagnostics = true, | ||
) | ||
|
||
statsigServer = StatsigServer.create() | ||
statsigServer.initializeAsync("test-key", options).get() | ||
|
||
val gateRes1 = statsigServer.checkGateSync(user, "always_on_gate") | ||
Assert.assertTrue(gateRes1) | ||
|
||
user.email = "test@statsig.com" | ||
val gateRes2 = statsigServer.checkGateSync(user, "on_for_statsig_email") | ||
Assert.assertTrue(gateRes2) | ||
statsigServer.shutdown() | ||
} | ||
|
||
@Test | ||
fun testNetworkNotCallWhenBootstrapIsPresent() { | ||
options = StatsigOptions( | ||
api = mockServer.url("/v1").toString(), | ||
bootstrapValues = downloadConfigSpecsResponse, | ||
) | ||
statsigServer = StatsigServer.create() | ||
statsigServer.initializeAsync("secret-local", options).get() | ||
|
||
Assert.assertFalse(didCallDownloadConfig) | ||
} | ||
|
||
@Test | ||
fun testCallsNetworkWhenAdapterIsEmpty() { | ||
val options = StatsigOptions( | ||
api = mockServer.url("/v1").toString(), | ||
) | ||
statsigServer = StatsigServer.create() | ||
statsigServer.initializeAsync("secret-local", options).get() | ||
|
||
Assert.assertTrue(didCallDownloadConfig) | ||
statsigServer.shutdown() | ||
} | ||
|
||
@Test | ||
fun testNetworkNotCalledWhenAdapterEnable() { | ||
// if dataStore(cached one) is enabled | ||
// should not trigger network request | ||
val options = StatsigOptions( | ||
api = mockServer.url("/v1").toString(), | ||
dataStore = TestDataAdapter() | ||
) | ||
statsigServer = StatsigServer.create() | ||
statsigServer.initializeAsync("secret-local", options).get() | ||
|
||
Assert.assertFalse(didCallDownloadConfig) | ||
|
||
// Test dataStore still works | ||
val dataStoreGateRes = statsigServer.checkGateSync(user, "gate_from_adapter_always_on") | ||
Assert.assertTrue(dataStoreGateRes) | ||
statsigServer.shutdown() | ||
} | ||
} |