|
1 | 1 | /* eslint-disable @typescript-eslint/unbound-method */
|
2 | 2 | import { BrowserClient } from '@sentry/browser';
|
3 | 3 | import { getMainCarrier, Hub } from '@sentry/hub';
|
| 4 | +import * as hubModule from '@sentry/hub'; |
4 | 5 | import * as utilsModule from '@sentry/utils'; // for mocking
|
5 | 6 | import { getGlobalObject, isNodeEnv, logger } from '@sentry/utils';
|
6 | 7 | import * as nodeHttpModule from 'http';
|
7 | 8 |
|
| 9 | +import { BrowserTracing } from '../src/browser/browsertracing'; |
8 | 10 | import { addExtensionMethods } from '../src/hubextensions';
|
| 11 | +import { extractTraceparentData, TRACEPARENT_REGEXP } from '../src/utils'; |
| 12 | +import { addDOMPropertiesToGlobal, getSymbolObjectKeyByName } from './testutils'; |
9 | 13 |
|
10 | 14 | addExtensionMethods();
|
11 | 15 |
|
12 | 16 | const mathRandom = jest.spyOn(Math, 'random');
|
| 17 | + |
| 18 | +// we have to add things into the real global object (rather than mocking the return value of getGlobalObject) because |
| 19 | +// there are modules which call getGlobalObject as they load, which is seemingly too early for jest to intervene |
| 20 | +addDOMPropertiesToGlobal(['XMLHttpRequest', 'Event', 'location', 'document']); |
| 21 | + |
13 | 22 | describe('Hub', () => {
|
14 | 23 | beforeEach(() => {
|
15 | 24 | jest.spyOn(logger, 'warn');
|
@@ -339,12 +348,84 @@ describe('Hub', () => {
|
339 | 348 | expect(child.sampled).toBe(transaction.sampled);
|
340 | 349 | });
|
341 | 350 |
|
342 |
| - it('should propagate sampling decision to child transactions in XHR header', () => { |
343 |
| - // TODO fix this and write the test |
| 351 | + it('should propagate positive sampling decision to child transactions in XHR header', () => { |
| 352 | + const hub = new Hub( |
| 353 | + new BrowserClient({ |
| 354 | + dsn: 'https://[email protected]/1121', |
| 355 | + tracesSampleRate: 1, |
| 356 | + integrations: [new BrowserTracing()], |
| 357 | + }), |
| 358 | + ); |
| 359 | + jest.spyOn(hubModule, 'getCurrentHub').mockReturnValue(hub); |
| 360 | + |
| 361 | + const transaction = hub.startTransaction({ name: 'dogpark' }); |
| 362 | + hub.configureScope(scope => { |
| 363 | + scope.setSpan(transaction); |
| 364 | + }); |
| 365 | + |
| 366 | + const request = new XMLHttpRequest(); |
| 367 | + request.open('GET', '/chase-partners'); |
| 368 | + |
| 369 | + // mock a response having been received successfully (we have to do it in this roundabout way because readyState |
| 370 | + // is readonly and changing it doesn't trigger a readystatechange event) |
| 371 | + Object.defineProperty(request, 'readyState', { value: 4 }); |
| 372 | + request.dispatchEvent(new Event('readystatechange')); |
| 373 | + |
| 374 | + // this looks weird, it's true, but it's really just `request.impl.flag.requestHeaders` - it's just that the |
| 375 | + // `impl` key is a symbol rather than a string, and therefore needs to be referred to by reference rather than |
| 376 | + // value |
| 377 | + const headers = (request as any)[getSymbolObjectKeyByName(request, 'impl') as symbol].flag.requestHeaders; |
| 378 | + |
| 379 | + // check that sentry-trace header is added to request |
| 380 | + expect(headers).toEqual(expect.objectContaining({ 'sentry-trace': expect.stringMatching(TRACEPARENT_REGEXP) })); |
| 381 | + |
| 382 | + // check that sampling decision is passed down correctly |
| 383 | + expect(transaction.sampled).toBe(true); |
| 384 | + expect(extractTraceparentData(headers['sentry-trace'])!.parentSampled).toBe(true); |
| 385 | + }); |
| 386 | + |
| 387 | + it('should propagate negative sampling decision to child transactions in XHR header', () => { |
| 388 | + const hub = new Hub( |
| 389 | + new BrowserClient({ |
| 390 | + dsn: 'https://[email protected]/1121', |
| 391 | + tracesSampleRate: 1, |
| 392 | + integrations: [new BrowserTracing()], |
| 393 | + }), |
| 394 | + ); |
| 395 | + jest.spyOn(hubModule, 'getCurrentHub').mockReturnValue(hub); |
| 396 | + |
| 397 | + const transaction = hub.startTransaction({ name: 'dogpark', sampled: false }); |
| 398 | + hub.configureScope(scope => { |
| 399 | + scope.setSpan(transaction); |
| 400 | + }); |
| 401 | + |
| 402 | + const request = new XMLHttpRequest(); |
| 403 | + request.open('GET', '/chase-partners'); |
| 404 | + |
| 405 | + // mock a response having been received successfully (we have to do it in this roundabout way because readyState |
| 406 | + // is readonly and changing it doesn't trigger a readystatechange event) |
| 407 | + Object.defineProperty(request, 'readyState', { value: 4 }); |
| 408 | + request.dispatchEvent(new Event('readystatechange')); |
| 409 | + |
| 410 | + // this looks weird, it's true, but it's really just `request.impl.flag.requestHeaders` - it's just that the |
| 411 | + // `impl` key is a symbol rather than a string, and therefore needs to be referred to by reference rather than |
| 412 | + // value |
| 413 | + const headers = (request as any)[getSymbolObjectKeyByName(request, 'impl') as symbol].flag.requestHeaders; |
| 414 | + |
| 415 | + // check that sentry-trace header is added to request |
| 416 | + expect(headers).toEqual(expect.objectContaining({ 'sentry-trace': expect.stringMatching(TRACEPARENT_REGEXP) })); |
| 417 | + |
| 418 | + // check that sampling decision is passed down correctly |
| 419 | + expect(transaction.sampled).toBe(false); |
| 420 | + expect(extractTraceparentData(headers['sentry-trace'])!.parentSampled).toBe(false); |
| 421 | + }); |
| 422 | + |
| 423 | + it('should propagate positive sampling decision to child transactions in fetch header', () => { |
| 424 | + // TODO |
344 | 425 | });
|
345 | 426 |
|
346 |
| - it('should propagate sampling decision to child transactions in fetch header', () => { |
347 |
| - // TODO fix this and write the test |
| 427 | + it('should propagate negative sampling decision to child transactions in fetch header', () => { |
| 428 | + // TODO |
348 | 429 | });
|
349 | 430 |
|
350 | 431 | it("should inherit parent's positive sampling decision if tracesSampler is undefined", () => {
|
|
0 commit comments