diff --git a/packages/core/RNSentryAndroidTester/app/src/test/java/io/sentry/react/RNSentrySDKTest.kt b/packages/core/RNSentryAndroidTester/app/src/test/java/io/sentry/react/RNSentrySDKTest.kt new file mode 100644 index 0000000000..722d4e95cc --- /dev/null +++ b/packages/core/RNSentryAndroidTester/app/src/test/java/io/sentry/react/RNSentrySDKTest.kt @@ -0,0 +1,138 @@ +package io.sentry.react + +import android.content.Context +import com.facebook.react.bridge.JavaOnlyMap +import com.facebook.react.bridge.ReadableMap +import io.sentry.ILogger +import io.sentry.Sentry.OptionsConfiguration +import io.sentry.SentryLevel +import io.sentry.android.core.SentryAndroidOptions +import org.json.JSONObject +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertThrows +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.ArgumentMatchers.contains +import org.mockito.ArgumentMatchers.eq +import org.mockito.MockedStatic +import org.mockito.Mockito.mock +import org.mockito.Mockito.mockStatic +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@RunWith(JUnit4::class) +class RNSentrySDKTest { + private val configurationFile = "sentry.options.json" + + private lateinit var mockLogger: ILogger + private lateinit var mockContext: Context + private lateinit var mockConfiguration: OptionsConfiguration + private lateinit var mockedRNSentryStart: MockedStatic + private lateinit var mockedRNSentryJsonUtils: MockedStatic + + @Before + fun setUp() { + MockitoAnnotations.openMocks(this) + mockLogger = mock(ILogger::class.java) + mockContext = mock(Context::class.java) + mockConfiguration = mock(OptionsConfiguration::class.java) as OptionsConfiguration + mockedRNSentryStart = mockStatic(RNSentryStart::class.java) + mockedRNSentryJsonUtils = mockStatic(RNSentryJsonUtils::class.java) + } + + @After + fun tearDown() { + mockedRNSentryStart.close() + mockedRNSentryJsonUtils.close() + } + + @Test + fun `init with passed configuration callback when no valid json file is provided`() { + val mockJsonObject = null + mockedRNSentryJsonUtils + .`when` { + RNSentryJsonUtils.getOptionsFromConfigurationFile(mockContext, configurationFile, mockLogger) + }.thenReturn(mockJsonObject) + RNSentrySDK.init(mockContext, mockConfiguration, mockLogger) + + verify(mockLogger).log( + eq(SentryLevel.WARNING), + contains("Failed to load configuration file(sentry.options.json), starting with configuration callback."), + ) + + mockedRNSentryStart.verify { + RNSentryStart.startWithConfiguration(mockContext, mockConfiguration) + } + } + + @Test + fun `init with passed configuration callback when no valid readable map is created`() { + val mockJsonObject = JSONObject() + mockedRNSentryJsonUtils + .`when` { + RNSentryJsonUtils.getOptionsFromConfigurationFile(mockContext, configurationFile, mockLogger) + }.thenReturn(mockJsonObject) + val mockReadableMap = null + mockedRNSentryJsonUtils + .`when` { + RNSentryJsonUtils.jsonObjectToReadableMap( + mockJsonObject, + ) + }.thenReturn(mockReadableMap) + + RNSentrySDK.init(mockContext, mockConfiguration, mockLogger) + + verify(mockLogger).log( + eq(SentryLevel.WARNING), + contains("Failed to load configuration file(sentry.options.json), starting with configuration callback."), + ) + mockedRNSentryStart.verify { + RNSentryStart.startWithConfiguration(mockContext, mockConfiguration) + } + } + + @Test + fun `init with the json file and the passed configuration when a valid json is provided`() { + val mockJsonObject = JSONObject() + mockedRNSentryJsonUtils + .`when` { + RNSentryJsonUtils.getOptionsFromConfigurationFile(mockContext, configurationFile, mockLogger) + }.thenReturn(mockJsonObject) + val mockReadableMap = + JavaOnlyMap.of( + "dsn", + "https://abc@def.ingest.sentry.io/1234567", + ) + + mockedRNSentryJsonUtils + .`when` { + RNSentryJsonUtils.jsonObjectToReadableMap( + mockJsonObject, + ) + }.thenReturn(mockReadableMap) + + RNSentrySDK.init(mockContext, mockConfiguration, mockLogger) + + mockedRNSentryStart.verify { + RNSentryStart.startWithOptions(mockContext, mockReadableMap, mockConfiguration, null, mockLogger) + } + } + + @Test + fun `fails with an error when there is an unhandled exception in initialisation`() { + mockedRNSentryJsonUtils + .`when` { + RNSentryJsonUtils.getOptionsFromConfigurationFile(mockContext, configurationFile, mockLogger) + }.thenThrow(RuntimeException("Test exception")) + + val exception = + assertThrows(RuntimeException::class.java) { + RNSentrySDK.init(mockContext, mockConfiguration, mockLogger) + } + + assertEquals("Failed to initialize Sentry's React Native SDK", exception.message) + } +} diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentrySDK.java b/packages/core/android/src/main/java/io/sentry/react/RNSentrySDK.java index 5949b85c5f..f7218a39c6 100644 --- a/packages/core/android/src/main/java/io/sentry/react/RNSentrySDK.java +++ b/packages/core/android/src/main/java/io/sentry/react/RNSentrySDK.java @@ -25,28 +25,51 @@ private RNSentrySDK() { * * @param context Android Context * @param configuration configuration options + * @param logger logger */ public static void init( @NotNull final Context context, - @NotNull Sentry.OptionsConfiguration configuration) { + @NotNull Sentry.OptionsConfiguration configuration, + @NotNull ILogger logger) { try { JSONObject jsonObject = RNSentryJsonUtils.getOptionsFromConfigurationFile(context, CONFIGURATION_FILE, logger); ReadableMap rnOptions = RNSentryJsonUtils.jsonObjectToReadableMap(jsonObject); + if (rnOptions == null) { + logger.log( + SentryLevel.WARNING, + "Failed to load configuration file(" + + CONFIGURATION_FILE + + "), starting with configuration callback."); + RNSentryStart.startWithConfiguration(context, configuration); + return; + } RNSentryStart.startWithOptions(context, rnOptions, configuration, null, logger); } catch (Exception e) { logger.log( SentryLevel.ERROR, "Failed to start Sentry with options from configuration file.", e); - throw new RuntimeException(e); + throw new RuntimeException("Failed to initialize Sentry's React Native SDK", e); } } + /** + * Start the Native Android SDK with the provided options + * + * @param context Android Context + * @param configuration configuration options + */ + public static void init( + @NotNull final Context context, + @NotNull Sentry.OptionsConfiguration configuration) { + init(context, configuration, logger); + } + /** * Start the Native Android SDK with options from `sentry.options.json` configuration file * * @param context Android Context */ public static void init(@NotNull final Context context) { - init(context, options -> {}); + init(context, options -> {}, logger); } } diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentryStart.java b/packages/core/android/src/main/java/io/sentry/react/RNSentryStart.java index 6af6b44a7f..ae3468d6ef 100644 --- a/packages/core/android/src/main/java/io/sentry/react/RNSentryStart.java +++ b/packages/core/android/src/main/java/io/sentry/react/RNSentryStart.java @@ -34,6 +34,12 @@ private RNSentryStart() { throw new AssertionError("Utility class should not be instantiated"); } + public static void startWithConfiguration( + @NotNull final Context context, + @NotNull Sentry.OptionsConfiguration configuration) { + SentryAndroid.init(context, configuration); + } + public static void startWithOptions( @NotNull final Context context, @NotNull final ReadableMap rnOptions,