Skip to content

Commit 3db962c

Browse files
authored
Merge 8fc973f into 1e5dbde
2 parents 1e5dbde + 8fc973f commit 3db962c

File tree

12 files changed

+452
-37
lines changed

12 files changed

+452
-37
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
- 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))
2020
- Add RN SDK package to `sdk.packages` for Cocoa ([#4381](https://github.com/getsentry/sentry-react-native/pull/4381))
2121
- Add experimental version of `startWithConfigureOptions` for Apple platforms ([#4444](https://github.com/getsentry/sentry-react-native/pull/4444))
22+
- Add experimental version of `init` with optional `OptionsConfiguration<SentryAndroidOptions>` for Android ([#4451](https://github.com/getsentry/sentry-react-native/pull/4451))
2223
- Add initialization using `sentry.options.json` for Apple platforms ([#4447](https://github.com/getsentry/sentry-react-native/pull/4447))
24+
- Add initialization using `sentry.options.json` for Android ([#4451](https://github.com/getsentry/sentry-react-native/pull/4451))
2325

2426
### Internal
2527

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"dsn": "invalid-dsn"
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
invalid-options
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"dsn": "https://[email protected]/123456"
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
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 io.sentry.ILogger
7+
import io.sentry.Sentry
8+
import io.sentry.Sentry.OptionsConfiguration
9+
import io.sentry.android.core.AndroidLogger
10+
import io.sentry.android.core.SentryAndroidOptions
11+
import org.junit.After
12+
import org.junit.Assert.assertEquals
13+
import org.junit.Assert.assertFalse
14+
import org.junit.Assert.assertTrue
15+
import org.junit.Before
16+
import org.junit.Test
17+
import org.junit.runner.RunWith
18+
19+
@RunWith(AndroidJUnit4::class)
20+
class RNSentrySDKTest {
21+
private val logger: ILogger = AndroidLogger(RNSentrySDKTest::class.java.simpleName)
22+
private lateinit var context: Context
23+
24+
companion object {
25+
private const val INITIALISATION_ERROR = "Failed to initialize Sentry's React Native SDK"
26+
private const val VALID_OPTIONS = "sentry.options.json"
27+
private const val INVALID_OPTIONS = "invalid.options.json"
28+
private const val INVALID_JSON = "invalid.options.txt"
29+
private const val MISSING = "non-existing-file"
30+
31+
private val validConfig =
32+
OptionsConfiguration<SentryAndroidOptions> { options ->
33+
options.dsn = "https://[email protected]/123456"
34+
}
35+
private val invalidConfig =
36+
OptionsConfiguration<SentryAndroidOptions> { options ->
37+
options.dsn = "invalid-dsn"
38+
}
39+
private val emptyConfig = OptionsConfiguration<SentryAndroidOptions> {}
40+
}
41+
42+
@Before
43+
fun setUp() {
44+
context = InstrumentationRegistry.getInstrumentation().context
45+
}
46+
47+
@After
48+
fun tearDown() {
49+
Sentry.close()
50+
}
51+
52+
@Test
53+
fun initialisesSuccessfullyWithDefaultValidJsonFile() { // sentry.options.json
54+
RNSentrySDK.init(context)
55+
assertTrue(Sentry.isEnabled())
56+
}
57+
58+
@Test
59+
fun initialisesSuccessfullyWithValidConfigurationAndDefaultValidJsonFile() {
60+
RNSentrySDK.init(context, validConfig)
61+
assertTrue(Sentry.isEnabled())
62+
}
63+
64+
@Test
65+
fun initialisesSuccessfullyWithValidConfigurationAndInvalidJsonFile() {
66+
RNSentrySDK.init(context, validConfig, INVALID_OPTIONS, logger)
67+
assertTrue(Sentry.isEnabled())
68+
}
69+
70+
@Test
71+
fun initialisesSuccessfullyWithValidConfigurationAndMissingJsonFile() {
72+
RNSentrySDK.init(context, validConfig, MISSING, logger)
73+
assertTrue(Sentry.isEnabled())
74+
}
75+
76+
@Test
77+
fun initialisesSuccessfullyWithValidConfigurationAndErrorInParsingJsonFile() {
78+
RNSentrySDK.init(context, validConfig, INVALID_JSON, logger)
79+
assertTrue(Sentry.isEnabled())
80+
}
81+
82+
@Test
83+
fun initialisesSuccessfullyWithNoConfigurationAndValidJsonFile() {
84+
RNSentrySDK.init(context, emptyConfig, VALID_OPTIONS, logger)
85+
assertTrue(Sentry.isEnabled())
86+
}
87+
88+
@Test
89+
fun failsToInitialiseWithNoConfigurationAndInvalidJsonFile() {
90+
try {
91+
RNSentrySDK.init(context, emptyConfig, INVALID_OPTIONS, logger)
92+
} catch (e: Exception) {
93+
assertEquals(INITIALISATION_ERROR, e.message)
94+
}
95+
assertFalse(Sentry.isEnabled())
96+
}
97+
98+
@Test
99+
fun failsToInitialiseWithInvalidConfigAndInvalidJsonFile() {
100+
try {
101+
RNSentrySDK.init(context, invalidConfig, INVALID_OPTIONS, logger)
102+
} catch (e: Exception) {
103+
assertEquals(INITIALISATION_ERROR, e.message)
104+
}
105+
assertFalse(Sentry.isEnabled())
106+
}
107+
108+
@Test
109+
fun failsToInitialiseWithInvalidConfigAndValidJsonFile() {
110+
try {
111+
RNSentrySDK.init(context, invalidConfig, VALID_OPTIONS, logger)
112+
} catch (e: Exception) {
113+
assertEquals(INITIALISATION_ERROR, e.message)
114+
}
115+
assertFalse(Sentry.isEnabled())
116+
}
117+
118+
@Test
119+
fun failsToInitialiseWithInvalidConfigurationAndDefaultValidJsonFile() {
120+
try {
121+
RNSentrySDK.init(context, invalidConfig)
122+
} catch (e: Exception) {
123+
assertEquals(INITIALISATION_ERROR, e.message)
124+
}
125+
assertFalse(Sentry.isEnabled())
126+
}
127+
}
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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package io.sentry.react;
2+
3+
import io.sentry.Sentry.OptionsConfiguration;
4+
import io.sentry.android.core.SentryAndroidOptions;
5+
import java.util.List;
6+
import org.jetbrains.annotations.NotNull;
7+
8+
class RNSentryCompositeOptionsConfiguration implements OptionsConfiguration<SentryAndroidOptions> {
9+
private final @NotNull List<OptionsConfiguration<SentryAndroidOptions>> configurations;
10+
11+
@SafeVarargs
12+
protected RNSentryCompositeOptionsConfiguration(
13+
@NotNull OptionsConfiguration<SentryAndroidOptions>... configurations) {
14+
this.configurations = List.of(configurations);
15+
}
16+
17+
@Override
18+
public void configure(@NotNull SentryAndroidOptions options) {
19+
for (OptionsConfiguration<SentryAndroidOptions> configuration : configurations) {
20+
if (configuration != null) {
21+
configuration.configure(options);
22+
}
23+
}
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package io.sentry.react;
2+
3+
import android.content.Context;
4+
import io.sentry.ILogger;
5+
import io.sentry.SentryLevel;
6+
import java.io.BufferedReader;
7+
import java.io.InputStream;
8+
import java.io.InputStreamReader;
9+
import org.jetbrains.annotations.NotNull;
10+
import org.jetbrains.annotations.Nullable;
11+
import org.json.JSONObject;
12+
13+
final class RNSentryJsonUtils {
14+
private RNSentryJsonUtils() {
15+
throw new AssertionError("Utility class should not be instantiated");
16+
}
17+
18+
static @Nullable JSONObject getOptionsFromConfigurationFile(
19+
@NotNull Context context, @NotNull String fileName, @NotNull ILogger logger) {
20+
try (InputStream inputStream = context.getAssets().open(fileName);
21+
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
22+
23+
StringBuilder stringBuilder = new StringBuilder();
24+
String line;
25+
while ((line = reader.readLine()) != null) {
26+
stringBuilder.append(line);
27+
}
28+
String configFileContent = stringBuilder.toString();
29+
return new JSONObject(configFileContent);
30+
31+
} catch (Exception e) {
32+
logger.log(
33+
SentryLevel.ERROR,
34+
"Failed to read configuration file. Please make sure "
35+
+ fileName
36+
+ " exists in the root of your project.",
37+
e);
38+
return null;
39+
}
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package io.sentry.react;
2+
3+
import android.content.Context;
4+
import com.facebook.react.bridge.ReadableMap;
5+
import io.sentry.ILogger;
6+
import io.sentry.Sentry;
7+
import io.sentry.SentryLevel;
8+
import io.sentry.android.core.AndroidLogger;
9+
import io.sentry.android.core.SentryAndroidOptions;
10+
import org.jetbrains.annotations.NotNull;
11+
import org.json.JSONObject;
12+
13+
public final class RNSentrySDK {
14+
private static final String CONFIGURATION_FILE = "sentry.options.json";
15+
private static final String NAME = "RNSentrySDK";
16+
17+
private static final ILogger logger = new AndroidLogger(NAME);
18+
19+
private RNSentrySDK() {
20+
throw new AssertionError("Utility class should not be instantiated");
21+
}
22+
23+
static void init(
24+
@NotNull final Context context,
25+
@NotNull Sentry.OptionsConfiguration<SentryAndroidOptions> configuration,
26+
@NotNull String configurationFile,
27+
@NotNull ILogger logger) {
28+
try {
29+
JSONObject jsonObject =
30+
RNSentryJsonUtils.getOptionsFromConfigurationFile(context, configurationFile, logger);
31+
if (jsonObject == null) {
32+
RNSentryStart.startWithConfiguration(context, configuration);
33+
return;
34+
}
35+
ReadableMap rnOptions = RNSentryJsonConverter.convertToWritable(jsonObject);
36+
if (rnOptions == null) {
37+
RNSentryStart.startWithConfiguration(context, configuration);
38+
return;
39+
}
40+
RNSentryStart.startWithOptions(context, rnOptions, configuration, logger);
41+
} catch (Exception e) {
42+
logger.log(
43+
SentryLevel.ERROR, "Failed to start Sentry with options from configuration file.", e);
44+
throw new RuntimeException("Failed to initialize Sentry's React Native SDK", e);
45+
}
46+
}
47+
48+
/**
49+
* @experimental Start the Native Android SDK with the provided configuration options. Uses as a
50+
* base configurations the `sentry.options.json` configuration file if it exists.
51+
* @param context Android Context
52+
* @param configuration configuration options
53+
*/
54+
public static void init(
55+
@NotNull final Context context,
56+
@NotNull Sentry.OptionsConfiguration<SentryAndroidOptions> configuration) {
57+
init(context, configuration, CONFIGURATION_FILE, logger);
58+
}
59+
60+
/**
61+
* @experimental Start the Native Android SDK with options from `sentry.options.json`
62+
* configuration file.
63+
* @param context Android Context
64+
*/
65+
public static void init(@NotNull final Context context) {
66+
init(context, options -> {}, CONFIGURATION_FILE, logger);
67+
}
68+
}

0 commit comments

Comments
 (0)