@@ -12,6 +12,7 @@ import {
1212} from '../../src/browser/request' ;
1313import { addExtensionMethods } from '../../src/hubextensions' ;
1414import * as tracingUtils from '../../src/utils' ;
15+ import { objectFromEntries } from '../testutils' ;
1516
1617// This is a normal base64 regex, modified to reflect that fact that we strip the trailing = or == off
1718const stripped_base64 = '([a-zA-Z0-9+/]{4})*([a-zA-Z0-9+/]{2,3})?' ;
@@ -23,9 +24,29 @@ const TRACESTATE_HEADER_REGEX = new RegExp(
2324
2425beforeAll ( ( ) => {
2526 addExtensionMethods ( ) ;
26- // @ts -ignore need to override global Request because it's not in the jest environment (even with an
27- // `@jest-environment jsdom` directive, for some reason)
28- global . Request = { } ;
27+
28+ // Add Request to the global scope (necessary because for some reason Request isn't in the jest environment, even with
29+ // an `@jest-environment jsdom` directive)
30+
31+ type MockHeaders = {
32+ [ key : string ] : any ;
33+ append : ( key : string , value : string ) => void ;
34+ } ;
35+
36+ class Request {
37+ public headers : MockHeaders ;
38+ constructor ( ) {
39+ // We need our headers to act like an object for key-lookup purposes, but also have an append method that adds
40+ // items as its siblings. This hack precludes a key named `append`, of course, but for our purposes it's enough.
41+ const headers = { } as MockHeaders ;
42+ headers . append = ( key : string , value : any ) : void => {
43+ headers [ key ] = value ;
44+ } ;
45+ this . headers = headers ;
46+ }
47+ }
48+
49+ ( global as any ) . Request = Request ;
2950} ) ;
3051
3152const hasTracingEnabled = jest . spyOn ( tracingUtils , 'hasTracingEnabled' ) ;
@@ -63,7 +84,7 @@ describe('registerRequestInstrumentation', () => {
6384 } ) ;
6485} ) ;
6586
66- describe ( 'callbacks' , ( ) => {
87+ describe ( 'fetch and xhr callbacks' , ( ) => {
6788 let hub : Hub ;
6889 let transaction : Transaction ;
6990 const alwaysCreateSpan = ( ) => true ;
@@ -199,15 +220,67 @@ describe('callbacks', () => {
199220 expect ( newSpan ! . status ) . toBe ( SpanStatus . fromHttpCode ( 404 ) ) ;
200221 } ) ;
201222
202- it ( 'adds tracing headers to fetch requests', ( ) => {
203- // make a local copy so the global one doesn't get mutated
204- const handlerData = { ...fetchHandlerData } ;
223+ describe ( 'adding tracing headers to fetch requests', ( ) => {
224+ it ( 'can handle headers added with an `append` method' , ( ) => {
225+ const handlerData : FetchData = { ...fetchHandlerData , args : [ new Request ( 'http://dogs.are.great' ) , { } ] } ;
205226
206- fetchCallback ( handlerData , alwaysCreateSpan , { } ) ;
227+ fetchCallback ( handlerData , alwaysCreateSpan , { } ) ;
207228
208- const headers = ( handlerData . args [ 1 ] . headers as Record < string , string > ) || { } ;
209- expect ( headers [ 'sentry-trace' ] ) . toBeDefined ( ) ;
210- expect ( headers [ 'tracestate' ] ) . toBeDefined ( ) ;
229+ const headers = handlerData . args [ 1 ] . headers ;
230+ expect ( headers [ 'sentry-trace' ] ) . toBeDefined ( ) ;
231+ expect ( headers [ 'tracestate' ] ) . toBeDefined ( ) ;
232+ } ) ;
233+
234+ it ( 'can handle existing headers in array form' , ( ) => {
235+ const handlerData = {
236+ ...fetchHandlerData ,
237+ args : [
238+ 'http://dogs.are.great/' ,
239+ {
240+ headers : [
241+ [ 'GREETING_PROTOCOL' , 'mutual butt sniffing' ] ,
242+ [ 'TAIL_ACTION' , 'wagging' ] ,
243+ ] ,
244+ } ,
245+ ] ,
246+ } ;
247+
248+ fetchCallback ( handlerData , alwaysCreateSpan , { } ) ;
249+
250+ const headers = objectFromEntries ( ( handlerData . args [ 1 ] as any ) . headers ) ;
251+ expect ( headers [ 'sentry-trace' ] ) . toBeDefined ( ) ;
252+ expect ( headers [ 'tracestate' ] ) . toBeDefined ( ) ;
253+ } ) ;
254+
255+ it ( 'can handle existing headers in object form' , ( ) => {
256+ const handlerData = {
257+ ...fetchHandlerData ,
258+ args : [
259+ 'http://dogs.are.great/' ,
260+ {
261+ headers : { GREETING_PROTOCOL : 'mutual butt sniffing' , TAIL_ACTION : 'wagging' } ,
262+ } ,
263+ ] ,
264+ } ;
265+
266+ fetchCallback ( handlerData , alwaysCreateSpan , { } ) ;
267+
268+ const headers = ( handlerData . args [ 1 ] as any ) . headers ;
269+ expect ( headers [ 'sentry-trace' ] ) . toBeDefined ( ) ;
270+ expect ( headers [ 'tracestate' ] ) . toBeDefined ( ) ;
271+ } ) ;
272+
273+ it ( 'can handle there being no existing headers' , ( ) => {
274+ // override the value of `args`, even though we're overriding it with the same data, as a means of deep copying
275+ // the one part which gets mutated
276+ const handlerData = { ...fetchHandlerData , args : [ 'http://dogs.are.great/' , { } ] } ;
277+
278+ fetchCallback ( handlerData , alwaysCreateSpan , { } ) ;
279+
280+ const headers = ( handlerData . args [ 1 ] as any ) . headers ;
281+ expect ( headers [ 'sentry-trace' ] ) . toBeDefined ( ) ;
282+ expect ( headers [ 'tracestate' ] ) . toBeDefined ( ) ;
283+ } ) ;
211284 } ) ;
212285 } ) ;
213286
0 commit comments