From 25df0c9348d3240c7bb7ae4450e5efdccc034472 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Tue, 28 Apr 2026 14:08:30 +0200 Subject: [PATCH 1/3] refactor(ios): remove dead SentrySDKWrapper init surface `SentrySDKWrapper.createOptionsWithDictionary:isSessionReplayEnabled:error:` (and `setupWithDictionary:`, `startWithOptions:`, `enableAutoSessionTracking`, `enableWatchdogTerminationTracking`) became dead when v8 moved init to `RNSentryStart` (#5582 / #4442). The dead methods had a parallel test suite that kept passing, which is exactly how the bugs in #6012 and #6014 shipped: new code was added to `SentrySDKWrapper` because the tests there made it look like the live surface. Delete the dead methods, migrate the still-relevant test coverage to `RNSentryStart`-based tests, drop duplicate tests already pinned on `RNSentryStart`. `SentrySDKWrapper.h` is not in the podspec's `public_header_files`, so no downstream consumers are affected. The kept methods (`configureScope:`, `crash`, `close`, `crashedLastRun`, `debug`, `releaseName`) continue to be used from `RNSentry.mm`. Closes #6015. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../RNSentryCocoaTesterTests/RNSentryTests.m | 1856 +++++++---------- packages/core/ios/SentrySDKWrapper.h | 15 - packages/core/ios/SentrySDKWrapper.m | 137 -- 3 files changed, 713 insertions(+), 1295 deletions(-) diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.m b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.m index a15639423b..25991c4a0b 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.m +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.m @@ -2,7 +2,6 @@ #import "RNSentry+Test.h" #import "RNSentryReplay.h" #import "RNSentryStart+Test.h" -#import "SentrySDKWrapper.h" #import #import #import @@ -17,967 +16,171 @@ @interface RNSentryInitNativeSdkTests : XCTestCase @implementation RNSentryInitNativeSdkTests -- (void)testCreateOptionsWithDictionaryRemovesPerformanceProperties +- (void)testEventFromSentryCocoaReactNativeHasOriginAndEnvironmentTags { RNSentry *rnSentry = [[RNSentry alloc] init]; - NSError *error = nil; - - NSDictionary *_Nonnull mockedReactNativeDictionary = - @{ @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"beforeSend" : @"will_be_overwritten", - @"tracesSampleRate" : @1, - @"tracesSampler" : ^(SentrySamplingContext *_Nonnull samplingContext) { return @1; -} -, @"enableTracing" : @YES, -} -; -mockedReactNativeDictionary = [rnSentry prepareOptions:mockedReactNativeDictionary]; -SentryOptions *actualOptions = - [SentrySDKWrapper createOptionsWithDictionary:mockedReactNativeDictionary - isSessionReplayEnabled:NO - error:&error]; - -XCTAssertNotNil(actualOptions, @"Did not create sentry options"); -XCTAssertNil(error, @"Should not pass no error"); -XCTAssertNotNil( - actualOptions.beforeSend, @"Before send is overwriten by the native RNSentry implementation"); -XCTAssertEqual( - actualOptions.tracesSampleRate, nil, @"Traces sample rate should not be passed to native"); -XCTAssertEqual(actualOptions.tracesSampler, nil, @"Traces sampler should not be passed to native"); -// Note: enableTracing property is deprecated in Sentry Cocoa SDK v7 -// Tracing is disabled by setting tracesSampleRate and tracesSampler to nil -} - -- (void)testCaptureFailedRequestsIsDisabled -{ - NSError *error = nil; - - NSDictionary *_Nonnull mockedReactNativeDictionary = @{ - @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", + SentryEvent *testEvent = [[SentryEvent alloc] init]; + testEvent.sdk = @{ + @"name" : @"sentry.cocoa.react-native", }; - SentryOptions *actualOptions = - [SentrySDKWrapper createOptionsWithDictionary:mockedReactNativeDictionary - isSessionReplayEnabled:NO - error:&error]; - - XCTAssertNotNil(actualOptions, @"Did not create sentry options"); - XCTAssertNil(error, @"Should not pass no error"); - XCTAssertFalse(actualOptions.enableCaptureFailedRequests); -} -- (void)testCreateOptionsWithDictionaryNativeCrashHandlingDefault -{ - NSError *error = nil; + [rnSentry setEventOriginTag:testEvent]; - NSDictionary *_Nonnull mockedReactNativeDictionary = @{ - @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - }; - SentryOptions *actualOptions = - [SentrySDKWrapper createOptionsWithDictionary:mockedReactNativeDictionary - isSessionReplayEnabled:NO - error:&error]; - XCTAssertNotNil(actualOptions, @"Did not create sentry options"); - XCTAssertNil(error, @"Should not pass no error"); - XCTAssertTrue(actualOptions.enableCrashHandler, @"Did not set native crash handling"); + XCTAssertEqual(testEvent.tags[@"event.origin"], @"ios"); + XCTAssertEqual(testEvent.tags[@"event.environment"], @"native"); } -- (void)testCreateOptionsWithDictionaryAutoPerformanceTracingDefault +- (void)testEventFromSentryReactNativeOriginAndEnvironmentTagsAreOverwritten { - NSError *error = nil; - - NSDictionary *_Nonnull mockedReactNativeDictionary = @{ - @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", + RNSentry *rnSentry = [[RNSentry alloc] init]; + SentryEvent *testEvent = [[SentryEvent alloc] init]; + testEvent.sdk = @{ + @"name" : @"sentry.cocoa.react-native", }; - SentryOptions *actualOptions = - [SentrySDKWrapper createOptionsWithDictionary:mockedReactNativeDictionary - isSessionReplayEnabled:NO - error:&error]; - XCTAssertNotNil(actualOptions, @"Did not create sentry options"); - XCTAssertNil(error, @"Should not pass no error"); - XCTAssertEqual( - actualOptions.enableAutoPerformanceTracing, true, @"Did not set Auto Performance Tracing"); -} - -- (void)testCreateOptionsWithDictionaryNativeCrashHandlingEnabled -{ - NSError *error = nil; - - NSDictionary *_Nonnull mockedReactNativeDictionary = @{ - @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"enableNativeCrashHandling" : @YES, + testEvent.tags = @{ + @"event.origin" : @"testEventOriginTag", + @"event.environment" : @"testEventEnvironmentTag", }; - SentryOptions *actualOptions = - [SentrySDKWrapper createOptionsWithDictionary:mockedReactNativeDictionary - isSessionReplayEnabled:NO - error:&error]; - XCTAssertNotNil(actualOptions, @"Did not create sentry options"); - XCTAssertNil(error, @"Should not pass no error"); - XCTAssertTrue(actualOptions.enableCrashHandler, @"Did not set native crash handling"); -} -- (void)testCreateOptionsWithDictionaryAutoPerformanceTracingEnabled -{ - NSError *error = nil; + [rnSentry setEventOriginTag:testEvent]; - NSDictionary *_Nonnull mockedReactNativeDictionary = @{ - @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"enableAutoPerformanceTracing" : @YES, - }; - SentryOptions *actualOptions = - [SentrySDKWrapper createOptionsWithDictionary:mockedReactNativeDictionary - isSessionReplayEnabled:NO - error:&error]; - XCTAssertNotNil(actualOptions, @"Did not create sentry options"); - XCTAssertNil(error, @"Should not pass no error"); - XCTAssertEqual( - actualOptions.enableAutoPerformanceTracing, true, @"Did not set Auto Performance Tracing"); + XCTAssertEqual(testEvent.tags[@"event.origin"], @"ios"); + XCTAssertEqual(testEvent.tags[@"event.environment"], @"native"); } -- (void)testCreateOptionsWithDictionaryNativeCrashHandlingDisabled -{ - NSError *error = nil; +void (^expectRejecterNotCalled)(NSString *, NSString *, NSError *) + = ^(NSString *code, NSString *message, NSError *error) { + @throw [NSException exceptionWithName:@"Promise Rejector should not be called." + reason:nil + userInfo:nil]; + }; - NSDictionary *_Nonnull mockedReactNativeDictionary = @{ - @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"enableNativeCrashHandling" : @NO, - }; - SentryOptions *actualOptions = - [SentrySDKWrapper createOptionsWithDictionary:mockedReactNativeDictionary - isSessionReplayEnabled:NO - error:&error]; - XCTAssertNotNil(actualOptions, @"Did not create sentry options"); - XCTAssertNil(error, @"Should not pass no error"); - XCTAssertFalse(actualOptions.enableCrashHandler, @"Did not disable native crash handling"); -} +uint64_t MOCKED_SYMBOL_ADDRESS = 123; +char const *MOCKED_SYMBOL_NAME = "symbolicatedname"; -- (void)testCreateOptionsWithDictionaryAutoPerformanceTracingDisabled +int +sucessfulSymbolicate(const void *, Dl_info *info) { - NSError *error = nil; - - NSDictionary *_Nonnull mockedReactNativeDictionary = @{ - @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"enableAutoPerformanceTracing" : @NO, - }; - SentryOptions *actualOptions = - [SentrySDKWrapper createOptionsWithDictionary:mockedReactNativeDictionary - isSessionReplayEnabled:NO - error:&error]; - XCTAssertNotNil(actualOptions, @"Did not create sentry options"); - XCTAssertNil(error, @"Should not pass no error"); - XCTAssertEqual(actualOptions.enableAutoPerformanceTracing, false, - @"Did not disable Auto Performance Tracing"); + info->dli_saddr = (void *)MOCKED_SYMBOL_ADDRESS; + info->dli_sname = MOCKED_SYMBOL_NAME; + return 1; } -- (void)testCreateOptionsWithDictionarySpotlightEnabled +- (void)prepareNativeFrameMocksWithLocalSymbolication:(BOOL)debug { - NSError *error = nil; + SentryOptions *sentryOptions = [[SentryOptions alloc] init]; + sentryOptions.debug = debug; // no local symbolication - NSDictionary *_Nonnull mockedReactNativeDictionary = @{ - @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"spotlight" : @YES, - @"defaultSidecarUrl" : @"http://localhost:8969/teststream", - }; - SentryOptions *actualOptions = - [SentrySDKWrapper createOptionsWithDictionary:mockedReactNativeDictionary - isSessionReplayEnabled:NO - error:&error]; - XCTAssertNotNil(actualOptions, @"Did not create sentry options"); - XCTAssertNil(error, @"Should not pass no error"); - XCTAssertTrue(actualOptions.enableSpotlight, @"Did not enable spotlight"); - XCTAssertEqual(actualOptions.spotlightUrl, @"http://localhost:8969/teststream"); -} + id sentrySDKMock = OCMClassMock([SentrySDKInternal class]); + OCMStub([(Class)sentrySDKMock options]).andReturn(sentryOptions); -- (void)testCreateOptionsWithDictionarySpotlightOne -{ - NSError *error = nil; + id sentryDependencyContainerMock = OCMClassMock([SentryDependencyContainer class]); + OCMStub(ClassMethod([sentryDependencyContainerMock sharedInstance])) + .andReturn(sentryDependencyContainerMock); - NSDictionary *_Nonnull mockedReactNativeDictionary = @{ - @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"spotlight" : @1, - @"defaultSidecarUrl" : @"http://localhost:8969/teststream", - }; - SentryOptions *actualOptions = - [SentrySDKWrapper createOptionsWithDictionary:mockedReactNativeDictionary - isSessionReplayEnabled:NO - error:&error]; - XCTAssertNotNil(actualOptions, @"Did not create sentry options"); - XCTAssertNil(error, @"Should not pass no error"); - XCTAssertTrue(actualOptions.enableSpotlight, @"Did not enable spotlight"); - XCTAssertEqual(actualOptions.spotlightUrl, @"http://localhost:8969/teststream"); -} + id sentryBinaryImageInfoMockOne = OCMClassMock([SentryBinaryImageInfo class]); + OCMStub([(SentryBinaryImageInfo *)sentryBinaryImageInfoMockOne address]) + .andReturn([@112233 unsignedLongLongValue]); + OCMStub([sentryBinaryImageInfoMockOne name]).andReturn(@"testnameone"); -- (void)testCreateOptionsWithDictionarySpotlightUrl -{ - NSError *error = nil; + id sentryBinaryImageInfoMockTwo = OCMClassMock([SentryBinaryImageInfo class]); + OCMStub([(SentryBinaryImageInfo *)sentryBinaryImageInfoMockTwo address]) + .andReturn([@112233 unsignedLongLongValue]); + OCMStub([sentryBinaryImageInfoMockTwo name]).andReturn(@"testnametwo"); - NSDictionary *_Nonnull mockedReactNativeDictionary = @{ - @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"spotlight" : @"http://localhost:8969/teststream", + id sentryBinaryImageCacheMock = OCMClassMock([SentryBinaryImageCache class]); + OCMStub([(SentryDependencyContainer *)sentryDependencyContainerMock binaryImageCache]) + .andReturn(sentryBinaryImageCacheMock); + OCMStub([sentryBinaryImageCacheMock imageByAddress:[@123 unsignedLongLongValue]]) + .andReturn(sentryBinaryImageInfoMockOne); + OCMStub([sentryBinaryImageCacheMock imageByAddress:[@456 unsignedLongLongValue]]) + .andReturn(sentryBinaryImageInfoMockTwo); + + NSDictionary *serializedDebugImage = @{ + @"uuid" : @"mockuuid", + @"debug_id" : @"mockdebugid", + @"type" : @"macho", + @"image_addr" : @"0x000000000001b669", }; - SentryOptions *actualOptions = - [SentrySDKWrapper createOptionsWithDictionary:mockedReactNativeDictionary - isSessionReplayEnabled:NO - error:&error]; - XCTAssertNotNil(actualOptions, @"Did not create sentry options"); - XCTAssertNil(error, @"Should not pass no error"); - XCTAssertTrue(actualOptions.enableSpotlight, @"Did not enable spotlight"); - XCTAssertEqual(actualOptions.spotlightUrl, @"http://localhost:8969/teststream"); -} + id sentryDebugImageMock = OCMClassMock([SentryDebugMeta class]); + OCMStub([sentryDebugImageMock serialize]).andReturn(serializedDebugImage); -- (void)testCreateOptionsWithDictionarySpotlightDisabled -{ - NSError *error = nil; + id sentryDebugImageProviderMock = OCMClassMock([SentryDebugImageProvider class]); + OCMStub( + [sentryDebugImageProviderMock + getDebugImagesForImageAddressesFromCache:[NSSet setWithObject:@"0x000000000001b669"]]) + .andReturn(@[ sentryDebugImageMock ]); - NSDictionary *_Nonnull mockedReactNativeDictionary = @{ - @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"spotlight" : @NO, - }; - SentryOptions *actualOptions = - [SentrySDKWrapper createOptionsWithDictionary:mockedReactNativeDictionary - isSessionReplayEnabled:NO - error:&error]; - XCTAssertNotNil(actualOptions, @"Did not create sentry options"); - XCTAssertNil(error, @"Should not pass no error"); - XCTAssertFalse(actualOptions.enableSpotlight, @"Did not disable spotlight"); + OCMStub([sentryDependencyContainerMock debugImageProvider]) + .andReturn(sentryDebugImageProviderMock); } -- (void)testCreateOptionsWithDictionarySpotlightZero +- (void)testFetchNativeStackFramesByInstructionsServerSymbolication { - NSError *error = nil; + [self prepareNativeFrameMocksWithLocalSymbolication:NO]; + RNSentry *rnSentry = [[RNSentry alloc] init]; + NSDictionary *actual = [rnSentry fetchNativeStackFramesBy:@[ @123, @456 ] + symbolicate:sucessfulSymbolicate]; - NSDictionary *_Nonnull mockedReactNativeDictionary = @{ - @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"spotlight" : @0, + NSDictionary *expected = @{ + @"debugMetaImages" : @[ + @{ + @"uuid" : @"mockuuid", + @"debug_id" : @"mockdebugid", + @"type" : @"macho", + @"image_addr" : @"0x000000000001b669", + }, + ], + @"frames" : @[ + @{ + @"package" : @"testnameone", + @"in_app" : @NO, + @"platform" : @"cocoa", + @"instruction_addr" : @"0x000000000000007b", // 123 + @"image_addr" : @"0x000000000001b669", // 112233 + }, + @{ + @"package" : @"testnametwo", + @"in_app" : @NO, + @"platform" : @"cocoa", + @"instruction_addr" : @"0x00000000000001c8", // 456 + @"image_addr" : @"0x000000000001b669", // 445566 + }, + ], }; - SentryOptions *actualOptions = - [SentrySDKWrapper createOptionsWithDictionary:mockedReactNativeDictionary - isSessionReplayEnabled:NO - error:&error]; - XCTAssertNotNil(actualOptions, @"Did not create sentry options"); - XCTAssertNil(error, @"Should not pass no error"); - XCTAssertFalse(actualOptions.enableSpotlight, @"Did not disable spotlight"); + XCTAssertTrue([actual isEqualToDictionary:expected]); } -- (void)testCreateOptionsWithDictionaryEnableUnhandledCPPExceptionsV2Enabled +- (void)testFetchNativeStackFramesByInstructionsOnDeviceSymbolication { - NSError *error = nil; + [self prepareNativeFrameMocksWithLocalSymbolication:YES]; + RNSentry *rnSentry = [[RNSentry alloc] init]; + NSDictionary *actual = [rnSentry fetchNativeStackFramesBy:@[ @123, @456 ] + symbolicate:sucessfulSymbolicate]; - NSDictionary *_Nonnull mockedReactNativeDictionary = @{ - @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"_experiments" : @ { - @"enableUnhandledCPPExceptionsV2" : @YES, - }, - }; - SentryOptions *actualOptions = - [SentrySDKWrapper createOptionsWithDictionary:mockedReactNativeDictionary - isSessionReplayEnabled:NO - error:&error]; - - XCTAssertNotNil(actualOptions, @"Did not create sentry options"); - XCTAssertNil(error, @"Should not pass no error"); - - id experimentalOptions = [actualOptions valueForKey:@"experimental"]; - XCTAssertNotNil(experimentalOptions, @"Experimental options should not be nil"); - - BOOL enableUnhandledCPPExceptions = - [[experimentalOptions valueForKey:@"enableUnhandledCPPExceptionsV2"] boolValue]; - XCTAssertTrue( - enableUnhandledCPPExceptions, @"enableUnhandledCPPExceptionsV2 should be enabled"); -} - -- (void)testCreateOptionsWithDictionaryEnableUnhandledCPPExceptionsV2Disabled -{ - NSError *error = nil; - - NSDictionary *_Nonnull mockedReactNativeDictionary = @{ - @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"_experiments" : @ { - @"enableUnhandledCPPExceptionsV2" : @NO, - }, - }; - SentryOptions *actualOptions = - [SentrySDKWrapper createOptionsWithDictionary:mockedReactNativeDictionary - isSessionReplayEnabled:NO - error:&error]; - - XCTAssertNotNil(actualOptions, @"Did not create sentry options"); - XCTAssertNil(error, @"Should not pass no error"); - - id experimentalOptions = [actualOptions valueForKey:@"experimental"]; - XCTAssertNotNil(experimentalOptions, @"Experimental options should not be nil"); - - BOOL enableUnhandledCPPExceptions = - [[experimentalOptions valueForKey:@"enableUnhandledCPPExceptionsV2"] boolValue]; - XCTAssertFalse( - enableUnhandledCPPExceptions, @"enableUnhandledCPPExceptionsV2 should be disabled"); -} - -- (void)testCreateOptionsWithDictionaryEnableUnhandledCPPExceptionsV2Default -{ - NSError *error = nil; - - NSDictionary *_Nonnull mockedReactNativeDictionary = @{ - @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - }; - SentryOptions *actualOptions = - [SentrySDKWrapper createOptionsWithDictionary:mockedReactNativeDictionary - isSessionReplayEnabled:NO - error:&error]; - - XCTAssertNotNil(actualOptions, @"Did not create sentry options"); - XCTAssertNil(error, @"Should not pass no error"); - - // Test that when no _experiments are provided, the experimental option defaults to false - id experimentalOptions = [actualOptions valueForKey:@"experimental"]; - XCTAssertNotNil(experimentalOptions, @"Experimental options should not be nil"); - - BOOL enableUnhandledCPPExceptions = - [[experimentalOptions valueForKey:@"enableUnhandledCPPExceptionsV2"] boolValue]; - XCTAssertFalse( - enableUnhandledCPPExceptions, @"enableUnhandledCPPExceptionsV2 should default to disabled"); -} - -- (void)testCreateOptionsWithDictionaryEnableLogsEnabled -{ - NSError *error = nil; - - NSDictionary *_Nonnull mockedReactNativeDictionary = @{ - @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"enableLogs" : @YES, - }; - SentryOptions *actualOptions = - [SentrySDKWrapper createOptionsWithDictionary:mockedReactNativeDictionary - isSessionReplayEnabled:NO - error:&error]; - - XCTAssertNotNil(actualOptions, @"Did not create sentry options"); - XCTAssertNil(error, @"Should not pass no error"); - - BOOL enableLogs = [[actualOptions valueForKey:@"enableLogs"] boolValue]; - XCTAssertTrue(enableLogs, @"enableLogs should be enabled"); -} - -- (void)testCreateOptionsWithDictionaryEnableLogsDisabled -{ - NSError *error = nil; - - NSDictionary *_Nonnull mockedReactNativeDictionary = @{ - @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"enableLogs" : @NO, - }; - SentryOptions *actualOptions = - [SentrySDKWrapper createOptionsWithDictionary:mockedReactNativeDictionary - isSessionReplayEnabled:NO - error:&error]; - - XCTAssertNotNil(actualOptions, @"Did not create sentry options"); - XCTAssertNil(error, @"Should not pass no error"); - BOOL enableLogs = [[actualOptions valueForKey:@"enableLogs"] boolValue]; - XCTAssertFalse(enableLogs, @"enableLogs should be disabled"); -} - -- (void)testPassesErrorOnWrongDsn -{ - NSError *error = nil; - - NSDictionary *_Nonnull mockedReactNativeDictionary = @{ - @"dsn" : @"not_a_valid_dsn", - }; - SentryOptions *actualOptions = - [SentrySDKWrapper createOptionsWithDictionary:mockedReactNativeDictionary - isSessionReplayEnabled:NO - error:&error]; - - XCTAssertNil(actualOptions, @"Created invalid sentry options"); - XCTAssertNotNil(error, @"Did not created error on invalid dsn"); -} - -- (void)testBeforeBreadcrumbsCallbackFiltersOutSentryDsnRequestBreadcrumbs -{ - RNSentry *rnSentry = [[RNSentry alloc] init]; - NSError *error = nil; - - NSDictionary *_Nonnull mockedDictionary = @{ - @"dsn" : @"https://abc@def.ingest.sentry.io/1234567", - @"devServerUrl" : @"http://localhost:8081" - }; - mockedDictionary = [rnSentry prepareOptions:mockedDictionary]; - SentryOptions *options = [SentrySDKWrapper createOptionsWithDictionary:mockedDictionary - isSessionReplayEnabled:NO - error:&error]; - SentryBreadcrumb *breadcrumb = [[SentryBreadcrumb alloc] init]; - breadcrumb.type = @"http"; - breadcrumb.data = @{ @"url" : @"https://def.ingest.sentry.io/1234567" }; - - SentryBreadcrumb *result = options.beforeBreadcrumb(breadcrumb); - - XCTAssertNil(result, @"Breadcrumb should be filtered out"); -} - -- (void)testBeforeBreadcrumbsCallbackFiltersOutDevServerRequestBreadcrumbs -{ - NSError *error = nil; - - NSString *mockDevServer = @"http://localhost:8081"; - - NSDictionary *_Nonnull mockedDictionary = - @{ @"dsn" : @"https://abc@def.ingest.sentry.io/1234567", @"devServerUrl" : mockDevServer }; - SentryOptions *options = [SentrySDKWrapper createOptionsWithDictionary:mockedDictionary - isSessionReplayEnabled:NO - error:&error]; - SentryBreadcrumb *breadcrumb = [[SentryBreadcrumb alloc] init]; - breadcrumb.type = @"http"; - breadcrumb.data = @{ @"url" : mockDevServer }; - - SentryBreadcrumb *result = options.beforeBreadcrumb(breadcrumb); - - XCTAssertNil(result, @"Breadcrumb should be filtered out"); -} - -- (void)testBeforeBreadcrumbsCallbackDoesNotFiltersOutNonDevServerOrDsnRequestBreadcrumbs -{ - NSError *error = nil; - - NSDictionary *_Nonnull mockedDictionary = @{ - @"dsn" : @"https://abc@def.ingest.sentry.io/1234567", - @"devServerUrl" : @"http://localhost:8081" - }; - SentryOptions *options = [SentrySDKWrapper createOptionsWithDictionary:mockedDictionary - isSessionReplayEnabled:NO - error:&error]; - SentryBreadcrumb *breadcrumb = [[SentryBreadcrumb alloc] init]; - breadcrumb.type = @"http"; - breadcrumb.data = @{ @"url" : @"http://testurl.com/service" }; - - SentryBreadcrumb *result = options.beforeBreadcrumb(breadcrumb); - - XCTAssertEqual(breadcrumb, result); -} - -- (void)testBeforeBreadcrumbsCallbackKeepsBreadcrumbWhenDevServerUrlIsNotPassedAndDsnDoesNotMatch -{ - NSError *error = nil; - - NSDictionary *_Nonnull mockedDictionary = @{ // dsn is always validated in SentryOptions initialization - @"dsn" : @"https://abc@def.ingest.sentry.io/1234567" - }; - SentryOptions *options = [SentrySDKWrapper createOptionsWithDictionary:mockedDictionary - isSessionReplayEnabled:NO - error:&error]; - SentryBreadcrumb *breadcrumb = [[SentryBreadcrumb alloc] init]; - breadcrumb.type = @"http"; - breadcrumb.data = @{ @"url" : @"http://testurl.com/service" }; - - SentryBreadcrumb *result = options.beforeBreadcrumb(breadcrumb); - - XCTAssertEqual(breadcrumb, result); -} - -- (void)testEventFromSentryCocoaReactNativeHasOriginAndEnvironmentTags -{ - RNSentry *rnSentry = [[RNSentry alloc] init]; - SentryEvent *testEvent = [[SentryEvent alloc] init]; - testEvent.sdk = @{ - @"name" : @"sentry.cocoa.react-native", - }; - - [rnSentry setEventOriginTag:testEvent]; - - XCTAssertEqual(testEvent.tags[@"event.origin"], @"ios"); - XCTAssertEqual(testEvent.tags[@"event.environment"], @"native"); -} - -- (void)testEventFromSentryReactNativeOriginAndEnvironmentTagsAreOverwritten -{ - RNSentry *rnSentry = [[RNSentry alloc] init]; - SentryEvent *testEvent = [[SentryEvent alloc] init]; - testEvent.sdk = @{ - @"name" : @"sentry.cocoa.react-native", - }; - testEvent.tags = @{ - @"event.origin" : @"testEventOriginTag", - @"event.environment" : @"testEventEnvironmentTag", - }; - - [rnSentry setEventOriginTag:testEvent]; - - XCTAssertEqual(testEvent.tags[@"event.origin"], @"ios"); - XCTAssertEqual(testEvent.tags[@"event.environment"], @"native"); -} - -void (^expectRejecterNotCalled)(NSString *, NSString *, NSError *) - = ^(NSString *code, NSString *message, NSError *error) { - @throw [NSException exceptionWithName:@"Promise Rejector should not be called." - reason:nil - userInfo:nil]; - }; - -uint64_t MOCKED_SYMBOL_ADDRESS = 123; -char const *MOCKED_SYMBOL_NAME = "symbolicatedname"; - -int -sucessfulSymbolicate(const void *, Dl_info *info) -{ - info->dli_saddr = (void *)MOCKED_SYMBOL_ADDRESS; - info->dli_sname = MOCKED_SYMBOL_NAME; - return 1; -} - -- (void)prepareNativeFrameMocksWithLocalSymbolication:(BOOL)debug -{ - SentryOptions *sentryOptions = [[SentryOptions alloc] init]; - sentryOptions.debug = debug; // no local symbolication - - id sentrySDKMock = OCMClassMock([SentrySDKInternal class]); - OCMStub([(Class)sentrySDKMock options]).andReturn(sentryOptions); - - id sentryDependencyContainerMock = OCMClassMock([SentryDependencyContainer class]); - OCMStub(ClassMethod([sentryDependencyContainerMock sharedInstance])) - .andReturn(sentryDependencyContainerMock); - - id sentryBinaryImageInfoMockOne = OCMClassMock([SentryBinaryImageInfo class]); - OCMStub([(SentryBinaryImageInfo *)sentryBinaryImageInfoMockOne address]) - .andReturn([@112233 unsignedLongLongValue]); - OCMStub([sentryBinaryImageInfoMockOne name]).andReturn(@"testnameone"); - - id sentryBinaryImageInfoMockTwo = OCMClassMock([SentryBinaryImageInfo class]); - OCMStub([(SentryBinaryImageInfo *)sentryBinaryImageInfoMockTwo address]) - .andReturn([@112233 unsignedLongLongValue]); - OCMStub([sentryBinaryImageInfoMockTwo name]).andReturn(@"testnametwo"); - - id sentryBinaryImageCacheMock = OCMClassMock([SentryBinaryImageCache class]); - OCMStub([(SentryDependencyContainer *)sentryDependencyContainerMock binaryImageCache]) - .andReturn(sentryBinaryImageCacheMock); - OCMStub([sentryBinaryImageCacheMock imageByAddress:[@123 unsignedLongLongValue]]) - .andReturn(sentryBinaryImageInfoMockOne); - OCMStub([sentryBinaryImageCacheMock imageByAddress:[@456 unsignedLongLongValue]]) - .andReturn(sentryBinaryImageInfoMockTwo); - - NSDictionary *serializedDebugImage = @{ - @"uuid" : @"mockuuid", - @"debug_id" : @"mockdebugid", - @"type" : @"macho", - @"image_addr" : @"0x000000000001b669", - }; - id sentryDebugImageMock = OCMClassMock([SentryDebugMeta class]); - OCMStub([sentryDebugImageMock serialize]).andReturn(serializedDebugImage); - - id sentryDebugImageProviderMock = OCMClassMock([SentryDebugImageProvider class]); - OCMStub( - [sentryDebugImageProviderMock - getDebugImagesForImageAddressesFromCache:[NSSet setWithObject:@"0x000000000001b669"]]) - .andReturn(@[ sentryDebugImageMock ]); - - OCMStub([sentryDependencyContainerMock debugImageProvider]) - .andReturn(sentryDebugImageProviderMock); -} - -- (void)testFetchNativeStackFramesByInstructionsServerSymbolication -{ - [self prepareNativeFrameMocksWithLocalSymbolication:NO]; - RNSentry *rnSentry = [[RNSentry alloc] init]; - NSDictionary *actual = [rnSentry fetchNativeStackFramesBy:@[ @123, @456 ] - symbolicate:sucessfulSymbolicate]; - - NSDictionary *expected = @{ - @"debugMetaImages" : @[ - @{ - @"uuid" : @"mockuuid", - @"debug_id" : @"mockdebugid", - @"type" : @"macho", - @"image_addr" : @"0x000000000001b669", - }, - ], - @"frames" : @[ - @{ - @"package" : @"testnameone", - @"in_app" : @NO, - @"platform" : @"cocoa", - @"instruction_addr" : @"0x000000000000007b", // 123 - @"image_addr" : @"0x000000000001b669", // 112233 - }, - @{ - @"package" : @"testnametwo", - @"in_app" : @NO, - @"platform" : @"cocoa", - @"instruction_addr" : @"0x00000000000001c8", // 456 - @"image_addr" : @"0x000000000001b669", // 445566 - }, - ], - }; - XCTAssertTrue([actual isEqualToDictionary:expected]); -} - -- (void)testFetchNativeStackFramesByInstructionsOnDeviceSymbolication -{ - [self prepareNativeFrameMocksWithLocalSymbolication:YES]; - RNSentry *rnSentry = [[RNSentry alloc] init]; - NSDictionary *actual = [rnSentry fetchNativeStackFramesBy:@[ @123, @456 ] - symbolicate:sucessfulSymbolicate]; - - NSDictionary *expected = @{ - @"frames" : @[ - @{ - @"function" : @"symbolicatedname", - @"package" : @"testnameone", - @"in_app" : @NO, - @"platform" : @"cocoa", - @"symbol_addr" : @"0x000000000000007b", // 123 - @"instruction_addr" : @"0x000000000000007b", // 123 - @"image_addr" : @"0x000000000001b669", // 112233 - }, - @{ - @"function" : @"symbolicatedname", - @"package" : @"testnametwo", - @"in_app" : @NO, - @"platform" : @"cocoa", - @"symbol_addr" : @"0x000000000000007b", // 123 - @"instruction_addr" : @"0x00000000000001c8", // 456 - @"image_addr" : @"0x000000000001b669", // 445566 - }, - ], - }; - XCTAssertTrue([actual isEqualToDictionary:expected]); -} - -- (void)testIgnoreErrorsDropsMatchingExceptionValue -{ - RNSentry *rnSentry = [[RNSentry alloc] init]; - NSError *error = nil; - NSMutableDictionary *mockedOptions = [@{ - @"dsn" : @"https://abc@def.ingest.sentry.io/1234567", - @"ignoreErrorsRegex" : @[ @"IgnoreMe.*" ] - } mutableCopy]; - mockedOptions = [rnSentry prepareOptions:mockedOptions]; - SentryOptions *options = [SentrySDKWrapper createOptionsWithDictionary:mockedOptions - isSessionReplayEnabled:NO - error:&error]; - XCTAssertNotNil(options); - XCTAssertNil(error); - SentryEvent *event = [[SentryEvent alloc] init]; - SentryException *exception = [SentryException alloc]; - exception.value = @"IgnoreMe: This should be ignored"; - event.exceptions = @[ exception ]; - SentryEvent *result = options.beforeSend(event); - XCTAssertNil(result, @"Event with matching exception.value should be dropped"); -} - -- (void)testIgnoreErrorsDropsMatchingEventMessage -{ - RNSentry *rnSentry = [[RNSentry alloc] init]; - NSError *error = nil; - NSMutableDictionary *mockedOptions = [@{ - @"dsn" : @"https://abc@def.ingest.sentry.io/1234567", - @"ignoreErrorsStr" : @[ @"DropThisError" ] - } mutableCopy]; - mockedOptions = [rnSentry prepareOptions:mockedOptions]; - SentryOptions *options = [SentrySDKWrapper createOptionsWithDictionary:mockedOptions - isSessionReplayEnabled:NO - error:&error]; - XCTAssertNotNil(options); - XCTAssertNotNil(options); - XCTAssertNil(error); - SentryEvent *event = [[SentryEvent alloc] init]; - SentryMessage *msg = [SentryMessage alloc]; - msg.message = @"DropThisError: should be dropped"; - event.message = msg; - SentryEvent *result = options.beforeSend(event); - XCTAssertNil(result, @"Event with matching event.message.formatted should be dropped"); -} - -- (void)testIgnoreErrorsDoesNotDropNonMatchingEvent -{ - RNSentry *rnSentry = [[RNSentry alloc] init]; - NSError *error = nil; - NSMutableDictionary *mockedOptions = [@{ - @"dsn" : @"https://abc@def.ingest.sentry.io/1234567", - @"ignoreErrorsRegex" : @[ @"IgnoreMe.*" ] - } mutableCopy]; - mockedOptions = [rnSentry prepareOptions:mockedOptions]; - SentryOptions *options = [SentrySDKWrapper createOptionsWithDictionary:mockedOptions - isSessionReplayEnabled:NO - error:&error]; - XCTAssertNotNil(options); - XCTAssertNotNil(options); - XCTAssertNil(error); - SentryEvent *event = [[SentryEvent alloc] init]; - SentryException *exception = [SentryException alloc]; - exception.value = @"SomeOtherError: should not be ignored"; - event.exceptions = @[ exception ]; - SentryMessage *msg = [SentryMessage alloc]; - msg.message = @"SomeOtherMessage"; - event.message = msg; - SentryEvent *result = options.beforeSend(event); - XCTAssertNotNil(result, @"Event with non-matching error should not be dropped"); -} - -- (void)testIgnoreErrorsDropsMatchingExactString -{ - RNSentry *rnSentry = [[RNSentry alloc] init]; - NSError *error = nil; - NSMutableDictionary *mockedOptions = [@{ - @"dsn" : @"https://abc@def.ingest.sentry.io/1234567", - @"ignoreErrorsStr" : @[ @"ExactError" ] - } mutableCopy]; - mockedOptions = [rnSentry prepareOptions:mockedOptions]; - SentryOptions *options = [SentrySDKWrapper createOptionsWithDictionary:mockedOptions - isSessionReplayEnabled:NO - error:&error]; - XCTAssertNotNil(options); - XCTAssertNotNil(options); - XCTAssertNil(error); - SentryEvent *event = [[SentryEvent alloc] init]; - SentryMessage *msg = [SentryMessage alloc]; - msg.message = @"ExactError"; - event.message = msg; - SentryEvent *result = options.beforeSend(event); - XCTAssertNil(result, @"Event with exactly matching string should be dropped"); -} - -- (void)testIgnoreErrorsRegexAndStringBothWork -{ - RNSentry *rnSentry = [[RNSentry alloc] init]; - NSError *error = nil; - NSMutableDictionary *mockedOptions = [@{ - @"dsn" : @"https://abc@def.ingest.sentry.io/1234567", - @"ignoreErrorsStr" : @[ @"ExactError" ], - @"ignoreErrorsRegex" : @[ @"IgnoreMe.*" ], - - } mutableCopy]; - mockedOptions = [rnSentry prepareOptions:mockedOptions]; - SentryOptions *options = [SentrySDKWrapper createOptionsWithDictionary:mockedOptions - isSessionReplayEnabled:NO - error:&error]; - XCTAssertNotNil(options); - XCTAssertNotNil(options); - XCTAssertNil(error); - // Test regex match - SentryEvent *event1 = [[SentryEvent alloc] init]; - SentryException *exception = [SentryException alloc]; - exception.value = @"IgnoreMe: This should be ignored"; - event1.exceptions = @[ exception ]; - SentryEvent *result1 = options.beforeSend(event1); - XCTAssertNil(result1, @"Event with matching regex should be dropped"); - // Test exact string match - SentryEvent *event2 = [[SentryEvent alloc] init]; - SentryMessage *msg = [SentryMessage alloc]; - msg.message = @"ExactError"; - event2.message = msg; - SentryEvent *result2 = options.beforeSend(event2); - XCTAssertNil(result2, @"Event with exactly matching string should be dropped"); - // Test non-matching - SentryEvent *event3 = [[SentryEvent alloc] init]; - SentryMessage *msg3 = [SentryMessage alloc]; - msg3.message = @"OtherError"; - event3.message = msg3; - SentryEvent *result3 = options.beforeSend(event3); - XCTAssertNotNil(result3, @"Event with non-matching error should not be dropped"); -} - -- (void)testBeforeSendFiltersOutUnhandledJSException -{ - RNSentry *rnSentry = [[RNSentry alloc] init]; - NSError *error = nil; - NSMutableDictionary *mockedOptions = [@{ - @"dsn" : @"https://abc@def.ingest.sentry.io/1234567", - } mutableCopy]; - mockedOptions = [rnSentry prepareOptions:mockedOptions]; - SentryOptions *options = [SentrySDKWrapper createOptionsWithDictionary:mockedOptions - isSessionReplayEnabled:NO - error:&error]; - XCTAssertNotNil(options); - XCTAssertNil(error); - - SentryEvent *event = [[SentryEvent alloc] init]; - SentryException *exception = [SentryException alloc]; - exception.type = @"Unhandled JS Exception"; - exception.value = @"Error: Test error"; - event.exceptions = @[ exception ]; - SentryEvent *result = options.beforeSend(event); - XCTAssertNil(result, @"Event with Unhandled JS Exception should be dropped"); -} - -- (void)testBeforeSendFiltersOutJSErrorCppException -{ - RNSentry *rnSentry = [[RNSentry alloc] init]; - NSError *error = nil; - NSMutableDictionary *mockedOptions = [@{ - @"dsn" : @"https://abc@def.ingest.sentry.io/1234567", - } mutableCopy]; - mockedOptions = [rnSentry prepareOptions:mockedOptions]; - SentryOptions *options = [SentrySDKWrapper createOptionsWithDictionary:mockedOptions - isSessionReplayEnabled:NO - error:&error]; - XCTAssertNotNil(options); - XCTAssertNil(error); - - // Test C++ exception with ExceptionsManager.reportException in value (actual format from New - // Architecture) The exception type is "C++ Exception" and the value contains the mangled name - // and error message - SentryEvent *event1 = [[SentryEvent alloc] init]; - SentryException *exception1 = [SentryException alloc]; - exception1.type = @"C++ Exception"; - exception1.value = @"N8facebook3jsi7JSErrorE: ExceptionsManager.reportException raised an " - @"exception: Unhandled JS Exception: Error: Test error"; - event1.exceptions = @[ exception1 ]; - SentryEvent *result1 = options.beforeSend(event1); - XCTAssertNil( - result1, @"Event with ExceptionsManager.reportException in value should be dropped"); - - // Test exception value containing ExceptionsManager.reportException (alternative format) - SentryEvent *event2 = [[SentryEvent alloc] init]; - SentryException *exception2 = [SentryException alloc]; - exception2.type = @"SomeOtherException"; - exception2.value = @"ExceptionsManager.reportException raised an exception: Unhandled JS " - @"Exception: Error: Test"; - event2.exceptions = @[ exception2 ]; - SentryEvent *result2 = options.beforeSend(event2); - XCTAssertNil( - result2, @"Event with ExceptionsManager.reportException in value should be dropped"); - - // Test that legitimate C++ exceptions without ExceptionsManager.reportException are not - // filtered - SentryEvent *event3 = [[SentryEvent alloc] init]; - SentryException *exception3 = [SentryException alloc]; - exception3.type = @"C++ Exception"; - exception3.value = @"std::runtime_error: Some other C++ error occurred"; - event3.exceptions = @[ exception3 ]; - SentryEvent *result3 = options.beforeSend(event3); - XCTAssertNotNil(result3, - @"Legitimate C++ exception without ExceptionsManager.reportException should not be " - @"dropped"); -} - -- (void)testCreateOptionsWithDictionaryEnableSessionReplayInUnreliableEnvironmentDefault -{ - NSError *error = nil; - - NSDictionary *_Nonnull mockedReactNativeDictionary = @{ - @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - }; - SentryOptions *actualOptions = - [SentrySDKWrapper createOptionsWithDictionary:mockedReactNativeDictionary - isSessionReplayEnabled:NO - error:&error]; - - XCTAssertNotNil(actualOptions, @"Did not create sentry options"); - XCTAssertNil(error, @"Should not pass no error"); - - id experimentalOptions = [actualOptions valueForKey:@"experimental"]; - XCTAssertNotNil(experimentalOptions, @"Experimental options should not be nil"); - - BOOL enableUnhandledCPPExceptions = - [[experimentalOptions valueForKey:@"enableSessionReplayInUnreliableEnvironment"] boolValue]; - XCTAssertFalse(enableUnhandledCPPExceptions, - @"enableSessionReplayInUnreliableEnvironment should be disabled"); -} - -- (void)testCreateOptionsWithDictionaryEnableSessionReplayInUnreliableEnvironmentWithErrorSampleRate -{ - NSError *error = nil; - - NSMutableDictionary *_Nonnull mockedReactNativeDictionary = [@{ - @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"replaysOnErrorSampleRate" : @1.0, - @"replaysSessionSampleRate" : @0 - } mutableCopy]; - BOOL enableReplay = [RNSentryReplay updateOptions:mockedReactNativeDictionary]; - SentryOptions *actualOptions = - [SentrySDKWrapper createOptionsWithDictionary:mockedReactNativeDictionary - isSessionReplayEnabled:enableReplay - error:&error]; - - XCTAssertNotNil(actualOptions, @"Did not create sentry options"); - XCTAssertNil(error, @"Should not pass no error"); - - id experimentalOptions = [actualOptions valueForKey:@"experimental"]; - XCTAssertNotNil(experimentalOptions, @"Experimental options should not be nil"); - - BOOL enableUnhandledCPPExceptions = - [[experimentalOptions valueForKey:@"enableSessionReplayInUnreliableEnvironment"] boolValue]; - XCTAssertTrue(enableUnhandledCPPExceptions, - @"enableSessionReplayInUnreliableEnvironment should be enabled"); -} - -- (void) - testCreateOptionsWithDictionaryEnableSessionReplayInUnreliableEnvironmentWithSessionSampleRate -{ - NSError *error = nil; - - NSMutableDictionary *_Nonnull mockedReactNativeDictionary = [@{ - @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"replaysOnErrorSampleRate" : @0.0, - @"replaysSessionSampleRate" : @0.1 - } mutableCopy]; - BOOL enableReplay = [RNSentryReplay updateOptions:mockedReactNativeDictionary]; - SentryOptions *actualOptions = - [SentrySDKWrapper createOptionsWithDictionary:mockedReactNativeDictionary - isSessionReplayEnabled:enableReplay - error:&error]; - XCTAssertNotNil(actualOptions, @"Did not create sentry options"); - XCTAssertNil(error, @"Should not pass no error"); - - id experimentalOptions = [actualOptions valueForKey:@"experimental"]; - XCTAssertNotNil(experimentalOptions, @"Experimental options should not be nil"); - - BOOL enableUnhandledCPPExceptions = - [[experimentalOptions valueForKey:@"enableSessionReplayInUnreliableEnvironment"] boolValue]; - XCTAssertTrue(enableUnhandledCPPExceptions, - @"enableSessionReplayInUnreliableEnvironment should be enabled"); -} - -- (void) - testCreateOptionsWithDictionaryEnableSessionReplayInUnreliableEnvironmentWithSessionSampleRates -{ - NSError *error = nil; - - NSMutableDictionary *_Nonnull mockedReactNativeDictionary = [@{ - @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"replaysOnErrorSampleRate" : @1.0, - @"replaysSessionSampleRate" : @0.1 - } mutableCopy]; - BOOL enableReplay = [RNSentryReplay updateOptions:mockedReactNativeDictionary]; - SentryOptions *actualOptions = - [SentrySDKWrapper createOptionsWithDictionary:mockedReactNativeDictionary - isSessionReplayEnabled:enableReplay - error:&error]; - - XCTAssertNotNil(actualOptions, @"Did not create sentry options"); - XCTAssertNil(error, @"Should not pass no error"); - - id experimentalOptions = [actualOptions valueForKey:@"experimental"]; - XCTAssertNotNil(experimentalOptions, @"Experimental options should not be nil"); - - BOOL enableUnhandledCPPExceptions = - [[experimentalOptions valueForKey:@"enableSessionReplayInUnreliableEnvironment"] boolValue]; - XCTAssertTrue(enableUnhandledCPPExceptions, - @"enableSessionReplayInUnreliableEnvironment should be enabled"); -} - -- (void)testCreateOptionsWithDictionaryEnableSessionReplayInUnreliableEnvironmentDisabled -{ - NSError *error = nil; - - NSDictionary *_Nonnull mockedReactNativeDictionary = @{ - @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"replaysOnErrorSampleRate" : @0, - @"replaysSessionSampleRate" : @0 + NSDictionary *expected = @{ + @"frames" : @[ + @{ + @"function" : @"symbolicatedname", + @"package" : @"testnameone", + @"in_app" : @NO, + @"platform" : @"cocoa", + @"symbol_addr" : @"0x000000000000007b", // 123 + @"instruction_addr" : @"0x000000000000007b", // 123 + @"image_addr" : @"0x000000000001b669", // 112233 + }, + @{ + @"function" : @"symbolicatedname", + @"package" : @"testnametwo", + @"in_app" : @NO, + @"platform" : @"cocoa", + @"symbol_addr" : @"0x000000000000007b", // 123 + @"instruction_addr" : @"0x00000000000001c8", // 456 + @"image_addr" : @"0x000000000001b669", // 445566 + }, + ], }; - SentryOptions *actualOptions = - [SentrySDKWrapper createOptionsWithDictionary:mockedReactNativeDictionary - isSessionReplayEnabled:NO - error:&error]; - - XCTAssertNotNil(actualOptions, @"Did not create sentry options"); - XCTAssertNil(error, @"Should not pass no error"); - - id experimentalOptions = [actualOptions valueForKey:@"experimental"]; - XCTAssertNotNil(experimentalOptions, @"Experimental options should not be nil"); - - BOOL enableUnhandledCPPExceptions = - [[experimentalOptions valueForKey:@"enableSessionReplayInUnreliableEnvironment"] boolValue]; - XCTAssertFalse(enableUnhandledCPPExceptions, - @"enableSessionReplayInUnreliableEnvironment should be disabled"); + XCTAssertTrue([actual isEqualToDictionary:expected]); } - (void)testCreateUserWithGeoDataCreatesSentryGeoObject @@ -1153,430 +356,797 @@ - (void)testStartCreateOptionsWithDictionaryNativeCrashHandlingEnabled XCTAssertEqual(actualOptions.enableCrashHandler, YES, @"Did not set native crash handling"); } -- (void)testStartCreateOptionsWithDictionaryAutoPerformanceTracingEnabled +- (void)testStartCreateOptionsWithDictionaryAutoPerformanceTracingEnabled +{ + NSError *error = nil; + + NSDictionary *_Nonnull mockedReactNativeDictionary = @{ + @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", + @"enableAutoPerformanceTracing" : @YES, + }; + SentryOptions *actualOptions = + [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; + XCTAssertNotNil(actualOptions, @"Did not create sentry options"); + XCTAssertNil(error, @"Should not pass no error"); + XCTAssertEqual( + actualOptions.enableAutoPerformanceTracing, true, @"Did not set Auto Performance Tracing"); +} + +- (void)testStartCreateOptionsWithDictionaryNativeCrashHandlingDisabled +{ + NSError *error = nil; + + NSDictionary *_Nonnull mockedReactNativeDictionary = @{ + @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", + @"enableNativeCrashHandling" : @NO, + }; + SentryOptions *actualOptions = + [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; + XCTAssertNotNil(actualOptions, @"Did not create sentry options"); + XCTAssertNil(error, @"Should not pass no error"); + XCTAssertEqual(actualOptions.enableCrashHandler, NO, @"Did not disable native crash handling"); +} + +- (void)testStartCreateOptionsWithDictionaryAutoPerformanceTracingDisabled +{ + NSError *error = nil; + + NSDictionary *_Nonnull mockedReactNativeDictionary = @{ + @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", + @"enableAutoPerformanceTracing" : @NO, + }; + SentryOptions *actualOptions = + [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; + XCTAssertNotNil(actualOptions, @"Did not create sentry options"); + XCTAssertNil(error, @"Should not pass no error"); + XCTAssertEqual(actualOptions.enableAutoPerformanceTracing, false, + @"Did not disable Auto Performance Tracing"); +} + +- (void)testStartCreateOptionsWithDictionarySpotlightEnabled +{ + NSError *error = nil; + + NSDictionary *_Nonnull mockedReactNativeDictionary = @{ + @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", + @"spotlight" : @YES, + @"defaultSidecarUrl" : @"http://localhost:8969/teststream", + }; + SentryOptions *actualOptions = + [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; + XCTAssertNotNil(actualOptions, @"Did not create sentry options"); + XCTAssertNil(error, @"Should not pass no error"); + XCTAssertTrue(actualOptions.enableSpotlight, @"Did not enable spotlight"); + XCTAssertEqual(actualOptions.spotlightUrl, @"http://localhost:8969/teststream"); +} + +- (void)testStartCreateOptionsWithDictionarySpotlightOne +{ + NSError *error = nil; + + NSDictionary *_Nonnull mockedReactNativeDictionary = @{ + @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", + @"spotlight" : @1, + @"defaultSidecarUrl" : @"http://localhost:8969/teststream", + }; + SentryOptions *actualOptions = + [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; + XCTAssertNotNil(actualOptions, @"Did not create sentry options"); + XCTAssertNil(error, @"Should not pass no error"); + XCTAssertTrue(actualOptions.enableSpotlight, @"Did not enable spotlight"); + XCTAssertEqual(actualOptions.spotlightUrl, @"http://localhost:8969/teststream"); +} + +- (void)testStartCreateOptionsWithDictionarySpotlightUrl +{ + NSError *error = nil; + + NSDictionary *_Nonnull mockedReactNativeDictionary = @{ + @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", + @"spotlight" : @"http://localhost:8969/teststream", + }; + SentryOptions *actualOptions = + [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; + XCTAssertNotNil(actualOptions, @"Did not create sentry options"); + XCTAssertNil(error, @"Should not pass no error"); + XCTAssertTrue(actualOptions.enableSpotlight, @"Did not enable spotlight"); + XCTAssertEqual(actualOptions.spotlightUrl, @"http://localhost:8969/teststream"); +} + +- (void)testStartCreateOptionsWithDictionarySpotlightDisabled +{ + NSError *error = nil; + + NSDictionary *_Nonnull mockedReactNativeDictionary = @{ + @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", + @"spotlight" : @NO, + }; + SentryOptions *actualOptions = + [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; + XCTAssertNotNil(actualOptions, @"Did not create sentry options"); + XCTAssertNil(error, @"Should not pass no error"); + XCTAssertFalse(actualOptions.enableSpotlight, @"Did not disable spotlight"); +} + +- (void)testStartCreateOptionsWithDictionarySpotlightZero +{ + NSError *error = nil; + + NSDictionary *_Nonnull mockedReactNativeDictionary = @{ + @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", + @"spotlight" : @0, + }; + SentryOptions *actualOptions = + [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; + XCTAssertNotNil(actualOptions, @"Did not create sentry options"); + XCTAssertNil(error, @"Should not pass no error"); + XCTAssertFalse(actualOptions.enableSpotlight, @"Did not disable spotlight"); +} + +- (void)testStartPassesErrorOnWrongDsn +{ + NSError *error = nil; + + NSDictionary *_Nonnull mockedReactNativeDictionary = @{ + @"dsn" : @"not_a_valid_dsn", + }; + SentryOptions *actualOptions = + [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; + + XCTAssertNil(actualOptions, @"Created invalid sentry options"); + XCTAssertNotNil(error, @"Did not created error on invalid dsn"); +} + +- (void)testStartBeforeBreadcrumbsCallbackFiltersOutSentryDsnRequestBreadcrumbs +{ + NSError *error = nil; + + NSDictionary *_Nonnull mockedDictionary = @{ + @"dsn" : @"https://abc@def.ingest.sentry.io/1234567", + @"devServerUrl" : @"http://localhost:8081" + }; + SentryOptions *options = [RNSentryStart createOptionsWithDictionary:mockedDictionary + error:&error]; + + SentryBreadcrumb *breadcrumb = [[SentryBreadcrumb alloc] init]; + breadcrumb.type = @"http"; + breadcrumb.data = @{ @"url" : @"https://def.ingest.sentry.io/1234567" }; + + SentryBreadcrumb *result = options.beforeBreadcrumb(breadcrumb); + + XCTAssertNil(result, @"Breadcrumb should be filtered out"); +} + +- (void)testStartBeforeBreadcrumbsCallbackFiltersOutDevServerRequestBreadcrumbs +{ + NSError *error = nil; + + NSString *mockDevServer = @"http://localhost:8081"; + + NSDictionary *_Nonnull mockedDictionary = + @{ @"dsn" : @"https://abc@def.ingest.sentry.io/1234567", @"devServerUrl" : mockDevServer }; + SentryOptions *options = [RNSentryStart createOptionsWithDictionary:mockedDictionary + error:&error]; + + SentryBreadcrumb *breadcrumb = [[SentryBreadcrumb alloc] init]; + breadcrumb.type = @"http"; + breadcrumb.data = @{ @"url" : mockDevServer }; + + SentryBreadcrumb *result = options.beforeBreadcrumb(breadcrumb); + + XCTAssertNil(result, @"Breadcrumb should be filtered out"); +} + +- (void)testStartBeforeBreadcrumbsCallbackDoesNotFiltersOutNonDevServerOrDsnRequestBreadcrumbs +{ + NSError *error = nil; + + NSDictionary *_Nonnull mockedDictionary = @{ + @"dsn" : @"https://abc@def.ingest.sentry.io/1234567", + @"devServerUrl" : @"http://localhost:8081" + }; + SentryOptions *options = [RNSentryStart createOptionsWithDictionary:mockedDictionary + error:&error]; + + SentryBreadcrumb *breadcrumb = [[SentryBreadcrumb alloc] init]; + breadcrumb.type = @"http"; + breadcrumb.data = @{ @"url" : @"http://testurl.com/service" }; + + SentryBreadcrumb *result = options.beforeBreadcrumb(breadcrumb); + + XCTAssertEqual(breadcrumb, result); +} + +- (void) + testStartBeforeBreadcrumbsCallbackKeepsBreadcrumbWhenDevServerUrlIsNotPassedAndDsnDoesNotMatch +{ + NSError *error = nil; + + NSDictionary *_Nonnull mockedDictionary = @{ // dsn is always validated in SentryOptions initialization + @"dsn" : @"https://abc@def.ingest.sentry.io/1234567" + }; + SentryOptions *options = [RNSentryStart createOptionsWithDictionary:mockedDictionary + error:&error]; + + SentryBreadcrumb *breadcrumb = [[SentryBreadcrumb alloc] init]; + breadcrumb.type = @"http"; + breadcrumb.data = @{ @"url" : @"http://testurl.com/service" }; + + SentryBreadcrumb *result = options.beforeBreadcrumb(breadcrumb); + + XCTAssertEqual(breadcrumb, result); +} + +#if SENTRY_TARGET_PROFILING_SUPPORTED +// Regression test for the v8.0.0 bug where the init path (RNSentryStart) did not +// handle `_experiments.profilingOptions`, silently dropping iOS UI profiling config. +// This pins the full entry point used by `initNativeSdk` in RNSentry.mm. +- (void)testStartWithDictionaryInstallsConfigureProfilingFromExperimentsProfilingOptions +{ + NSError *error = nil; + + NSDictionary *_Nonnull mockedReactNativeDictionary = @{ + @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", + @"_experiments" : @ { + @"profilingOptions" : @ { + @"profileSessionSampleRate" : @1.0, + @"lifecycle" : @"trace", + @"startOnAppStart" : @YES, + }, + }, + }; + [RNSentryStart startWithOptions:mockedReactNativeDictionary error:&error]; + SentryOptions *actualOptions = PrivateSentrySDKOnly.options; + + XCTAssertNotNil(actualOptions, @"Did not create sentry options"); + XCTAssertNil(error, @"Should not pass no error"); + XCTAssertNotNil(actualOptions.configureProfiling, + @"configureProfiling must be installed after startWithOptions when profilingOptions is " + @"present"); + + SentryProfileOptions *probe = [[SentryProfileOptions alloc] init]; + actualOptions.configureProfiling(probe); + XCTAssertEqual(probe.sessionSampleRate, 1.0f); + XCTAssertEqual(probe.lifecycle, SentryProfileLifecycleTrace); + XCTAssertTrue(probe.profileAppStarts); +} + +- (void)testStartCreateOptionsWithDictionaryProfilingOptionsInstallsConfigureProfiling { NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"enableAutoPerformanceTracing" : @YES, + @"_experiments" : @ { + @"profilingOptions" : @ { + @"profileSessionSampleRate" : @1.0, + @"lifecycle" : @"trace", + @"startOnAppStart" : @YES, + }, + }, }; SentryOptions *actualOptions = [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; + XCTAssertNotNil(actualOptions, @"Did not create sentry options"); XCTAssertNil(error, @"Should not pass no error"); - XCTAssertEqual( - actualOptions.enableAutoPerformanceTracing, true, @"Did not set Auto Performance Tracing"); + XCTAssertNotNil(actualOptions.configureProfiling, + @"configureProfiling callback should be installed when profilingOptions is present"); + + SentryProfileOptions *probe = [[SentryProfileOptions alloc] init]; + actualOptions.configureProfiling(probe); + XCTAssertEqual(probe.sessionSampleRate, 1.0f); + XCTAssertEqual(probe.lifecycle, SentryProfileLifecycleTrace); + XCTAssertTrue(probe.profileAppStarts); } -- (void)testStartCreateOptionsWithDictionaryNativeCrashHandlingDisabled +- (void)testStartCreateOptionsWithDictionaryProfilingOptionsMissingDoesNotInstallConfigureProfiling { NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"enableNativeCrashHandling" : @NO, }; SentryOptions *actualOptions = [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; + XCTAssertNotNil(actualOptions, @"Did not create sentry options"); XCTAssertNil(error, @"Should not pass no error"); - XCTAssertEqual(actualOptions.enableCrashHandler, NO, @"Did not disable native crash handling"); + XCTAssertNil(actualOptions.configureProfiling, + @"configureProfiling callback should not be installed without profilingOptions"); } -- (void)testStartCreateOptionsWithDictionaryAutoPerformanceTracingDisabled +- (void)testStartCreateOptionsWithDictionaryEmptyExperimentsDoesNotInstallConfigureProfiling { NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"enableAutoPerformanceTracing" : @NO, + @"_experiments" : @ { }, }; SentryOptions *actualOptions = [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; + XCTAssertNotNil(actualOptions, @"Did not create sentry options"); XCTAssertNil(error, @"Should not pass no error"); - XCTAssertEqual(actualOptions.enableAutoPerformanceTracing, false, - @"Did not disable Auto Performance Tracing"); + XCTAssertNil(actualOptions.configureProfiling, + @"configureProfiling callback should not be installed when profilingOptions is absent"); } +#endif -- (void)testStartCreateOptionsWithDictionarySpotlightEnabled +- (void)testStartCreateOptionsWithDictionaryEnableUnhandledCPPExceptionsV2Enabled { NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"spotlight" : @YES, - @"defaultSidecarUrl" : @"http://localhost:8969/teststream", + @"_experiments" : @ { + @"enableUnhandledCPPExceptionsV2" : @YES, + }, }; SentryOptions *actualOptions = [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; + XCTAssertNotNil(actualOptions, @"Did not create sentry options"); XCTAssertNil(error, @"Should not pass no error"); - XCTAssertTrue(actualOptions.enableSpotlight, @"Did not enable spotlight"); - XCTAssertEqual(actualOptions.spotlightUrl, @"http://localhost:8969/teststream"); + XCTAssertTrue(actualOptions.experimental.enableUnhandledCPPExceptionsV2, + @"enableUnhandledCPPExceptionsV2 should be enabled"); } -- (void)testStartCreateOptionsWithDictionarySpotlightOne +- (void)testStartCreateOptionsWithDictionaryEnableUnhandledCPPExceptionsV2Disabled { NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"spotlight" : @1, - @"defaultSidecarUrl" : @"http://localhost:8969/teststream", + @"_experiments" : @ { + @"enableUnhandledCPPExceptionsV2" : @NO, + }, }; SentryOptions *actualOptions = [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; + XCTAssertNotNil(actualOptions, @"Did not create sentry options"); XCTAssertNil(error, @"Should not pass no error"); - XCTAssertTrue(actualOptions.enableSpotlight, @"Did not enable spotlight"); - XCTAssertEqual(actualOptions.spotlightUrl, @"http://localhost:8969/teststream"); + XCTAssertFalse(actualOptions.experimental.enableUnhandledCPPExceptionsV2, + @"enableUnhandledCPPExceptionsV2 should be disabled"); } -- (void)testStartCreateOptionsWithDictionarySpotlightUrl +- (void)testStartCreateOptionsWithDictionaryEnableUnhandledCPPExceptionsV2Default { NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"spotlight" : @"http://localhost:8969/teststream", }; SentryOptions *actualOptions = [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; + XCTAssertNotNil(actualOptions, @"Did not create sentry options"); XCTAssertNil(error, @"Should not pass no error"); - XCTAssertTrue(actualOptions.enableSpotlight, @"Did not enable spotlight"); - XCTAssertEqual(actualOptions.spotlightUrl, @"http://localhost:8969/teststream"); + XCTAssertFalse(actualOptions.experimental.enableUnhandledCPPExceptionsV2, + @"enableUnhandledCPPExceptionsV2 should default to disabled"); } -- (void)testStartCreateOptionsWithDictionarySpotlightDisabled +- (void)testStartEventFromSentryCocoaReactNativeHasOriginAndEnvironmentTags { - NSError *error = nil; + SentryEvent *testEvent = [[SentryEvent alloc] init]; + testEvent.sdk = @{ + @"name" : @"sentry.cocoa.react-native", + }; - NSDictionary *_Nonnull mockedReactNativeDictionary = @{ - @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"spotlight" : @NO, + [RNSentryStart setEventOriginTag:testEvent]; + + XCTAssertEqual(testEvent.tags[@"event.origin"], @"ios"); + XCTAssertEqual(testEvent.tags[@"event.environment"], @"native"); +} + +- (void)testStartEventFromSentryReactNativeOriginAndEnvironmentTagsAreOverwritten +{ + SentryEvent *testEvent = [[SentryEvent alloc] init]; + testEvent.sdk = @{ + @"name" : @"sentry.cocoa.react-native", }; - SentryOptions *actualOptions = - [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; - XCTAssertNotNil(actualOptions, @"Did not create sentry options"); - XCTAssertNil(error, @"Should not pass no error"); - XCTAssertFalse(actualOptions.enableSpotlight, @"Did not disable spotlight"); + testEvent.tags = @{ + @"event.origin" : @"testEventOriginTag", + @"event.environment" : @"testEventEnvironmentTag", + }; + + [RNSentryStart setEventOriginTag:testEvent]; + + XCTAssertEqual(testEvent.tags[@"event.origin"], @"ios"); + XCTAssertEqual(testEvent.tags[@"event.environment"], @"native"); } -- (void)testStartCreateOptionsWithDictionarySpotlightZero +#pragma mark - captureReplayWithReturnValue Tests + +#if SENTRY_TARGET_REPLAY_SUPPORTED +- (void)testCaptureReplayWithReturnValueReturnsFalseWhenReplayNotRunning +{ + // Without starting the SDK with replay enabled, + // the replay integration should not be available + // and captureReplayWithReturnValue should return NO + BOOL result = [RNSentry captureReplayWithReturnValue]; + XCTAssertFalse(result, + @"captureReplayWithReturnValue should return NO when replay integration is not available"); +} + +- (void)testCaptureReplayWithReturnValueReturnsFalseWithoutInit +{ + // When the SDK is not initialized at all, there should be no replay integration + // and captureReplayWithReturnValue should return NO without crashing + BOOL result = [RNSentry captureReplayWithReturnValue]; + XCTAssertFalse(result, + @"captureReplayWithReturnValue should return NO without crashing when SDK is not " + @"initialized"); +} +#endif + +#pragma mark - RNSentryStart enableLogs Tests + +- (void)testStartCreateOptionsWithDictionaryEnableLogsEnabled { NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"spotlight" : @0, + @"enableLogs" : @YES, }; SentryOptions *actualOptions = [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; + XCTAssertNotNil(actualOptions, @"Did not create sentry options"); XCTAssertNil(error, @"Should not pass no error"); - XCTAssertFalse(actualOptions.enableSpotlight, @"Did not disable spotlight"); + + BOOL enableLogs = [[actualOptions valueForKey:@"enableLogs"] boolValue]; + XCTAssertTrue(enableLogs, @"enableLogs should be enabled"); } -- (void)testStartPassesErrorOnWrongDsn +- (void)testStartCreateOptionsWithDictionaryEnableLogsDisabled { NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ - @"dsn" : @"not_a_valid_dsn", + @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", + @"enableLogs" : @NO, }; SentryOptions *actualOptions = [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; - XCTAssertNil(actualOptions, @"Created invalid sentry options"); - XCTAssertNotNil(error, @"Did not created error on invalid dsn"); + XCTAssertNotNil(actualOptions, @"Did not create sentry options"); + XCTAssertNil(error, @"Should not pass no error"); + BOOL enableLogs = [[actualOptions valueForKey:@"enableLogs"] boolValue]; + XCTAssertFalse(enableLogs, @"enableLogs should be disabled"); } -- (void)testStartBeforeBreadcrumbsCallbackFiltersOutSentryDsnRequestBreadcrumbs +#pragma mark - RNSentryStart beforeBreadcrumb edge case + +- (void) + testStartBeforeBreadcrumbsCallbackKeepsBreadcrumbWhenDevServerUrlIsNotPassedAndDsnDoesNotMatch { NSError *error = nil; - NSDictionary *_Nonnull mockedDictionary = @{ - @"dsn" : @"https://abc@def.ingest.sentry.io/1234567", - @"devServerUrl" : @"http://localhost:8081" + NSDictionary *_Nonnull mockedDictionary = @{ // dsn is always validated in SentryOptions initialization + @"dsn" : @"https://abc@def.ingest.sentry.io/1234567" }; SentryOptions *options = [RNSentryStart createOptionsWithDictionary:mockedDictionary error:&error]; - SentryBreadcrumb *breadcrumb = [[SentryBreadcrumb alloc] init]; breadcrumb.type = @"http"; - breadcrumb.data = @{ @"url" : @"https://def.ingest.sentry.io/1234567" }; + breadcrumb.data = @{ @"url" : @"http://testurl.com/service" }; SentryBreadcrumb *result = options.beforeBreadcrumb(breadcrumb); - XCTAssertNil(result, @"Breadcrumb should be filtered out"); + XCTAssertEqual(breadcrumb, result); } -- (void)testStartBeforeBreadcrumbsCallbackFiltersOutDevServerRequestBreadcrumbs +#pragma mark - RNSentryStart ignoreErrors Tests + +- (void)testStartIgnoreErrorsDropsMatchingExceptionValue { + RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; - - NSString *mockDevServer = @"http://localhost:8081"; - - NSDictionary *_Nonnull mockedDictionary = - @{ @"dsn" : @"https://abc@def.ingest.sentry.io/1234567", @"devServerUrl" : mockDevServer }; - SentryOptions *options = [RNSentryStart createOptionsWithDictionary:mockedDictionary - error:&error]; - - SentryBreadcrumb *breadcrumb = [[SentryBreadcrumb alloc] init]; - breadcrumb.type = @"http"; - breadcrumb.data = @{ @"url" : mockDevServer }; - - SentryBreadcrumb *result = options.beforeBreadcrumb(breadcrumb); - - XCTAssertNil(result, @"Breadcrumb should be filtered out"); + NSMutableDictionary *mockedOptions = [@{ + @"dsn" : @"https://abc@def.ingest.sentry.io/1234567", + @"ignoreErrorsRegex" : @[ @"IgnoreMe.*" ] + } mutableCopy]; + mockedOptions = [rnSentry prepareOptions:mockedOptions]; + SentryOptions *options = [RNSentryStart createOptionsWithDictionary:mockedOptions error:&error]; + XCTAssertNotNil(options); + XCTAssertNil(error); + SentryEvent *event = [[SentryEvent alloc] init]; + SentryException *exception = [SentryException alloc]; + exception.value = @"IgnoreMe: This should be ignored"; + event.exceptions = @[ exception ]; + SentryEvent *result = options.beforeSend(event); + XCTAssertNil(result, @"Event with matching exception.value should be dropped"); } -- (void)testStartBeforeBreadcrumbsCallbackDoesNotFiltersOutNonDevServerOrDsnRequestBreadcrumbs +- (void)testStartIgnoreErrorsDropsMatchingEventMessage { + RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; - - NSDictionary *_Nonnull mockedDictionary = @{ + NSMutableDictionary *mockedOptions = [@{ @"dsn" : @"https://abc@def.ingest.sentry.io/1234567", - @"devServerUrl" : @"http://localhost:8081" - }; - SentryOptions *options = [RNSentryStart createOptionsWithDictionary:mockedDictionary - error:&error]; - - SentryBreadcrumb *breadcrumb = [[SentryBreadcrumb alloc] init]; - breadcrumb.type = @"http"; - breadcrumb.data = @{ @"url" : @"http://testurl.com/service" }; + @"ignoreErrorsStr" : @[ @"DropThisError" ] + } mutableCopy]; + mockedOptions = [rnSentry prepareOptions:mockedOptions]; + SentryOptions *options = [RNSentryStart createOptionsWithDictionary:mockedOptions error:&error]; + XCTAssertNotNil(options); + XCTAssertNil(error); + SentryEvent *event = [[SentryEvent alloc] init]; + SentryMessage *msg = [SentryMessage alloc]; + msg.message = @"DropThisError: should be dropped"; + event.message = msg; + SentryEvent *result = options.beforeSend(event); + XCTAssertNil(result, @"Event with matching event.message.formatted should be dropped"); +} - SentryBreadcrumb *result = options.beforeBreadcrumb(breadcrumb); +- (void)testStartIgnoreErrorsDoesNotDropNonMatchingEvent +{ + RNSentry *rnSentry = [[RNSentry alloc] init]; + NSError *error = nil; + NSMutableDictionary *mockedOptions = [@{ + @"dsn" : @"https://abc@def.ingest.sentry.io/1234567", + @"ignoreErrorsRegex" : @[ @"IgnoreMe.*" ] + } mutableCopy]; + mockedOptions = [rnSentry prepareOptions:mockedOptions]; + SentryOptions *options = [RNSentryStart createOptionsWithDictionary:mockedOptions error:&error]; + XCTAssertNotNil(options); + XCTAssertNil(error); + SentryEvent *event = [[SentryEvent alloc] init]; + SentryException *exception = [SentryException alloc]; + exception.value = @"SomeOtherError: should not be ignored"; + event.exceptions = @[ exception ]; + SentryMessage *msg = [SentryMessage alloc]; + msg.message = @"SomeOtherMessage"; + event.message = msg; + SentryEvent *result = options.beforeSend(event); + XCTAssertNotNil(result, @"Event with non-matching error should not be dropped"); +} - XCTAssertEqual(breadcrumb, result); +- (void)testStartIgnoreErrorsDropsMatchingExactString +{ + RNSentry *rnSentry = [[RNSentry alloc] init]; + NSError *error = nil; + NSMutableDictionary *mockedOptions = [@{ + @"dsn" : @"https://abc@def.ingest.sentry.io/1234567", + @"ignoreErrorsStr" : @[ @"ExactError" ] + } mutableCopy]; + mockedOptions = [rnSentry prepareOptions:mockedOptions]; + SentryOptions *options = [RNSentryStart createOptionsWithDictionary:mockedOptions error:&error]; + XCTAssertNotNil(options); + XCTAssertNil(error); + SentryEvent *event = [[SentryEvent alloc] init]; + SentryMessage *msg = [SentryMessage alloc]; + msg.message = @"ExactError"; + event.message = msg; + SentryEvent *result = options.beforeSend(event); + XCTAssertNil(result, @"Event with exactly matching string should be dropped"); } -- (void) - testStartBeforeBreadcrumbsCallbackKeepsBreadcrumbWhenDevServerUrlIsNotPassedAndDsnDoesNotMatch +- (void)testStartIgnoreErrorsRegexAndStringBothWork { + RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; + NSMutableDictionary *mockedOptions = [@{ + @"dsn" : @"https://abc@def.ingest.sentry.io/1234567", + @"ignoreErrorsStr" : @[ @"ExactError" ], + @"ignoreErrorsRegex" : @[ @"IgnoreMe.*" ], - NSDictionary *_Nonnull mockedDictionary = @{ // dsn is always validated in SentryOptions initialization - @"dsn" : @"https://abc@def.ingest.sentry.io/1234567" - }; - SentryOptions *options = [RNSentryStart createOptionsWithDictionary:mockedDictionary - error:&error]; + } mutableCopy]; + mockedOptions = [rnSentry prepareOptions:mockedOptions]; + SentryOptions *options = [RNSentryStart createOptionsWithDictionary:mockedOptions error:&error]; + XCTAssertNotNil(options); + XCTAssertNil(error); + SentryEvent *event1 = [[SentryEvent alloc] init]; + SentryException *exception = [SentryException alloc]; + exception.value = @"IgnoreMe: This should be ignored"; + event1.exceptions = @[ exception ]; + SentryEvent *result1 = options.beforeSend(event1); + XCTAssertNil(result1, @"Event with matching regex should be dropped"); + SentryEvent *event2 = [[SentryEvent alloc] init]; + SentryMessage *msg = [SentryMessage alloc]; + msg.message = @"ExactError"; + event2.message = msg; + SentryEvent *result2 = options.beforeSend(event2); + XCTAssertNil(result2, @"Event with exactly matching string should be dropped"); + SentryEvent *event3 = [[SentryEvent alloc] init]; + SentryMessage *msg3 = [SentryMessage alloc]; + msg3.message = @"OtherError"; + event3.message = msg3; + SentryEvent *result3 = options.beforeSend(event3); + XCTAssertNotNil(result3, @"Event with non-matching error should not be dropped"); +} - SentryBreadcrumb *breadcrumb = [[SentryBreadcrumb alloc] init]; - breadcrumb.type = @"http"; - breadcrumb.data = @{ @"url" : @"http://testurl.com/service" }; +#pragma mark - RNSentryStart beforeSend duplicate filtering - SentryBreadcrumb *result = options.beforeBreadcrumb(breadcrumb); +- (void)testStartBeforeSendFiltersOutUnhandledJSException +{ + RNSentry *rnSentry = [[RNSentry alloc] init]; + NSError *error = nil; + NSMutableDictionary *mockedOptions = [@{ + @"dsn" : @"https://abc@def.ingest.sentry.io/1234567", + } mutableCopy]; + mockedOptions = [rnSentry prepareOptions:mockedOptions]; + SentryOptions *options = [RNSentryStart createOptionsWithDictionary:mockedOptions error:&error]; + [RNSentryStart updateWithReactFinals:options]; + XCTAssertNotNil(options); + XCTAssertNil(error); - XCTAssertEqual(breadcrumb, result); + SentryEvent *event = [[SentryEvent alloc] init]; + SentryException *exception = [SentryException alloc]; + exception.type = @"Unhandled JS Exception"; + exception.value = @"Error: Test error"; + event.exceptions = @[ exception ]; + SentryEvent *result = options.beforeSend(event); + XCTAssertNil(result, @"Event with Unhandled JS Exception should be dropped"); } -#if SENTRY_TARGET_PROFILING_SUPPORTED -// Regression test for the v8.0.0 bug where the init path (RNSentryStart) did not -// handle `_experiments.profilingOptions`, silently dropping iOS UI profiling config. -// This pins the full entry point used by `initNativeSdk` in RNSentry.mm. -- (void)testStartWithDictionaryInstallsConfigureProfilingFromExperimentsProfilingOptions +- (void)testStartBeforeSendFiltersOutJSErrorCppException { + RNSentry *rnSentry = [[RNSentry alloc] init]; NSError *error = nil; + NSMutableDictionary *mockedOptions = [@{ + @"dsn" : @"https://abc@def.ingest.sentry.io/1234567", + } mutableCopy]; + mockedOptions = [rnSentry prepareOptions:mockedOptions]; + SentryOptions *options = [RNSentryStart createOptionsWithDictionary:mockedOptions error:&error]; + [RNSentryStart updateWithReactFinals:options]; + XCTAssertNotNil(options); + XCTAssertNil(error); - NSDictionary *_Nonnull mockedReactNativeDictionary = @{ - @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"_experiments" : @ { - @"profilingOptions" : @ { - @"profileSessionSampleRate" : @1.0, - @"lifecycle" : @"trace", - @"startOnAppStart" : @YES, - }, - }, - }; - [RNSentryStart startWithOptions:mockedReactNativeDictionary error:&error]; - SentryOptions *actualOptions = PrivateSentrySDKOnly.options; + SentryEvent *event1 = [[SentryEvent alloc] init]; + SentryException *exception1 = [SentryException alloc]; + exception1.type = @"C++ Exception"; + exception1.value = @"N8facebook3jsi7JSErrorE: ExceptionsManager.reportException raised an " + @"exception: Unhandled JS Exception: Error: Test error"; + event1.exceptions = @[ exception1 ]; + SentryEvent *result1 = options.beforeSend(event1); + XCTAssertNil( + result1, @"Event with ExceptionsManager.reportException in value should be dropped"); - XCTAssertNotNil(actualOptions, @"Did not create sentry options"); - XCTAssertNil(error, @"Should not pass no error"); - XCTAssertNotNil(actualOptions.configureProfiling, - @"configureProfiling must be installed after startWithOptions when profilingOptions is " - @"present"); + SentryEvent *event2 = [[SentryEvent alloc] init]; + SentryException *exception2 = [SentryException alloc]; + exception2.type = @"SomeOtherException"; + exception2.value = @"ExceptionsManager.reportException raised an exception: Unhandled JS " + @"Exception: Error: Test"; + event2.exceptions = @[ exception2 ]; + SentryEvent *result2 = options.beforeSend(event2); + XCTAssertNil( + result2, @"Event with ExceptionsManager.reportException in value should be dropped"); - SentryProfileOptions *probe = [[SentryProfileOptions alloc] init]; - actualOptions.configureProfiling(probe); - XCTAssertEqual(probe.sessionSampleRate, 1.0f); - XCTAssertEqual(probe.lifecycle, SentryProfileLifecycleTrace); - XCTAssertTrue(probe.profileAppStarts); + SentryEvent *event3 = [[SentryEvent alloc] init]; + SentryException *exception3 = [SentryException alloc]; + exception3.type = @"C++ Exception"; + exception3.value = @"std::runtime_error: Some other C++ error occurred"; + event3.exceptions = @[ exception3 ]; + SentryEvent *result3 = options.beforeSend(event3); + XCTAssertNotNil(result3, + @"Legitimate C++ exception without ExceptionsManager.reportException should not be " + @"dropped"); } -- (void)testStartCreateOptionsWithDictionaryProfilingOptionsInstallsConfigureProfiling +#pragma mark - RNSentryStart enableSessionReplayInUnreliableEnvironment Tests + +- (void)testStartCreateOptionsWithDictionaryEnableSessionReplayInUnreliableEnvironmentDefault { NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"_experiments" : @ { - @"profilingOptions" : @ { - @"profileSessionSampleRate" : @1.0, - @"lifecycle" : @"trace", - @"startOnAppStart" : @YES, - }, - }, }; SentryOptions *actualOptions = [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; XCTAssertNotNil(actualOptions, @"Did not create sentry options"); XCTAssertNil(error, @"Should not pass no error"); - XCTAssertNotNil(actualOptions.configureProfiling, - @"configureProfiling callback should be installed when profilingOptions is present"); - SentryProfileOptions *probe = [[SentryProfileOptions alloc] init]; - actualOptions.configureProfiling(probe); - XCTAssertEqual(probe.sessionSampleRate, 1.0f); - XCTAssertEqual(probe.lifecycle, SentryProfileLifecycleTrace); - XCTAssertTrue(probe.profileAppStarts); + id experimentalOptions = [actualOptions valueForKey:@"experimental"]; + XCTAssertNotNil(experimentalOptions, @"Experimental options should not be nil"); + + BOOL enableSessionReplayInUnreliableEnvironment = + [[experimentalOptions valueForKey:@"enableSessionReplayInUnreliableEnvironment"] boolValue]; + XCTAssertFalse(enableSessionReplayInUnreliableEnvironment, + @"enableSessionReplayInUnreliableEnvironment should be disabled"); } -- (void)testStartCreateOptionsWithDictionaryProfilingOptionsMissingDoesNotInstallConfigureProfiling +- (void) + testStartCreateOptionsWithDictionaryEnableSessionReplayInUnreliableEnvironmentWithErrorSampleRate { NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", + @"replaysOnErrorSampleRate" : @1.0, + @"replaysSessionSampleRate" : @0 }; SentryOptions *actualOptions = [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; XCTAssertNotNil(actualOptions, @"Did not create sentry options"); XCTAssertNil(error, @"Should not pass no error"); - XCTAssertNil(actualOptions.configureProfiling, - @"configureProfiling callback should not be installed without profilingOptions"); -} - -- (void)testStartCreateOptionsWithDictionaryEmptyExperimentsDoesNotInstallConfigureProfiling -{ - NSError *error = nil; - NSDictionary *_Nonnull mockedReactNativeDictionary = @{ - @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"_experiments" : @ { }, - }; - SentryOptions *actualOptions = - [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; + id experimentalOptions = [actualOptions valueForKey:@"experimental"]; + XCTAssertNotNil(experimentalOptions, @"Experimental options should not be nil"); - XCTAssertNotNil(actualOptions, @"Did not create sentry options"); - XCTAssertNil(error, @"Should not pass no error"); - XCTAssertNil(actualOptions.configureProfiling, - @"configureProfiling callback should not be installed when profilingOptions is absent"); + BOOL enableSessionReplayInUnreliableEnvironment = + [[experimentalOptions valueForKey:@"enableSessionReplayInUnreliableEnvironment"] boolValue]; + XCTAssertTrue(enableSessionReplayInUnreliableEnvironment, + @"enableSessionReplayInUnreliableEnvironment should be enabled"); } -#endif -- (void)testStartCreateOptionsWithDictionaryEnableUnhandledCPPExceptionsV2Enabled +- (void) + testStartCreateOptionsWithDictionaryEnableSessionReplayInUnreliableEnvironmentWithSessionSampleRate { NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"_experiments" : @ { - @"enableUnhandledCPPExceptionsV2" : @YES, - }, + @"replaysOnErrorSampleRate" : @0.0, + @"replaysSessionSampleRate" : @0.1 }; SentryOptions *actualOptions = [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; - XCTAssertNotNil(actualOptions, @"Did not create sentry options"); XCTAssertNil(error, @"Should not pass no error"); - XCTAssertTrue(actualOptions.experimental.enableUnhandledCPPExceptionsV2, - @"enableUnhandledCPPExceptionsV2 should be enabled"); + + id experimentalOptions = [actualOptions valueForKey:@"experimental"]; + XCTAssertNotNil(experimentalOptions, @"Experimental options should not be nil"); + + BOOL enableSessionReplayInUnreliableEnvironment = + [[experimentalOptions valueForKey:@"enableSessionReplayInUnreliableEnvironment"] boolValue]; + XCTAssertTrue(enableSessionReplayInUnreliableEnvironment, + @"enableSessionReplayInUnreliableEnvironment should be enabled"); } -- (void)testStartCreateOptionsWithDictionaryEnableUnhandledCPPExceptionsV2Disabled +- (void) + testStartCreateOptionsWithDictionaryEnableSessionReplayInUnreliableEnvironmentWithSessionSampleRates { NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", - @"_experiments" : @ { - @"enableUnhandledCPPExceptionsV2" : @NO, - }, + @"replaysOnErrorSampleRate" : @1.0, + @"replaysSessionSampleRate" : @0.1 }; SentryOptions *actualOptions = [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; XCTAssertNotNil(actualOptions, @"Did not create sentry options"); XCTAssertNil(error, @"Should not pass no error"); - XCTAssertFalse(actualOptions.experimental.enableUnhandledCPPExceptionsV2, - @"enableUnhandledCPPExceptionsV2 should be disabled"); + + id experimentalOptions = [actualOptions valueForKey:@"experimental"]; + XCTAssertNotNil(experimentalOptions, @"Experimental options should not be nil"); + + BOOL enableSessionReplayInUnreliableEnvironment = + [[experimentalOptions valueForKey:@"enableSessionReplayInUnreliableEnvironment"] boolValue]; + XCTAssertTrue(enableSessionReplayInUnreliableEnvironment, + @"enableSessionReplayInUnreliableEnvironment should be enabled"); } -- (void)testStartCreateOptionsWithDictionaryEnableUnhandledCPPExceptionsV2Default +- (void)testStartCreateOptionsWithDictionaryEnableSessionReplayInUnreliableEnvironmentDisabled { NSError *error = nil; NSDictionary *_Nonnull mockedReactNativeDictionary = @{ @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", + @"replaysOnErrorSampleRate" : @0, + @"replaysSessionSampleRate" : @0 }; SentryOptions *actualOptions = [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; XCTAssertNotNil(actualOptions, @"Did not create sentry options"); XCTAssertNil(error, @"Should not pass no error"); - XCTAssertFalse(actualOptions.experimental.enableUnhandledCPPExceptionsV2, - @"enableUnhandledCPPExceptionsV2 should default to disabled"); -} - -- (void)testStartEventFromSentryCocoaReactNativeHasOriginAndEnvironmentTags -{ - SentryEvent *testEvent = [[SentryEvent alloc] init]; - testEvent.sdk = @{ - @"name" : @"sentry.cocoa.react-native", - }; - - [RNSentryStart setEventOriginTag:testEvent]; - - XCTAssertEqual(testEvent.tags[@"event.origin"], @"ios"); - XCTAssertEqual(testEvent.tags[@"event.environment"], @"native"); -} - -- (void)testStartEventFromSentryReactNativeOriginAndEnvironmentTagsAreOverwritten -{ - SentryEvent *testEvent = [[SentryEvent alloc] init]; - testEvent.sdk = @{ - @"name" : @"sentry.cocoa.react-native", - }; - testEvent.tags = @{ - @"event.origin" : @"testEventOriginTag", - @"event.environment" : @"testEventEnvironmentTag", - }; - - [RNSentryStart setEventOriginTag:testEvent]; - - XCTAssertEqual(testEvent.tags[@"event.origin"], @"ios"); - XCTAssertEqual(testEvent.tags[@"event.environment"], @"native"); -} - -#pragma mark - captureReplayWithReturnValue Tests -#if SENTRY_TARGET_REPLAY_SUPPORTED -- (void)testCaptureReplayWithReturnValueReturnsFalseWhenReplayNotRunning -{ - // Without starting the SDK with replay enabled, - // the replay integration should not be available - // and captureReplayWithReturnValue should return NO - BOOL result = [RNSentry captureReplayWithReturnValue]; - XCTAssertFalse(result, - @"captureReplayWithReturnValue should return NO when replay integration is not available"); -} + id experimentalOptions = [actualOptions valueForKey:@"experimental"]; + XCTAssertNotNil(experimentalOptions, @"Experimental options should not be nil"); -- (void)testCaptureReplayWithReturnValueReturnsFalseWithoutInit -{ - // When the SDK is not initialized at all, there should be no replay integration - // and captureReplayWithReturnValue should return NO without crashing - BOOL result = [RNSentry captureReplayWithReturnValue]; - XCTAssertFalse(result, - @"captureReplayWithReturnValue should return NO without crashing when SDK is not " - @"initialized"); + BOOL enableSessionReplayInUnreliableEnvironment = + [[experimentalOptions valueForKey:@"enableSessionReplayInUnreliableEnvironment"] boolValue]; + XCTAssertFalse(enableSessionReplayInUnreliableEnvironment, + @"enableSessionReplayInUnreliableEnvironment should be disabled"); } -#endif @end diff --git a/packages/core/ios/SentrySDKWrapper.h b/packages/core/ios/SentrySDKWrapper.h index ce6463d77f..0126e47b64 100644 --- a/packages/core/ios/SentrySDKWrapper.h +++ b/packages/core/ios/SentrySDKWrapper.h @@ -1,6 +1,5 @@ #import -@class SentryOptions; @class SentryScope; @interface SentrySDKWrapper : NSObject @@ -13,22 +12,8 @@ + (BOOL)crashedLastRun; -+ (void)startWithOptions:(SentryOptions *)options; - -+ (SentryOptions *)createOptionsWithDictionary:(NSDictionary *)options - isSessionReplayEnabled:(BOOL)isSessionReplayEnabled - error:(NSError **)errorPointer; - -+ (void)setupWithDictionary:(NSDictionary *)options - isSessionReplayEnabled:(BOOL)isSessionReplayEnabled - error:(NSError **)errorPointer; - + (BOOL)debug; + (NSString *)releaseName; -+ (BOOL)enableAutoSessionTracking; - -+ (BOOL)enableWatchdogTerminationTracking; - @end diff --git a/packages/core/ios/SentrySDKWrapper.m b/packages/core/ios/SentrySDKWrapper.m index 38647527a0..57458d291f 100644 --- a/packages/core/ios/SentrySDKWrapper.m +++ b/packages/core/ios/SentrySDKWrapper.m @@ -1,15 +1,8 @@ #import "SentrySDKWrapper.h" -#import "RNSentryExperimentalOptions.h" -#import "RNSentryVersion.h" @import Sentry; @implementation SentrySDKWrapper -+ (void)startWithOptions:(SentryOptions *)options -{ - [SentrySDK startWithOptions:options]; -} - + (void)crash { [SentrySDK crash]; @@ -30,126 +23,6 @@ + (void)configureScope:(void (^)(SentryScope *scope))callback [SentrySDK configureScope:callback]; } -+ (SentryOptions *)createOptionsWithDictionary:(NSDictionary *)options - isSessionReplayEnabled:(BOOL)isSessionReplayEnabled - error:(NSError *__autoreleasing *)errorPointer -{ - NSString *dsn = [self getURLFromDSN:[options valueForKey:@"dsn"]]; - SentryOptions *sentryOptions = [PrivateSentrySDKOnly optionsWithDictionary:options - didFailWithError:errorPointer]; - if (*errorPointer != nil) { - return nil; - } - - // Exclude Dev Server and Sentry Dsn request from Breadcrumbs - NSString *devServerUrl = [options valueForKey:@"devServerUrl"]; - sentryOptions.beforeBreadcrumb - = ^SentryBreadcrumb *_Nullable(SentryBreadcrumb *_Nonnull breadcrumb) - { - NSString *url = breadcrumb.data[@"url"] ?: @""; - - if ([@"http" isEqualToString:breadcrumb.type] - && ((dsn != nil && [url hasPrefix:dsn]) - || (devServerUrl != nil && [url hasPrefix:devServerUrl]))) { - return nil; - } - return breadcrumb; - }; - - if ([options valueForKey:@"enableNativeCrashHandling"] != nil) { - BOOL enableNativeCrashHandling = [options[@"enableNativeCrashHandling"] boolValue]; - - if (!enableNativeCrashHandling) { - sentryOptions.enableCrashHandler = NO; - } - } - - // Set spotlight option - if ([options valueForKey:@"spotlight"] != nil) { - id spotlightValue = [options valueForKey:@"spotlight"]; - if ([spotlightValue isKindOfClass:[NSString class]]) { - NSLog(@"Using Spotlight on address: %@", spotlightValue); - sentryOptions.enableSpotlight = true; - sentryOptions.spotlightUrl = spotlightValue; - } else if ([spotlightValue isKindOfClass:[NSNumber class]]) { - sentryOptions.enableSpotlight = [spotlightValue boolValue]; - id defaultSpotlightUrl = [options valueForKey:@"defaultSidecarUrl"]; - if (defaultSpotlightUrl != nil) { - sentryOptions.spotlightUrl = defaultSpotlightUrl; - } - } - } - - if ([options valueForKey:@"enableLogs"] != nil) { - id enableLogsValue = [options valueForKey:@"enableLogs"]; - if ([enableLogsValue isKindOfClass:[NSNumber class]]) { - [RNSentryExperimentalOptions setEnableLogs:[enableLogsValue boolValue] - sentryOptions:sentryOptions]; - } - } - - // Enable the App start and Frames tracking measurements - if ([options valueForKey:@"enableAutoPerformanceTracing"] != nil) { - BOOL enableAutoPerformanceTracing = [options[@"enableAutoPerformanceTracing"] boolValue]; - PrivateSentrySDKOnly.appStartMeasurementHybridSDKMode = enableAutoPerformanceTracing; -#if TARGET_OS_IPHONE || TARGET_OS_MACCATALYST - PrivateSentrySDKOnly.framesTrackingMeasurementHybridSDKMode = enableAutoPerformanceTracing; -#endif - } - - // Failed requests can only be enabled in one SDK to avoid duplicates - sentryOptions.enableCaptureFailedRequests = NO; - - NSDictionary *experiments = options[@"_experiments"]; - if (experiments != nil && [experiments isKindOfClass:[NSDictionary class]]) { - BOOL enableUnhandledCPPExceptions = - [experiments[@"enableUnhandledCPPExceptionsV2"] boolValue]; - [RNSentryExperimentalOptions setEnableUnhandledCPPExceptionsV2:enableUnhandledCPPExceptions - sentryOptions:sentryOptions]; - - // Configure iOS UI Profiling - NSDictionary *profilingOptions = experiments[@"profilingOptions"]; - if (profilingOptions != nil && [profilingOptions isKindOfClass:[NSDictionary class]]) { - [RNSentryExperimentalOptions configureProfilingWithOptions:profilingOptions - sentryOptions:sentryOptions]; - } - } - - if (isSessionReplayEnabled) { - [RNSentryExperimentalOptions setEnableSessionReplayInUnreliableEnvironment:YES - sentryOptions:sentryOptions]; - } - return sentryOptions; -} - -+ (NSString *_Nullable)getURLFromDSN:(NSString *)dsn -{ - NSURL *url = [NSURL URLWithString:dsn]; - if (!url) { - return nil; - } - return [NSString stringWithFormat:@"%@://%@", url.scheme, url.host]; -} - -+ (void)setupWithDictionary:(NSDictionary *_Nonnull)options - isSessionReplayEnabled:(BOOL)isSessionReplayEnabled - error:(NSError *_Nonnull *_Nonnull)errorPointer -{ - SentryOptions *sentryOptions = [self createOptionsWithDictionary:options - isSessionReplayEnabled:isSessionReplayEnabled - error:errorPointer]; - if (!options) { - return; - } - - NSString *sdkVersion = [PrivateSentrySDKOnly getSdkVersionString]; - [PrivateSentrySDKOnly setSdkName:NATIVE_SDK_NAME andVersionString:sdkVersion]; - [PrivateSentrySDKOnly addSdkPackage:REACT_NATIVE_SDK_PACKAGE_NAME - version:REACT_NATIVE_SDK_PACKAGE_VERSION]; - - [SentrySDKWrapper startWithOptions:sentryOptions]; -} - + (BOOL)debug { return PrivateSentrySDKOnly.options.debug; @@ -160,14 +33,4 @@ + (NSString *)releaseName return PrivateSentrySDKOnly.options.releaseName; } -+ (BOOL)enableAutoSessionTracking -{ - return PrivateSentrySDKOnly.options.enableAutoSessionTracking; -} - -+ (BOOL)enableWatchdogTerminationTracking -{ - return PrivateSentrySDKOnly.options.enableWatchdogTerminationTracking; -} - @end From c0a7243d253ffdac75753248f5e7201ae415cc90 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Tue, 28 Apr 2026 14:58:17 +0200 Subject: [PATCH 2/3] =?UTF-8?q?fix(test):=20drop=20duplicate=20testStartBe?= =?UTF-8?q?foreBreadcrumbs=E2=80=A6=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The migration in the previous commit added a `testStart…KeepsBreadcrumbWhenDevServerUrlIsNotPassed…` method that already existed in the `RNSentryStart Tests` block. Compile failed in CI with "Duplicate declaration of method". The earlier `@interface RNSentryStart Tests` already covers this scenario; the migrated copy is redundant. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../RNSentryCocoaTesterTests/RNSentryTests.m | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.m b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.m index 25991c4a0b..9c5de19cd0 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.m +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.m @@ -819,27 +819,6 @@ - (void)testStartCreateOptionsWithDictionaryEnableLogsDisabled XCTAssertFalse(enableLogs, @"enableLogs should be disabled"); } -#pragma mark - RNSentryStart beforeBreadcrumb edge case - -- (void) - testStartBeforeBreadcrumbsCallbackKeepsBreadcrumbWhenDevServerUrlIsNotPassedAndDsnDoesNotMatch -{ - NSError *error = nil; - - NSDictionary *_Nonnull mockedDictionary = @{ // dsn is always validated in SentryOptions initialization - @"dsn" : @"https://abc@def.ingest.sentry.io/1234567" - }; - SentryOptions *options = [RNSentryStart createOptionsWithDictionary:mockedDictionary - error:&error]; - SentryBreadcrumb *breadcrumb = [[SentryBreadcrumb alloc] init]; - breadcrumb.type = @"http"; - breadcrumb.data = @{ @"url" : @"http://testurl.com/service" }; - - SentryBreadcrumb *result = options.beforeBreadcrumb(breadcrumb); - - XCTAssertEqual(breadcrumb, result); -} - #pragma mark - RNSentryStart ignoreErrors Tests - (void)testStartIgnoreErrorsDropsMatchingExceptionValue From c8717edb929289f5ff52285e4e3898e4a6886c65 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Tue, 28 Apr 2026 15:08:39 +0200 Subject: [PATCH 3/3] fix(test): use designated initializers for SentryException and SentryMessage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Warden flagged the migrated tests using `[SentryException alloc]` and `[SentryMessage alloc]` without `init`. Both classes have `SENTRY_NO_INIT` and require designated initializers (`initWithValue:type:` and `initWithFormatted:`), so the alloc-only pattern is undefined behavior — property assignments happened to work via zero-initialized memory but the test could crash or behave inconsistently and mask real regressions. The pattern was inherited from the original SentrySDKWrapper-based tests this PR migrated; same fix applied across all 12 occurrences. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../RNSentryCocoaTesterTests/RNSentryTests.m | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.m b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.m index 9c5de19cd0..8065b78c15 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.m +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.m @@ -834,8 +834,8 @@ - (void)testStartIgnoreErrorsDropsMatchingExceptionValue XCTAssertNotNil(options); XCTAssertNil(error); SentryEvent *event = [[SentryEvent alloc] init]; - SentryException *exception = [SentryException alloc]; - exception.value = @"IgnoreMe: This should be ignored"; + SentryException *exception = + [[SentryException alloc] initWithValue:@"IgnoreMe: This should be ignored" type:nil]; event.exceptions = @[ exception ]; SentryEvent *result = options.beforeSend(event); XCTAssertNil(result, @"Event with matching exception.value should be dropped"); @@ -854,7 +854,8 @@ - (void)testStartIgnoreErrorsDropsMatchingEventMessage XCTAssertNotNil(options); XCTAssertNil(error); SentryEvent *event = [[SentryEvent alloc] init]; - SentryMessage *msg = [SentryMessage alloc]; + SentryMessage *msg = + [[SentryMessage alloc] initWithFormatted:@"DropThisError: should be dropped"]; msg.message = @"DropThisError: should be dropped"; event.message = msg; SentryEvent *result = options.beforeSend(event); @@ -874,10 +875,10 @@ - (void)testStartIgnoreErrorsDoesNotDropNonMatchingEvent XCTAssertNotNil(options); XCTAssertNil(error); SentryEvent *event = [[SentryEvent alloc] init]; - SentryException *exception = [SentryException alloc]; - exception.value = @"SomeOtherError: should not be ignored"; + SentryException *exception = + [[SentryException alloc] initWithValue:@"SomeOtherError: should not be ignored" type:nil]; event.exceptions = @[ exception ]; - SentryMessage *msg = [SentryMessage alloc]; + SentryMessage *msg = [[SentryMessage alloc] initWithFormatted:@"SomeOtherMessage"]; msg.message = @"SomeOtherMessage"; event.message = msg; SentryEvent *result = options.beforeSend(event); @@ -897,7 +898,7 @@ - (void)testStartIgnoreErrorsDropsMatchingExactString XCTAssertNotNil(options); XCTAssertNil(error); SentryEvent *event = [[SentryEvent alloc] init]; - SentryMessage *msg = [SentryMessage alloc]; + SentryMessage *msg = [[SentryMessage alloc] initWithFormatted:@"ExactError"]; msg.message = @"ExactError"; event.message = msg; SentryEvent *result = options.beforeSend(event); @@ -919,19 +920,19 @@ - (void)testStartIgnoreErrorsRegexAndStringBothWork XCTAssertNotNil(options); XCTAssertNil(error); SentryEvent *event1 = [[SentryEvent alloc] init]; - SentryException *exception = [SentryException alloc]; - exception.value = @"IgnoreMe: This should be ignored"; + SentryException *exception = + [[SentryException alloc] initWithValue:@"IgnoreMe: This should be ignored" type:nil]; event1.exceptions = @[ exception ]; SentryEvent *result1 = options.beforeSend(event1); XCTAssertNil(result1, @"Event with matching regex should be dropped"); SentryEvent *event2 = [[SentryEvent alloc] init]; - SentryMessage *msg = [SentryMessage alloc]; + SentryMessage *msg = [[SentryMessage alloc] initWithFormatted:@"ExactError"]; msg.message = @"ExactError"; event2.message = msg; SentryEvent *result2 = options.beforeSend(event2); XCTAssertNil(result2, @"Event with exactly matching string should be dropped"); SentryEvent *event3 = [[SentryEvent alloc] init]; - SentryMessage *msg3 = [SentryMessage alloc]; + SentryMessage *msg3 = [[SentryMessage alloc] initWithFormatted:@"OtherError"]; msg3.message = @"OtherError"; event3.message = msg3; SentryEvent *result3 = options.beforeSend(event3); @@ -954,9 +955,8 @@ - (void)testStartBeforeSendFiltersOutUnhandledJSException XCTAssertNil(error); SentryEvent *event = [[SentryEvent alloc] init]; - SentryException *exception = [SentryException alloc]; - exception.type = @"Unhandled JS Exception"; - exception.value = @"Error: Test error"; + SentryException *exception = [[SentryException alloc] initWithValue:@"Error: Test error" + type:@"Unhandled JS Exception"]; event.exceptions = @[ exception ]; SentryEvent *result = options.beforeSend(event); XCTAssertNil(result, @"Event with Unhandled JS Exception should be dropped"); @@ -976,29 +976,29 @@ - (void)testStartBeforeSendFiltersOutJSErrorCppException XCTAssertNil(error); SentryEvent *event1 = [[SentryEvent alloc] init]; - SentryException *exception1 = [SentryException alloc]; - exception1.type = @"C++ Exception"; - exception1.value = @"N8facebook3jsi7JSErrorE: ExceptionsManager.reportException raised an " - @"exception: Unhandled JS Exception: Error: Test error"; + SentryException *exception1 = [[SentryException alloc] + initWithValue:@"N8facebook3jsi7JSErrorE: ExceptionsManager.reportException raised an " + @"exception: Unhandled JS Exception: Error: Test error" + type:@"C++ Exception"]; event1.exceptions = @[ exception1 ]; SentryEvent *result1 = options.beforeSend(event1); XCTAssertNil( result1, @"Event with ExceptionsManager.reportException in value should be dropped"); SentryEvent *event2 = [[SentryEvent alloc] init]; - SentryException *exception2 = [SentryException alloc]; - exception2.type = @"SomeOtherException"; - exception2.value = @"ExceptionsManager.reportException raised an exception: Unhandled JS " - @"Exception: Error: Test"; + SentryException *exception2 = [[SentryException alloc] + initWithValue:@"ExceptionsManager.reportException raised an exception: Unhandled JS " + @"Exception: Error: Test" + type:@"SomeOtherException"]; event2.exceptions = @[ exception2 ]; SentryEvent *result2 = options.beforeSend(event2); XCTAssertNil( result2, @"Event with ExceptionsManager.reportException in value should be dropped"); SentryEvent *event3 = [[SentryEvent alloc] init]; - SentryException *exception3 = [SentryException alloc]; - exception3.type = @"C++ Exception"; - exception3.value = @"std::runtime_error: Some other C++ error occurred"; + SentryException *exception3 = + [[SentryException alloc] initWithValue:@"std::runtime_error: Some other C++ error occurred" + type:@"C++ Exception"]; event3.exceptions = @[ exception3 ]; SentryEvent *result3 = options.beforeSend(event3); XCTAssertNotNil(result3,