diff --git a/src/main/kotlin/com/statsig/sdk/Statsig.kt b/src/main/kotlin/com/statsig/sdk/Statsig.kt index 16774e6..0350c42 100644 --- a/src/main/kotlin/com/statsig/sdk/Statsig.kt +++ b/src/main/kotlin/com/statsig/sdk/Statsig.kt @@ -811,5 +811,18 @@ class Statsig { } return initialized } + + @JvmStatic + fun batchGetExperimentsAsync( + user: StatsigUser, + experimentNames: List + ): CompletableFuture> { + if (!checkInitialized()) { + return CompletableFuture.completedFuture( + experimentNames.associateWith { DynamicConfig.empty(it) } + ) + } + return statsigServer.batchGetExperimentsAsync(user, experimentNames) + } } } diff --git a/src/main/kotlin/com/statsig/sdk/StatsigServer.kt b/src/main/kotlin/com/statsig/sdk/StatsigServer.kt index 76ab213..97ba7b4 100644 --- a/src/main/kotlin/com/statsig/sdk/StatsigServer.kt +++ b/src/main/kotlin/com/statsig/sdk/StatsigServer.kt @@ -262,6 +262,12 @@ sealed class StatsigServer { @JvmSynthetic internal abstract fun getCustomLogger(): LoggerInterface + + abstract fun batchGetExperimentsAsync( + user: StatsigUser, + experimentNames: List + ): CompletableFuture> + companion object { @JvmStatic @@ -1348,4 +1354,21 @@ private class StatsigServerImpl() : throw e } } + + override fun batchGetExperimentsAsync( + user: StatsigUser, + experimentNames: List + ): CompletableFuture> { + if (!isSDKInitialized()) { + return CompletableFuture.completedFuture( + experimentNames.associateWith { DynamicConfig.empty(it) } + ) + } + + return statsigScope.future { + experimentNames.associateWith { experimentName -> + getExperiment(user, experimentName) + } + } + } } diff --git a/src/test/java/com/statsig/sdk/StatsigServerTest.kt b/src/test/java/com/statsig/sdk/StatsigServerTest.kt new file mode 100644 index 0000000..d6cff5a --- /dev/null +++ b/src/test/java/com/statsig/sdk/StatsigServerTest.kt @@ -0,0 +1,99 @@ +package com.statsig.sdk + +import junit.framework.TestCase.* +import kotlinx.coroutines.runBlocking +import org.junit.Test + +class StatsigServerTest { + @Test + fun `test batchGetExperimentsAsync returns multiple experiments correctly`() = runBlocking { + val server = StatsigServer.create() + server.initialize( + "secret-key", + StatsigOptions() + ) + + val user = StatsigUser("123") + val experimentNames = listOf("experiment_1", "experiment_2", "experiment_3") + + val result = server.batchGetExperimentsAsync(user, experimentNames).get() + + assertNotNull(result) + assertEquals(3, result.size) + experimentNames.forEach { name -> + assertTrue(result.containsKey(name)) + val config = result[name] + assertNotNull(config) + assertEquals(name, config?.name) + + val singleResult = server.getExperimentAsync(user, name).get() + assertEquals(singleResult.value, config?.value) + assertEquals(singleResult.ruleID, config?.ruleID) + } + } + + @Test + fun `test batchGetExperimentsAsync returns empty configs when not initialized`() = runBlocking { + val server = StatsigServer.create() + val user = StatsigUser("123") + val experimentNames = listOf("experiment_1", "experiment_2") + + val result = server.batchGetExperimentsAsync(user, experimentNames).get() + + assertNotNull(result) + assertEquals(2, result.size) + experimentNames.forEach { name -> + assertTrue(result.containsKey(name)) + val config = result[name] + assertNotNull(config) + assertEquals(name, config?.name) + assertTrue(config?.value?.isEmpty() ?: false) + } + } + + @Test + fun `test batchGetExperimentsAsync with empty experiment list`() = runBlocking { + val server = StatsigServer.create() + server.initialize( + "secret-key", + StatsigOptions() + ) + + val user = StatsigUser("123") + val experimentNames = emptyList() + + val result = server.batchGetExperimentsAsync(user, experimentNames).get() + + assertNotNull(result) + assertTrue(result.isEmpty()) + } + + @Test + fun `test batchGetExperimentsAsync results match individual getExperiment calls`() = runBlocking { + val server = StatsigServer.create() + server.initialize( + "secret-key", + StatsigOptions() + ) + + val user = StatsigUser("123") + val experimentNames = listOf("experiment_1", "experiment_2") + + val batchResults = server.batchGetExperimentsAsync(user, experimentNames).get() + val individualResults = experimentNames.associateWith { + server.getExperimentAsync(user, it).get() + } + + assertEquals(individualResults.size, batchResults.size) + experimentNames.forEach { name -> + val batchConfig = batchResults[name] + val individualConfig = individualResults[name] + + assertNotNull(batchConfig) + assertNotNull(individualConfig) + assertEquals(individualConfig?.value, batchConfig?.value) + assertEquals(individualConfig?.ruleID, batchConfig?.ruleID) + assertEquals(individualConfig?.name, batchConfig?.name) + } + } +} \ No newline at end of file