@@ -6,12 +6,41 @@ import { Span, SpanStatus, Transaction } from '../../src';
6
6
import { fetchCallback , FetchData , instrumentOutgoingRequests , xhrCallback , XHRData } from '../../src/browser/request' ;
7
7
import { addExtensionMethods } from '../../src/hubextensions' ;
8
8
import * as tracingUtils from '../../src/utils' ;
9
+ import { objectFromEntries } from '../testutils' ;
10
+
11
+ // This is a normal base64 regex, modified to reflect that fact that we strip the trailing = or == off
12
+ const stripped_base64 = '([a-zA-Z0-9+/]{4})*([a-zA-Z0-9+/]{2,3})?' ;
13
+
14
+ const TRACESTATE_HEADER_REGEX = new RegExp (
15
+ `sentry=(${ stripped_base64 } )` + // our part of the header - should be the only part or at least the first part
16
+ `(,\\w+=\\w+)*` , // any number of copies of a comma followed by `name=value`
17
+ ) ;
9
18
10
19
beforeAll ( ( ) => {
11
20
addExtensionMethods ( ) ;
12
- // @ts -ignore need to override global Request because it's not in the jest environment (even with an
13
- // `@jest-environment jsdom` directive, for some reason)
14
- global . Request = { } ;
21
+
22
+ // Add Request to the global scope (necessary because for some reason Request isn't in the jest environment, even with
23
+ // an `@jest-environment jsdom` directive)
24
+
25
+ type MockHeaders = {
26
+ [ key : string ] : any ;
27
+ append : ( key : string , value : string ) => void ;
28
+ } ;
29
+
30
+ class Request {
31
+ public headers : MockHeaders ;
32
+ constructor ( ) {
33
+ // We need our headers to act like an object for key-lookup purposes, but also have an append method that adds
34
+ // items as its siblings. This hack precludes a key named `append`, of course, but for our purposes it's enough.
35
+ const headers = { } as MockHeaders ;
36
+ headers . append = ( key : string , value : any ) : void => {
37
+ headers [ key ] = value ;
38
+ } ;
39
+ this . headers = headers ;
40
+ }
41
+ }
42
+
43
+ ( global as any ) . Request = Request ;
15
44
} ) ;
16
45
17
46
const hasTracingEnabled = jest . spyOn ( tracingUtils , 'hasTracingEnabled' ) ;
@@ -49,15 +78,15 @@ describe('instrumentOutgoingRequests', () => {
49
78
} ) ;
50
79
} ) ;
51
80
52
- describe ( 'callbacks' , ( ) => {
81
+ describe ( 'fetch and xhr callbacks' , ( ) => {
53
82
let hub : Hub ;
54
83
let transaction : Transaction ;
55
84
const alwaysCreateSpan = ( ) => true ;
56
85
const neverCreateSpan = ( ) => false ;
57
86
const fetchHandlerData : FetchData = {
58
87
args : [ 'http://dogs.are.great/' , { } ] ,
59
88
fetchData : { url : 'http://dogs.are.great/' , method : 'GET' } ,
60
- startTimestamp : 1356996072000 ,
89
+ startTimestamp : 2012112120121231 ,
61
90
} ;
62
91
const xhrHandlerData : XHRData = {
63
92
xhr : {
@@ -72,18 +101,27 @@ describe('callbacks', () => {
72
101
// setRequestHeader: XMLHttpRequest.prototype.setRequestHeader,
73
102
setRequestHeader,
74
103
} ,
75
- startTimestamp : 1353501072000 ,
104
+ startTimestamp : 2012112120121231 ,
76
105
} ;
77
- const endTimestamp = 1356996072000 ;
106
+ const endTimestamp = 2013041520130908 ;
78
107
79
108
beforeAll ( ( ) => {
80
- hub = new Hub ( new BrowserClient ( { tracesSampleRate : 1 } ) ) ;
109
+ hub = new Hub (
110
+ new BrowserClient ( {
111
+ dsn :
'https://[email protected] /12312012' ,
112
+ environment : 'dogpark' ,
113
+ release : 'off.leash.park' ,
114
+
115
+ tracesSampleRate : 1 ,
116
+ } ) ,
117
+ ) ;
81
118
makeMain ( hub ) ;
82
119
} ) ;
83
120
84
121
beforeEach ( ( ) => {
85
- transaction = hub . startTransaction ( { name : 'organizations/users/:userid ' , op : 'pageload ' } ) as Transaction ;
122
+ transaction = hub . startTransaction ( { name : 'meetNewDogFriend ' , op : 'wag.tail ' } ) as Transaction ;
86
123
hub . configureScope ( scope => scope . setSpan ( transaction ) ) ;
124
+ jest . clearAllMocks ( ) ;
87
125
} ) ;
88
126
89
127
describe ( 'fetchCallback()' , ( ) => {
@@ -111,20 +149,17 @@ describe('callbacks', () => {
111
149
expect ( spans ) . toEqual ( { } ) ;
112
150
} ) ;
113
151
114
- it ( 'does not add fetch request headers if tracing is disabled' , ( ) => {
152
+ it ( 'does not add tracing headers if tracing is disabled' , ( ) => {
115
153
hasTracingEnabled . mockReturnValueOnce ( false ) ;
116
154
117
155
// make a local copy so the global one doesn't get mutated
118
- const handlerData : FetchData = {
119
- args : [ 'http://dogs.are.great/' , { } ] ,
120
- fetchData : { url : 'http://dogs.are.great/' , method : 'GET' } ,
121
- startTimestamp : 1353501072000 ,
122
- } ;
156
+ const handlerData = { ...fetchHandlerData } ;
123
157
124
158
fetchCallback ( handlerData , alwaysCreateSpan , { } ) ;
125
159
126
160
const headers = ( handlerData . args [ 1 ] . headers as Record < string , string > ) || { } ;
127
161
expect ( headers [ 'sentry-trace' ] ) . not . toBeDefined ( ) ;
162
+ expect ( headers [ 'tracestate' ] ) . not . toBeDefined ( ) ;
128
163
} ) ;
129
164
130
165
it ( 'creates and finishes fetch span on active transaction' , ( ) => {
@@ -179,8 +214,67 @@ describe('callbacks', () => {
179
214
expect ( newSpan ! . status ) . toBe ( SpanStatus . fromHttpCode ( 404 ) ) ;
180
215
} ) ;
181
216
182
- it ( 'adds sentry-trace header to fetch requests' , ( ) => {
183
- // TODO
217
+ describe ( 'adding tracing headers to fetch requests' , ( ) => {
218
+ it ( 'can handle headers added with an `append` method' , ( ) => {
219
+ const handlerData : FetchData = { ...fetchHandlerData , args : [ new Request ( 'http://dogs.are.great' ) , { } ] } ;
220
+
221
+ fetchCallback ( handlerData , alwaysCreateSpan , { } ) ;
222
+
223
+ const headers = handlerData . args [ 1 ] . headers ;
224
+ expect ( headers [ 'sentry-trace' ] ) . toBeDefined ( ) ;
225
+ expect ( headers [ 'tracestate' ] ) . toBeDefined ( ) ;
226
+ } ) ;
227
+
228
+ it ( 'can handle existing headers in array form' , ( ) => {
229
+ const handlerData = {
230
+ ...fetchHandlerData ,
231
+ args : [
232
+ 'http://dogs.are.great/' ,
233
+ {
234
+ headers : [
235
+ [ 'GREETING_PROTOCOL' , 'mutual butt sniffing' ] ,
236
+ [ 'TAIL_ACTION' , 'wagging' ] ,
237
+ ] ,
238
+ } ,
239
+ ] ,
240
+ } ;
241
+
242
+ fetchCallback ( handlerData , alwaysCreateSpan , { } ) ;
243
+
244
+ const headers = objectFromEntries ( ( handlerData . args [ 1 ] as any ) . headers ) ;
245
+ expect ( headers [ 'sentry-trace' ] ) . toBeDefined ( ) ;
246
+ expect ( headers [ 'tracestate' ] ) . toBeDefined ( ) ;
247
+ } ) ;
248
+
249
+ it ( 'can handle existing headers in object form' , ( ) => {
250
+ const handlerData = {
251
+ ...fetchHandlerData ,
252
+ args : [
253
+ 'http://dogs.are.great/' ,
254
+ {
255
+ headers : { GREETING_PROTOCOL : 'mutual butt sniffing' , TAIL_ACTION : 'wagging' } ,
256
+ } ,
257
+ ] ,
258
+ } ;
259
+
260
+ fetchCallback ( handlerData , alwaysCreateSpan , { } ) ;
261
+
262
+ const headers = ( handlerData . args [ 1 ] as any ) . headers ;
263
+ expect ( headers [ 'sentry-trace' ] ) . toBeDefined ( ) ;
264
+ expect ( headers [ 'tracestate' ] ) . toBeDefined ( ) ;
265
+ } ) ;
266
+
267
+ it ( 'can handle there being no existing headers' , ( ) => {
268
+ // override the value of `args`, even though we're overriding it with the same data, as a means of deep copying
269
+ // the one part which gets mutated
270
+ const handlerData = { ...fetchHandlerData , args : [ 'http://dogs.are.great/' , { } ] } ;
271
+
272
+ fetchCallback ( handlerData , alwaysCreateSpan , { } ) ;
273
+
274
+ const headers = ( handlerData . args [ 1 ] as any ) . headers ;
275
+ expect ( headers [ 'sentry-trace' ] ) . toBeDefined ( ) ;
276
+ expect ( headers [ 'tracestate' ] ) . toBeDefined ( ) ;
277
+ } ) ;
184
278
} ) ;
185
279
} ) ;
186
280
@@ -201,21 +295,22 @@ describe('callbacks', () => {
201
295
expect ( spans ) . toEqual ( { } ) ;
202
296
} ) ;
203
297
204
- it ( 'does not add xhr request headers if tracing is disabled' , ( ) => {
298
+ it ( 'does not add tracing headers if tracing is disabled' , ( ) => {
205
299
hasTracingEnabled . mockReturnValueOnce ( false ) ;
206
300
207
301
xhrCallback ( xhrHandlerData , alwaysCreateSpan , { } ) ;
208
302
209
303
expect ( setRequestHeader ) . not . toHaveBeenCalled ( ) ;
210
304
} ) ;
211
305
212
- it ( 'adds sentry-trace header to XHR requests' , ( ) => {
306
+ it ( 'adds tracing headers to XHR requests' , ( ) => {
213
307
xhrCallback ( xhrHandlerData , alwaysCreateSpan , { } ) ;
214
308
215
309
expect ( setRequestHeader ) . toHaveBeenCalledWith (
216
310
'sentry-trace' ,
217
311
expect . stringMatching ( tracingUtils . SENTRY_TRACE_REGEX ) ,
218
312
) ;
313
+ expect ( setRequestHeader ) . toHaveBeenCalledWith ( 'tracestate' , expect . stringMatching ( TRACESTATE_HEADER_REGEX ) ) ;
219
314
} ) ;
220
315
221
316
it ( 'creates and finishes XHR span on active transaction' , ( ) => {
0 commit comments