Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

### Features

- Add `strictTraceContinuation` and `orgId` options for trace continuation validation ([#5828](https://github.com/getsentry/sentry-react-native/pull/5828))
- Support `SENTRY_ENVIRONMENT` in bare React Native builds ([#5823](https://github.com/getsentry/sentry-react-native/pull/5823))

### Fixes
Expand Down
28 changes: 28 additions & 0 deletions packages/core/test/sdk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,34 @@ describe('Tests the SDK functionality', () => {
});
});

describe('strictTraceContinuation', () => {
it('passes strictTraceContinuation option through to client options', () => {
init({
strictTraceContinuation: true,
});
expect(usedOptions()?.strictTraceContinuation).toBe(true);
});

it('passes orgId option through to client options', () => {
init({
orgId: '12345',
});
expect(usedOptions()?.orgId).toBe('12345');
});

it('passes numeric orgId option through to client options', () => {
init({
orgId: 12345,
});
expect(usedOptions()?.orgId).toBe(12345);
});

it('defaults strictTraceContinuation to undefined when not set', () => {
init({});
expect(usedOptions()?.strictTraceContinuation).toBeUndefined();
});
});

describe('beforeBreadcrumb', () => {
it('should filters out dev server breadcrumbs', () => {
const devServerUrl = 'http://localhost:8081';
Expand Down
261 changes: 261 additions & 0 deletions packages/core/test/strictTraceContinuation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
import { continueTrace, getCurrentScope, setCurrentClient } from '@sentry/core';
import { getDefaultTestClientOptions, TestClient } from './mocks/client';

describe('strictTraceContinuation', () => {
let client: TestClient;

afterEach(() => {
jest.clearAllMocks();
});

describe('with matching org IDs', () => {
beforeEach(() => {
client = new TestClient(
getDefaultTestClientOptions({
tracesSampleRate: 1.0,
dsn: 'https://abc@o123.ingest.sentry.io/1234',
}),
);
setCurrentClient(client);
client.init();
});

it('continues trace when baggage org_id matches DSN org ID', () => {
const scope = continueTrace(
{
sentryTrace: '12312012123120121231201212312012-1121201211212012-1',
baggage: 'sentry-org_id=123',
},
() => {
return getCurrentScope();
},
);

expect(scope.getPropagationContext().traceId).toBe('12312012123120121231201212312012');
expect(scope.getPropagationContext().parentSpanId).toBe('1121201211212012');
});
});

describe('with mismatching org IDs', () => {
beforeEach(() => {
client = new TestClient(
getDefaultTestClientOptions({
tracesSampleRate: 1.0,
dsn: 'https://abc@o123.ingest.sentry.io/1234',
}),
);
setCurrentClient(client);
client.init();
});

it('starts new trace when baggage org_id does not match DSN org ID', () => {
const scope = continueTrace(
{
sentryTrace: '12312012123120121231201212312012-1121201211212012-1',
baggage: 'sentry-org_id=456',
},
() => {
return getCurrentScope();
},
);

expect(scope.getPropagationContext().traceId).not.toBe('12312012123120121231201212312012');
expect(scope.getPropagationContext().parentSpanId).toBeUndefined();
});
});

describe('with orgId option override', () => {
beforeEach(() => {
client = new TestClient(
getDefaultTestClientOptions({
tracesSampleRate: 1.0,
dsn: 'https://abc@o123.ingest.sentry.io/1234',
orgId: '999',
}),
);
setCurrentClient(client);
client.init();
});

it('uses orgId option over DSN-extracted org ID', () => {
// baggage org_id=123 matches DSN but NOT the orgId option (999)
const scope = continueTrace(
{
sentryTrace: '12312012123120121231201212312012-1121201211212012-1',
baggage: 'sentry-org_id=123',
},
() => {
return getCurrentScope();
},
);

// Should start new trace because orgId option (999) != baggage org_id (123)
expect(scope.getPropagationContext().traceId).not.toBe('12312012123120121231201212312012');
expect(scope.getPropagationContext().parentSpanId).toBeUndefined();
});

it('continues trace when baggage matches orgId option', () => {
const scope = continueTrace(
{
sentryTrace: '12312012123120121231201212312012-1121201211212012-1',
baggage: 'sentry-org_id=999',
},
() => {
return getCurrentScope();
},
);

expect(scope.getPropagationContext().traceId).toBe('12312012123120121231201212312012');
expect(scope.getPropagationContext().parentSpanId).toBe('1121201211212012');
});
});

describe('strictTraceContinuation=true', () => {
beforeEach(() => {
client = new TestClient(
getDefaultTestClientOptions({
tracesSampleRate: 1.0,
dsn: 'https://abc@o123.ingest.sentry.io/1234',
strictTraceContinuation: true,
}),
);
setCurrentClient(client);
client.init();
});

it('starts new trace when baggage has no org_id', () => {
const scope = continueTrace(
{
sentryTrace: '12312012123120121231201212312012-1121201211212012-1',
baggage: 'sentry-environment=production',
},
() => {
return getCurrentScope();
},
);

expect(scope.getPropagationContext().traceId).not.toBe('12312012123120121231201212312012');
expect(scope.getPropagationContext().parentSpanId).toBeUndefined();
});

it('starts new trace when SDK has no org_id but baggage does', () => {
// Use a DSN without org ID in hostname
const clientWithoutOrgId = new TestClient(
getDefaultTestClientOptions({
tracesSampleRate: 1.0,
dsn: 'https://abc@sentry.example.com/1234',
strictTraceContinuation: true,
}),
);
setCurrentClient(clientWithoutOrgId);
clientWithoutOrgId.init();

const scope = continueTrace(
{
sentryTrace: '12312012123120121231201212312012-1121201211212012-1',
baggage: 'sentry-org_id=123',
},
() => {
return getCurrentScope();
},
);

expect(scope.getPropagationContext().traceId).not.toBe('12312012123120121231201212312012');
expect(scope.getPropagationContext().parentSpanId).toBeUndefined();
});

it('continues trace when both org IDs are missing', () => {
const clientWithoutOrgId = new TestClient(
getDefaultTestClientOptions({
tracesSampleRate: 1.0,
dsn: 'https://abc@sentry.example.com/1234',
strictTraceContinuation: true,
}),
);
setCurrentClient(clientWithoutOrgId);
clientWithoutOrgId.init();

const scope = continueTrace(
{
sentryTrace: '12312012123120121231201212312012-1121201211212012-1',
baggage: 'sentry-environment=production',
},
() => {
return getCurrentScope();
},
);

expect(scope.getPropagationContext().traceId).toBe('12312012123120121231201212312012');
expect(scope.getPropagationContext().parentSpanId).toBe('1121201211212012');
});
});

describe('strictTraceContinuation=false (default)', () => {
beforeEach(() => {
client = new TestClient(
getDefaultTestClientOptions({
tracesSampleRate: 1.0,
dsn: 'https://abc@o123.ingest.sentry.io/1234',
strictTraceContinuation: false,
}),
);
setCurrentClient(client);
client.init();
});

it('continues trace when baggage has no org_id', () => {
const scope = continueTrace(
{
sentryTrace: '12312012123120121231201212312012-1121201211212012-1',
baggage: 'sentry-environment=production',
},
() => {
return getCurrentScope();
},
);

expect(scope.getPropagationContext().traceId).toBe('12312012123120121231201212312012');
expect(scope.getPropagationContext().parentSpanId).toBe('1121201211212012');
});

it('continues trace when SDK has no org_id but baggage does', () => {
const clientWithoutOrgId = new TestClient(
getDefaultTestClientOptions({
tracesSampleRate: 1.0,
dsn: 'https://abc@sentry.example.com/1234',
strictTraceContinuation: false,
}),
);
setCurrentClient(clientWithoutOrgId);
clientWithoutOrgId.init();

const scope = continueTrace(
{
sentryTrace: '12312012123120121231201212312012-1121201211212012-1',
baggage: 'sentry-org_id=123',
},
() => {
return getCurrentScope();
},
);

expect(scope.getPropagationContext().traceId).toBe('12312012123120121231201212312012');
expect(scope.getPropagationContext().parentSpanId).toBe('1121201211212012');
});

it('still starts new trace when org IDs mismatch', () => {
const scope = continueTrace(
{
sentryTrace: '12312012123120121231201212312012-1121201211212012-1',
baggage: 'sentry-org_id=456',
},
() => {
return getCurrentScope();
},
);

expect(scope.getPropagationContext().traceId).not.toBe('12312012123120121231201212312012');
expect(scope.getPropagationContext().parentSpanId).toBeUndefined();
});
});
});
32 changes: 32 additions & 0 deletions packages/core/test/wrapper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,38 @@ describe('Tests Native Wrapper', () => {
expect(initParameter.enableLogs).toBe(expectedEnableLogs);
expect(initParameter.logsOrigin).toBeUndefined();
});

test('passes strictTraceContinuation option to native SDK', async () => {
await NATIVE.initNativeSdk({
dsn: 'test',
enableNative: true,
autoInitializeNativeSdk: true,
strictTraceContinuation: true,
devServerUrl: undefined,
defaultSidecarUrl: undefined,
mobileReplayOptions: undefined,
});

expect(RNSentry.initNativeSdk).toHaveBeenCalled();
const initParameter = (RNSentry.initNativeSdk as jest.MockedFunction<any>).mock.calls[0][0];
expect(initParameter.strictTraceContinuation).toBe(true);
});

test('passes orgId option to native SDK', async () => {
await NATIVE.initNativeSdk({
dsn: 'test',
enableNative: true,
autoInitializeNativeSdk: true,
orgId: '12345',
devServerUrl: undefined,
defaultSidecarUrl: undefined,
mobileReplayOptions: undefined,
});

expect(RNSentry.initNativeSdk).toHaveBeenCalled();
const initParameter = (RNSentry.initNativeSdk as jest.MockedFunction<any>).mock.calls[0][0];
expect(initParameter.orgId).toBe('12345');
});
});

describe('sendEnvelope', () => {
Expand Down
Loading