Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(experimental): Initialize Android SDK from json configuration #4451

Merged
merged 50 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
bf7c762
Extract Android SDK Init
antonis Jan 14, 2025
897d918
Update tests
antonis Jan 14, 2025
e06e6bb
Adds changelog
antonis Jan 14, 2025
903fc55
Fix lint issues
antonis Jan 14, 2025
739ad30
Rename RNSentryStart instance for clarity
antonis Jan 15, 2025
0f06266
WIP: Init with hardcoded json string
antonis Jan 15, 2025
42c32ec
Adds changelog
antonis Jan 15, 2025
a98bd88
Expose only a Java Map parameter in the startWithOptions
antonis Jan 15, 2025
1da8ca3
Fix lint issue
antonis Jan 15, 2025
be6b815
Convert RNSentrySDK to utility class
antonis Jan 15, 2025
2163884
Initialise from assets json file
antonis Jan 15, 2025
8454727
Copy configuration json in the android assets folder before building
antonis Jan 16, 2025
1c31e21
Adds map converter tests
antonis Jan 16, 2025
ff20ed9
Disable by default in the sample
antonis Jan 16, 2025
6509ada
Cleanup sentry.options.json from assets after the build
antonis Jan 16, 2025
dcb7e2e
Converts RNSentryStart to utility class
antonis Jan 20, 2025
99410e9
Merge branch 'main' into antonis/extract-android-sdk-init
antonis Jan 20, 2025
06b1def
Merge branch 'antonis/extract-android-sdk-init' into antonis/init-fro…
antonis Jan 20, 2025
f96e2d1
Align with merged convertion of RNSentryStart to utility class
antonis Jan 20, 2025
7788b81
Align with Sentry Android naming
antonis Jan 20, 2025
25d142a
Override json configuration with passed Android configuration
antonis Jan 20, 2025
cedd24a
Fix comment
antonis Jan 20, 2025
3d41a98
Merge branch 'main' into antonis/extract-android-sdk-init
antonis Jan 21, 2025
0de5bfa
Merge branch 'antonis/extract-android-sdk-init' into antonis/init-fro…
antonis Jan 21, 2025
d909ed4
Update changelog
antonis Jan 21, 2025
c26ca00
Merge branch 'capture-app-start-errors' into antonis/init-from-json
antonis Jan 22, 2025
dfb12d0
Fix changelog
antonis Jan 22, 2025
108c625
Merge branch 'capture-app-start-errors' into antonis/init-from-json
antonis Jan 23, 2025
710f1a0
Use RNSentryJsonConverter.convertToWritable
antonis Jan 23, 2025
7469dc8
Move getOptionsFromConfigurationFile in a utility class
antonis Jan 23, 2025
e266ae1
Handle initialisation errors
antonis Jan 23, 2025
7522fce
Remove beforeSend initialisation option
antonis Jan 24, 2025
5ebe8fb
Lower access level were possible
antonis Jan 24, 2025
b6a36a1
Allow combining more than two configurations
antonis Jan 24, 2025
8e87539
Add default and final configuration
antonis Jan 24, 2025
e0264ef
Add a flag to disable reading from the file
antonis Jan 24, 2025
927a003
Adds RNSentrySDK tests
antonis Jan 24, 2025
007a9d9
Fix lint issue
antonis Jan 24, 2025
32bc0a7
Update changelog
antonis Jan 24, 2025
8fc973f
Updates javadoc
antonis Jan 24, 2025
58e6e9c
Merge branch 'capture-app-start-errors' into antonis/init-from-json
krystofwoldrich Feb 3, 2025
821fef9
Ignore JavascriptException by default
antonis Feb 3, 2025
43b26db
Move SDK name and version to react defaults
antonis Feb 3, 2025
d2fd58f
Set current activity in defaults
antonis Feb 3, 2025
5b9665a
Defaults override file/rn configuration
antonis Feb 3, 2025
2b7a319
Add tests for defaults and finals
antonis Feb 4, 2025
d236c82
Allow passing sdk init function
antonis Feb 4, 2025
ced915d
Add tests verifying defaults, finals and overriding behavior
antonis Feb 4, 2025
1c96a27
Revert "Allow passing sdk init function"
antonis Feb 4, 2025
8a87565
Use Sentry.getCurrentHub().options to check the initialised options
antonis Feb 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
- 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))
- Add RN SDK package to `sdk.packages` for Cocoa ([#4381](https://github.com/getsentry/sentry-react-native/pull/4381))
- Add experimental version of `startWithConfigureOptions` for Apple platforms ([#4444](https://github.com/getsentry/sentry-react-native/pull/4444))
- Add experimental version of `init` with optional `OptionsConfiguration<SentryAndroidOptions>` for Android ([#4451](https://github.com/getsentry/sentry-react-native/pull/4451))
- Add initialization using `sentry.options.json` for Apple platforms ([#4447](https://github.com/getsentry/sentry-react-native/pull/4447))
- Add initialization using `sentry.options.json` for Android ([#4451](https://github.com/getsentry/sentry-react-native/pull/4451))

### Internal

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"dsn": "invalid-dsn"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
invalid-options
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"dsn": "https://[email protected]/123456",
"enableTracing": true,
"tracesSampleRate": 1.0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
package io.sentry.react

import android.content.Context
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.facebook.react.common.JavascriptException
import io.sentry.Hint
import io.sentry.ILogger
import io.sentry.Sentry
import io.sentry.Sentry.OptionsConfiguration
import io.sentry.SentryEvent
import io.sentry.android.core.AndroidLogger
import io.sentry.android.core.SentryAndroidOptions
import io.sentry.protocol.SdkVersion
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class RNSentrySDKTest {
krystofwoldrich marked this conversation as resolved.
Show resolved Hide resolved
private val logger: ILogger = AndroidLogger(RNSentrySDKTest::class.java.simpleName)
private lateinit var context: Context

companion object {
private const val INITIALISATION_ERROR = "Failed to initialize Sentry's React Native SDK"
private const val VALID_OPTIONS = "sentry.options.json"
private const val INVALID_OPTIONS = "invalid.options.json"
private const val INVALID_JSON = "invalid.options.txt"
private const val MISSING = "non-existing-file"

private val validConfig =
OptionsConfiguration<SentryAndroidOptions> { options ->
options.dsn = "https://[email protected]/123456"
}
private val invalidConfig =
OptionsConfiguration<SentryAndroidOptions> { options ->
options.dsn = "invalid-dsn"
}
private val emptyConfig = OptionsConfiguration<SentryAndroidOptions> {}
}

@Before
fun setUp() {
context = InstrumentationRegistry.getInstrumentation().context
}

@After
fun tearDown() {
Sentry.close()
}

@Test
fun initialisesSuccessfullyWithDefaultValidJsonFile() { // sentry.options.json
RNSentrySDK.init(context)
assertTrue(Sentry.isEnabled())
}

@Test
fun initialisesSuccessfullyWithValidConfigurationAndDefaultValidJsonFile() {
RNSentrySDK.init(context, validConfig)
assertTrue(Sentry.isEnabled())
}

@Test
fun initialisesSuccessfullyWithValidConfigurationAndInvalidJsonFile() {
RNSentrySDK.init(context, validConfig, INVALID_OPTIONS, logger)
assertTrue(Sentry.isEnabled())
}

@Test
fun initialisesSuccessfullyWithValidConfigurationAndMissingJsonFile() {
RNSentrySDK.init(context, validConfig, MISSING, logger)
assertTrue(Sentry.isEnabled())
}

@Test
fun initialisesSuccessfullyWithValidConfigurationAndErrorInParsingJsonFile() {
RNSentrySDK.init(context, validConfig, INVALID_JSON, logger)
assertTrue(Sentry.isEnabled())
}

@Test
fun initialisesSuccessfullyWithNoConfigurationAndValidJsonFile() {
RNSentrySDK.init(context, emptyConfig, VALID_OPTIONS, logger)
assertTrue(Sentry.isEnabled())
}

@Test
fun failsToInitialiseWithNoConfigurationAndInvalidJsonFile() {
try {
RNSentrySDK.init(context, emptyConfig, INVALID_OPTIONS, logger)
} catch (e: Exception) {
assertEquals(INITIALISATION_ERROR, e.message)
}
assertFalse(Sentry.isEnabled())
}

@Test
fun failsToInitialiseWithInvalidConfigAndInvalidJsonFile() {
try {
RNSentrySDK.init(context, invalidConfig, INVALID_OPTIONS, logger)
} catch (e: Exception) {
assertEquals(INITIALISATION_ERROR, e.message)
}
assertFalse(Sentry.isEnabled())
}

@Test
fun failsToInitialiseWithInvalidConfigAndValidJsonFile() {
try {
RNSentrySDK.init(context, invalidConfig, VALID_OPTIONS, logger)
} catch (e: Exception) {
assertEquals(INITIALISATION_ERROR, e.message)
}
assertFalse(Sentry.isEnabled())
}

@Test
fun failsToInitialiseWithInvalidConfigurationAndDefaultValidJsonFile() {
try {
RNSentrySDK.init(context, invalidConfig)
} catch (e: Exception) {
assertEquals(INITIALISATION_ERROR, e.message)
}
assertFalse(Sentry.isEnabled())
}

@Test
fun defaultsAndFinalsAreSetWithValidJsonFile() {
RNSentrySDK.init(context, emptyConfig, VALID_OPTIONS, logger)
val actualOptions = Sentry.getCurrentHub().options as SentryAndroidOptions
verifyDefaults(actualOptions)
verifyFinals(actualOptions)
// options file
assert(actualOptions.dsn == "https://[email protected]/123456")
}

@Test
fun defaultsAndFinalsAreSetWithValidConfiguration() {
RNSentrySDK.init(context, validConfig, MISSING, logger)
val actualOptions = Sentry.getCurrentHub().options as SentryAndroidOptions
verifyDefaults(actualOptions)
verifyFinals(actualOptions)
// configuration
assert(actualOptions.dsn == "https://[email protected]/123456")
}

@Test
fun defaultsOverrideOptionsJsonFile() {
RNSentrySDK.init(context, emptyConfig, VALID_OPTIONS, logger)
val actualOptions = Sentry.getCurrentHub().options as SentryAndroidOptions
assertNull(actualOptions.tracesSampleRate)
assertEquals(false, actualOptions.enableTracing)
}

@Test
fun configurationOverridesDefaultOptions() {
val validConfig =
OptionsConfiguration<SentryAndroidOptions> { options ->
options.dsn = "https://[email protected]/123456"
options.tracesSampleRate = 0.5
options.enableTracing = true
}
RNSentrySDK.init(context, validConfig, MISSING, logger)
val actualOptions = Sentry.getCurrentHub().options as SentryAndroidOptions
assertEquals(0.5, actualOptions.tracesSampleRate)
assertEquals(true, actualOptions.enableTracing)
assert(actualOptions.dsn == "https://[email protected]/123456")
}

private fun verifyDefaults(actualOptions: SentryAndroidOptions) {
assertTrue(actualOptions.ignoredExceptionsForType.contains(JavascriptException::class.java))
assertEquals(RNSentryVersion.ANDROID_SDK_NAME, actualOptions.sdkVersion?.name)
assertEquals(
io.sentry.android.core.BuildConfig.VERSION_NAME,
actualOptions.sdkVersion?.version,
)
val pack = actualOptions.sdkVersion?.packages?.first { it.name == RNSentryVersion.REACT_NATIVE_SDK_PACKAGE_NAME }
assertNotNull(pack)
assertEquals(RNSentryVersion.REACT_NATIVE_SDK_PACKAGE_VERSION, pack?.version)
assertNull(actualOptions.tracesSampleRate)
assertNull(actualOptions.tracesSampler)
assertEquals(false, actualOptions.enableTracing)
}

private fun verifyFinals(actualOptions: SentryAndroidOptions) {
val event =
SentryEvent().apply { sdk = SdkVersion(RNSentryVersion.ANDROID_SDK_NAME, "1.0") }
val result = actualOptions.beforeSend?.execute(event, Hint())
assertNotNull(result)
assertEquals("android", result?.getTag("event.origin"))
assertEquals("java", result?.getTag("event.environment"))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package io.sentry.react

import io.sentry.Sentry.OptionsConfiguration
import io.sentry.android.core.SentryAndroidOptions
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify

@RunWith(JUnit4::class)
class RNSentryCompositeOptionsConfigurationTest {
@Test
fun `configure should call base and overriding configurations`() {
val baseConfig: OptionsConfiguration<SentryAndroidOptions> = mock()
val overridingConfig: OptionsConfiguration<SentryAndroidOptions> = mock()

val compositeConfig = RNSentryCompositeOptionsConfiguration(baseConfig, overridingConfig)
val options = SentryAndroidOptions()
compositeConfig.configure(options)

verify(baseConfig).configure(options)
verify(overridingConfig).configure(options)
}

@Test
fun `configure should apply base configuration and override values`() {
val baseConfig =
OptionsConfiguration<SentryAndroidOptions> { options ->
options.dsn = "https://[email protected]"
options.isDebug = false
options.release = "some-release"
}
val overridingConfig =
OptionsConfiguration<SentryAndroidOptions> { options ->
options.dsn = "https://[email protected]"
options.isDebug = true
options.environment = "production"
}

val compositeConfig = RNSentryCompositeOptionsConfiguration(baseConfig, overridingConfig)
val options = SentryAndroidOptions()
compositeConfig.configure(options)

assert(options.dsn == "https://[email protected]") // overridden value
assert(options.isDebug) // overridden value
assert(options.release == "some-release") // base value not overridden
assert(options.environment == "production") // overridden value not in base
}
}
Loading
Loading