Skip to content

Commit 84b12da

Browse files
committed
add more tests
1 parent 7778fb8 commit 84b12da

File tree

1 file changed

+304
-0
lines changed

1 file changed

+304
-0
lines changed
Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
/**
2+
* @vitest-environment jsdom
3+
*/
4+
5+
import '../utils/mock-internal-setTimeout';
6+
import { getCurrentScope } from '@sentry/core';
7+
import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest';
8+
import { handleAfterSendEvent } from '../../src/coreHandlers/handleAfterSendEvent';
9+
import type { ReplayContainer } from '../../src/replay';
10+
import { Transaction } from '../fixtures/transaction';
11+
import { BASE_TIMESTAMP } from '../index';
12+
import type { RecordMock } from '../mocks/mockRrweb';
13+
import { resetSdkMock } from '../mocks/resetSdkMock';
14+
import { getTestEventIncremental } from '../utils/getTestEvent';
15+
16+
let replay: ReplayContainer;
17+
let mockRecord: RecordMock;
18+
19+
describe('Integration | traceIds', () => {
20+
beforeAll(() => {
21+
vi.useFakeTimers();
22+
});
23+
24+
afterEach(() => {
25+
replay.stop();
26+
});
27+
28+
it('preserves the most recent trace id across flushes', async () => {
29+
({ replay, mockRecord } = await resetSdkMock({
30+
replayOptions: {
31+
stickySession: false,
32+
},
33+
sentryOptions: {
34+
replaysSessionSampleRate: 1.0,
35+
replaysOnErrorSampleRate: 0.0,
36+
},
37+
}));
38+
39+
const handler = handleAfterSendEvent(replay);
40+
41+
// After initial flush from resetSdkMock, the context has a propagation
42+
// context trace ID. Clear it for a clean test.
43+
replay['_context'].traceIds = [];
44+
45+
// Simulate 3 transaction events with known trace IDs
46+
handler(Transaction('trace-aaa'), { statusCode: 200 });
47+
handler(Transaction('trace-bbb'), { statusCode: 200 });
48+
handler(Transaction('trace-ccc'), { statusCode: 200 });
49+
50+
expect(replay.getContext().traceIds).toHaveLength(3);
51+
52+
// Emit a recording event so the event buffer is not empty (flush needs events)
53+
mockRecord._emitter(getTestEventIncremental({ timestamp: BASE_TIMESTAMP }));
54+
55+
// Trigger a flush by advancing timers
56+
await vi.advanceTimersToNextTimerAsync();
57+
58+
// After the flush, _clearContext should preserve only the most recent trace id
59+
expect(replay.getContext().traceIds).toHaveLength(1);
60+
expect(replay.getContext().traceIds[0]![1]).toBe('trace-ccc');
61+
62+
// Verify the sent replay event contained all 3 trace IDs
63+
expect(replay).toHaveLastSentReplay({
64+
replayEventPayload: expect.objectContaining({
65+
trace_ids: ['trace-aaa', 'trace-bbb', 'trace-ccc'],
66+
traces_by_timestamp: [
67+
[expect.any(Number), 'trace-aaa'],
68+
[expect.any(Number), 'trace-bbb'],
69+
[expect.any(Number), 'trace-ccc'],
70+
],
71+
}),
72+
});
73+
});
74+
75+
it('carries over last trace id to next segment', async () => {
76+
({ replay, mockRecord } = await resetSdkMock({
77+
replayOptions: {
78+
stickySession: false,
79+
},
80+
sentryOptions: {
81+
replaysSessionSampleRate: 1.0,
82+
replaysOnErrorSampleRate: 0.0,
83+
},
84+
}));
85+
86+
const handler = handleAfterSendEvent(replay);
87+
88+
// Clear initial propagation context trace for clean test
89+
replay['_context'].traceIds = [];
90+
91+
// Add two trace IDs
92+
handler(Transaction('trace-first'), { statusCode: 200 });
93+
handler(Transaction('trace-second'), { statusCode: 200 });
94+
95+
// Emit a recording event so the buffer has events for the flush
96+
mockRecord._emitter(getTestEventIncremental({ timestamp: BASE_TIMESTAMP }));
97+
98+
// Flush segment 1
99+
await vi.advanceTimersToNextTimerAsync();
100+
101+
// After flush, last trace id should carry over
102+
expect(replay.getContext().traceIds).toHaveLength(1);
103+
expect(replay.getContext().traceIds[0]![1]).toBe('trace-second');
104+
105+
// Add a new trace ID for the next segment
106+
handler(Transaction('trace-third'), { statusCode: 200 });
107+
108+
// Emit another recording event for the next flush
109+
mockRecord._emitter(getTestEventIncremental({ timestamp: BASE_TIMESTAMP + 5000 }));
110+
111+
// Flush segment 2
112+
await vi.advanceTimersToNextTimerAsync();
113+
114+
// The second segment should include the carried-over trace-second plus the new trace-third
115+
expect(replay).toHaveLastSentReplay({
116+
replayEventPayload: expect.objectContaining({
117+
trace_ids: ['trace-second', 'trace-third'],
118+
traces_by_timestamp: [
119+
[expect.any(Number), 'trace-second'],
120+
[expect.any(Number), 'trace-third'],
121+
],
122+
}),
123+
});
124+
});
125+
126+
it('falls back to propagation context trace id when no transaction events are captured', async () => {
127+
({ replay, mockRecord } = await resetSdkMock({
128+
replayOptions: {
129+
stickySession: false,
130+
},
131+
sentryOptions: {
132+
replaysSessionSampleRate: 1.0,
133+
replaysOnErrorSampleRate: 0.0,
134+
},
135+
}));
136+
137+
// Clear initial trace IDs
138+
replay['_context'].traceIds = [];
139+
140+
// Set a known trace ID on the current scope's propagation context
141+
const knownTraceId = 'abc123def456abc123def456abc123de';
142+
getCurrentScope().setPropagationContext({
143+
traceId: knownTraceId,
144+
sampleRand: 1,
145+
});
146+
147+
// Emit a recording event so the buffer has events for the flush
148+
mockRecord._emitter(getTestEventIncremental({ timestamp: BASE_TIMESTAMP }));
149+
150+
// Flush without sending any transaction events
151+
await vi.advanceTimersToNextTimerAsync();
152+
153+
// The replay event should contain the propagation context trace ID
154+
expect(replay).toHaveLastSentReplay({
155+
replayEventPayload: expect.objectContaining({
156+
trace_ids: [knownTraceId],
157+
traces_by_timestamp: [[expect.any(Number), knownTraceId]],
158+
}),
159+
});
160+
});
161+
162+
it('does not use propagation context fallback when transaction trace ids exist', async () => {
163+
({ replay, mockRecord } = await resetSdkMock({
164+
replayOptions: {
165+
stickySession: false,
166+
},
167+
sentryOptions: {
168+
replaysSessionSampleRate: 1.0,
169+
replaysOnErrorSampleRate: 0.0,
170+
},
171+
}));
172+
173+
const handler = handleAfterSendEvent(replay);
174+
175+
// Clear initial trace IDs
176+
replay['_context'].traceIds = [];
177+
178+
// Set a known propagation context trace ID that should NOT appear
179+
getCurrentScope().setPropagationContext({
180+
traceId: 'propagation00000000000000000000',
181+
sampleRand: 1,
182+
});
183+
184+
// Send a transaction event
185+
handler(Transaction('actual-trace-id'), { statusCode: 200 });
186+
187+
// Emit a recording event so the buffer has events for the flush
188+
mockRecord._emitter(getTestEventIncremental({ timestamp: BASE_TIMESTAMP }));
189+
190+
// Flush
191+
await vi.advanceTimersToNextTimerAsync();
192+
193+
// The replay event should only contain the transaction trace ID, not propagation context
194+
expect(replay).toHaveLastSentReplay({
195+
replayEventPayload: expect.objectContaining({
196+
trace_ids: ['actual-trace-id'],
197+
traces_by_timestamp: [[expect.any(Number), 'actual-trace-id']],
198+
}),
199+
});
200+
});
201+
202+
it('deduplicates trace_ids but preserves all entries in traces_by_timestamp', async () => {
203+
({ replay, mockRecord } = await resetSdkMock({
204+
replayOptions: {
205+
stickySession: false,
206+
},
207+
sentryOptions: {
208+
replaysSessionSampleRate: 1.0,
209+
replaysOnErrorSampleRate: 0.0,
210+
},
211+
}));
212+
213+
const handler = handleAfterSendEvent(replay);
214+
215+
// Clear initial trace IDs
216+
replay['_context'].traceIds = [];
217+
218+
// Send multiple transactions with the same trace ID
219+
handler(Transaction('same-trace-id'), { statusCode: 200 });
220+
handler(Transaction('same-trace-id'), { statusCode: 200 });
221+
handler(Transaction('different-trace-id'), { statusCode: 200 });
222+
223+
// Emit a recording event so the buffer has events for the flush
224+
mockRecord._emitter(getTestEventIncremental({ timestamp: BASE_TIMESTAMP }));
225+
226+
// Flush
227+
await vi.advanceTimersToNextTimerAsync();
228+
229+
// trace_ids should be deduplicated, but traces_by_timestamp should have all entries
230+
expect(replay).toHaveLastSentReplay({
231+
replayEventPayload: expect.objectContaining({
232+
trace_ids: ['same-trace-id', 'different-trace-id'],
233+
traces_by_timestamp: [
234+
[expect.any(Number), 'same-trace-id'],
235+
[expect.any(Number), 'same-trace-id'],
236+
[expect.any(Number), 'different-trace-id'],
237+
],
238+
}),
239+
});
240+
});
241+
242+
it('skips transaction events without start_timestamp', async () => {
243+
({ replay } = await resetSdkMock({
244+
replayOptions: {
245+
stickySession: false,
246+
},
247+
sentryOptions: {
248+
replaysSessionSampleRate: 1.0,
249+
replaysOnErrorSampleRate: 0.0,
250+
},
251+
}));
252+
253+
const handler = handleAfterSendEvent(replay);
254+
255+
// Clear initial trace IDs
256+
replay['_context'].traceIds = [];
257+
258+
// Create a transaction without start_timestamp
259+
const transactionWithoutTimestamp = Transaction('trace-no-ts');
260+
delete transactionWithoutTimestamp.start_timestamp;
261+
262+
handler(transactionWithoutTimestamp, { statusCode: 200 });
263+
264+
// Also send a valid transaction
265+
handler(Transaction('trace-valid'), { statusCode: 200 });
266+
267+
// Only the valid transaction should be recorded
268+
const traceIds = replay.getContext().traceIds;
269+
expect(traceIds).toHaveLength(1);
270+
expect(traceIds[0]![1]).toBe('trace-valid');
271+
});
272+
273+
it('preserves single trace id in _clearContext when only one exists', async () => {
274+
({ replay, mockRecord } = await resetSdkMock({
275+
replayOptions: {
276+
stickySession: false,
277+
},
278+
sentryOptions: {
279+
replaysSessionSampleRate: 1.0,
280+
replaysOnErrorSampleRate: 0.0,
281+
},
282+
}));
283+
284+
const handler = handleAfterSendEvent(replay);
285+
286+
// Clear initial trace IDs
287+
replay['_context'].traceIds = [];
288+
289+
// Add a single transaction
290+
handler(Transaction('only-trace'), { statusCode: 200 });
291+
292+
expect(replay.getContext().traceIds).toHaveLength(1);
293+
294+
// Emit a recording event so the buffer has events for the flush
295+
mockRecord._emitter(getTestEventIncremental({ timestamp: BASE_TIMESTAMP }));
296+
297+
// Flush
298+
await vi.advanceTimersToNextTimerAsync();
299+
300+
// With only 1 trace id, _clearContext should preserve it (length <= 1, no slicing needed)
301+
expect(replay.getContext().traceIds).toHaveLength(1);
302+
expect(replay.getContext().traceIds[0]![1]).toBe('only-trace');
303+
});
304+
});

0 commit comments

Comments
 (0)