diff --git a/CHANGELOG.md b/CHANGELOG.md index c090f2a762..54033f5b6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Fixes +- Use `appLaunchedInForeground` to determine invalid app start data on Android ([#4146](https://github.com/getsentry/sentry-react-native/pull/4146)) - Upload source maps for all release variants on Android (not only the last found) ([#4125](https://github.com/getsentry/sentry-react-native/pull/4125)) ### Dependencies diff --git a/RNSentryAndroidTester/app/src/test/java/io/sentry/react/RNSentryModuleImplTest.kt b/RNSentryAndroidTester/app/src/test/java/io/sentry/react/RNSentryModuleImplTest.kt new file mode 100644 index 0000000000..5a6585ab80 --- /dev/null +++ b/RNSentryAndroidTester/app/src/test/java/io/sentry/react/RNSentryModuleImplTest.kt @@ -0,0 +1,96 @@ +package io.sentry.react + +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.WritableMap +import io.sentry.ILogger +import io.sentry.SentryLevel +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mockito.* +import org.mockito.MockitoAnnotations +import org.mockito.MockedStatic +import org.mockito.Mockito.mockStatic +import org.mockito.kotlin.whenever + +@RunWith(JUnit4::class) +class RNSentryModuleImplTest { + + private lateinit var module: RNSentryModuleImpl + private lateinit var promise: Promise + private lateinit var logger: ILogger + private var argumentsMock: MockedStatic? = null + + @Captor + private lateinit var writableMapCaptor: ArgumentCaptor + + @Before + fun setUp() { + MockitoAnnotations.openMocks(this) + val reactContext = mock(ReactApplicationContext::class.java) + promise = mock(Promise::class.java) + logger = mock(ILogger::class.java) + val packageManager = mock(PackageManager::class.java) + val packageInfo = mock(PackageInfo::class.java) + + whenever(reactContext.packageManager).thenReturn(packageManager) + whenever(packageManager.getPackageInfo(anyString(), anyInt())).thenReturn(packageInfo) + + module = RNSentryModuleImpl(reactContext) + + // Mock the Arguments class + argumentsMock = mockStatic(Arguments::class.java) + val writableMap = mock(WritableMap::class.java) + whenever(Arguments.createMap()).thenReturn(writableMap) + } + + @After + fun tearDown() { + argumentsMock?.close() + } + + @Test + fun `fetchNativeAppStart resolves promise with null when app is not launched in the foreground`() { + // Mock the app start measurement + val appStartMeasurement = mapOf() + + // Call the method + module.fetchNativeAppStart(promise, appStartMeasurement, logger, false) + + // Verify a warning log is emitted + verify(logger, org.mockito.kotlin.times(1)).log( + SentryLevel.WARNING, + "Invalid app start data: app not launched in foreground." + ) + + // Verify the promise is resolved with null + verify(promise).resolve(null) + } + + @Test + fun `fetchNativeAppStart resolves promise with app start data when app is launched in the foreground`() { + // Mock the app start measurement + val appStartMeasurement = mapOf() + + // Call the method + module.fetchNativeAppStart(promise, appStartMeasurement, logger, true) + + // Verify no logs are emitted + verify(logger, org.mockito.kotlin.times(0)).log(any(), any()) + + // Verify the promise is resolved with the expected data + verify(promise).resolve(any(WritableMap::class.java)) + verify(promise).resolve(writableMapCaptor.capture()) + val capturedMap = writableMapCaptor.value + assertEquals(false, capturedMap.getBoolean("has_fetched")) + } +} diff --git a/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java b/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java index e72549f034..cd498308f3 100644 --- a/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java +++ b/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java @@ -380,9 +380,17 @@ public void fetchNativeRelease(Promise promise) { } public void fetchNativeAppStart(Promise promise) { - final Map measurement = InternalSentrySdk.getAppStartMeasurement(); + fetchNativeAppStart(promise, InternalSentrySdk.getAppStartMeasurement(), logger, AppStartMetrics.getInstance().isAppLaunchedInForeground()); + } + + protected void fetchNativeAppStart(Promise promise, final Map appStartMeasurement, ILogger logger, boolean isAppLaunchedInForeground) { + if (!isAppLaunchedInForeground) { + logger.log(SentryLevel.WARNING, "Invalid app start data: app not launched in foreground."); + promise.resolve(null); + return; + } - WritableMap mutableMeasurement = (WritableMap) RNSentryMapConverter.convertToWritable(measurement); + WritableMap mutableMeasurement = (WritableMap) RNSentryMapConverter.convertToWritable(appStartMeasurement); mutableMeasurement.putBoolean("has_fetched", hasFetchedAppStart); // This is always set to true, as we would only allow an app start fetch to only