Skip to content

Commit 6f2d7b0

Browse files
committed
add tests
1 parent 6f4d088 commit 6f2d7b0

File tree

3 files changed

+479
-0
lines changed

3 files changed

+479
-0
lines changed

sentry-launchdarkly-android/build.gradle.kts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
plugins {
22
id("com.android.library")
3+
alias(libs.plugins.kotlin.android)
34
jacoco
45
alias(libs.plugins.jacoco.android)
56
alias(libs.plugins.gradle.versions)
@@ -28,6 +29,10 @@ android {
2829
getByName("release") { consumerProguardFiles("proguard-rules.pro") }
2930
}
3031

32+
kotlin {
33+
compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8
34+
}
35+
3136
testOptions {
3237
animationsDisabled = true
3338
unitTests.apply {
@@ -60,7 +65,10 @@ dependencies {
6065
// tests
6166
testImplementation(projects.sentry)
6267
testImplementation(projects.sentryTestSupport)
68+
testImplementation(kotlin(Config.kotlinStdLib, Config.kotlinStdLibVersionAndroid))
6369
testImplementation(libs.androidx.test.ext.junit)
70+
testImplementation(libs.kotlin.test.junit)
71+
testImplementation(libs.mockito.kotlin)
6472
testImplementation(libs.mockito.inline)
6573
testImplementation(libs.launchdarkly.android)
6674
}
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
package io.sentry.launchdarkly.android
2+
3+
import com.launchdarkly.sdk.EvaluationDetail
4+
import com.launchdarkly.sdk.LDValue
5+
import com.launchdarkly.sdk.LDValueType
6+
import com.launchdarkly.sdk.android.integrations.EvaluationSeriesContext
7+
import io.sentry.ILogger
8+
import io.sentry.IScopes
9+
import io.sentry.SentryLevel
10+
import io.sentry.SentryOptions
11+
import kotlin.test.AfterTest
12+
import kotlin.test.BeforeTest
13+
import kotlin.test.Test
14+
import kotlin.test.assertEquals
15+
import org.mockito.kotlin.any
16+
import org.mockito.kotlin.eq
17+
import org.mockito.kotlin.mock
18+
import org.mockito.kotlin.never
19+
import org.mockito.kotlin.verify
20+
import org.mockito.kotlin.whenever
21+
22+
class SentryLaunchDarklyAndroidHookTest {
23+
24+
private lateinit var mockScopes: IScopes
25+
private lateinit var mockOptions: SentryOptions
26+
private lateinit var mockLogger: ILogger
27+
private lateinit var hook: SentryLaunchDarklyAndroidHook
28+
29+
@BeforeTest
30+
fun setUp() {
31+
mockScopes = mock()
32+
mockOptions = mock()
33+
mockLogger = mock()
34+
whenever(mockScopes.options).thenReturn(mockOptions)
35+
whenever(mockOptions.logger).thenReturn(mockLogger)
36+
hook = SentryLaunchDarklyAndroidHook(mockScopes)
37+
}
38+
39+
@Test
40+
fun `afterEvaluation with boolean value calls addFeatureFlag`() {
41+
val flagKey = "test-flag"
42+
val flagValue = true
43+
44+
val seriesContext = createSeriesContext(flagKey)
45+
46+
val ldValue = mock<LDValue>()
47+
whenever(ldValue.getType()).thenReturn(LDValueType.BOOLEAN)
48+
whenever(ldValue.booleanValue()).thenReturn(flagValue)
49+
50+
val evaluationDetail = mock<EvaluationDetail<LDValue>>()
51+
whenever(evaluationDetail.getValue()).thenReturn(ldValue)
52+
53+
val seriesData = mutableMapOf<String, Any>()
54+
seriesData["existingKey"] = "existingValue"
55+
56+
val result = hook.afterEvaluation(seriesContext, seriesData, evaluationDetail)
57+
58+
verify(mockScopes).addFeatureFlag(eq(flagKey), eq(flagValue))
59+
assertEquals(seriesData, result)
60+
assertEquals("existingValue", result["existingKey"])
61+
}
62+
63+
@Test
64+
fun `afterEvaluation with false boolean value calls addFeatureFlag`() {
65+
val flagKey = "test-flag"
66+
val flagValue = false
67+
68+
val seriesContext = createSeriesContext(flagKey)
69+
70+
val ldValue = mock<LDValue>()
71+
whenever(ldValue.getType()).thenReturn(LDValueType.BOOLEAN)
72+
whenever(ldValue.booleanValue()).thenReturn(flagValue)
73+
74+
val evaluationDetail = mock<EvaluationDetail<LDValue>>()
75+
whenever(evaluationDetail.getValue()).thenReturn(ldValue)
76+
77+
val seriesData = mutableMapOf<String, Any>()
78+
seriesData["existingKey"] = "existingValue"
79+
80+
val result = hook.afterEvaluation(seriesContext, seriesData, evaluationDetail)
81+
82+
verify(mockScopes).addFeatureFlag(eq(flagKey), eq(flagValue))
83+
assertEquals(seriesData, result)
84+
assertEquals("existingValue", result["existingKey"])
85+
}
86+
87+
@Test
88+
fun `afterEvaluation with non-boolean value does not call addFeatureFlag`() {
89+
val flagKey = "test-flag"
90+
91+
val seriesContext = createSeriesContext(flagKey)
92+
93+
val ldValue = mock<LDValue>()
94+
whenever(ldValue.getType()).thenReturn(LDValueType.STRING)
95+
96+
val evaluationDetail = mock<EvaluationDetail<LDValue>>()
97+
whenever(evaluationDetail.getValue()).thenReturn(ldValue)
98+
99+
val seriesData = mutableMapOf<String, Any>()
100+
seriesData["existingKey"] = "existingValue"
101+
102+
val result = hook.afterEvaluation(seriesContext, seriesData, evaluationDetail)
103+
104+
verify(mockScopes, never()).addFeatureFlag(any(), any())
105+
assertEquals(seriesData, result)
106+
assertEquals("existingValue", result["existingKey"])
107+
}
108+
109+
@Test
110+
fun `afterEvaluation with null seriesContext returns seriesData`() {
111+
val evaluationDetail = mock<EvaluationDetail<LDValue>>()
112+
val seriesData = mutableMapOf<String, Any>()
113+
seriesData["existingKey"] = "existingValue"
114+
115+
val result = hook.afterEvaluation(null, seriesData, evaluationDetail)
116+
117+
verify(mockScopes, never()).addFeatureFlag(any(), any())
118+
assertEquals(seriesData, result)
119+
assertEquals("existingValue", result["existingKey"])
120+
}
121+
122+
@Test
123+
fun `afterEvaluation with null evaluationDetail returns seriesData`() {
124+
val seriesContext = mock<EvaluationSeriesContext>()
125+
val seriesData = mutableMapOf<String, Any>()
126+
seriesData["existingKey"] = "existingValue"
127+
128+
val result = hook.afterEvaluation(seriesContext, seriesData, null)
129+
130+
verify(mockScopes, never()).addFeatureFlag(any(), any())
131+
assertEquals(seriesData, result)
132+
assertEquals("existingValue", result["existingKey"])
133+
}
134+
135+
@Test
136+
fun `afterEvaluation with null flagKey returns seriesData`() {
137+
val seriesContext = createSeriesContext(null)
138+
139+
val ldValue = mock<LDValue>()
140+
whenever(ldValue.getType()).thenReturn(LDValueType.BOOLEAN)
141+
142+
val evaluationDetail = mock<EvaluationDetail<LDValue>>()
143+
whenever(evaluationDetail.getValue()).thenReturn(ldValue)
144+
145+
val seriesData = mutableMapOf<String, Any>()
146+
seriesData["existingKey"] = "existingValue"
147+
148+
val result = hook.afterEvaluation(seriesContext, seriesData, evaluationDetail)
149+
150+
verify(mockScopes, never()).addFeatureFlag(any(), any())
151+
assertEquals(seriesData, result)
152+
assertEquals("existingValue", result["existingKey"])
153+
}
154+
155+
@Test
156+
fun `afterEvaluation with null value returns seriesData`() {
157+
val flagKey = "test-flag"
158+
159+
val seriesContext = createSeriesContext(flagKey)
160+
161+
val evaluationDetail = mock<EvaluationDetail<LDValue>>()
162+
whenever(evaluationDetail.getValue()).thenReturn(null)
163+
164+
val seriesData = mutableMapOf<String, Any>()
165+
seriesData["existingKey"] = "existingValue"
166+
167+
val result = hook.afterEvaluation(seriesContext, seriesData, evaluationDetail)
168+
169+
verify(mockScopes, never()).addFeatureFlag(any(), any())
170+
assertEquals(seriesData, result)
171+
assertEquals("existingValue", result["existingKey"])
172+
}
173+
174+
@Test
175+
fun `afterEvaluation with exception logs error`() {
176+
val flagKey = "test-flag"
177+
178+
val seriesContext = createSeriesContext(flagKey)
179+
180+
val ldValue = mock<LDValue>()
181+
whenever(ldValue.getType()).thenThrow(RuntimeException("Test exception"))
182+
183+
val evaluationDetail = mock<EvaluationDetail<LDValue>>()
184+
whenever(evaluationDetail.getValue()).thenReturn(ldValue)
185+
186+
val seriesData = mutableMapOf<String, Any>()
187+
seriesData["existingKey"] = "existingValue"
188+
189+
val result = hook.afterEvaluation(seriesContext, seriesData, evaluationDetail)
190+
191+
verify(mockLogger).log(eq(SentryLevel.ERROR), eq("Failed to capture feature flag evaluation"), any())
192+
verify(mockScopes, never()).addFeatureFlag(any(), any())
193+
assertEquals(seriesData, result)
194+
assertEquals("existingValue", result["existingKey"])
195+
}
196+
197+
@Test
198+
fun `afterEvaluation returns original seriesData`() {
199+
val flagKey = "test-flag"
200+
val flagValue = true
201+
202+
val seriesContext = createSeriesContext(flagKey)
203+
204+
val ldValue = mock<LDValue>()
205+
whenever(ldValue.getType()).thenReturn(LDValueType.BOOLEAN)
206+
whenever(ldValue.booleanValue()).thenReturn(flagValue)
207+
208+
val evaluationDetail = mock<EvaluationDetail<LDValue>>()
209+
whenever(evaluationDetail.getValue()).thenReturn(ldValue)
210+
211+
val seriesData = mutableMapOf<String, Any>()
212+
seriesData["key"] = "value"
213+
214+
val result = hook.afterEvaluation(seriesContext, seriesData, evaluationDetail)
215+
216+
verify(mockScopes).addFeatureFlag(eq(flagKey), eq(flagValue))
217+
assertEquals(seriesData, result)
218+
assertEquals("value", result["key"])
219+
}
220+
221+
private fun createSeriesContext(flagKey: String?): EvaluationSeriesContext {
222+
val seriesContext = mock<EvaluationSeriesContext>()
223+
try {
224+
val field = EvaluationSeriesContext::class.java.getField("flagKey")
225+
field.isAccessible = true
226+
field.set(seriesContext, flagKey)
227+
} catch (e: Exception) {
228+
throw RuntimeException("Failed to set flagKey field", e)
229+
}
230+
return seriesContext
231+
}
232+
}
233+

0 commit comments

Comments
 (0)