Skip to content

Commit a1cb36d

Browse files
authored
feat(experimental): Initialize Android SDK from json configuration (#4451)
1 parent 22a5f81 commit a1cb36d

File tree

13 files changed

+614
-73
lines changed

13 files changed

+614
-73
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323
- Rename `navigation.processing` span to more expressive `Navigation dispatch to screen A mounted/navigation cancelled` ([#4423](https://github.com/getsentry/sentry-react-native/pull/4423))
2424
- Add RN SDK package to `sdk.packages` for Cocoa ([#4381](https://github.com/getsentry/sentry-react-native/pull/4381))
2525
- Add experimental version of `startWithConfigureOptions` for Apple platforms ([#4444](https://github.com/getsentry/sentry-react-native/pull/4444))
26+
- Add experimental version of `init` with optional `OptionsConfiguration<SentryAndroidOptions>` for Android ([#4451](https://github.com/getsentry/sentry-react-native/pull/4451))
2627
- Add initialization using `sentry.options.json` for Apple platforms ([#4447](https://github.com/getsentry/sentry-react-native/pull/4447))
28+
- Add initialization using `sentry.options.json` for Android ([#4451](https://github.com/getsentry/sentry-react-native/pull/4451))
2729

2830
### Internal
2931

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"dsn": "invalid-dsn"
3+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
invalid-options
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"dsn": "https://[email protected]/123456",
3+
"enableTracing": true,
4+
"tracesSampleRate": 1.0
5+
}
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
package io.sentry.react
2+
3+
import android.content.Context
4+
import androidx.test.ext.junit.runners.AndroidJUnit4
5+
import androidx.test.platform.app.InstrumentationRegistry
6+
import com.facebook.react.common.JavascriptException
7+
import io.sentry.Hint
8+
import io.sentry.ILogger
9+
import io.sentry.Sentry
10+
import io.sentry.Sentry.OptionsConfiguration
11+
import io.sentry.SentryEvent
12+
import io.sentry.android.core.AndroidLogger
13+
import io.sentry.android.core.SentryAndroidOptions
14+
import io.sentry.protocol.SdkVersion
15+
import org.junit.After
16+
import org.junit.Assert.assertEquals
17+
import org.junit.Assert.assertFalse
18+
import org.junit.Assert.assertNotNull
19+
import org.junit.Assert.assertNull
20+
import org.junit.Assert.assertTrue
21+
import org.junit.Before
22+
import org.junit.Test
23+
import org.junit.runner.RunWith
24+
25+
@RunWith(AndroidJUnit4::class)
26+
class RNSentrySDKTest {
27+
private val logger: ILogger = AndroidLogger(RNSentrySDKTest::class.java.simpleName)
28+
private lateinit var context: Context
29+
30+
companion object {
31+
private const val INITIALISATION_ERROR = "Failed to initialize Sentry's React Native SDK"
32+
private const val VALID_OPTIONS = "sentry.options.json"
33+
private const val INVALID_OPTIONS = "invalid.options.json"
34+
private const val INVALID_JSON = "invalid.options.txt"
35+
private const val MISSING = "non-existing-file"
36+
37+
private val validConfig =
38+
OptionsConfiguration<SentryAndroidOptions> { options ->
39+
options.dsn = "https://[email protected]/123456"
40+
}
41+
private val invalidConfig =
42+
OptionsConfiguration<SentryAndroidOptions> { options ->
43+
options.dsn = "invalid-dsn"
44+
}
45+
private val emptyConfig = OptionsConfiguration<SentryAndroidOptions> {}
46+
}
47+
48+
@Before
49+
fun setUp() {
50+
context = InstrumentationRegistry.getInstrumentation().context
51+
}
52+
53+
@After
54+
fun tearDown() {
55+
Sentry.close()
56+
}
57+
58+
@Test
59+
fun initialisesSuccessfullyWithDefaultValidJsonFile() { // sentry.options.json
60+
RNSentrySDK.init(context)
61+
assertTrue(Sentry.isEnabled())
62+
}
63+
64+
@Test
65+
fun initialisesSuccessfullyWithValidConfigurationAndDefaultValidJsonFile() {
66+
RNSentrySDK.init(context, validConfig)
67+
assertTrue(Sentry.isEnabled())
68+
}
69+
70+
@Test
71+
fun initialisesSuccessfullyWithValidConfigurationAndInvalidJsonFile() {
72+
RNSentrySDK.init(context, validConfig, INVALID_OPTIONS, logger)
73+
assertTrue(Sentry.isEnabled())
74+
}
75+
76+
@Test
77+
fun initialisesSuccessfullyWithValidConfigurationAndMissingJsonFile() {
78+
RNSentrySDK.init(context, validConfig, MISSING, logger)
79+
assertTrue(Sentry.isEnabled())
80+
}
81+
82+
@Test
83+
fun initialisesSuccessfullyWithValidConfigurationAndErrorInParsingJsonFile() {
84+
RNSentrySDK.init(context, validConfig, INVALID_JSON, logger)
85+
assertTrue(Sentry.isEnabled())
86+
}
87+
88+
@Test
89+
fun initialisesSuccessfullyWithNoConfigurationAndValidJsonFile() {
90+
RNSentrySDK.init(context, emptyConfig, VALID_OPTIONS, logger)
91+
assertTrue(Sentry.isEnabled())
92+
}
93+
94+
@Test
95+
fun failsToInitialiseWithNoConfigurationAndInvalidJsonFile() {
96+
try {
97+
RNSentrySDK.init(context, emptyConfig, INVALID_OPTIONS, logger)
98+
} catch (e: Exception) {
99+
assertEquals(INITIALISATION_ERROR, e.message)
100+
}
101+
assertFalse(Sentry.isEnabled())
102+
}
103+
104+
@Test
105+
fun failsToInitialiseWithInvalidConfigAndInvalidJsonFile() {
106+
try {
107+
RNSentrySDK.init(context, invalidConfig, INVALID_OPTIONS, logger)
108+
} catch (e: Exception) {
109+
assertEquals(INITIALISATION_ERROR, e.message)
110+
}
111+
assertFalse(Sentry.isEnabled())
112+
}
113+
114+
@Test
115+
fun failsToInitialiseWithInvalidConfigAndValidJsonFile() {
116+
try {
117+
RNSentrySDK.init(context, invalidConfig, VALID_OPTIONS, logger)
118+
} catch (e: Exception) {
119+
assertEquals(INITIALISATION_ERROR, e.message)
120+
}
121+
assertFalse(Sentry.isEnabled())
122+
}
123+
124+
@Test
125+
fun failsToInitialiseWithInvalidConfigurationAndDefaultValidJsonFile() {
126+
try {
127+
RNSentrySDK.init(context, invalidConfig)
128+
} catch (e: Exception) {
129+
assertEquals(INITIALISATION_ERROR, e.message)
130+
}
131+
assertFalse(Sentry.isEnabled())
132+
}
133+
134+
@Test
135+
fun defaultsAndFinalsAreSetWithValidJsonFile() {
136+
RNSentrySDK.init(context, emptyConfig, VALID_OPTIONS, logger)
137+
val actualOptions = Sentry.getCurrentHub().options as SentryAndroidOptions
138+
verifyDefaults(actualOptions)
139+
verifyFinals(actualOptions)
140+
// options file
141+
assert(actualOptions.dsn == "https://[email protected]/123456")
142+
}
143+
144+
@Test
145+
fun defaultsAndFinalsAreSetWithValidConfiguration() {
146+
RNSentrySDK.init(context, validConfig, MISSING, logger)
147+
val actualOptions = Sentry.getCurrentHub().options as SentryAndroidOptions
148+
verifyDefaults(actualOptions)
149+
verifyFinals(actualOptions)
150+
// configuration
151+
assert(actualOptions.dsn == "https://[email protected]/123456")
152+
}
153+
154+
@Test
155+
fun defaultsOverrideOptionsJsonFile() {
156+
RNSentrySDK.init(context, emptyConfig, VALID_OPTIONS, logger)
157+
val actualOptions = Sentry.getCurrentHub().options as SentryAndroidOptions
158+
assertNull(actualOptions.tracesSampleRate)
159+
assertEquals(false, actualOptions.enableTracing)
160+
}
161+
162+
@Test
163+
fun configurationOverridesDefaultOptions() {
164+
val validConfig =
165+
OptionsConfiguration<SentryAndroidOptions> { options ->
166+
options.dsn = "https://[email protected]/123456"
167+
options.tracesSampleRate = 0.5
168+
options.enableTracing = true
169+
}
170+
RNSentrySDK.init(context, validConfig, MISSING, logger)
171+
val actualOptions = Sentry.getCurrentHub().options as SentryAndroidOptions
172+
assertEquals(0.5, actualOptions.tracesSampleRate)
173+
assertEquals(true, actualOptions.enableTracing)
174+
assert(actualOptions.dsn == "https://[email protected]/123456")
175+
}
176+
177+
private fun verifyDefaults(actualOptions: SentryAndroidOptions) {
178+
assertTrue(actualOptions.ignoredExceptionsForType.contains(JavascriptException::class.java))
179+
assertEquals(RNSentryVersion.ANDROID_SDK_NAME, actualOptions.sdkVersion?.name)
180+
assertEquals(
181+
io.sentry.android.core.BuildConfig.VERSION_NAME,
182+
actualOptions.sdkVersion?.version,
183+
)
184+
val pack = actualOptions.sdkVersion?.packages?.first { it.name == RNSentryVersion.REACT_NATIVE_SDK_PACKAGE_NAME }
185+
assertNotNull(pack)
186+
assertEquals(RNSentryVersion.REACT_NATIVE_SDK_PACKAGE_VERSION, pack?.version)
187+
assertNull(actualOptions.tracesSampleRate)
188+
assertNull(actualOptions.tracesSampler)
189+
assertEquals(false, actualOptions.enableTracing)
190+
}
191+
192+
private fun verifyFinals(actualOptions: SentryAndroidOptions) {
193+
val event =
194+
SentryEvent().apply { sdk = SdkVersion(RNSentryVersion.ANDROID_SDK_NAME, "1.0") }
195+
val result = actualOptions.beforeSend?.execute(event, Hint())
196+
assertNotNull(result)
197+
assertEquals("android", result?.getTag("event.origin"))
198+
assertEquals("java", result?.getTag("event.environment"))
199+
}
200+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package io.sentry.react
2+
3+
import io.sentry.Sentry.OptionsConfiguration
4+
import io.sentry.android.core.SentryAndroidOptions
5+
import org.junit.Test
6+
import org.junit.runner.RunWith
7+
import org.junit.runners.JUnit4
8+
import org.mockito.kotlin.mock
9+
import org.mockito.kotlin.verify
10+
11+
@RunWith(JUnit4::class)
12+
class RNSentryCompositeOptionsConfigurationTest {
13+
@Test
14+
fun `configure should call base and overriding configurations`() {
15+
val baseConfig: OptionsConfiguration<SentryAndroidOptions> = mock()
16+
val overridingConfig: OptionsConfiguration<SentryAndroidOptions> = mock()
17+
18+
val compositeConfig = RNSentryCompositeOptionsConfiguration(baseConfig, overridingConfig)
19+
val options = SentryAndroidOptions()
20+
compositeConfig.configure(options)
21+
22+
verify(baseConfig).configure(options)
23+
verify(overridingConfig).configure(options)
24+
}
25+
26+
@Test
27+
fun `configure should apply base configuration and override values`() {
28+
val baseConfig =
29+
OptionsConfiguration<SentryAndroidOptions> { options ->
30+
options.dsn = "https://[email protected]"
31+
options.isDebug = false
32+
options.release = "some-release"
33+
}
34+
val overridingConfig =
35+
OptionsConfiguration<SentryAndroidOptions> { options ->
36+
options.dsn = "https://[email protected]"
37+
options.isDebug = true
38+
options.environment = "production"
39+
}
40+
41+
val compositeConfig = RNSentryCompositeOptionsConfiguration(baseConfig, overridingConfig)
42+
val options = SentryAndroidOptions()
43+
compositeConfig.configure(options)
44+
45+
assert(options.dsn == "https://[email protected]") // overridden value
46+
assert(options.isDebug) // overridden value
47+
assert(options.release == "some-release") // base value not overridden
48+
assert(options.environment == "production") // overridden value not in base
49+
}
50+
}

0 commit comments

Comments
 (0)