@datadog/mobile-react-native
Version:
A client-side React Native module to interact with Datadog
1,277 lines (1,070 loc) • 64.8 kB
text/typescript
/* eslint-disable @typescript-eslint/ban-ts-comment */
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/
import { NativeModules } from 'react-native';
import { InternalLog } from '../../InternalLog';
import { SdkVerbosity } from '../../SdkVerbosity';
import { BufferSingleton } from '../../sdk/DatadogProvider/Buffer/BufferSingleton';
import { DdSdk } from '../../sdk/DdSdk';
import { GlobalState } from '../../sdk/GlobalState/GlobalState';
import { DdRum } from '../DdRum';
import type { ActionEventMapper } from '../eventMappers/actionEventMapper';
import type { ErrorEventMapper } from '../eventMappers/errorEventMapper';
import type { ResourceEventMapper } from '../eventMappers/resourceEventMapper';
import { DatadogTracingContext } from '../instrumentation/resourceTracking/distributedTracing/DatadogTracingContext';
import { DatadogTracingIdentifier } from '../instrumentation/resourceTracking/distributedTracing/DatadogTracingIdentifier';
import { TracingIdFormat } from '../instrumentation/resourceTracking/distributedTracing/TracingIdentifier';
import { TracingIdentifierUtils } from '../instrumentation/resourceTracking/distributedTracing/__tests__/__utils__/TracingIdentifierUtils';
import { setCachedSessionId } from '../sessionId/sessionIdHelper';
import type { FirstPartyHost } from '../types';
import { ErrorSource, PropagatorType, RumActionType } from '../types';
import * as TracingContextUtils from './__utils__/TracingHeadersUtils';
jest.mock('../../utils/time-provider/DefaultTimeProvider', () => {
return {
DefaultTimeProvider: jest.fn().mockImplementation(() => {
return { now: jest.fn().mockReturnValue(456) };
})
};
});
jest.mock('../../InternalLog', () => {
return {
InternalLog: {
log: jest.fn()
},
DATADOG_MESSAGE_PREFIX: 'DATADOG:'
};
});
describe('DdRum', () => {
beforeEach(() => {
jest.clearAllMocks();
BufferSingleton.onInitialization();
setCachedSessionId(undefined as any);
});
describe('Context validation', () => {
describe('DdRum.startView', () => {
test('uses given context when context is valid', async () => {
const context = {
testA: 123,
testB: 'ok'
};
await DdRum.startView('key', 'name', context);
expect(NativeModules.DdRum.startView).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
context,
expect.anything()
);
});
test('uses empty context with error when context is invalid or null', async () => {
const context: any = 123;
await DdRum.startView('key', 'name', context);
expect(InternalLog.log).toHaveBeenNthCalledWith(
2,
expect.anything(),
SdkVerbosity.ERROR
);
expect(NativeModules.DdRum.startView).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
{},
expect.anything()
);
});
test('nests given context in new object when context is array', async () => {
const context: any = [123, '456'];
await DdRum.startView('key', 'name', context);
expect(InternalLog.log).toHaveBeenNthCalledWith(
2,
expect.anything(),
SdkVerbosity.WARN
);
expect(NativeModules.DdRum.startView).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
{ context },
expect.anything()
);
});
});
describe('DdRum.stopView', () => {
test('uses given context when context is valid', async () => {
const context = {
testA: 123,
testB: 'ok'
};
await DdRum.startView('key', 'name');
await DdRum.stopView('key', context);
expect(NativeModules.DdRum.stopView).toHaveBeenCalledWith(
'key',
context,
expect.anything()
);
});
test('uses empty context with error when context is invalid or null', async () => {
const context: any = 123;
await DdRum.startView('key', 'name');
await DdRum.stopView('key', context);
expect(InternalLog.log).toHaveBeenNthCalledWith(
3,
expect.anything(),
SdkVerbosity.ERROR
);
expect(NativeModules.DdRum.stopView).toHaveBeenCalledWith(
'key',
{},
expect.anything()
);
});
test('nests given context in new object when context is array', async () => {
const context: any = [123, '456'];
await DdRum.startView('key', 'name');
await DdRum.stopView('key', context);
expect(InternalLog.log).toHaveBeenNthCalledWith(
3,
expect.anything(),
SdkVerbosity.WARN
);
expect(NativeModules.DdRum.stopView).toHaveBeenCalledWith(
'key',
{ context },
expect.anything()
);
});
});
describe('DdRum.startAction', () => {
test('uses given context when context is valid', async () => {
const context = {
testA: 123,
testB: 'ok'
};
await DdRum.startAction(RumActionType.SCROLL, 'name', context);
expect(NativeModules.DdRum.startAction).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
context,
expect.anything()
);
});
test('uses empty context with error when context is invalid or null', async () => {
const context: any = 123;
await DdRum.startAction(RumActionType.SCROLL, 'name', context);
expect(InternalLog.log).toHaveBeenNthCalledWith(
2,
expect.anything(),
SdkVerbosity.ERROR
);
expect(NativeModules.DdRum.startAction).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
{},
expect.anything()
);
});
test('nests given context in new object when context is array', async () => {
const context: any = [123, '456'];
await DdRum.startAction(RumActionType.SCROLL, 'name', context);
expect(InternalLog.log).toHaveBeenNthCalledWith(
2,
expect.anything(),
SdkVerbosity.WARN
);
expect(NativeModules.DdRum.startAction).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
{ context },
expect.anything()
);
});
});
describe('DdRum.stopAction', () => {
describe('New API', () => {
test('uses given context when context is valid', async () => {
const context = {
testA: 123,
testB: 'ok'
};
await DdRum.startAction(RumActionType.SCROLL, 'name');
await DdRum.stopAction(
RumActionType.SCROLL,
'name',
context
);
expect(NativeModules.DdRum.stopAction).toHaveBeenCalledWith(
RumActionType.SCROLL,
'name',
context,
expect.anything()
);
});
test('uses empty context with error when context is invalid or null', async () => {
const context: any = 123;
await DdRum.startAction(RumActionType.SCROLL, 'name');
await DdRum.stopAction(
RumActionType.SCROLL,
'name',
context
);
expect(InternalLog.log).toHaveBeenNthCalledWith(
3,
expect.anything(),
SdkVerbosity.ERROR
);
expect(NativeModules.DdRum.stopAction).toHaveBeenCalledWith(
RumActionType.SCROLL,
'name',
{},
expect.anything()
);
});
test('nests given context in new object when context is array', async () => {
const context: any = [123, '456'];
await DdRum.startAction(RumActionType.SCROLL, 'name');
await DdRum.stopAction(
RumActionType.SCROLL,
'name',
context
);
expect(InternalLog.log).toHaveBeenNthCalledWith(
3,
expect.anything(),
SdkVerbosity.WARN
);
expect(NativeModules.DdRum.stopAction).toHaveBeenCalledWith(
RumActionType.SCROLL,
'name',
{ context },
expect.anything()
);
});
});
describe('Old API', () => {
test('uses given context when context is valid', async () => {
const context = {
testA: 123,
testB: 'ok'
};
await DdRum.startAction(RumActionType.SCROLL, 'name');
await DdRum.stopAction(context);
expect(NativeModules.DdRum.stopAction).toHaveBeenCalledWith(
RumActionType.SCROLL,
'name',
context,
expect.anything()
);
});
test('uses empty context with error when context is invalid or null', async () => {
await DdRum.startAction(RumActionType.SCROLL, 'name');
await DdRum.stopAction(undefined);
expect(NativeModules.DdRum.stopAction).toHaveBeenCalledWith(
RumActionType.SCROLL,
'name',
{},
expect.anything()
);
});
test('nests given context in new object when context is array', async () => {
const context: any = [123, '456'];
await DdRum.startAction(RumActionType.SCROLL, 'name');
await DdRum.stopAction(context);
expect(InternalLog.log).toHaveBeenNthCalledWith(
3,
expect.anything(),
SdkVerbosity.WARN
);
expect(NativeModules.DdRum.stopAction).toHaveBeenCalledWith(
RumActionType.SCROLL,
'name',
{ context },
expect.anything()
);
});
});
});
describe('DdRum.startResource', () => {
test('uses given context when context is valid', async () => {
const context = {
testA: 123,
testB: 'ok'
};
await DdRum.startResource('key', 'method', 'url', context);
expect(NativeModules.DdRum.startResource).toHaveBeenCalledWith(
'key',
'method',
'url',
context,
expect.anything()
);
});
test('uses empty context with error when context is invalid or null', async () => {
const context: any = 123;
await DdRum.startResource('key', 'method', 'url', context);
expect(InternalLog.log).toHaveBeenNthCalledWith(
2,
expect.anything(),
SdkVerbosity.ERROR
);
expect(NativeModules.DdRum.startResource).toHaveBeenCalledWith(
'key',
'method',
'url',
{},
expect.anything()
);
});
test('nests given context in new object when context is array', async () => {
const context: any = [123, '456'];
await DdRum.startResource('key', 'method', 'url', context);
expect(InternalLog.log).toHaveBeenNthCalledWith(
2,
expect.anything(),
SdkVerbosity.WARN
);
expect(NativeModules.DdRum.startResource).toHaveBeenCalledWith(
'key',
'method',
'url',
{ context },
expect.anything()
);
});
});
describe('DdRum.stopResource', () => {
test('uses given context when context is valid', async () => {
const context = {
testA: 123,
testB: 'ok'
};
await DdRum.startResource('key', 'method', 'url', {});
await DdRum.stopResource('key', 200, 'other', -1, context);
expect(NativeModules.DdRum.stopResource).toHaveBeenCalledWith(
'key',
200,
'other',
-1,
context,
expect.anything()
);
});
test('uses empty context with error when context is invalid or null', async () => {
const context: any = 123;
await DdRum.startResource('key', 'method', 'url', {});
await DdRum.stopResource('key', 200, 'other', -1, context);
expect(InternalLog.log).toHaveBeenNthCalledWith(
2,
expect.anything(),
SdkVerbosity.ERROR
);
expect(NativeModules.DdRum.stopResource).toHaveBeenCalledWith(
'key',
200,
'other',
-1,
{},
expect.anything()
);
});
test('nests given context in new object when context is array', async () => {
const context: any = [123, '456'];
await DdRum.startResource('key', 'method', 'url', {});
await DdRum.stopResource('key', 200, 'other', -1, context);
expect(InternalLog.log).toHaveBeenNthCalledWith(
2,
expect.anything(),
SdkVerbosity.WARN
);
expect(NativeModules.DdRum.stopResource).toHaveBeenCalledWith(
'key',
200,
'other',
-1,
{ context },
expect.anything()
);
});
});
describe('Tracing Context APIs', () => {
describe('Types and Enums', () => {
it('exposes TracingIdFormat enum', () => {
expect(TracingIdFormat).toBeDefined();
});
it('exposes DatadogTracingIdentifier enum', () => {
expect(DatadogTracingIdentifier).toBeDefined();
});
it('exposes DatadogTracingContext class', () => {
expect(DatadogTracingContext).toBeDefined();
});
});
describe('DdRum.generateTraceId', () => {
it('generates 128-bit trace ID (100 iterations)', () => {
for (let i = 0; i < 100; i++) {
const traceId = DdRum.generateTraceId();
expect(traceId).toBeDefined();
expect(
TracingIdentifierUtils.isWithin128Bits(
traceId.toString(TracingIdFormat.decimal)
)
).toBe(true);
}
});
});
describe('DdRum.generateSpanId', () => {
it('generates 64-bit span ID (100 iterations)', () => {
for (let i = 0; i < 100; i++) {
const spanId = DdRum.generateSpanId();
expect(spanId).toBeDefined();
expect(
TracingIdentifierUtils.isWithin64Bits(
spanId.toString(TracingIdFormat.decimal)
)
).toBe(true);
}
});
});
describe('DdRum.getTracingContext', () => {
it('returns tracing context with DATADOG propagator and sampling rate (50% 0, 50% 100)', () => {
for (let i = 0; i < 100; i++) {
const url = 'https://www.example.com';
const tracingSamplingRate =
Math.random() < 0.5 ? 0 : 100;
const firstPartyHosts: FirstPartyHost[] = [
{
match: 'example.com',
propagatorTypes: [PropagatorType.DATADOG]
}
];
const tracingContext = DdRum.getTracingContext(
url,
tracingSamplingRate,
firstPartyHosts
);
const resourceContext = tracingContext.getRumResourceContext();
expect(Object.keys(resourceContext)).toHaveLength(3);
TracingContextUtils.verifyRumResourceContext(
tracingContext
);
const headers = tracingContext.getHeadersForRequestAsArray();
expect(headers).toHaveLength(5);
TracingContextUtils.verifyDatadogHeaders(
headers,
tracingSamplingRate === 100
);
}
});
it('returns tracing context with TRACECONTEXT propagator and sampling rate (50% 0, 50% 100)', () => {
for (let i = 0; i < 100; i++) {
const url = 'https://www.example.com';
const tracingSamplingRate =
Math.random() < 0.5 ? 0 : 100;
const firstPartyHosts: FirstPartyHost[] = [
{
match: 'example.com',
propagatorTypes: [PropagatorType.TRACECONTEXT]
}
];
const tracingContext = DdRum.getTracingContext(
url,
tracingSamplingRate,
firstPartyHosts
);
const resourceContext = tracingContext.getRumResourceContext();
expect(Object.keys(resourceContext)).toHaveLength(3);
TracingContextUtils.verifyRumResourceContext(
tracingContext
);
const headers = tracingContext.getHeadersForRequestAsArray();
expect(headers).toHaveLength(2);
TracingContextUtils.verifyTraceContextHeaders(
headers,
tracingSamplingRate === 100
);
}
});
it('returns tracing context with B3 propagator and sampling rate (50% 0, 50% 100)', () => {
for (let i = 0; i < 100; i++) {
const url = 'https://www.example.com';
const tracingSamplingRate =
Math.random() < 0.5 ? 0 : 100;
const firstPartyHosts: FirstPartyHost[] = [
{
match: 'example.com',
propagatorTypes: [PropagatorType.B3]
}
];
const tracingContext = DdRum.getTracingContext(
url,
tracingSamplingRate,
firstPartyHosts
);
const resourceContext = tracingContext.getRumResourceContext();
expect(Object.keys(resourceContext)).toHaveLength(3);
TracingContextUtils.verifyRumResourceContext(
tracingContext
);
const headers = tracingContext.getHeadersForRequestAsArray();
expect(headers).toHaveLength(1);
TracingContextUtils.verifyB3Headers(
headers,
tracingSamplingRate === 100
);
}
});
it('returns tracing context with B3MULTI propagator and sampling rate (50% 0, 50% 100)', () => {
for (let i = 0; i < 100; i++) {
const url = 'https://www.example.com';
const tracingSamplingRate =
Math.random() < 0.5 ? 0 : 100;
const firstPartyHosts: FirstPartyHost[] = [
{
match: 'example.com',
propagatorTypes: [PropagatorType.B3MULTI]
}
];
const tracingContext = DdRum.getTracingContext(
url,
tracingSamplingRate,
firstPartyHosts
);
const resourceContext = tracingContext.getRumResourceContext();
expect(Object.keys(resourceContext)).toHaveLength(3);
TracingContextUtils.verifyRumResourceContext(
tracingContext
);
const headers = tracingContext.getHeadersForRequestAsArray();
expect(headers).toHaveLength(3);
TracingContextUtils.verifyB3MultiHeaders(
headers,
tracingSamplingRate === 100
);
}
});
it('returns tracing context with all propagators and sampling rate (50% 0, 50% 100)', () => {
for (let i = 0; i < 100; i++) {
const url = 'https://www.example.com';
const tracingSamplingRate =
Math.random() < 0.5 ? 0 : 100;
const firstPartyHosts: FirstPartyHost[] = [
{
match: 'example.com',
propagatorTypes: [
PropagatorType.DATADOG,
PropagatorType.TRACECONTEXT,
PropagatorType.B3,
PropagatorType.B3MULTI
]
}
];
const tracingContext = DdRum.getTracingContext(
url,
tracingSamplingRate,
firstPartyHosts
);
const resourceContext = tracingContext.getRumResourceContext();
expect(Object.keys(resourceContext)).toHaveLength(3);
TracingContextUtils.verifyRumResourceContext(
tracingContext
);
const headers = tracingContext.getHeadersForRequestAsArray();
expect(headers).toHaveLength(11);
TracingContextUtils.verifyDatadogHeaders(
headers,
tracingSamplingRate === 100
);
TracingContextUtils.verifyTraceContextHeaders(
headers,
tracingSamplingRate === 100
);
TracingContextUtils.verifyB3Headers(
headers,
tracingSamplingRate === 100
);
TracingContextUtils.verifyB3MultiHeaders(
headers,
tracingSamplingRate === 100
);
}
});
it('injects headers and context correctly with all propagators and sampling rate (50% 0, 50% 100)', () => {
for (let i = 0; i < 100; i++) {
const url = 'https://www.example.com';
const tracingSamplingRate =
Math.random() < 0.5 ? 0 : 100;
const firstPartyHosts: FirstPartyHost[] = [
{
match: 'example.com',
propagatorTypes: [
PropagatorType.DATADOG,
PropagatorType.TRACECONTEXT,
PropagatorType.B3,
PropagatorType.B3MULTI
]
}
];
const tracingContext = DdRum.getTracingContext(
url,
tracingSamplingRate,
firstPartyHosts
);
const resourceContext: Record<
string,
string | number
> = {};
tracingContext.injectRumResourceContext(
(attribute: string, value: string | number) => {
resourceContext[attribute] = value;
}
);
expect(Object.keys(resourceContext)).toHaveLength(3);
TracingContextUtils.verifyRumResourceContext(
tracingContext,
resourceContext
);
const headers: { header: string; value: string }[] = [];
tracingContext.injectHeadersForRequest(
(header: string, value: string) => {
headers.push({ header, value });
}
);
expect(headers).toHaveLength(11);
TracingContextUtils.verifyDatadogHeaders(
headers,
tracingSamplingRate === 100
);
TracingContextUtils.verifyTraceContextHeaders(
headers,
tracingSamplingRate === 100
);
TracingContextUtils.verifyB3Headers(
headers,
tracingSamplingRate === 100
);
TracingContextUtils.verifyB3MultiHeaders(
headers,
tracingSamplingRate === 100
);
}
});
it('returns empty tracing context for non-matching host with all propagators and sampling rate 100', () => {
const url = 'https://not-the-right-host.com';
const firstPartyHosts: FirstPartyHost[] = [
{
match: 'example.com',
propagatorTypes: [
PropagatorType.DATADOG,
PropagatorType.TRACECONTEXT,
PropagatorType.B3,
PropagatorType.B3MULTI
]
}
];
const tracingContext = DdRum.getTracingContext(
url,
100,
firstPartyHosts
);
const resourceContext = tracingContext.getRumResourceContext();
expect(Object.keys(resourceContext)).toHaveLength(1);
TracingContextUtils.verifyRumResourceContext(
tracingContext
);
const headers = tracingContext.getHeadersForRequestAsArray();
expect(headers).toHaveLength(0);
});
it('returns empty tracing context with no propagators and sampling rate 100', () => {
const url = 'https://www.example.com';
const firstPartyHosts: FirstPartyHost[] = [
{
match: 'example.com',
propagatorTypes: []
}
];
const tracingContext = DdRum.getTracingContext(
url,
100,
firstPartyHosts
);
const resourceContext = tracingContext.getRumResourceContext();
expect(Object.keys(resourceContext)).toHaveLength(1);
TracingContextUtils.verifyRumResourceContext(
tracingContext
);
const headers = tracingContext.getHeadersForRequestAsArray();
expect(headers).toHaveLength(0);
});
});
describe('DdRum.getTracingContextForPropagators', () => {
it('returns tracing context with DATADOG propagator and sampling rate (50% 0, 50% 100)', () => {
for (let i = 0; i < 100; i++) {
const tracingSamplingRate =
Math.random() < 0.5 ? 0 : 100;
const tracingContext = DdRum.getTracingContextForPropagators(
[PropagatorType.DATADOG],
tracingSamplingRate
);
const resourceContext = tracingContext.getRumResourceContext();
expect(Object.keys(resourceContext)).toHaveLength(3);
TracingContextUtils.verifyRumResourceContext(
tracingContext
);
const headers = tracingContext.getHeadersForRequestAsArray();
expect(headers).toHaveLength(5);
TracingContextUtils.verifyDatadogHeaders(
headers,
tracingSamplingRate === 100
);
}
});
it('returns tracing context with TRACECONTEXT propagator and sampling rate (50% 0, 50% 100)', () => {
for (let i = 0; i < 100; i++) {
const tracingSamplingRate =
Math.random() < 0.5 ? 0 : 100;
const tracingContext = DdRum.getTracingContextForPropagators(
[PropagatorType.TRACECONTEXT],
tracingSamplingRate
);
const resourceContext = tracingContext.getRumResourceContext();
expect(Object.keys(resourceContext)).toHaveLength(3);
TracingContextUtils.verifyRumResourceContext(
tracingContext
);
const headers = tracingContext.getHeadersForRequestAsArray();
expect(headers).toHaveLength(2);
TracingContextUtils.verifyTraceContextHeaders(
headers,
tracingSamplingRate === 100
);
}
});
it('returns tracing context with B3 propagator and sampling rate (50% 0, 50% 100)', () => {
for (let i = 0; i < 100; i++) {
const tracingSamplingRate =
Math.random() < 0.5 ? 0 : 100;
const tracingContext = DdRum.getTracingContextForPropagators(
[PropagatorType.B3],
tracingSamplingRate
);
const resourceContext = tracingContext.getRumResourceContext();
expect(Object.keys(resourceContext)).toHaveLength(3);
TracingContextUtils.verifyRumResourceContext(
tracingContext
);
const headers = tracingContext.getHeadersForRequestAsArray();
expect(headers).toHaveLength(1);
TracingContextUtils.verifyB3Headers(
headers,
tracingSamplingRate === 100
);
}
});
it('returns tracing context with B3MULTI propagator and sampling rate (50% 0, 50% 100)', () => {
for (let i = 0; i < 100; i++) {
const tracingSamplingRate =
Math.random() < 0.5 ? 0 : 100;
const tracingContext = DdRum.getTracingContextForPropagators(
[PropagatorType.B3MULTI],
tracingSamplingRate
);
const resourceContext = tracingContext.getRumResourceContext();
expect(Object.keys(resourceContext)).toHaveLength(3);
TracingContextUtils.verifyRumResourceContext(
tracingContext
);
const headers = tracingContext.getHeadersForRequestAsArray();
expect(headers).toHaveLength(3);
TracingContextUtils.verifyB3MultiHeaders(
headers,
tracingSamplingRate === 100
);
}
});
it('returns tracing context with all propagators and sampling rate (50% 0, 50% 100)', () => {
for (let i = 0; i < 100; i++) {
const tracingSamplingRate =
Math.random() < 0.5 ? 0 : 100;
const tracingContext = DdRum.getTracingContextForPropagators(
[
PropagatorType.DATADOG,
PropagatorType.TRACECONTEXT,
PropagatorType.B3MULTI,
PropagatorType.B3
],
tracingSamplingRate
);
const resourceContext = tracingContext.getRumResourceContext();
expect(Object.keys(resourceContext)).toHaveLength(3);
TracingContextUtils.verifyRumResourceContext(
tracingContext
);
const headers = tracingContext.getHeadersForRequestAsArray();
expect(headers).toHaveLength(11);
TracingContextUtils.verifyDatadogHeaders(
headers,
tracingSamplingRate === 100
);
TracingContextUtils.verifyTraceContextHeaders(
headers,
tracingSamplingRate === 100
);
TracingContextUtils.verifyB3Headers(
headers,
tracingSamplingRate === 100
);
TracingContextUtils.verifyB3MultiHeaders(
headers,
tracingSamplingRate === 100
);
}
});
it('tracing context contains RUM session ID in baggage when RUM Session ID is cached', () => {
for (let i = 0; i < 100; i++) {
const randomSessionId = `test-${Math.random()}`;
setCachedSessionId(randomSessionId);
const tracingContext = DdRum.getTracingContextForPropagators(
[
PropagatorType.DATADOG,
PropagatorType.TRACECONTEXT,
PropagatorType.B3MULTI,
PropagatorType.B3
],
100
);
const requestHeaders = tracingContext.getHeadersForRequest();
expect(requestHeaders).toHaveProperty('baggage');
expect(requestHeaders['baggage']).toBe(
`session.id=${randomSessionId}`
);
const resourceContext = tracingContext.getRumResourceContext();
expect(resourceContext['baggage']).toBeUndefined();
}
});
it('injects headers and context correctly with all propagators and sampling rate (50% 0, 50% 100)', () => {
for (let i = 0; i < 100; i++) {
const tracingSamplingRate =
Math.random() < 0.5 ? 0 : 100;
const tracingContext = DdRum.getTracingContextForPropagators(
[
PropagatorType.DATADOG,
PropagatorType.TRACECONTEXT,
PropagatorType.B3,
PropagatorType.B3MULTI
],
tracingSamplingRate
);
const resourceContext: Record<
string,
string | number
> = {};
tracingContext.injectRumResourceContext(
(attribute: string, value: string | number) => {
resourceContext[attribute] = value;
}
);
expect(Object.keys(resourceContext)).toHaveLength(3);
TracingContextUtils.verifyRumResourceContext(
tracingContext,
resourceContext
);
const headers: { header: string; value: string }[] = [];
tracingContext.injectHeadersForRequest(
(header: string, value: string) => {
headers.push({ header, value });
}
);
expect(headers).toHaveLength(11);
TracingContextUtils.verifyDatadogHeaders(
headers,
tracingSamplingRate === 100
);
TracingContextUtils.verifyTraceContextHeaders(
headers,
tracingSamplingRate === 100
);
TracingContextUtils.verifyB3Headers(
headers,
tracingSamplingRate === 100
);
TracingContextUtils.verifyB3MultiHeaders(
headers,
tracingSamplingRate === 100
);
}
});
it('returns empty tracing context for empty propagators and sampling rate 100', () => {
const tracingContext = DdRum.getTracingContextForPropagators(
[],
100
);
const resourceContext = tracingContext.getRumResourceContext();
expect(Object.keys(resourceContext)).toHaveLength(1);
TracingContextUtils.verifyRumResourceContext(
tracingContext
);
const headers = tracingContext.getHeadersForRequestAsArray();
expect(headers).toHaveLength(0);
});
});
});
describe('DdRum.addAction', () => {
test('uses given context when context is valid', async () => {
const context = {
testA: 123,
testB: 'ok'
};
await DdRum.addAction(RumActionType.SCROLL, 'name', context);
expect(NativeModules.DdRum.addAction).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
context,
expect.anything()
);
});
test('uses empty context with error when context is invalid or null', async () => {
const context: any = 123;
await DdRum.addAction(RumActionType.SCROLL, 'name', context);
expect(InternalLog.log).toHaveBeenNthCalledWith(
1,
expect.anything(),
SdkVerbosity.ERROR
);
expect(NativeModules.DdRum.addAction).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
{},
expect.anything()
);
});
test('nests given context in new object when context is array', async () => {
const context: any = [123, '456'];
await DdRum.addAction(RumActionType.SCROLL, 'name', context);
expect(InternalLog.log).toHaveBeenNthCalledWith(
1,
expect.anything(),
SdkVerbosity.WARN
);
expect(NativeModules.DdRum.addAction).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
{ context },
expect.anything()
);
});
});
describe('DdRum.addError', () => {
test('uses given context when context is valid', async () => {
const context = {
testA: 123,
testB: 'ok'
};
await DdRum.addError(
'error',
ErrorSource.CUSTOM,
'stacktrace',
context
);
expect(NativeModules.DdRum.addError).toHaveBeenCalledWith(
'error',
ErrorSource.CUSTOM,
'stacktrace',
{
...context,
'_dd.error.source_type': 'react-native'
},
expect.anything(),
''
);
});
test('uses empty context with error when context is invalid or null', async () => {
const context: any = 123;
await DdRum.addError(
'error',
ErrorSource.CUSTOM,
'stacktrace',
context
);
expect(InternalLog.log).toHaveBeenNthCalledWith(
1,
expect.stringContaining('Adding RUM Error'),
SdkVerbosity.DEBUG
);
expect(InternalLog.log).toHaveBeenNthCalledWith(
2,
expect.stringContaining('Context will be empty'),
SdkVerbosity.ERROR
);
expect(NativeModules.DdRum.addError).toHaveBeenCalledWith(
'error',
ErrorSource.CUSTOM,
'stacktrace',
{
'_dd.error.source_type': 'react-native'
},
expect.anything(),
''
);
});
test('nests given context in new object when context is array', async () => {
const context: any = [123, '456'];
await DdRum.addError(
'error',
ErrorSource.CUSTOM,
'stacktrace',
context
);
expect(InternalLog.log).toHaveBeenNthCalledWith(
1,
expect.stringContaining('Adding RUM Error'),
SdkVerbosity.DEBUG
);
expect(InternalLog.log).toHaveBeenNthCalledWith(
2,
expect.stringContaining(
'The given context is an array, it will be nested'
),
SdkVerbosity.WARN
);
expect(NativeModules.DdRum.addError).toHaveBeenCalledWith(
'error',
ErrorSource.CUSTOM,
'stacktrace',
{
context,
'_dd.error.source_type': 'react-native'
},
expect.anything(),
''
);
});
});
});
describe('DdRum.stopAction', () => {
test('calls the native SDK when called with new API', async () => {
await DdRum.stopAction(
RumActionType.SCROLL,
'page',
{ user: 'me' },
123
);
expect(NativeModules.DdRum.stopAction).toHaveBeenCalledWith(
RumActionType.SCROLL,
'page',
{ user: 'me' },
123
);
});
test('calls the native SDK when called with new API with default values', async () => {
await DdRum.stopAction(RumActionType.SCROLL, 'page');
expect(NativeModules.DdRum.stopAction).toHaveBeenCalledWith(
RumActionType.SCROLL,
'page',
{},
456
);
});
test('does not call the native SDK when startAction has not been called before and using old API', async () => {
await DdRum.stopAction({ user: 'me' }, 789);
expect(NativeModules.DdRum.stopAction).not.toHaveBeenCalled();
expect(DdSdk.telemetryDebug).not.toHaveBeenCalled();
});
test('calls the native SDK when called with old API', async () => {