Skip to content

Commit abb3451

Browse files
committed
fix: small fixups on status + added own test class
Signed-off-by: Nicklas Lundin <nicklasl@spotify.com>
1 parent 3c29502 commit abb3451

File tree

2 files changed

+101
-13
lines changed

2 files changed

+101
-13
lines changed

android/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.kt

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ import kotlinx.coroutines.CoroutineScope
66
import kotlinx.coroutines.Deferred
77
import kotlinx.coroutines.Dispatchers
88
import kotlinx.coroutines.async
9+
import kotlinx.coroutines.flow.Flow
910
import kotlinx.coroutines.flow.MutableSharedFlow
10-
import kotlinx.coroutines.flow.SharedFlow
11+
import kotlinx.coroutines.flow.distinctUntilChanged
1112
import java.util.concurrent.CancellationException
1213

1314
@Suppress("TooManyFunctions")
@@ -27,7 +28,7 @@ object OpenFeatureAPI {
2728
/**
2829
* A flow of [OpenFeatureStatus] that emits the current status of the SDK.
2930
*/
30-
val statusFlow: SharedFlow<OpenFeatureStatus> get() = _statusFlow
31+
val statusFlow: Flow<OpenFeatureStatus> get() = _statusFlow.distinctUntilChanged()
3132

3233
var hooks: List<Hook<*>> = listOf()
3334
private set
@@ -73,22 +74,21 @@ object OpenFeatureAPI {
7374
initialContext: EvaluationContext? = null
7475
) {
7576
this@OpenFeatureAPI.provider = provider
76-
// TODO consider if stale status should emit? _statusFlow.tryEmit(OpenFeatureStatus.Stale)
77+
_statusFlow.emit(OpenFeatureStatus.NotReady)
7778
if (initialContext != null) context = initialContext
7879
try {
7980
getProvider().initialize(context)
80-
_statusFlow.tryEmit(OpenFeatureStatus.Ready)
81+
_statusFlow.emit(OpenFeatureStatus.Ready)
8182
} catch (e: OpenFeatureError) {
82-
_statusFlow.tryEmit(OpenFeatureStatus.Error(e))
83+
_statusFlow.emit(OpenFeatureStatus.Error(e))
8384
} catch (e: Throwable) {
84-
_statusFlow.tryEmit(
85+
_statusFlow.emit(
8586
OpenFeatureStatus.Error(
8687
OpenFeatureError.GeneralError(
8788
e.message ?: e.javaClass.name
8889
)
8990
)
9091
)
91-
// TODO deal with things by setting status to Error or Fatal
9292
}
9393
}
9494

@@ -104,6 +104,7 @@ object OpenFeatureAPI {
104104
*/
105105
fun clearProvider() {
106106
provider = NOOP_PROVIDER
107+
_statusFlow.tryEmit(OpenFeatureStatus.NotReady)
107108
}
108109

109110
/**
@@ -148,15 +149,14 @@ object OpenFeatureAPI {
148149
val oldContext = context
149150
context = evaluationContext
150151
if (oldContext != evaluationContext) {
151-
_statusFlow.tryEmit(OpenFeatureStatus.Reconciling)
152+
_statusFlow.emit(OpenFeatureStatus.Reconciling)
152153
try {
153154
getProvider().onContextSet(oldContext, evaluationContext)
154-
_statusFlow.tryEmit(OpenFeatureStatus.Ready)
155+
_statusFlow.emit(OpenFeatureStatus.Ready)
155156
} catch (e: OpenFeatureError) {
156-
_statusFlow.tryEmit(OpenFeatureStatus.Error(e))
157-
// TODO how do we handle fatal errors?
157+
_statusFlow.emit(OpenFeatureStatus.Error(e))
158158
} catch (e: Throwable) {
159-
_statusFlow.tryEmit(
159+
_statusFlow.emit(
160160
OpenFeatureStatus.Error(
161161
OpenFeatureError.GeneralError(
162162
e.message ?: e.javaClass.name
@@ -211,6 +211,7 @@ object OpenFeatureAPI {
211211
fun shutdown() {
212212
setEvaluationContextJob?.cancel(CancellationException("Set context job was cancelled"))
213213
setProviderJob?.cancel(CancellationException("Provider set job was cancelled"))
214+
provider = NOOP_PROVIDER
214215
_statusFlow.tryEmit(OpenFeatureStatus.NotReady)
215216
getProvider().shutdown()
216217
clearHooks()
@@ -219,5 +220,5 @@ object OpenFeatureAPI {
219220
/**
220221
* Get the current [OpenFeatureStatus] of the SDK.
221222
*/
222-
fun getStatus(): OpenFeatureStatus = statusFlow.replayCache.first()
223+
fun getStatus(): OpenFeatureStatus = _statusFlow.replayCache.first()
223224
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package dev.openfeature.sdk
2+
3+
import dev.openfeature.sdk.helpers.BrokenInitProvider
4+
import dev.openfeature.sdk.helpers.DoSomethingProvider
5+
import junit.framework.Assert.assertEquals
6+
import junit.framework.Assert.assertTrue
7+
import kotlinx.coroutines.cancelAndJoin
8+
import kotlinx.coroutines.launch
9+
import kotlinx.coroutines.test.runTest
10+
import org.junit.After
11+
import org.junit.Test
12+
13+
class StatusTests {
14+
15+
@After
16+
fun tearDown() {
17+
// It becomes important to clear the provider after each test since the SDK is a singleton
18+
OpenFeatureAPI.shutdown()
19+
}
20+
21+
@Test
22+
fun testNoProviderSet() {
23+
assertEquals(OpenFeatureStatus.NotReady, OpenFeatureAPI.getStatus())
24+
}
25+
26+
@Test
27+
fun testProviderTransitionsToReadyAndNotReadyAfterShutdown() = runTest {
28+
assertEquals(OpenFeatureStatus.NotReady, OpenFeatureAPI.getStatus())
29+
OpenFeatureAPI.setProviderAndWait(NoOpProvider())
30+
assertEquals(OpenFeatureStatus.Ready, OpenFeatureAPI.getStatus())
31+
OpenFeatureAPI.shutdown()
32+
assertEquals(OpenFeatureStatus.NotReady, OpenFeatureAPI.getStatus())
33+
}
34+
35+
@Test
36+
fun testProviderThrowsDuringInit() = runTest {
37+
assertEquals(OpenFeatureStatus.NotReady, OpenFeatureAPI.getStatus())
38+
OpenFeatureAPI.setProviderAndWait(BrokenInitProvider())
39+
assertTrue(OpenFeatureAPI.getStatus() is OpenFeatureStatus.Error)
40+
OpenFeatureAPI.shutdown()
41+
assertEquals(OpenFeatureStatus.NotReady, OpenFeatureAPI.getStatus())
42+
}
43+
44+
@Test
45+
fun testClearProviderEmitsNotReady() = runTest {
46+
assertEquals(OpenFeatureStatus.NotReady, OpenFeatureAPI.getStatus())
47+
OpenFeatureAPI.setProviderAndWait(NoOpProvider())
48+
assertEquals(OpenFeatureStatus.Ready, OpenFeatureAPI.getStatus())
49+
OpenFeatureAPI.clearProvider()
50+
assertEquals(OpenFeatureStatus.NotReady, OpenFeatureAPI.getStatus())
51+
}
52+
53+
@Test
54+
fun testProviderTransitionsToReconcilingOnContextSet() = runTest {
55+
assertEquals(OpenFeatureStatus.NotReady, OpenFeatureAPI.getStatus())
56+
val statuses = mutableListOf<OpenFeatureStatus>()
57+
val job = launch {
58+
OpenFeatureAPI.statusFlow.collect {
59+
statuses.add(it)
60+
}
61+
}
62+
OpenFeatureAPI.setProviderAndWait(DoSomethingProvider())
63+
OpenFeatureAPI.setEvaluationContextAndWait(ImmutableContext("some value"))
64+
testScheduler.advanceUntilIdle()
65+
assertEquals(4, statuses.size)
66+
OpenFeatureAPI.setEvaluationContextAndWait(ImmutableContext("some other value"))
67+
testScheduler.advanceUntilIdle()
68+
assertEquals(6, statuses.size)
69+
OpenFeatureAPI.shutdown()
70+
testScheduler.advanceUntilIdle()
71+
assertEquals(OpenFeatureStatus.NotReady, OpenFeatureAPI.getStatus())
72+
job.cancelAndJoin()
73+
assertEquals(7, statuses.size)
74+
assertEquals(
75+
listOf(
76+
OpenFeatureStatus.NotReady,
77+
OpenFeatureStatus.Ready,
78+
OpenFeatureStatus.Reconciling,
79+
OpenFeatureStatus.Ready,
80+
OpenFeatureStatus.Reconciling,
81+
OpenFeatureStatus.Ready,
82+
OpenFeatureStatus.NotReady
83+
),
84+
statuses
85+
)
86+
}
87+
}

0 commit comments

Comments
 (0)