@microsoft/applicationinsights-dependencies-js 2.7.2-nightly.2111-05 → 2.7.2-nightly.2111-09

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/Tests/Unit/src/ajax.tests.ts +2708 -0
  2. package/Tests/Unit/src/dependencies.tests.ts +7 -0
  3. package/Tests/UnitTests.html +52 -0
  4. package/Tests/tsconfig.json +13 -0
  5. package/api-extractor.json +361 -0
  6. package/applicationinsights-dependencies-js.build.error.log +20 -0
  7. package/applicationinsights-dependencies-js.build.log +249 -0
  8. package/browser/applicationinsights-dependencies-js.integrity.json +9 -9
  9. package/browser/applicationinsights-dependencies-js.js +1 -1
  10. package/browser/applicationinsights-dependencies-js.js.map +1 -1
  11. package/browser/applicationinsights-dependencies-js.min.js +1 -1
  12. package/browser/applicationinsights-dependencies-js.min.js.map +1 -1
  13. package/dist/applicationinsights-dependencies-js.api.json +1 -1
  14. package/dist/applicationinsights-dependencies-js.d.ts +1 -1
  15. package/dist/applicationinsights-dependencies-js.js +1 -1
  16. package/dist/applicationinsights-dependencies-js.js.map +1 -1
  17. package/dist/applicationinsights-dependencies-js.min.js +1 -1
  18. package/dist/applicationinsights-dependencies-js.min.js.map +1 -1
  19. package/dist/applicationinsights-dependencies-js.rollup.d.ts +1 -1
  20. package/dist-esm/TraceParent.js +1 -1
  21. package/dist-esm/ajax.js +1 -1
  22. package/dist-esm/ajaxRecord.js +1 -1
  23. package/dist-esm/ajaxUtils.js +1 -1
  24. package/dist-esm/applicationinsights-dependencies-js.js +1 -1
  25. package/microsoft-applicationinsights-dependencies-js-2.7.2-nightly.2111-09.tgz +0 -0
  26. package/package.json +3 -3
  27. package/rollup.config.js +139 -0
  28. package/temp/applicationinsights-dependencies-js.api.md +136 -0
  29. package/tslint.json +8 -0
  30. package/types/tsdoc-metadata.json +1 -1
@@ -0,0 +1,2708 @@
1
+ import { SinonStub } from "sinon";
2
+ import { Assert, AITestClass, PollingAssert } from "@microsoft/ai-test-framework";
3
+ import { AjaxMonitor } from "../../../src/ajax";
4
+ import { DisabledPropertyName, IConfig, DistributedTracingModes, RequestHeaders, IDependencyTelemetry, IRequestContext } from "@microsoft/applicationinsights-common";
5
+ import {
6
+ AppInsightsCore, IConfiguration, ITelemetryItem, ITelemetryPlugin, IChannelControls, _InternalMessageId,
7
+ getPerformance, getGlobalInst, getGlobal
8
+ } from "@microsoft/applicationinsights-core-js";
9
+
10
+ interface IFetchArgs {
11
+ input: RequestInfo,
12
+ init: RequestInit
13
+ }
14
+
15
+ function hookFetch<T>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void): IFetchArgs[] {
16
+ let calls:IFetchArgs[] = [];
17
+ let global = getGlobal() as any;
18
+ global.fetch = function(input: RequestInfo, init?: RequestInit) {
19
+ calls.push({
20
+ input,
21
+ init
22
+ });
23
+ return new window["SimpleSyncPromise"](executor);
24
+ }
25
+
26
+ return calls;
27
+ }
28
+
29
+ function hookTrackDependencyInternal(ajaxMonitor: AjaxMonitor) {
30
+ let orgInternalDependency: (dependency: IDependencyTelemetry, properties?: { [key: string]: any }) => void = ajaxMonitor["trackDependencyDataInternal"];
31
+ let dependencyArgs: IDependencyTelemetry[] = [];
32
+
33
+ ajaxMonitor["trackDependencyDataInternal"] = function (dependency: IDependencyTelemetry, properties?: { [key: string]: any }) {
34
+ let orgArguments = arguments;
35
+ dependencyArgs.push({ ...dependency});
36
+ orgInternalDependency.apply(ajaxMonitor, orgArguments);
37
+ };
38
+
39
+ return dependencyArgs;
40
+ }
41
+
42
+ export class AjaxTests extends AITestClass {
43
+ private _fetch;
44
+ private _ajax:AjaxMonitor = null;
45
+ private _context:any = {};
46
+
47
+ public testInitialize() {
48
+ this._context = {};
49
+ this.useFakeServer = true;
50
+ this._fetch = getGlobalInst("fetch");
51
+ }
52
+
53
+ public testCleanup() {
54
+ this._context = {};
55
+ if (this._ajax !== null) {
56
+ this._ajax.teardown();
57
+ this._ajax = null;
58
+ }
59
+ getGlobal().fetch = this._fetch;
60
+ }
61
+
62
+ public registerTests() {
63
+ this.testCase({
64
+ name: "Dependencies Configuration: Config can be set from root config",
65
+ test: () => {
66
+ this._ajax = new AjaxMonitor();
67
+ let dependencyFields = hookTrackDependencyInternal(this._ajax);
68
+ let appInsightsCore = new AppInsightsCore();
69
+ let coreConfig = {
70
+ instrumentationKey: "instrumentation_key",
71
+ maxAjaxCallsPerView: 5,
72
+ };
73
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
74
+ let trackSpy = this.sandbox.spy(appInsightsCore, "track")
75
+ let throwSpy = this.sandbox.spy(appInsightsCore.logger, "throwInternal");
76
+
77
+ for (let lp = 0; lp < 5; lp++) {
78
+ var xhr = new XMLHttpRequest();
79
+ xhr.open("GET", "http://microsoft.com");
80
+ xhr.setRequestHeader("header name", "header value");
81
+ xhr.send();
82
+ // Emulate response
83
+ (<any>xhr).respond(200, {"Content-Type": "application/json; charset=utf-8", "Access-Control-Allow-Origin": "*"}, "");
84
+ }
85
+ Assert.equal(5, trackSpy.callCount, "Track has been called 5 times");
86
+ Assert.equal(false, throwSpy.called, "We should not have thrown an internal error -- yet");
87
+
88
+ var xhr = new XMLHttpRequest();
89
+ xhr.open("GET", "http://microsoft.com");
90
+ xhr.setRequestHeader("header name", "header value");
91
+ xhr.send();
92
+ // Emulate response
93
+ (<any>xhr).respond(200, {"Content-Type": "application/json; charset=utf-8", "Access-Control-Allow-Origin": "*"}, "");
94
+
95
+ Assert.equal(5, trackSpy.callCount, "Track has still only been called 5 times");
96
+ Assert.equal(true, throwSpy.called, "We should have thrown an internal error");
97
+ Assert.equal(_InternalMessageId.MaxAjaxPerPVExceeded, throwSpy.args[0][1], "Reported error should be max exceeded");
98
+ Assert.equal(true, throwSpy.args[0][2].indexOf("ajax per page view limit") !== -1, "Reported error should be contain text describing the issue");
99
+
100
+ Assert.equal(6, dependencyFields.length, "trackDependencyDataInternal should have been called");
101
+ for (let lp = 0; lp < 6; lp++) {
102
+ Assert.ok(dependencyFields[lp].startTime, `startTime ${lp} was specified before trackDependencyDataInternal was called`);
103
+ }
104
+ }
105
+ });
106
+
107
+ this.testCase({
108
+ name: "Dependencies Configuration: Make sure we don't fail for invalid arguments",
109
+ test: () => {
110
+ this._ajax = new AjaxMonitor();
111
+ let dependencyFields = hookTrackDependencyInternal(this._ajax);
112
+ let appInsightsCore = new AppInsightsCore();
113
+ let coreConfig = {
114
+ instrumentationKey: "instrumentation_key",
115
+ maxAjaxCallsPerView: 5,
116
+ };
117
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
118
+ let trackSpy = this.sandbox.spy(appInsightsCore, "track")
119
+ let throwSpy = this.sandbox.spy(appInsightsCore.logger, "throwInternal");
120
+
121
+ var xhr = new XMLHttpRequest();
122
+ xhr.open("GET", null);
123
+ xhr.setRequestHeader("header name", "header value");
124
+ xhr.send();
125
+ // Emulate response
126
+ (<any>xhr).respond(200, {"Content-Type": "application/json; charset=utf-8", "Access-Control-Allow-Origin": "*"}, "");
127
+
128
+ var xhr = new XMLHttpRequest();
129
+ xhr.open("GET", undefined);
130
+ xhr.setRequestHeader("header name", "header value");
131
+ xhr.send();
132
+ // Emulate response
133
+ (<any>xhr).respond(200, {"Content-Type": "application/json; charset=utf-8", "Access-Control-Allow-Origin": "*"}, "");
134
+
135
+ Assert.equal(2, trackSpy.callCount, "Track has been called 2 times");
136
+ Assert.equal(2, dependencyFields.length, "trackDependencyDataInternal should have been called");
137
+ Assert.ok(dependencyFields[0].startTime, "startTime 0 was specified before trackDependencyDataInternal was called")
138
+ Assert.ok(dependencyFields[1].startTime, "startTime 1 was specified before trackDependencyDataInternal was called")
139
+
140
+ Assert.equal(false, throwSpy.called, "We should not have thrown an internal error -- yet");
141
+ }
142
+ });
143
+
144
+ this.testCase({
145
+ name: "Ajax: xhr.open gets instrumented",
146
+ test: () => {
147
+ this._ajax = new AjaxMonitor();
148
+ let appInsightsCore = new AppInsightsCore();
149
+ let coreConfig = { instrumentationKey: "instrumentationKey", extensionConfig: {"AjaxPlugin": {}}};
150
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
151
+
152
+ // act
153
+ var xhr = new XMLHttpRequest();
154
+ xhr.open("GET", "http://microsoft.com");
155
+
156
+ // assert
157
+ Assert.ok((<any>xhr).ajaxData)
158
+ var ajaxData = (<any>xhr).ajaxData;
159
+ Assert.equal("http://microsoft.com", ajaxData.requestUrl, "RequestUrl is collected correctly");
160
+ }
161
+ });
162
+
163
+ this.testCase({
164
+ name: "Ajax: xhr with disabled flag isn't tracked",
165
+ test: () => {
166
+ this._ajax = new AjaxMonitor();
167
+ let appInsightsCore = new AppInsightsCore();
168
+ let coreConfig: IConfiguration & IConfig = { instrumentationKey: "", disableAjaxTracking: false };
169
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
170
+
171
+ // act
172
+ var xhr = new XMLHttpRequest();
173
+ xhr[DisabledPropertyName] = true;
174
+ xhr.open("GET", "http://microsoft.com");
175
+
176
+ // assert
177
+ Assert.equal(undefined, (<any>xhr).ajaxData, "RequestUrl is collected correctly");
178
+ }
179
+ });
180
+
181
+ this.testCase({
182
+ name: "Ajax: xhr with disabled flag isn't tracked and any followup request to the same URL event without the disabled flag are also not tracked",
183
+ test: () => {
184
+ this._ajax = new AjaxMonitor();
185
+ let appInsightsCore = new AppInsightsCore();
186
+ let coreConfig: IConfiguration & IConfig = { instrumentationKey: "", disableAjaxTracking: false };
187
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
188
+
189
+ // act
190
+ var xhr = new XMLHttpRequest();
191
+ xhr[DisabledPropertyName] = true;
192
+ xhr.open("GET", "http://microsoft.com");
193
+
194
+ // assert
195
+ Assert.equal(undefined, (<any>xhr).ajaxData, "RequestUrl is collected correctly");
196
+
197
+ xhr = new XMLHttpRequest();
198
+ xhr.open("GET", "http://microsoft.com");
199
+
200
+ // assert
201
+ Assert.equal(undefined, (<any>xhr).ajaxData, "Follow up GET Request was not instrumented");
202
+
203
+ xhr = new XMLHttpRequest();
204
+ xhr.open("POST", "http://microsoft.com");
205
+
206
+ // assert
207
+ Assert.equal(undefined, (<any>xhr).ajaxData, "Follow up POST Request was not instrumented");
208
+ }
209
+ });
210
+
211
+ this.testCase({
212
+ name: "Ajax: xhr without disabled flag but with exclude string configured are not tracked",
213
+ test: () => {
214
+ this._ajax = new AjaxMonitor();
215
+ let appInsightsCore = new AppInsightsCore();
216
+ const ExcludeRequestRegex = ["microsoft"];
217
+ let coreConfig: IConfiguration & IConfig = { instrumentationKey: "", disableAjaxTracking: true, excludeRequestFromAutoTrackingPatterns: ExcludeRequestRegex };
218
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
219
+
220
+ // act
221
+ var xhr = new XMLHttpRequest();
222
+ xhr.open("GET", "http://microsoft.com");
223
+
224
+ // assert
225
+ Assert.equal(undefined, (<any>xhr).ajaxData, "RequestUrl is collected correctly");
226
+
227
+ xhr = new XMLHttpRequest();
228
+ xhr.open("GET", "http://microsoft.com");
229
+
230
+ // assert
231
+ Assert.equal(undefined, (<any>xhr).ajaxData, "Follow up GET Request was not instrumented");
232
+
233
+ xhr = new XMLHttpRequest();
234
+ xhr.open("POST", "http://microsoft.com");
235
+
236
+ // assert
237
+ Assert.equal(undefined, (<any>xhr).ajaxData, "Follow up POST Request was not instrumented");
238
+ }
239
+ });
240
+
241
+ this.testCase({
242
+ name: "Ajax: add context into custom dimension with call back configuration on AI initialization.",
243
+ test: () => {
244
+ this._ajax = new AjaxMonitor();
245
+ let dependencyFields = hookTrackDependencyInternal(this._ajax);
246
+ let appInsightsCore = new AppInsightsCore();
247
+ var trackStub = this.sandbox.stub(appInsightsCore, "track");
248
+
249
+ let coreConfig: IConfiguration & IConfig = {
250
+ instrumentationKey: "",
251
+ disableAjaxTracking: false,
252
+ addRequestContext: (requestContext: IRequestContext) => {
253
+ return {
254
+ test: "ajax context",
255
+ xhrStatus: requestContext.status
256
+ }
257
+ }
258
+ };
259
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
260
+
261
+ // act
262
+ var xhr = new XMLHttpRequest();
263
+ xhr.open("GET", "http://microsoft.com");
264
+ xhr.send();
265
+
266
+ // Emulate response
267
+ (<any>xhr).respond(200, {}, "");
268
+
269
+ Assert.equal(1, dependencyFields.length, "trackDependencyDataInternal was called");
270
+
271
+ // assert
272
+ Assert.ok(trackStub.calledOnce, "track is called");
273
+ let data = trackStub.args[0][0].baseData;
274
+ Assert.equal("Ajax", data.type, "request is Ajax type");
275
+ Assert.equal("ajax context", data.properties.test, "xhr request's request context is added when customer configures addRequestContext.");
276
+ Assert.equal(200, data.properties.xhrStatus, "xhr object properties are captured");
277
+ }
278
+ });
279
+
280
+ this.testCase({
281
+ name: "Ajax: xhr request header is tracked as part C data when enableRequestHeaderTracking flag is true",
282
+ test: () => {
283
+ this._ajax = new AjaxMonitor();
284
+ let appInsightsCore = new AppInsightsCore();
285
+ let coreConfig: IConfiguration & IConfig = { instrumentationKey: "abc", disableAjaxTracking: false, enableRequestHeaderTracking: true };
286
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
287
+
288
+ var trackStub = this.sandbox.stub(appInsightsCore, "track");
289
+
290
+ // act
291
+ var xhr = new XMLHttpRequest();
292
+ xhr.open("GET", "http://microsoft.com");
293
+ xhr.setRequestHeader("header name", "header value");
294
+ xhr.setRequestHeader("Authorization", "Authorization");
295
+ xhr.send();
296
+
297
+ // Emulate response
298
+ (<any>xhr).respond(200, {}, "");
299
+
300
+ // assert
301
+ Assert.ok(trackStub.calledOnce, "track is called");
302
+ let data = trackStub.args[0][0].baseData;
303
+ Assert.equal("Ajax", data.type, "request is Ajax type");
304
+ Assert.notEqual(undefined, data.properties.requestHeaders, "xhr request's request header is stored");
305
+ Assert.equal(undefined, data.properties.requestHeaders["Authorization"], "xhr request's request header is not ignored when the header is in ignoreHeaders");
306
+ Assert.equal(undefined, data.properties.responseHeaders, "xhr request's reponse header is not stored when enableResponseHeaderTracking flag is not set, default is false");
307
+ }
308
+ });
309
+
310
+ this.testCase({
311
+ name: "Ajax: xhr request header is tracked as part C data when enableResponseHeaderTracking flag is true",
312
+ test: () => {
313
+ this._ajax = new AjaxMonitor();
314
+ let appInsightsCore = new AppInsightsCore();
315
+ let coreConfig: IConfiguration & IConfig = { instrumentationKey: "abc", disableAjaxTracking: false, enableResponseHeaderTracking: true };
316
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
317
+
318
+ var trackStub = this.sandbox.stub(appInsightsCore, "track");
319
+
320
+ // act
321
+ var xhr = new XMLHttpRequest();
322
+ xhr.open("GET", "http://microsoft.com");
323
+ xhr.send();
324
+
325
+ // Emulate response
326
+ (<any>xhr).respond(200, {"Content-Type": "application/json; charset=utf-8", "Access-Control-Allow-Origin": "*","Authorization":"Authorization"}, "");
327
+
328
+ // assert
329
+ Assert.ok(trackStub.calledOnce, "track is called");
330
+ let data = trackStub.args[0][0].baseData;
331
+ Assert.equal("Ajax", data.type, "request is Ajax type");
332
+ Assert.equal(undefined, data.properties.requestHeaders, "xhr request's request header is not stored when enableRequestHeaderTracking flag is not set, default is false");
333
+ Assert.notEqual(undefined, data.properties.responseHeaders, "xhr request's reponse header is stored");
334
+ Assert.equal(undefined, data.properties.responseHeaders["Authorization"], "xhr request's reponse header is not ignored when the header is in ignoreHeader");
335
+ }
336
+ });
337
+
338
+ this.testCase({
339
+ name: "Ajax: xhr respond error data is tracked as part C data when enableAjaxErrorStatusText flag is true",
340
+ test: () => {
341
+ this._ajax = new AjaxMonitor();
342
+ let appInsightsCore = new AppInsightsCore();
343
+ let coreConfig: IConfiguration & IConfig = { instrumentationKey: "abc", disableAjaxTracking: false, enableAjaxErrorStatusText: true };
344
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
345
+
346
+ var trackStub = this.sandbox.stub(appInsightsCore, "track");
347
+
348
+ // act
349
+ var xhr = new XMLHttpRequest();
350
+ xhr.open("GET", "http://microsoft.com");
351
+ xhr.send();
352
+
353
+ // Emulate response
354
+ (<any>xhr).respond(403, {}, "error data with status code 403");
355
+
356
+ // assert
357
+ Assert.ok(trackStub.calledOnce, "track is called");
358
+ let data = trackStub.args[0][0].baseData;
359
+ Assert.equal("Ajax", data.type, "request is Ajax type");
360
+ Assert.notEqual(undefined, data.properties.responseText, "xhr request's reponse error is stored in part C");
361
+ Assert.strictEqual("Forbidden - error data with status code 403", data.properties.responseText, "xhr responseType is \"\"");
362
+
363
+ // act
364
+ var xhr2 = new XMLHttpRequest();
365
+ xhr2.open("GET", "http://microsoft.com");
366
+ xhr2.responseType = "json";
367
+ xhr2.send();
368
+
369
+ // Emulate response
370
+ let responseJSON = '{ "error":"error data with status code 403" }';
371
+ (<any>xhr2).respond(403, {}, responseJSON);
372
+
373
+ // assert
374
+ Assert.ok(trackStub.calledTwice, "track is called");
375
+ data = trackStub.args[1][0].baseData;
376
+ Assert.equal("Ajax", data.type, "request is Ajax type");
377
+ Assert.notEqual(undefined, data.properties.responseText, "xhr request's reponse error is stored in part C");
378
+ Assert.strictEqual("Forbidden - {\"error\":\"error data with status code 403\"}", data.properties.responseText, "xhr responseType is JSON, response got parsed");
379
+ }
380
+ });
381
+
382
+ this.testCase({
383
+ name: "Ajax: xhr respond error data is not tracked as part C data when enableAjaxErrorStatusText flag is false",
384
+ test: () => {
385
+ this._ajax = new AjaxMonitor();
386
+ let appInsightsCore = new AppInsightsCore();
387
+ let coreConfig: IConfiguration & IConfig = { instrumentationKey: "abc", disableAjaxTracking: false, enableAjaxErrorStatusText: false };
388
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
389
+
390
+ var trackStub = this.sandbox.stub(appInsightsCore, "track");
391
+
392
+ // act
393
+ var xhr = new XMLHttpRequest();
394
+ xhr.open("GET", "http://microsoft.com");
395
+ xhr.send();
396
+
397
+ // Emulate response
398
+ (<any>xhr).respond(403, {}, "error data with status code 403");
399
+
400
+ // assert
401
+ Assert.ok(trackStub.calledOnce, "track is called");
402
+ let data = trackStub.args[0][0].baseData;
403
+ Assert.equal("Ajax", data.type, "request is Ajax type");
404
+ Assert.equal(undefined, data.properties.responseText, "xhr request's reponse error is not stored in part C");
405
+
406
+ // act
407
+ var xhr2 = new XMLHttpRequest();
408
+ xhr2.open("GET", "http://microsoft.com");
409
+ xhr2.responseType = "json";
410
+ xhr2.send();
411
+
412
+ // Emulate response
413
+ let responseJSON = '{ "error":"error data with status code 403" }';
414
+ (<any>xhr2).respond(403, {}, responseJSON);
415
+
416
+ // assert
417
+ Assert.ok(trackStub.calledTwice, "track is called");
418
+ data = trackStub.args[1][0].baseData;
419
+ Assert.equal("Ajax", data.type, "request is Ajax type");
420
+ Assert.equal(undefined, data.properties.responseText, "xhr request's reponse error is not stored in part C");
421
+ }
422
+ });
423
+
424
+ this.testCase({
425
+ name: "Ajax: xhr respond error data is not tracked as part C data when enableAjaxErrorStatusText flag is default",
426
+ test: () => {
427
+ this._ajax = new AjaxMonitor();
428
+ let appInsightsCore = new AppInsightsCore();
429
+ let coreConfig: IConfiguration & IConfig = { instrumentationKey: "abc", disableAjaxTracking: false };
430
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
431
+
432
+ var trackStub = this.sandbox.stub(appInsightsCore, "track");
433
+
434
+ // act
435
+ var xhr = new XMLHttpRequest();
436
+ xhr.open("GET", "http://microsoft.com");
437
+ xhr.send();
438
+
439
+ // Emulate response
440
+ (<any>xhr).respond(403, {}, "error data with status code 403");
441
+
442
+ // assert
443
+ Assert.ok(trackStub.calledOnce, "track is called");
444
+ let data = trackStub.args[0][0].baseData;
445
+ Assert.equal("Ajax", data.type, "request is Ajax type");
446
+ Assert.equal(undefined, data.properties.responseText, "xhr request's reponse error is not stored in part C");
447
+
448
+ // act
449
+ var xhr2 = new XMLHttpRequest();
450
+ xhr2.open("GET", "http://microsoft.com");
451
+ xhr2.responseType = "json";
452
+ xhr2.send();
453
+
454
+ // Emulate response
455
+ let responseJSON = '{ "error":"error data with status code 403" }';
456
+ (<any>xhr2).respond(403, {}, responseJSON);
457
+
458
+ // assert
459
+ Assert.ok(trackStub.calledTwice, "track is called");
460
+ data = trackStub.args[1][0].baseData;
461
+ Assert.equal("Ajax", data.type, "request is Ajax type");
462
+ Assert.equal(undefined, data.properties.responseText, "xhr request's reponse error is not stored in part C");
463
+ }
464
+ });
465
+
466
+ this.testCaseAsync({
467
+ name: "Fetch: fetch with disabled flag isn't tracked",
468
+ stepDelay: 10,
469
+ autoComplete: false,
470
+ timeOut: 10000,
471
+ steps: [ (testContext) => {
472
+ hookFetch((resolve) => {
473
+ AITestClass.orgSetTimeout(function() {
474
+ resolve({
475
+ headers: new Headers(),
476
+ ok: true,
477
+ body: null,
478
+ bodyUsed: false,
479
+ redirected: false,
480
+ status: 200,
481
+ statusText: "Hello",
482
+ trailer: null,
483
+ type: "basic",
484
+ url: "https://httpbin.org/status/200"
485
+ });
486
+ }, 0);
487
+ });
488
+
489
+ this._ajax = new AjaxMonitor();
490
+ let appInsightsCore = new AppInsightsCore();
491
+ let coreConfig = { instrumentationKey: "", disableFetchTracking: false };
492
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
493
+ let fetchSpy = this.sandbox.spy(appInsightsCore, "track")
494
+
495
+ // Act
496
+ Assert.ok(fetchSpy.notCalled, "No fetch called yet");
497
+ fetch("https://httpbin.org/status/200", {method: "post", [DisabledPropertyName]: true}).then(() => {
498
+ // Assert
499
+ Assert.ok(fetchSpy.notCalled, "The request was not tracked");
500
+ testContext.testDone();
501
+ }, () => {
502
+ Assert.ok(false, "fetch failed!");
503
+ testContext.testDone();
504
+ });
505
+ }]
506
+ });
507
+
508
+ this.testCaseAsync({
509
+ name: "Fetch: fetch with disabled flag isn't tracked and any followup request to the same URL event without the disabled flag are also not tracked",
510
+ stepDelay: 10,
511
+ autoComplete: false,
512
+ timeOut: 10000,
513
+ steps: [ (testContext) => {
514
+ hookFetch((resolve) => {
515
+ AITestClass.orgSetTimeout(function() {
516
+ resolve({
517
+ headers: new Headers(),
518
+ ok: true,
519
+ body: null,
520
+ bodyUsed: false,
521
+ redirected: false,
522
+ status: 200,
523
+ statusText: "Hello",
524
+ trailer: null,
525
+ type: "basic",
526
+ url: "https://httpbin.org/status/200"
527
+ });
528
+ }, 0);
529
+ });
530
+
531
+ this._ajax = new AjaxMonitor();
532
+ let appInsightsCore = new AppInsightsCore();
533
+ let coreConfig = { instrumentationKey: "", disableFetchTracking: false };
534
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
535
+ let fetchSpy = this.sandbox.spy(appInsightsCore, "track")
536
+
537
+ // Act
538
+ Assert.ok(fetchSpy.notCalled, "No fetch called yet");
539
+ fetch("https://httpbin.org/status/200", {method: "post", [DisabledPropertyName]: true}).then(() => {
540
+ // Assert
541
+ Assert.ok(fetchSpy.notCalled, "The initial request was not tracked");
542
+
543
+ fetch("https://httpbin.org/status/200", {method: "post" }).then(() => {
544
+ // Assert
545
+ Assert.ok(fetchSpy.notCalled, "The follow up request should also not have been tracked");
546
+ testContext.testDone();
547
+ }, () => {
548
+ Assert.ok(false, "fetch failed!");
549
+ testContext.testDone();
550
+ });
551
+ }, () => {
552
+ Assert.ok(false, "fetch failed!");
553
+ testContext.testDone();
554
+ });
555
+ }]
556
+ });
557
+
558
+ this.testCaseAsync({
559
+ name: "Fetch: fetch with disabled flag false and with exclude request regex pattern isn't tracked and any followup request to the same URL event without the disabled flag are also not tracked",
560
+ stepDelay: 10,
561
+ autoComplete: false,
562
+ timeOut: 10000,
563
+ steps: [ (testContext) => {
564
+ hookFetch((resolve) => {
565
+ AITestClass.orgSetTimeout(function() {
566
+ resolve({
567
+ headers: new Headers(),
568
+ ok: true,
569
+ body: null,
570
+ bodyUsed: false,
571
+ redirected: false,
572
+ status: 200,
573
+ statusText: "Hello",
574
+ trailer: null,
575
+ type: "basic",
576
+ url: "https://httpbin.org/status/200"
577
+ });
578
+ }, 0);
579
+ });
580
+
581
+ this._ajax = new AjaxMonitor();
582
+ let appInsightsCore = new AppInsightsCore();
583
+ const ExcludeRequestRegex = ["bin"];
584
+ let coreConfig = { instrumentationKey: "", disableFetchTracking: false, excludeRequestFromAutoTrackingPatterns: ExcludeRequestRegex };
585
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
586
+ let fetchSpy = this.sandbox.spy(appInsightsCore, "track")
587
+
588
+ // Act
589
+ Assert.ok(fetchSpy.notCalled, "No fetch called yet");
590
+ fetch("https://httpbin.org/status/200", {method: "post"}).then(() => {
591
+ // Assert
592
+ Assert.ok(fetchSpy.notCalled, "The initial request was not tracked");
593
+
594
+ fetch("https://httpbin.org/status/200", {method: "post" }).then(() => {
595
+ // Assert
596
+ Assert.ok(fetchSpy.notCalled, "The follow up request should also not have been tracked");
597
+ testContext.testDone();
598
+ }, () => {
599
+ Assert.ok(false, "fetch failed!");
600
+ testContext.testDone();
601
+ });
602
+ }, () => {
603
+ Assert.ok(false, "fetch failed!");
604
+ testContext.testDone();
605
+ });
606
+ }]
607
+ });
608
+
609
+ this.testCaseAsync({
610
+ name: "Fetch: add context into custom dimension with call back configuration on AI initialization.",
611
+ stepDelay: 10,
612
+ autoComplete: false,
613
+ timeOut: 10000,
614
+ steps: [ (testContext) => {
615
+ hookFetch((resolve) => {
616
+ AITestClass.orgSetTimeout(function() {
617
+ resolve({
618
+ headers: new Headers(),
619
+ ok: true,
620
+ body: null,
621
+ bodyUsed: false,
622
+ redirected: false,
623
+ status: 200,
624
+ statusText: "Hello",
625
+ trailer: null,
626
+ type: "basic",
627
+ url: "https://httpbin.org/status/200"
628
+ });
629
+ }, 0);
630
+ });
631
+
632
+ this._ajax = new AjaxMonitor();
633
+ let dependencyFields = hookTrackDependencyInternal(this._ajax);
634
+ let appInsightsCore = new AppInsightsCore();
635
+ let coreConfig = {
636
+ instrumentationKey: "",
637
+ disableFetchTracking: false,
638
+ addRequestContext: (requestContext: IRequestContext) => {
639
+ return {
640
+ test: "Fetch context",
641
+ fetchRequestUrl: requestContext.request,
642
+ fetchResponseType: (requestContext.response as Response).type
643
+ }
644
+ }
645
+ };
646
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
647
+ let fetchSpy = this.sandbox.spy(appInsightsCore, "track")
648
+
649
+ // Act
650
+ Assert.ok(fetchSpy.notCalled, "No fetch called yet");
651
+ fetch("https://httpbin.org/status/200", {method: "post", [DisabledPropertyName]: false}).then(() => {
652
+ // assert
653
+ Assert.ok(fetchSpy.calledOnce, "track is called");
654
+ let data = fetchSpy.args[0][0].baseData;
655
+ Assert.equal("Fetch", data.type, "request is Fetch type");
656
+ Assert.equal(1, dependencyFields.length, "trackDependencyDataInternal was called");
657
+ Assert.equal("Fetch context", data.properties.test, "Fetch request's request context is added when customer configures addRequestContext.");
658
+ Assert.equal("https://httpbin.org/status/200", data.properties.fetchRequestUrl, "Fetch request is captured.");
659
+ Assert.equal("basic", data.properties.fetchResponseType, "Fetch response is captured.");
660
+
661
+ testContext.testDone();
662
+ }, () => {
663
+ Assert.ok(false, "fetch failed!");
664
+ testContext.testDone();
665
+ });
666
+ }]
667
+ });
668
+
669
+ this.testCaseAsync({
670
+ name: "Fetch: fetch gets instrumented",
671
+ stepDelay: 10,
672
+ autoComplete: false,
673
+ timeOut: 10000,
674
+ steps: [ (testContext) => {
675
+ hookFetch((resolve) => {
676
+ AITestClass.orgSetTimeout(function() {
677
+ resolve({
678
+ headers: new Headers(),
679
+ ok: true,
680
+ body: null,
681
+ bodyUsed: false,
682
+ redirected: false,
683
+ status: 200,
684
+ statusText: "Hello",
685
+ trailer: null,
686
+ type: "basic",
687
+ url: "https://httpbin.org/status/200"
688
+ });
689
+ }, 0);
690
+ });
691
+
692
+ this._ajax = new AjaxMonitor();
693
+ let dependencyFields = hookTrackDependencyInternal(this._ajax);
694
+ let appInsightsCore = new AppInsightsCore();
695
+ let coreConfig = { instrumentationKey: "", disableFetchTracking: false };
696
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
697
+ let fetchSpy = this.sandbox.spy(appInsightsCore, "track")
698
+ let throwSpy = this.sandbox.spy(appInsightsCore.logger, "throwInternal");
699
+
700
+ // Act
701
+ Assert.ok(fetchSpy.notCalled, "No fetch called yet");
702
+ fetch("https://httpbin.org/status/200", {method: "post", [DisabledPropertyName]: false}).then(() => {
703
+ // Assert
704
+ Assert.ok(fetchSpy.calledOnce, "createFetchRecord called once after using fetch");
705
+ let data = fetchSpy.args[0][0].baseData;
706
+ Assert.equal("Fetch", data.type, "request is Fetch type");
707
+ Assert.ok(throwSpy.notCalled, "Make sure we didn't fail internally");
708
+ Assert.equal(1, dependencyFields.length, "trackDependencyDataInternal was called");
709
+ Assert.ok(dependencyFields[0].startTime, "startTime was specified before trackDependencyDataInternal was called")
710
+ testContext.testDone();
711
+ }, () => {
712
+ Assert.ok(false, "fetch failed!");
713
+ testContext.testDone();
714
+ });
715
+ }]
716
+ });
717
+
718
+ this.testCaseAsync({
719
+ name: "Fetch: instrumentation handles invalid / missing request or url",
720
+ stepDelay: 10,
721
+ autoComplete: false,
722
+ timeOut: 10000,
723
+ steps: [ (testContext) => {
724
+ hookFetch((resolve) => {
725
+ AITestClass.orgSetTimeout(function() {
726
+ resolve({
727
+ headers: new Headers(),
728
+ ok: true,
729
+ body: null,
730
+ bodyUsed: false,
731
+ redirected: false,
732
+ status: 200,
733
+ statusText: "Hello",
734
+ trailer: null,
735
+ type: "basic",
736
+ url: "https://httpbin.org/status/200"
737
+ });
738
+ }, 0);
739
+ });
740
+
741
+ this._ajax = new AjaxMonitor();
742
+ let dependencyFields = hookTrackDependencyInternal(this._ajax);
743
+ let appInsightsCore = new AppInsightsCore();
744
+ let coreConfig = { instrumentationKey: "", disableFetchTracking: false };
745
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
746
+ let fetchSpy = this.sandbox.spy(appInsightsCore, "track")
747
+ let throwSpy = this.sandbox.spy(appInsightsCore.logger, "throwInternal");
748
+
749
+ // Act
750
+ Assert.ok(fetchSpy.notCalled, "No fetch called yet");
751
+ fetch(null, {method: "post", [DisabledPropertyName]: false}).then(() => {
752
+ // Assert
753
+ Assert.ok(fetchSpy.calledOnce, "createFetchRecord called once after using fetch");
754
+ let data = fetchSpy.args[0][0].baseData;
755
+ Assert.equal("Fetch", data.type, "request is Fetch type");
756
+ Assert.equal(false, throwSpy.called, "We should not have failed internally");
757
+ Assert.equal(1, dependencyFields.length, "trackDependencyDataInternal was called");
758
+ Assert.ok(dependencyFields[0].startTime, "startTime was specified before trackDependencyDataInternal was called")
759
+
760
+ fetch(undefined, null).then(() => {
761
+ // Assert
762
+ Assert.ok(fetchSpy.calledTwice, "createFetchRecord called once after using fetch");
763
+ Assert.equal(false, throwSpy.called, "We should still not have failed internally");
764
+ Assert.equal(2, dependencyFields.length, "trackDependencyDataInternal was called");
765
+ Assert.ok(dependencyFields[1].startTime, "startTime was specified before trackDependencyDataInternal was called");
766
+ testContext.testDone();
767
+ }, () => {
768
+ Assert.ok(false, "fetch failed!");
769
+ testContext.testDone();
770
+ });
771
+ }, () => {
772
+ Assert.ok(false, "fetch failed!");
773
+ testContext.testDone();
774
+ });
775
+ }]
776
+ });
777
+
778
+ this.testCase({
779
+ name: "Fetch: fetch keeps custom headers",
780
+ test: () => {
781
+ hookFetch((resolve) => {
782
+ AITestClass.orgSetTimeout(function() {
783
+ resolve();
784
+ }, 0);
785
+ });
786
+
787
+ try {
788
+ this._ajax = new AjaxMonitor();
789
+ let appInsightsCore = new AppInsightsCore();
790
+ let coreConfig = {
791
+ instrumentationKey: "",
792
+ disableFetchTracking: false,
793
+ disableAjaxTracking: true
794
+ };
795
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
796
+ let fetchSpy = this.sandbox.spy(window, "fetch");
797
+
798
+ // Setup
799
+ let headers = new Headers();
800
+ headers.append('My-Header', 'Header field');
801
+ let init = {
802
+ method: 'get',
803
+ headers: headers
804
+ };
805
+ const url = 'https://httpbin.org/status/200';
806
+
807
+ let headerSpy = this.sandbox.spy(this._ajax, "includeCorrelationHeaders");
808
+
809
+ // Act
810
+ Assert.ok(fetchSpy.notCalled);
811
+ fetch(url, init);
812
+
813
+ // Assert
814
+ Assert.ok(fetchSpy.calledOnce);
815
+ Assert.ok(headerSpy.calledOnce);
816
+ Assert.deepEqual(init, headerSpy.returnValue || headerSpy.returnValues[0]);
817
+ } catch (e) {
818
+ console && console.warn("Exception: " + e);
819
+ Assert.ok(false, e);
820
+ }
821
+ }
822
+ });
823
+
824
+ this.testCaseAsync({
825
+ name: "Fetch: should create and pass a traceparent header if ai and w3c is enabled with custom headers",
826
+ stepDelay: 10,
827
+ timeOut: 10000,
828
+ steps: [ (testContext) => {
829
+ let fetchCalls = hookFetch((resolve) => {
830
+ AITestClass.orgSetTimeout(function() {
831
+ resolve({
832
+ headers: new Headers(),
833
+ ok: true,
834
+ body: null,
835
+ bodyUsed: false,
836
+ redirected: false,
837
+ status: 200,
838
+ statusText: "Hello",
839
+ trailer: null,
840
+ type: "basic",
841
+ url: "https://httpbin.org/status/200"
842
+ });
843
+ }, 0);
844
+ });
845
+
846
+ this._ajax = new AjaxMonitor();
847
+ let appInsightsCore = new AppInsightsCore();
848
+ let coreConfig = {
849
+ instrumentationKey: "instrumentationKey",
850
+ disableFetchTracking: false,
851
+ disableAjaxTracking: false,
852
+ extensionConfig: {
853
+ "AjaxDependencyPlugin": {
854
+ appId: "appId",
855
+ distributedTracingMode: DistributedTracingModes.AI_AND_W3C
856
+ }
857
+ }
858
+ };
859
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
860
+ let trackSpy = this.sandbox.spy(appInsightsCore, "track")
861
+ this._context["trackStub"] = trackSpy;
862
+
863
+ // Use test hook to simulate the correct url location
864
+ this._ajax["_currentWindowHost"] = "httpbin.org";
865
+
866
+ // Setup
867
+ let headers = new Headers();
868
+ headers.append('My-Header', 'Header field');
869
+ let init = {
870
+ method: 'get',
871
+ headers: headers
872
+ };
873
+ const url = 'https://httpbin.org/status/200';
874
+
875
+ // Act
876
+ Assert.ok(trackSpy.notCalled, "No fetch called yet");
877
+ fetch(url, init).then(() => {
878
+ // Assert
879
+ Assert.ok(trackSpy.called, "The request was not tracked");
880
+ // Assert that both headers are sent
881
+ Assert.equal(1, fetchCalls.length);
882
+ Assert.notEqual(undefined, fetchCalls[0].init, "Has init param");
883
+ let headers:Headers = fetchCalls[0].init.headers as Headers;
884
+ Assert.notEqual(undefined, headers, "has headers");
885
+ Assert.equal(true, headers.has("My-Header"), "My-Header should be present");
886
+ Assert.equal(true, headers.has(RequestHeaders.requestIdHeader), "AI header shoud be present"); // AI
887
+ Assert.equal(true, headers.has(RequestHeaders.traceParentHeader), "W3c header should be present"); // W3C
888
+ }, () => {
889
+ Assert.ok(false, "fetch failed!");
890
+ testContext.testDone();
891
+ });
892
+ }]
893
+ .concat(PollingAssert.createPollingAssert(() => {
894
+ let trackStub = this._context["trackStub"] as SinonStub;
895
+ if (trackStub.called) {
896
+ Assert.ok(trackStub.calledOnce, "track is called");
897
+ let data = trackStub.args[0][0].baseData;
898
+ Assert.equal("Fetch", data.type, "request is Fatch type");
899
+ var id = data.id;
900
+ Assert.equal("|", id[0]);
901
+ Assert.equal(".", id[id.length - 1]);
902
+ return true;
903
+ }
904
+
905
+ return false;
906
+ }, 'response received', 60, 1000) as any)
907
+ })
908
+
909
+ this.testCaseAsync({
910
+ name: "Fetch: should create and pass a traceparent header if ai and w3c is enabled with no init param",
911
+ stepDelay: 10,
912
+ timeOut: 10000,
913
+ steps: [ (testContext) => {
914
+ let fetchCalls = hookFetch((resolve) => {
915
+ AITestClass.orgSetTimeout(function() {
916
+ resolve({
917
+ headers: new Headers(),
918
+ ok: true,
919
+ body: null,
920
+ bodyUsed: false,
921
+ redirected: false,
922
+ status: 200,
923
+ statusText: "Hello",
924
+ trailer: null,
925
+ type: "basic",
926
+ url: "https://httpbin.org/status/200"
927
+ });
928
+ }, 0);
929
+ });
930
+
931
+ this._ajax = new AjaxMonitor();
932
+ let appInsightsCore = new AppInsightsCore();
933
+ let coreConfig = {
934
+ instrumentationKey: "instrumentationKey",
935
+ disableFetchTracking: false,
936
+ disableAjaxTracking: false,
937
+ extensionConfig: {
938
+ "AjaxDependencyPlugin": {
939
+ appId: "appId",
940
+ distributedTracingMode: DistributedTracingModes.AI_AND_W3C
941
+ }
942
+ }
943
+ };
944
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
945
+ let trackSpy = this.sandbox.spy(appInsightsCore, "track")
946
+ this._context["trackStub"] = trackSpy;
947
+
948
+ // Use test hook to simulate the correct url location
949
+ this._ajax["_currentWindowHost"] = "httpbin.org";
950
+
951
+ // Setup
952
+ const url = 'https://httpbin.org/status/200';
953
+
954
+ // Act
955
+ Assert.ok(trackSpy.notCalled, "No fetch called yet");
956
+ fetch(url).then(() => {
957
+ // Assert
958
+ Assert.ok(trackSpy.called, "The request was not tracked");
959
+ // Assert that both headers are sent
960
+ Assert.equal(1, fetchCalls.length);
961
+ Assert.notEqual(undefined, fetchCalls[0].init, "Has init param");
962
+ let headers:Headers = fetchCalls[0].init.headers as Headers;
963
+ Assert.notEqual(undefined, headers, "has headers");
964
+ Assert.equal(true, headers.has(RequestHeaders.requestIdHeader), "AI header shoud be present"); // AI
965
+ Assert.equal(true, headers.has(RequestHeaders.traceParentHeader), "W3c header should be present"); // W3C
966
+ }, () => {
967
+ Assert.ok(false, "fetch failed!");
968
+ testContext.testDone();
969
+ });
970
+ }]
971
+ .concat(PollingAssert.createPollingAssert(() => {
972
+ let trackStub = this._context["trackStub"] as SinonStub;
973
+ if (trackStub.called) {
974
+ Assert.ok(trackStub.calledOnce, "track is called");
975
+ let data = trackStub.args[0][0].baseData;
976
+ Assert.equal("Fetch", data.type, "request is Fatch type");
977
+ var id = data.id;
978
+ Assert.equal("|", id[0]);
979
+ Assert.equal(".", id[id.length - 1]);
980
+ return true;
981
+ }
982
+
983
+ return false;
984
+ }, 'response received', 60, 1000) as any)
985
+ })
986
+
987
+ this.testCaseAsync({
988
+ name: "Fetch: should create and pass a traceparent header if w3c only is enabled with custom headers",
989
+ stepDelay: 10,
990
+ timeOut: 10000,
991
+ steps: [ (testContext) => {
992
+ let fetchCalls = hookFetch((resolve) => {
993
+ AITestClass.orgSetTimeout(function() {
994
+ resolve({
995
+ headers: new Headers(),
996
+ ok: true,
997
+ body: null,
998
+ bodyUsed: false,
999
+ redirected: false,
1000
+ status: 200,
1001
+ statusText: "Hello",
1002
+ trailer: null,
1003
+ type: "basic",
1004
+ url: "https://httpbin.org/status/200"
1005
+ });
1006
+ }, 0);
1007
+ });
1008
+
1009
+ this._ajax = new AjaxMonitor();
1010
+ let appInsightsCore = new AppInsightsCore();
1011
+ let coreConfig = {
1012
+ instrumentationKey: "instrumentationKey",
1013
+ disableFetchTracking: false,
1014
+ disableAjaxTracking: false,
1015
+ extensionConfig: {
1016
+ "AjaxDependencyPlugin": {
1017
+ appId: "appId",
1018
+ distributedTracingMode: DistributedTracingModes.W3C
1019
+ }
1020
+ }
1021
+ };
1022
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
1023
+ let trackSpy = this.sandbox.spy(appInsightsCore, "track")
1024
+ this._context["trackStub"] = trackSpy;
1025
+
1026
+ // Use test hook to simulate the correct url location
1027
+ this._ajax["_currentWindowHost"] = "httpbin.org";
1028
+
1029
+ // Setup
1030
+ let headers = new Headers();
1031
+ headers.append('My-Header', 'Header field');
1032
+ let init = {
1033
+ method: 'get',
1034
+ headers: headers
1035
+ };
1036
+ const url = 'https://httpbin.org/status/200';
1037
+
1038
+ // Act
1039
+ Assert.ok(trackSpy.notCalled, "No fetch called yet");
1040
+ fetch(url, init).then(() => {
1041
+ // Assert
1042
+ Assert.ok(trackSpy.called, "The request was not tracked");
1043
+ // Assert that both headers are sent
1044
+ Assert.equal(1, fetchCalls.length);
1045
+ Assert.notEqual(undefined, fetchCalls[0].init, "Has init param");
1046
+ let headers:Headers = fetchCalls[0].init.headers as Headers;
1047
+ Assert.notEqual(undefined, headers, "has headers");
1048
+ Assert.equal(true, headers.has("My-Header"), "My-Header should be present");
1049
+ Assert.equal(false, headers.has(RequestHeaders.requestIdHeader), "AI header shoud be present"); // AI
1050
+ Assert.equal(true, headers.has(RequestHeaders.traceParentHeader), "W3c header should be present"); // W3C
1051
+ }, () => {
1052
+ Assert.ok(false, "fetch failed!");
1053
+ testContext.testDone();
1054
+ });
1055
+ }]
1056
+ .concat(PollingAssert.createPollingAssert(() => {
1057
+ let trackStub = this._context["trackStub"] as SinonStub;
1058
+ if (trackStub.called) {
1059
+ Assert.ok(trackStub.calledOnce, "track is called");
1060
+ let data = trackStub.args[0][0].baseData;
1061
+ Assert.equal("Fetch", data.type, "request is Fatch type");
1062
+ var id = data.id;
1063
+ Assert.equal("|", id[0]);
1064
+ Assert.equal(".", id[id.length - 1]);
1065
+ return true;
1066
+ }
1067
+
1068
+ return false;
1069
+ }, 'response received', 60, 1000) as any)
1070
+ })
1071
+
1072
+ this.testCaseAsync({
1073
+ name: "Fetch: should create and pass a traceparent header if w3c only is enabled with no init param",
1074
+ stepDelay: 10,
1075
+ timeOut: 10000,
1076
+ steps: [ (testContext) => {
1077
+ let fetchCalls = hookFetch((resolve) => {
1078
+ AITestClass.orgSetTimeout(function() {
1079
+ resolve({
1080
+ headers: new Headers(),
1081
+ ok: true,
1082
+ body: null,
1083
+ bodyUsed: false,
1084
+ redirected: false,
1085
+ status: 200,
1086
+ statusText: "Hello",
1087
+ trailer: null,
1088
+ type: "basic",
1089
+ url: "https://httpbin.org/status/200"
1090
+ });
1091
+ }, 0);
1092
+ });
1093
+
1094
+ this._ajax = new AjaxMonitor();
1095
+ let appInsightsCore = new AppInsightsCore();
1096
+ let coreConfig = {
1097
+ instrumentationKey: "instrumentationKey",
1098
+ disableFetchTracking: false,
1099
+ disableAjaxTracking: false,
1100
+ extensionConfig: {
1101
+ "AjaxDependencyPlugin": {
1102
+ appId: "appId",
1103
+ distributedTracingMode: DistributedTracingModes.W3C
1104
+ }
1105
+ }
1106
+ };
1107
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
1108
+ let trackSpy = this.sandbox.spy(appInsightsCore, "track")
1109
+ this._context["trackStub"] = trackSpy;
1110
+
1111
+ // Use test hook to simulate the correct url location
1112
+ this._ajax["_currentWindowHost"] = "httpbin.org";
1113
+
1114
+ // Setup
1115
+ const url = 'https://httpbin.org/status/200';
1116
+
1117
+ // Act
1118
+ Assert.ok(trackSpy.notCalled, "No fetch called yet");
1119
+ fetch(url).then(() => {
1120
+ // Assert
1121
+ Assert.ok(trackSpy.called, "The request was not tracked");
1122
+ // Assert that both headers are sent
1123
+ Assert.equal(1, fetchCalls.length);
1124
+ Assert.notEqual(undefined, fetchCalls[0].init, "Has init param");
1125
+ let headers:Headers = fetchCalls[0].init.headers as Headers;
1126
+ Assert.notEqual(undefined, headers, "has headers");
1127
+ Assert.equal(false, headers.has(RequestHeaders.requestIdHeader), "AI header shoud be present"); // AI
1128
+ Assert.equal(true, headers.has(RequestHeaders.traceParentHeader), "W3c header should be present"); // W3C
1129
+ }, () => {
1130
+ Assert.ok(false, "fetch failed!");
1131
+ testContext.testDone();
1132
+ });
1133
+ }]
1134
+ .concat(PollingAssert.createPollingAssert(() => {
1135
+ let trackStub = this._context["trackStub"] as SinonStub;
1136
+ if (trackStub.called) {
1137
+ Assert.ok(trackStub.calledOnce, "track is called");
1138
+ let data = trackStub.args[0][0].baseData;
1139
+ Assert.equal("Fetch", data.type, "request is Fatch type");
1140
+ var id = data.id;
1141
+ Assert.equal("|", id[0]);
1142
+ Assert.equal(".", id[id.length - 1]);
1143
+ return true;
1144
+ }
1145
+
1146
+ return false;
1147
+ }, 'response received', 60, 1000) as any)
1148
+ })
1149
+
1150
+ this.testCaseAsync({
1151
+ name: "Fetch: should create and pass a request header if AI only is enabled with custom headers",
1152
+ stepDelay: 10,
1153
+ timeOut: 10000,
1154
+ steps: [ (testContext) => {
1155
+ let fetchCalls = hookFetch((resolve) => {
1156
+ AITestClass.orgSetTimeout(function() {
1157
+ resolve({
1158
+ headers: new Headers(),
1159
+ ok: true,
1160
+ body: null,
1161
+ bodyUsed: false,
1162
+ redirected: false,
1163
+ status: 200,
1164
+ statusText: "Hello",
1165
+ trailer: null,
1166
+ type: "basic",
1167
+ url: "https://httpbin.org/status/200"
1168
+ });
1169
+ }, 0);
1170
+ });
1171
+
1172
+ this._ajax = new AjaxMonitor();
1173
+ let appInsightsCore = new AppInsightsCore();
1174
+ let coreConfig = {
1175
+ instrumentationKey: "instrumentationKey",
1176
+ disableFetchTracking: false,
1177
+ disableAjaxTracking: false,
1178
+ extensionConfig: {
1179
+ "AjaxDependencyPlugin": {
1180
+ appId: "appId",
1181
+ distributedTracingMode: DistributedTracingModes.AI
1182
+ }
1183
+ }
1184
+ };
1185
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
1186
+ let trackSpy = this.sandbox.spy(appInsightsCore, "track")
1187
+ this._context["trackStub"] = trackSpy;
1188
+
1189
+ // Use test hook to simulate the correct url location
1190
+ this._ajax["_currentWindowHost"] = "httpbin.org";
1191
+
1192
+ // Setup
1193
+ let headers = new Headers();
1194
+ headers.append('My-Header', 'Header field');
1195
+ let init = {
1196
+ method: 'get',
1197
+ headers: headers
1198
+ };
1199
+ const url = 'https://httpbin.org/status/200';
1200
+
1201
+ // Act
1202
+ Assert.ok(trackSpy.notCalled, "No fetch called yet");
1203
+ fetch(url, init).then(() => {
1204
+ // Assert
1205
+ Assert.ok(trackSpy.called, "The request was not tracked");
1206
+ // Assert that both headers are sent
1207
+ Assert.equal(1, fetchCalls.length);
1208
+ Assert.notEqual(undefined, fetchCalls[0].init, "Has init param");
1209
+ let headers:Headers = fetchCalls[0].init.headers as Headers;
1210
+ Assert.notEqual(undefined, headers, "has headers");
1211
+ Assert.equal(true, headers.has("My-Header"), "My-Header should be present");
1212
+ Assert.equal(true, headers.has(RequestHeaders.requestIdHeader), "AI header shoud be present"); // AI
1213
+ Assert.equal(false, headers.has(RequestHeaders.traceParentHeader), "W3c header should be present"); // W3C
1214
+ }, () => {
1215
+ Assert.ok(false, "fetch failed!");
1216
+ testContext.testDone();
1217
+ });
1218
+ }]
1219
+ .concat(PollingAssert.createPollingAssert(() => {
1220
+ let trackStub = this._context["trackStub"] as SinonStub;
1221
+ if (trackStub.called) {
1222
+ Assert.ok(trackStub.calledOnce, "track is called");
1223
+ let data = trackStub.args[0][0].baseData;
1224
+ Assert.equal("Fetch", data.type, "request is Fatch type");
1225
+ var id = data.id;
1226
+ Assert.equal("|", id[0]);
1227
+ return true;
1228
+ }
1229
+
1230
+ return false;
1231
+ }, 'response received', 60, 1000) as any)
1232
+ })
1233
+
1234
+ this.testCaseAsync({
1235
+ name: "Fetch: should create and pass a request header if AI only is enabled with no init param",
1236
+ stepDelay: 10,
1237
+ timeOut: 10000,
1238
+ steps: [ (testContext) => {
1239
+ let fetchCalls = hookFetch((resolve) => {
1240
+ AITestClass.orgSetTimeout(function() {
1241
+ resolve({
1242
+ headers: new Headers(),
1243
+ ok: true,
1244
+ body: null,
1245
+ bodyUsed: false,
1246
+ redirected: false,
1247
+ status: 200,
1248
+ statusText: "Hello",
1249
+ trailer: null,
1250
+ type: "basic",
1251
+ url: "https://httpbin.org/status/200"
1252
+ });
1253
+ }, 0);
1254
+ });
1255
+
1256
+ this._ajax = new AjaxMonitor();
1257
+ let appInsightsCore = new AppInsightsCore();
1258
+ let coreConfig = {
1259
+ instrumentationKey: "instrumentationKey",
1260
+ disableFetchTracking: false,
1261
+ disableAjaxTracking: false,
1262
+ extensionConfig: {
1263
+ "AjaxDependencyPlugin": {
1264
+ appId: "appId",
1265
+ distributedTracingMode: DistributedTracingModes.AI
1266
+ }
1267
+ }
1268
+ };
1269
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
1270
+ let trackSpy = this.sandbox.spy(appInsightsCore, "track")
1271
+ this._context["trackStub"] = trackSpy;
1272
+
1273
+ // Use test hook to simulate the correct url location
1274
+ this._ajax["_currentWindowHost"] = "httpbin.org";
1275
+
1276
+ // Setup
1277
+ const url = 'https://httpbin.org/status/200';
1278
+
1279
+ // Act
1280
+ Assert.ok(trackSpy.notCalled, "No fetch called yet");
1281
+ fetch(url).then(() => {
1282
+ // Assert
1283
+ Assert.ok(trackSpy.called, "The request was not tracked");
1284
+ // Assert that both headers are sent
1285
+ Assert.equal(1, fetchCalls.length);
1286
+ Assert.notEqual(undefined, fetchCalls[0].init, "Has init param");
1287
+ let headers:Headers = fetchCalls[0].init.headers as Headers;
1288
+ Assert.notEqual(undefined, headers, "has headers");
1289
+ Assert.equal(true, headers.has(RequestHeaders.requestIdHeader), "AI header shoud be present"); // AI
1290
+ Assert.equal(false, headers.has(RequestHeaders.traceParentHeader), "W3c header should be present"); // W3C
1291
+ }, () => {
1292
+ Assert.ok(false, "fetch failed!");
1293
+ testContext.testDone();
1294
+ });
1295
+ }]
1296
+ .concat(PollingAssert.createPollingAssert(() => {
1297
+ let trackStub = this._context["trackStub"] as SinonStub;
1298
+ if (trackStub.called) {
1299
+ Assert.ok(trackStub.calledOnce, "track is called");
1300
+ let data = trackStub.args[0][0].baseData;
1301
+ Assert.equal("Fetch", data.type, "request is Fatch type");
1302
+ var id = data.id;
1303
+ Assert.equal("|", id[0]);
1304
+ return true;
1305
+ }
1306
+
1307
+ return false;
1308
+ }, 'response received', 60, 1000) as any)
1309
+ })
1310
+
1311
+ this.testCaseAsync({
1312
+ name: "Fetch: should add request headers to all valid argument variants",
1313
+ stepDelay: 10,
1314
+ timeOut: 10000,
1315
+ useFakeTimers: true,
1316
+ steps: [ (testContext) => {
1317
+ this._context["fetchCalls"] = hookFetch((resolve) => {
1318
+ AITestClass.orgSetTimeout(function() {
1319
+ resolve({
1320
+ headers: new Headers(),
1321
+ ok: true,
1322
+ body: null,
1323
+ bodyUsed: false,
1324
+ redirected: false,
1325
+ status: 200,
1326
+ statusText: "Hello",
1327
+ trailer: null,
1328
+ type: "basic",
1329
+ url: "https://httpbin.org/status/200"
1330
+ });
1331
+ }, 0);
1332
+ });
1333
+
1334
+ this._ajax = new AjaxMonitor();
1335
+ let appInsightsCore = new AppInsightsCore();
1336
+ let coreConfig = {
1337
+ instrumentationKey: "instrumentationKey",
1338
+ disableFetchTracking: false,
1339
+ disableAjaxTracking: false,
1340
+ enableRequestHeaderTracking: true,
1341
+ extensionConfig: {
1342
+ "AjaxDependencyPlugin": {
1343
+ appId: "appId",
1344
+ distributedTracingMode: DistributedTracingModes.AI_AND_W3C
1345
+ }
1346
+ }
1347
+ };
1348
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
1349
+ let trackSpy = this.sandbox.spy(appInsightsCore, "track")
1350
+ this._context["trackStub"] = trackSpy;
1351
+
1352
+ // Use test hook to simulate the correct url location
1353
+ this._ajax["_currentWindowHost"] = "httpbin.org";
1354
+
1355
+ // Setup
1356
+ let headers = new Headers();
1357
+ headers.append('My-Header', 'Header field');
1358
+ headers.append("Authorization","Authorization");
1359
+ let init = {
1360
+ method: 'get',
1361
+ headers: headers
1362
+ };
1363
+ const url = 'https://httpbin.org/status/200';
1364
+
1365
+ Assert.ok(trackSpy.notCalled, "No fetch called yet");
1366
+ fetch(url);
1367
+ fetch(url, {});
1368
+ fetch(url, { headers: {} });
1369
+ fetch(url, { headers: new Headers() });
1370
+ fetch(url, { headers });
1371
+ fetch(url, init);
1372
+ fetch(new Request(url));
1373
+ fetch(new Request(url, {}));
1374
+ fetch(new Request(url, { headers: {} }));
1375
+ fetch(new Request(url, { headers: new Headers() }));
1376
+ fetch(new Request(url, { headers }));
1377
+ fetch(new Request(url, init));
1378
+ }]
1379
+ .concat(PollingAssert.createPollingAssert(() => {
1380
+ let trackStub = this._context["trackStub"] as SinonStub;
1381
+ let fetchCalls = this._context["fetchCalls"] as IFetchArgs[];
1382
+ Assert.ok(true, "Track: " + trackStub.args.length + " Fetch Calls: " + fetchCalls.length);
1383
+ if (trackStub.called && trackStub.args.length === 12 && fetchCalls.length === 12) {
1384
+ for (let lp = 0; lp < trackStub.args.length; lp++) {
1385
+ let evtData = trackStub.args[lp][0];
1386
+ this._checkFetchTraceId(evtData, "Fetch " + lp);
1387
+ let properties = evtData.baseData.properties || {};
1388
+ let trackHeaders = properties.requestHeaders || {};
1389
+
1390
+ Assert.notEqual(undefined, fetchCalls[lp].init, "Has init param");
1391
+ let headers:Headers = fetchCalls[lp].init.headers as Headers;
1392
+ Assert.notEqual(undefined, headers, "has headers");
1393
+ switch (lp) {
1394
+ case 4:
1395
+ case 5:
1396
+ case 10:
1397
+ case 11:
1398
+ // All headers should be added to the init (2nd param) as this overrides
1399
+ // any headers on any request object
1400
+ Assert.equal(true, headers.has("My-Header"), "My-Header should be present");
1401
+ Assert.equal(true, headers.has("Authorization"), "Authorization should be present");
1402
+ Assert.equal("Header field", trackHeaders["my-header"], "my-header present in outbound event");
1403
+ Assert.equal(undefined, trackHeaders["Authorization"],"Authorization header should be ignored")
1404
+ break;
1405
+ }
1406
+
1407
+ Assert.equal(true, headers.has(RequestHeaders.requestContextHeader), "requestContext header shoud be present");
1408
+ Assert.equal(true, headers.has(RequestHeaders.requestIdHeader), "AI header shoud be present"); // AI
1409
+ Assert.equal(true, headers.has(RequestHeaders.traceParentHeader), "W3c header should be present"); // W3C
1410
+
1411
+ Assert.notEqual(undefined, trackHeaders[RequestHeaders.requestIdHeader], "RequestId present in outbound event");
1412
+ Assert.notEqual(undefined, trackHeaders[RequestHeaders.requestContextHeader], "RequestContext present in outbound event");
1413
+ Assert.notEqual(undefined, trackHeaders[RequestHeaders.traceParentHeader], "traceParent present in outbound event");
1414
+
1415
+ }
1416
+
1417
+ return true;
1418
+ }
1419
+
1420
+ this.clock.tick(1000);
1421
+ return false;
1422
+ }, 'response received', 60, 1000) as any)
1423
+ })
1424
+
1425
+ this.testCase({
1426
+ name: "Ajax: successful request, ajax monitor doesn't change payload",
1427
+ test: () => {
1428
+ var callback = this.sandbox.spy();
1429
+ this._ajax = new AjaxMonitor();
1430
+ let appInsightsCore = new AppInsightsCore();
1431
+ let coreConfig = { instrumentationKey: "instrumentationKey", extensionConfig: {"AjaxPlugin": {}}};
1432
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
1433
+
1434
+ // Act
1435
+ var xhr = new XMLHttpRequest();
1436
+ xhr.onload = callback;
1437
+ xhr.open("GET", "example.com/bla");
1438
+ xhr.send();
1439
+
1440
+
1441
+ // Emulate response
1442
+ (<any>xhr).respond(200, { "Content-Type": "application/json" }, "bla");
1443
+ //Assert.ok((<any>ajaxMonitor)._trackAjaxAttempts === 1, "TrackAjax is called");
1444
+
1445
+ // Assert
1446
+ var result = callback.args[0][0].target;
1447
+ Assert.ok(callback.called, "Ajax callback is called");
1448
+ Assert.equal("bla", result.responseText, "Expected result match");
1449
+ Assert.equal(200, result.status, "Expected 200 response code");
1450
+ Assert.equal(4, xhr.readyState, "Expected readyState is 4 after request is finished");
1451
+ }
1452
+ });
1453
+
1454
+ this.testCase({
1455
+ name: "Ajax: custom onreadystatechange gets called",
1456
+ useFakeServer: true,
1457
+ useFakeTimers: true,
1458
+ test: () => {
1459
+ var onreadystatechangeSpy = this.sandbox.spy();
1460
+ this._ajax = new AjaxMonitor();
1461
+ let appInsightsCore = new AppInsightsCore();
1462
+ let coreConfig = { instrumentationKey: "instrumentationKey", extensionConfig: {"AjaxPlugin": {}}};
1463
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
1464
+ var trackStub = this.sandbox.stub(appInsightsCore, "track");
1465
+
1466
+ // Act
1467
+ var xhr = new XMLHttpRequest();
1468
+ xhr.onreadystatechange = onreadystatechangeSpy;
1469
+ xhr.open("GET", "example.com/bla");
1470
+ xhr.send();
1471
+
1472
+ Assert.ok(trackStub.notCalled, "track should not be called yet");
1473
+
1474
+ // Emulate response
1475
+ (<any>xhr).respond(200, {}, "");
1476
+
1477
+ // Assert
1478
+ Assert.ok(trackStub.called, "TrackAjax is called");
1479
+ Assert.ok(onreadystatechangeSpy.callCount >= 4, "custom onreadystatechange should be called");
1480
+ }
1481
+ });
1482
+
1483
+ this.testCase({
1484
+ name: "Ajax: 200 means success",
1485
+ test: () => {
1486
+ this._ajax = new AjaxMonitor();
1487
+ let appInsightsCore = new AppInsightsCore();
1488
+ let coreConfig = { instrumentationKey: "instrumentationKey", extensionConfig: {"AjaxPlugin": {}}};
1489
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
1490
+ var trackStub = this.sandbox.stub(appInsightsCore, "track");
1491
+
1492
+ // Act
1493
+ var xhr = new XMLHttpRequest();
1494
+ xhr.open("GET", "example.com/bla");
1495
+ xhr.send();
1496
+
1497
+ // Emulate response
1498
+ (<any>xhr).respond(200, {}, "");
1499
+
1500
+ // Assert
1501
+ let data = trackStub.args[0][0].baseData;
1502
+ Assert.equal("Ajax", data.type, "request is Ajax type");
1503
+ Assert.equal(true, data.success, "TrackAjax should receive true as a 'success' argument");
1504
+ }
1505
+ });
1506
+
1507
+ this.testCase({
1508
+ name: "Ajax: non 200 means failure",
1509
+ test: () => {
1510
+ this._ajax = new AjaxMonitor();
1511
+ let appInsightsCore = new AppInsightsCore();
1512
+ let coreConfig = { instrumentationKey: "instrumentationKey", extensionConfig: {"AjaxPlugin": {}}};
1513
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
1514
+ var trackStub = this.sandbox.stub(appInsightsCore, "track");
1515
+
1516
+ // Act
1517
+ var xhr = new XMLHttpRequest();
1518
+ xhr.open("GET", "example.com/bla");
1519
+ xhr.send();
1520
+
1521
+ // Emulate response
1522
+ (<any>xhr).respond(404, {}, "");
1523
+
1524
+ // Assert
1525
+ let data = trackStub.args[0][0].baseData;
1526
+ Assert.equal("Ajax", data.type, "request is Ajax type");
1527
+ Assert.equal(false, data.success, "TrackAjax should receive false as a 'success' argument");
1528
+ }
1529
+ });
1530
+
1531
+ [200, 201, 202, 203, 204, 301, 302, 303, 304].forEach((responseCode) => {
1532
+ this.testCase({
1533
+ name: "Ajax: test success http response code: " + responseCode,
1534
+ test: () => {
1535
+ this.testAjaxSuccess(responseCode, true);
1536
+ }
1537
+ })
1538
+ });
1539
+
1540
+ [400, 401, 402, 403, 404, 500, 501].forEach((responseCode) => {
1541
+ this.testCase({
1542
+ name: "Ajax: test failure http response code: " + responseCode,
1543
+ test: () => {
1544
+ this.testAjaxSuccess(responseCode, false);
1545
+ }
1546
+ })
1547
+ });
1548
+
1549
+ this.testCase({
1550
+ name: "Ajax: overriding ready state change handlers in all possible ways",
1551
+ test: () => {
1552
+ this._ajax = new AjaxMonitor();
1553
+ let appInsightsCore = new AppInsightsCore();
1554
+ let coreConfig = { instrumentationKey: "instrumentationKey", extensionConfig: {"AjaxPlugin": {}}};
1555
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
1556
+ var trackStub = this.sandbox.stub(appInsightsCore, "track");
1557
+ var cb1 = this.sandbox.spy();
1558
+ var cb2 = this.sandbox.spy();
1559
+ var cb3 = this.sandbox.spy();
1560
+ var cb4 = this.sandbox.spy();
1561
+ var cb5 = this.sandbox.spy();
1562
+ var cb6 = this.sandbox.spy();
1563
+ var cb7 = this.sandbox.spy();
1564
+
1565
+ // Act
1566
+ var xhr = new XMLHttpRequest();
1567
+ xhr.addEventListener("readystatechange", cb1);
1568
+ xhr.addEventListener("readystatechange", cb2);
1569
+ xhr.open("GET", "example.com/bla");
1570
+ xhr.onreadystatechange = cb3;
1571
+ xhr.addEventListener("readystatechange", cb4);
1572
+ xhr.addEventListener("readystatechange", cb5);
1573
+ xhr.send();
1574
+ xhr.addEventListener("readystatechange", cb6);
1575
+ xhr.addEventListener("readystatechange", cb7);
1576
+
1577
+ Assert.ok(!trackStub.called, "TrackAjax should not be called yet");
1578
+
1579
+ // Emulate response
1580
+ (<any>xhr).respond(404, {}, "");
1581
+
1582
+ // Assert
1583
+ Assert.ok(trackStub.calledOnce, "TrackAjax should be called");
1584
+ Assert.ok(cb1.called, "callback 1 should be called");
1585
+ Assert.ok(cb2.called, "callback 2 should be called");
1586
+ Assert.ok(cb3.called, "callback 3 should be called");
1587
+ Assert.ok(cb4.called, "callback 4 should be called");
1588
+ Assert.ok(cb5.called, "callback 5 should be called");
1589
+ Assert.ok(cb6.called, "callback 6 should be called");
1590
+ Assert.ok(cb7.called, "callback 7 should be called");
1591
+ }
1592
+ });
1593
+
1594
+ this.testCase({
1595
+ name: "Ajax: test ajax duration is calculated correctly",
1596
+ test: () => {
1597
+ var initialPerformance = window.performance;
1598
+ this._ajax = new AjaxMonitor();
1599
+ try {
1600
+ // Mocking window performance (sinon doesn't have it).
1601
+ // tick() is similar to sinon's clock.tick()
1602
+ (<any>window).performance = <any>{
1603
+ current: 0,
1604
+
1605
+ now: function () {
1606
+ return this.current;
1607
+ },
1608
+
1609
+ tick: function (ms: number) {
1610
+ this.current += ms;
1611
+ },
1612
+
1613
+ timing: initialPerformance.timing
1614
+ };
1615
+
1616
+ let appInsightsCore = new AppInsightsCore();
1617
+ let coreConfig = { instrumentationKey: "instrumentationKey", extensionConfig: {"AjaxPlugin": {}}};
1618
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
1619
+ var trackStub = this.sandbox.stub(appInsightsCore, "track");
1620
+ // tick to set the initial time be non zero
1621
+ (<any>window.performance).tick(23);
1622
+
1623
+ // Act
1624
+ var xhr = new XMLHttpRequest();
1625
+ var clock = this.clock;
1626
+ var expectedResponseDuration = 50;
1627
+ xhr.onreadystatechange = () => {
1628
+ if (xhr.readyState == 3) {
1629
+ (<any>window.performance).tick(expectedResponseDuration);
1630
+ }
1631
+ }
1632
+ xhr.open("GET", "example.com/bla");
1633
+ xhr.send();
1634
+ // Emulate response
1635
+ (<any>xhr).respond(404, {}, "");
1636
+
1637
+ // Assert
1638
+ Assert.ok(trackStub.calledOnce, "TrackAjax should be called");
1639
+ let data = trackStub.args[0][0].baseData;
1640
+ Assert.equal("Ajax", data.type, "It should be an XHR request");
1641
+ Assert.ok(data.startTime);
1642
+ Assert.equal(expectedResponseDuration, data.duration, "Ajax duration should match expected duration");
1643
+ } finally {
1644
+ (<any>window.performance) = initialPerformance;
1645
+ }
1646
+ }
1647
+ });
1648
+
1649
+ this.testCase({
1650
+ name: "Ajax: 2nd invokation of xhr.send doesn't cause send wrapper to execute 2nd time",
1651
+ test: () => {
1652
+ this._ajax = new AjaxMonitor();
1653
+ let appInsightsCore = new AppInsightsCore();
1654
+ let coreConfig = { instrumentationKey: "instrumentationKey", extensionConfig: {"AjaxPlugin": {}}};
1655
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
1656
+ var spy = this.sandbox.spy(this._ajax, "includeCorrelationHeaders");
1657
+ let trackSpy = this.sandbox.spy(appInsightsCore, "track")
1658
+ this._context["trackStub"] = trackSpy;
1659
+
1660
+ // Act
1661
+ var xhr = new XMLHttpRequest();
1662
+ xhr.open("GET", "example.com/bla");
1663
+ xhr.send();
1664
+
1665
+ try {
1666
+ xhr.send();
1667
+ } catch (e) { }
1668
+
1669
+ // Emulate response
1670
+ (<any>xhr).respond(200, {"Content-Type": "application/json; charset=utf-8", "Access-Control-Allow-Origin": "*"}, "");
1671
+
1672
+ // Assert
1673
+ Assert.ok(spy.calledOnce, "sendPrefixInstrumentor/includeCorrelationHeaders should be called only once");
1674
+ }
1675
+ });
1676
+
1677
+ this.testCase({
1678
+ name: "Ajax: 2nd invokation of xhr.send doesn't cause send wrapper to execute 2nd time even if after response",
1679
+ test: () => {
1680
+ this._ajax = new AjaxMonitor();
1681
+ let appInsightsCore = new AppInsightsCore();
1682
+ let coreConfig = { instrumentationKey: "instrumentationKey", extensionConfig: {"AjaxPlugin": {}}};
1683
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
1684
+ var spy = this.sandbox.spy(this._ajax, "includeCorrelationHeaders");
1685
+ let trackSpy = this.sandbox.spy(appInsightsCore, "track")
1686
+ this._context["trackStub"] = trackSpy;
1687
+
1688
+ // Act
1689
+ var xhr = new XMLHttpRequest();
1690
+ xhr.open("GET", "example.com/bla");
1691
+ xhr.send();
1692
+
1693
+ // Emulate response
1694
+ (<any>xhr).respond(200, {"Content-Type": "application/json; charset=utf-8", "Access-Control-Allow-Origin": "*"}, "");
1695
+
1696
+ try {
1697
+ xhr.send();
1698
+ } catch (e) { }
1699
+
1700
+ // Assert
1701
+ Assert.ok(spy.calledOnce, "sendPrefixInstrumentor/includeCorrelationHeaders should be called only once");
1702
+ }
1703
+ });
1704
+
1705
+ this.testCase({
1706
+ name: "Ajax: 2 invokation of xhr.open() doesn't cause send wrapper to execute 2nd time",
1707
+ test: () => {
1708
+ this._ajax = new AjaxMonitor();
1709
+ let appInsightsCore = new AppInsightsCore();
1710
+ let coreConfig = { instrumentationKey: "instrumentationKey", extensionConfig: {"AjaxPlugin": {}}};
1711
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
1712
+
1713
+ // Act
1714
+ var xhr = new XMLHttpRequest();
1715
+ xhr.open("GET", "example.com/bla");
1716
+
1717
+
1718
+ Assert.equal("GET", xhr["ajaxData"].method, "Expecting the ajax data set the method");
1719
+ // Reset the method to something else
1720
+ xhr["ajaxData"].method = "TEST";
1721
+
1722
+ try {
1723
+ xhr.open("GET", "example.com/bla");
1724
+ } catch (e) { }
1725
+
1726
+ Assert.equal("TEST", xhr["ajaxData"].method, "sendPrefixInstrumentor should be called only once");
1727
+ }
1728
+ });
1729
+
1730
+ this.testCase({
1731
+ name: "Ajax: should create and pass a traceparent header if w3c is enabled",
1732
+ test: () => {
1733
+ this._ajax = new AjaxMonitor();
1734
+ let appInsightsCore = new AppInsightsCore();
1735
+ let coreConfig = {
1736
+ instrumentationKey: "instrumentationKey",
1737
+ extensionConfig: {
1738
+ "AjaxDependencyPlugin": {
1739
+ appId: "appId",
1740
+ distributedTracingMode: DistributedTracingModes.AI_AND_W3C
1741
+ }
1742
+ }
1743
+ };
1744
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
1745
+ var trackStub = this.sandbox.stub(appInsightsCore, "track");
1746
+
1747
+ // Use test hook to simulate the correct url location
1748
+ this._ajax["_currentWindowHost"] = "www.example.com";
1749
+
1750
+ // Act
1751
+ var xhr = new XMLHttpRequest();
1752
+ var stub = this.sandbox.stub(xhr, "setRequestHeader");
1753
+ xhr.open("GET", "http://www.example.com");
1754
+ xhr.send();
1755
+
1756
+ // Assert that both headers are sent
1757
+ Assert.equal(true, stub.calledWith(RequestHeaders.requestIdHeader)); // AI
1758
+ Assert.equal(true, stub.calledWith(RequestHeaders.traceParentHeader)); // W3C
1759
+
1760
+ // Emulate response so perf monitoring is cleaned up
1761
+ (<any>xhr).respond(200, {"Content-Type": "application/json; charset=utf-8", "Access-Control-Allow-Origin": "*"}, "");
1762
+ var id = trackStub.args[0][0].baseData.id;
1763
+ Assert.equal("|", id[0]);
1764
+ Assert.equal(".", id[id.length - 1]);
1765
+ }
1766
+ });
1767
+
1768
+ this.testCase({
1769
+ name: "Ajax: should not create and pass a traceparent header if correlationHeaderExcludePatterns set to exclude all",
1770
+ test: () => {
1771
+ this._ajax = new AjaxMonitor();
1772
+ let appInsightsCore = new AppInsightsCore();
1773
+ let coreConfig = {
1774
+ instrumentationKey: "instrumentationKey",
1775
+ correlationHeaderExcludePatterns: [/.*/],
1776
+ extensionConfig: {
1777
+ "AjaxDependencyPlugin": {
1778
+ appId: "appId",
1779
+ distributedTracingMode: DistributedTracingModes.AI_AND_W3C
1780
+ }
1781
+ }
1782
+ };
1783
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
1784
+ var trackStub = this.sandbox.stub(appInsightsCore, "track");
1785
+
1786
+ // Use test hook to simulate the correct url location
1787
+ this._ajax["_currentWindowHost"] = "www.example.com";
1788
+
1789
+ // Act
1790
+ var xhr = new XMLHttpRequest();
1791
+ var stub = this.sandbox.stub(xhr, "setRequestHeader");
1792
+ xhr.open("GET", "http://www.example.com");
1793
+ xhr.send();
1794
+
1795
+ // Assert that both headers are not sent
1796
+ Assert.equal(false, stub.calledWith(RequestHeaders.requestIdHeader)); // AI
1797
+ Assert.equal(false, stub.calledWith(RequestHeaders.traceParentHeader)); // W3C
1798
+
1799
+ // Emulate response so perf monitoring is cleaned up
1800
+ (<any>xhr).respond(200, {"Content-Type": "application/json; charset=utf-8", "Access-Control-Allow-Origin": "*"}, "");
1801
+ var id = trackStub.args[0][0].baseData.id;
1802
+ Assert.equal("|", id[0]);
1803
+ Assert.equal(".", id[id.length - 1]);
1804
+ }
1805
+ });
1806
+
1807
+ this.testCase({
1808
+ name: "Ajax: should create and only pass a traceparent header if w3c is enabled",
1809
+ test: () => {
1810
+ this._ajax = new AjaxMonitor();
1811
+ let appInsightsCore = new AppInsightsCore();
1812
+ let coreConfig = {
1813
+ instrumentationKey: "instrumentationKey",
1814
+ extensionConfig: {
1815
+ "AjaxDependencyPlugin": {
1816
+ appId: "appId",
1817
+ distributedTracingMode: DistributedTracingModes.W3C
1818
+ }
1819
+ }
1820
+ };
1821
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
1822
+ this._ajax["_currentWindowHost"] = "www.example.com";
1823
+
1824
+ // Act
1825
+ var xhr = new XMLHttpRequest();
1826
+ var stub = this.sandbox.stub(xhr, "setRequestHeader");
1827
+ xhr.open("GET", "http://www.example.com");
1828
+ xhr.send();
1829
+
1830
+ // Assert that the AI header was not included
1831
+ Assert.equal(false, stub.calledWith(RequestHeaders.requestIdHeader)); // AI
1832
+ // Assert that the W3C header is included
1833
+ Assert.equal(true, stub.calledWith(RequestHeaders.traceParentHeader)); // W3C
1834
+
1835
+ // Emulate response
1836
+ (<any>xhr).respond(200, {"Content-Type": "application/json; charset=utf-8", "Access-Control-Allow-Origin": "*"}, "");
1837
+ }
1838
+ })
1839
+
1840
+ this.testCase({
1841
+ name: "Ajax: should create and only pass AI is enabled",
1842
+ test: () => {
1843
+ this._ajax = new AjaxMonitor();
1844
+ let appInsightsCore = new AppInsightsCore();
1845
+ let coreConfig = {
1846
+ instrumentationKey: "instrumentationKey",
1847
+ extensionConfig: {
1848
+ "AjaxDependencyPlugin": {
1849
+ appId: "appId",
1850
+ distributedTracingMode: DistributedTracingModes.AI
1851
+ }
1852
+ }
1853
+ };
1854
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
1855
+ this._ajax["_currentWindowHost"] = "www.example.com";
1856
+
1857
+ // Act
1858
+ var xhr = new XMLHttpRequest();
1859
+ var stub = this.sandbox.stub(xhr, "setRequestHeader");
1860
+ xhr.open("GET", "http://www.example.com");
1861
+ xhr.send();
1862
+
1863
+ // Assert that the AI header was not included
1864
+ Assert.equal(true, stub.calledWith(RequestHeaders.requestIdHeader)); // AI
1865
+ // Assert that the W3C header is included
1866
+ Assert.equal(false, stub.calledWith(RequestHeaders.traceParentHeader)); // W3C
1867
+
1868
+ // Emulate response
1869
+ (<any>xhr).respond(200, {"Content-Type": "application/json; charset=utf-8", "Access-Control-Allow-Origin": "*"}, "");
1870
+ }
1871
+ })
1872
+ }
1873
+
1874
+ private testAjaxSuccess(responseCode: number, success: boolean) {
1875
+ this._ajax = new AjaxMonitor();
1876
+ let appInsightsCore = new AppInsightsCore();
1877
+ let coreConfig = { instrumentationKey: "instrumentationKey", extensionConfig: {"AjaxPlugin": {}}};
1878
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
1879
+ var trackStub = this.sandbox.stub(appInsightsCore, "track");
1880
+
1881
+ // Act
1882
+ var xhr = new XMLHttpRequest();
1883
+ xhr.open("GET", "example.com/bla");
1884
+ xhr.send();
1885
+
1886
+ // Emulate response
1887
+ (<any>xhr).respond(responseCode, {}, "");
1888
+
1889
+ // Assert
1890
+ let data = trackStub.args[0][0].baseData;
1891
+ Assert.equal("Ajax", data.type, "request is Ajax type");
1892
+ Assert.equal(success, data.success, "TrackAjax should receive " + success + " as a 'success' argument");
1893
+ }
1894
+
1895
+ private _checkFetchTraceId(evtData:any, message:string) {
1896
+ Assert.notEqual(undefined, evtData, message + " - Must have track data");
1897
+ if (evtData) {
1898
+ let data = evtData.baseData;
1899
+ Assert.equal("Fetch", data.type, message + " - request is Fatch type");
1900
+ var id = data.id;
1901
+ Assert.equal("|", id[0], message + " - check id starts with |");
1902
+ Assert.equal(".", id[id.length - 1], message + " - check id ends with .");
1903
+ }
1904
+ }
1905
+ }
1906
+
1907
+ export class AjaxPerfTrackTests extends AITestClass {
1908
+
1909
+ private _fetch:any;
1910
+ private _ajax:AjaxMonitor;
1911
+ private _initialPerformance: Performance;
1912
+ private _perfEntries: PerformanceEntry[];
1913
+ private _context:any;
1914
+
1915
+ constructor(name?: string) {
1916
+ super(name);
1917
+
1918
+ this.useFakeServer = false;
1919
+ this._perfEntries = [];
1920
+ this._context = {};
1921
+ }
1922
+
1923
+ public addPerfEntry(entry:any) {
1924
+ this._perfEntries.push(entry);
1925
+ }
1926
+
1927
+ public testInitialize() {
1928
+ this._context = {};
1929
+ this._fetch = getGlobalInst("fetch");
1930
+
1931
+ let performance = getPerformance();
1932
+ if (performance && performance.clearResourceTimings) {
1933
+ // Make sure we don't pick up some elses value
1934
+ performance.clearResourceTimings();
1935
+ }
1936
+
1937
+ let testThis = this;
1938
+ testThis._initialPerformance = performance;
1939
+
1940
+ testThis._perfEntries = [];
1941
+
1942
+ // Add polyfil / mock
1943
+ (<any>window).performance = {
1944
+ _current: 0,
1945
+ _tick: function (ms: number) {
1946
+ this._current += ms;
1947
+ },
1948
+ now: function() {
1949
+ return this._current;
1950
+ },
1951
+ getEntries: function() {
1952
+ return testThis._perfEntries;
1953
+ },
1954
+ getEntriesByName: function (name) {
1955
+ let result = [];
1956
+ testThis._perfEntries.forEach((entry) => {
1957
+ if (entry.name === name) {
1958
+ result.push(entry);
1959
+ }
1960
+ });
1961
+ return result;
1962
+ },
1963
+ mark: function (name) {
1964
+ testThis.addPerfEntry({
1965
+ entryType: "mark",
1966
+ name: name,
1967
+ startTime: this._current,
1968
+ duration: 0
1969
+ });
1970
+ }
1971
+ }
1972
+ }
1973
+
1974
+ public testCleanup() {
1975
+ this._context = {};
1976
+ if (this._ajax) {
1977
+ this._ajax.teardown();
1978
+ this._ajax = null;
1979
+ }
1980
+
1981
+ if (this._initialPerformance) {
1982
+ (<any>window).performance = this._initialPerformance;
1983
+ this._initialPerformance = null;
1984
+ }
1985
+
1986
+ // Restore the real fetch
1987
+ window.fetch = this._fetch;
1988
+ }
1989
+
1990
+ public registerTests() {
1991
+
1992
+ this.testCaseAsync({
1993
+ name: "AjaxPerf: check that performance tracking is disabled for xhr requests by default",
1994
+ stepDelay: 10,
1995
+ steps: [ () => {
1996
+ let performance = getPerformance();
1997
+ let markSpy = this.sandbox.spy(performance, "mark");
1998
+
1999
+ this._ajax = new AjaxMonitor();
2000
+ let appInsightsCore = new AppInsightsCore();
2001
+ let coreConfig = {
2002
+ instrumentationKey: "instrumentationKey",
2003
+ extensionConfig: {
2004
+ "AjaxDependencyPlugin": {
2005
+ appId: "appId"
2006
+ }
2007
+ }
2008
+ };
2009
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
2010
+ this._ajax["_currentWindowHost"] = "httpbin.org";
2011
+
2012
+ // Used to "wait" for App Insights to finish initializing which should complete after the XHR request
2013
+ this._context["trackStub"] = this.sandbox.stub(appInsightsCore, "track");
2014
+
2015
+
2016
+ // Act
2017
+ var xhr = new XMLHttpRequest();
2018
+
2019
+ // trigger the request that should cause a track event once the xhr request is complete
2020
+ xhr.open("GET", "https://httpbin.org/status/200");
2021
+ xhr.send();
2022
+ Assert.equal(false, markSpy.called, "The code should not have called mark()");
2023
+ }]
2024
+ .concat(PollingAssert.createPollingAssert(() => {
2025
+ let trackStub = this._context["trackStub"] as SinonStub;
2026
+ if (trackStub.called) {
2027
+ Assert.ok(trackStub.calledOnce, "track is called");
2028
+ let data = trackStub.args[0][0].baseData;
2029
+ Assert.equal("Ajax", data.type, "request is Ajax type");
2030
+ let props = data.properties || {};
2031
+ Assert.equal(undefined, props.ajaxPerf, "Should contain properties perf object");
2032
+ return true;
2033
+ }
2034
+
2035
+ return false;
2036
+ }, 'response received', 600, 1000) as any)
2037
+ });
2038
+
2039
+ this.testCaseAsync({
2040
+ name: "AjaxPerf: check that performance tracking is included when enabled for xhr requests",
2041
+ stepDelay: 10,
2042
+ steps: [ (testContext) => {
2043
+ let performance = getPerformance();
2044
+ let markSpy = this.sandbox.spy(performance, "mark");
2045
+
2046
+ this._ajax = new AjaxMonitor();
2047
+ let appInsightsCore = new AppInsightsCore();
2048
+ let coreConfig = {
2049
+ instrumentationKey: "instrumentationKey",
2050
+ extensionConfig: {
2051
+ "AjaxDependencyPlugin": {
2052
+ appId: "appId",
2053
+ enableAjaxPerfTracking: true
2054
+ }
2055
+ }
2056
+ };
2057
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
2058
+ this._ajax["_currentWindowHost"] = "httpbin.org";
2059
+
2060
+ // Used to "wait" for App Insights to finish initializing which should complete after the XHR request
2061
+ this._context["trackStub"] = this.sandbox.stub(appInsightsCore, "track");
2062
+
2063
+ // Act
2064
+ var xhr = new XMLHttpRequest();
2065
+
2066
+ // trigger the request that should cause a track event once the xhr request is complete
2067
+ xhr.open("GET", "https://httpbin.org/status/200");
2068
+ xhr.send();
2069
+ Assert.equal(true, markSpy.called, "The code should have called been mark()");
2070
+ this.addPerfEntry({
2071
+ entryType: "resource",
2072
+ initiatorType: "xmlhttprequest",
2073
+ name: "https://httpbin.org/status/200",
2074
+ startTime: getPerformance().now(),
2075
+ duration: 10
2076
+ });
2077
+ }]
2078
+ .concat(PollingAssert.createPollingAssert(() => {
2079
+ let trackStub = this._context["trackStub"] as SinonStub;
2080
+ if (trackStub.called) {
2081
+ Assert.ok(trackStub.calledOnce, "track is called");
2082
+ let data = trackStub.args[0][0].baseData;
2083
+ Assert.equal("Ajax", data.type, "request is Ajax type");
2084
+ let props = data.properties;
2085
+ Assert.notEqual(undefined, props, "Should contain properties");
2086
+ if (props) {
2087
+ let perf = props.ajaxPerf || {};
2088
+ Assert.equal(10, perf.duration, "Duration exists - " + JSON.stringify(data));
2089
+ }
2090
+ return true;
2091
+ }
2092
+
2093
+ return false;
2094
+ }, 'response received', 600, 1000) as any)
2095
+ });
2096
+
2097
+ this.testCaseAsync({
2098
+ name: "AjaxPerf: check that performance tracking is included when enabled for xhr requests with server timing",
2099
+ stepDelay: 10,
2100
+ steps: [ (testContext) => {
2101
+ let performance = getPerformance();
2102
+ let markSpy = this.sandbox.spy(performance, "mark");
2103
+
2104
+ this._ajax = new AjaxMonitor();
2105
+ let appInsightsCore = new AppInsightsCore();
2106
+ let coreConfig = {
2107
+ instrumentationKey: "instrumentationKey",
2108
+ extensionConfig: {
2109
+ "AjaxDependencyPlugin": {
2110
+ appId: "appId",
2111
+ enableAjaxPerfTracking: true
2112
+ }
2113
+ }
2114
+ };
2115
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
2116
+ this._ajax["_currentWindowHost"] = "httpbin.org";
2117
+
2118
+ // Used to "wait" for App Insights to finish initializing which should complete after the XHR request
2119
+ this._context["trackStub"] = this.sandbox.stub(appInsightsCore, "track");
2120
+
2121
+ // Act
2122
+ var xhr = new XMLHttpRequest();
2123
+
2124
+ // trigger the request that should cause a track event once the xhr request is complete
2125
+ xhr.open("GET", "https://httpbin.org/status/200");
2126
+ xhr.send();
2127
+ Assert.equal(true, markSpy.called, "The code should have called been mark()");
2128
+ this.addPerfEntry({
2129
+ entryType: "resource",
2130
+ initiatorType: "xmlhttprequest",
2131
+ name: "https://httpbin.org/status/200",
2132
+ startTime: getPerformance().now(),
2133
+ duration: 10,
2134
+ serverTiming: [
2135
+ { name: "cache", duration: 23.2, description: "Cache Read"},
2136
+ { name: "db", duration: 53, description: ""},
2137
+ { name: "app", duration: 47.2, description: ""},
2138
+ { name: "dup", description: "dup1"},
2139
+ { name: "dup", description: "dup2"},
2140
+ ]
2141
+ });
2142
+ }]
2143
+ .concat(PollingAssert.createPollingAssert(() => {
2144
+ let trackStub = this._context["trackStub"] as SinonStub;
2145
+ if (trackStub.called) {
2146
+ Assert.ok(trackStub.calledOnce, "track is called");
2147
+ let data = trackStub.args[0][0].baseData;
2148
+ Assert.equal("Ajax", data.type, "request is Ajax type");
2149
+ let props = data.properties;
2150
+ Assert.notEqual(undefined, props, "Should contain properties");
2151
+ if (props) {
2152
+ let perf = props.ajaxPerf || {};
2153
+ Assert.equal(10, perf.duration, "Duration exists - " + JSON.stringify(data));
2154
+ Assert.equal(23.2, perf.serverTiming.cache.duration, "Check that the server timing was set")
2155
+ Assert.equal("Cache Read", perf.serverTiming.cache.description, "Check that the server timing was set")
2156
+ Assert.equal("dup1;dup2", perf.serverTiming.dup.description, "Check that the server timing was set")
2157
+ }
2158
+ return true;
2159
+ }
2160
+
2161
+ return false;
2162
+ }, 'response received', 600, 1000) as any)
2163
+ });
2164
+
2165
+ this.testCaseAsync({
2166
+ name: "AjaxPerf: check that performance tracking is reported, even if the entry is missing when enabled for xhr requests",
2167
+ stepDelay: 10,
2168
+ steps: [ (testContext) => {
2169
+ let performance = getPerformance();
2170
+ let markSpy = this.sandbox.spy(performance, "mark");
2171
+
2172
+ this._ajax = new AjaxMonitor();
2173
+ let appInsightsCore = new AppInsightsCore();
2174
+ let coreConfig = {
2175
+ instrumentationKey: "instrumentationKey",
2176
+ extensionConfig: {
2177
+ "AjaxDependencyPlugin": {
2178
+ appId: "appId",
2179
+ enableAjaxPerfTracking: true
2180
+ }
2181
+ }
2182
+ };
2183
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
2184
+ this._ajax["_currentWindowHost"] = "httpbin.org";
2185
+
2186
+ // Used to "wait" for App Insights to finish initializing which should complete after the XHR request
2187
+ this._context["trackStub"] = this.sandbox.stub(appInsightsCore, "track");
2188
+
2189
+ // Act
2190
+ var xhr = new XMLHttpRequest();
2191
+
2192
+ // trigger the request that should cause a track event once the xhr request is complete
2193
+ xhr.open("GET", "https://httpbin.org/status/200");
2194
+ xhr.send();
2195
+ Assert.equal(true, markSpy.called, "The code should have called been mark()");
2196
+ }]
2197
+ .concat(PollingAssert.createPollingAssert(() => {
2198
+ let trackStub = this._context["trackStub"] as SinonStub;
2199
+ if (trackStub.called) {
2200
+ Assert.ok(trackStub.calledOnce, "track is called");
2201
+ let data = trackStub.args[0][0].baseData;
2202
+ Assert.equal("Ajax", data.type, "request is Ajax type");
2203
+ let props = data.properties;
2204
+ Assert.notEqual(undefined, props, "Should contain properties");
2205
+ if (props) {
2206
+ let perf = props.ajaxPerf || {};
2207
+ Assert.equal(true, !!perf.missing, "Performance was executed but browser did not populate the window.performance entries - " + JSON.stringify(data));
2208
+ }
2209
+ return true;
2210
+ }
2211
+
2212
+ return false;
2213
+ }, 'response received', 60, 1000) as any)
2214
+ });
2215
+
2216
+ this.testCaseAsync({
2217
+ name: "AjaxPerf: check that performance tracking is disabled for fetch requests by default",
2218
+ stepDelay: 10,
2219
+ steps: [ (testContext) => {
2220
+
2221
+ hookFetch((resolve) => {
2222
+ AITestClass.orgSetTimeout(function() {
2223
+ resolve();
2224
+ }, 0);
2225
+ });
2226
+
2227
+ let performance = getPerformance();
2228
+ let markSpy = this.sandbox.spy(performance, "mark");
2229
+
2230
+ this._ajax = new AjaxMonitor();
2231
+ let appInsightsCore = new AppInsightsCore();
2232
+ let coreConfig = {
2233
+ instrumentationKey: "instrumentationKey",
2234
+ extensionConfig: {
2235
+ "AjaxDependencyPlugin": {
2236
+ appId: "appId"
2237
+ }
2238
+ }
2239
+ };
2240
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
2241
+ this._ajax["_currentWindowHost"] = "httpbin.org";
2242
+
2243
+ let trackSpy = this.sandbox.spy(appInsightsCore, "track")
2244
+ this._context["trackStub"] = trackSpy;
2245
+
2246
+ // Send fetch request that should trigger a track event when complete
2247
+ Assert.ok(trackSpy.notCalled, "No fetch called yet");
2248
+ fetch("https://httpbin.org/status/200", {method: "post", }).then((value) => {
2249
+ this._context["fetchComplete"] = true;
2250
+ return value;
2251
+ });
2252
+ Assert.equal(false, markSpy.called, "The code should not have called been mark()");
2253
+ }]
2254
+ .concat(PollingAssert.createPollingAssert(() => {
2255
+ let trackStub = this._context["trackStub"] as SinonStub;
2256
+ if (this._context["fetchComplete"]) {
2257
+ Assert.ok(trackStub.notCalled, "No fetch called yet");
2258
+ return true;
2259
+ }
2260
+
2261
+ return false;
2262
+ }, 'response received', 60, 1000) as any)
2263
+ });
2264
+
2265
+ this.testCaseAsync({
2266
+ name: "AjaxPerf: check that performance tracking is included for fetch requests when enabled",
2267
+ stepDelay: 10,
2268
+ steps: [ (testContext) => {
2269
+ hookFetch((resolve) => {
2270
+ AITestClass.orgSetTimeout(function() {
2271
+ resolve({
2272
+ headers: new Headers(),
2273
+ ok: true,
2274
+ body: null,
2275
+ bodyUsed: false,
2276
+ redirected: false,
2277
+ status: 200,
2278
+ statusText: "Hello",
2279
+ trailer: null,
2280
+ type: "basic",
2281
+ url: "https://httpbin.org/status/200"
2282
+ });
2283
+ }, 0);
2284
+ });
2285
+
2286
+ let performance = getPerformance();
2287
+ let markSpy = this.sandbox.spy(performance, "mark");
2288
+
2289
+ this._ajax = new AjaxMonitor();
2290
+ let appInsightsCore = new AppInsightsCore();
2291
+ let coreConfig = {
2292
+ instrumentationKey: "instrumentationKey",
2293
+ extensionConfig: {
2294
+ "AjaxDependencyPlugin": {
2295
+ appId: "appId",
2296
+ disableFetchTracking: false,
2297
+ enableAjaxPerfTracking: true
2298
+ }
2299
+ }
2300
+ };
2301
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
2302
+ this._ajax["_currentWindowHost"] = "www.example.com";
2303
+
2304
+ let trackSpy = this.sandbox.spy(appInsightsCore, "track")
2305
+ this._context["trackStub"] = trackSpy;
2306
+
2307
+ // Send fetch request that should trigger a track event when complete
2308
+ Assert.ok(trackSpy.notCalled, "No fetch called yet");
2309
+ fetch("https://httpbin.org/status/200", {method: "post" });
2310
+ Assert.equal(true, markSpy.called, "The code should have called been mark()");
2311
+ }]
2312
+ .concat(PollingAssert.createPollingAssert(() => {
2313
+ let trackStub = this._context["trackStub"] as SinonStub;
2314
+ if (trackStub.called) {
2315
+ window.console && window.console.warn("Performance Entries: " + window.performance.getEntries().length);
2316
+
2317
+ Assert.ok(trackStub.calledOnce, "track is called");
2318
+ let data = trackStub.args[0][0].baseData;
2319
+ Assert.equal("Fetch", data.type, "request is Fetch type");
2320
+ let props = data.properties;
2321
+ Assert.notEqual(undefined, props, "Should contain properties");
2322
+ if (props) {
2323
+ let perf = props.ajaxPerf || {};
2324
+ if (perf.missing) {
2325
+ Assert.equal(true, !!perf.missing, "Performance was executed but browser did not populate the window.performance entries - " + JSON.stringify(data));
2326
+ } else {
2327
+ Assert.notEqual(undefined, perf.duration, "Duration exists - " + JSON.stringify(data));
2328
+ }
2329
+ }
2330
+ return true;
2331
+ }
2332
+
2333
+ return false;
2334
+ }, 'response received', 30, 1000) as any)
2335
+ });
2336
+
2337
+ this.testCaseAsync({
2338
+ name: "AjaxPerf: check that performance tracking is included for fetch requests when enabled when the fetch has a delayed promise",
2339
+ stepDelay: 10,
2340
+ steps: [ (testContext) => {
2341
+ hookFetch((resolve) => {
2342
+ AITestClass.orgSetTimeout(function() {
2343
+ resolve({
2344
+ headers: new Headers(),
2345
+ ok: true,
2346
+ body: null,
2347
+ bodyUsed: false,
2348
+ redirected: false,
2349
+ status: 200,
2350
+ statusText: "Hello",
2351
+ trailer: null,
2352
+ type: "basic",
2353
+ url: "https://httpbin.org/status/200"
2354
+ });
2355
+ }, 500);
2356
+ });
2357
+
2358
+ let performance = getPerformance();
2359
+ let markSpy = this.sandbox.spy(performance, "mark");
2360
+
2361
+ this._ajax = new AjaxMonitor();
2362
+ let appInsightsCore = new AppInsightsCore();
2363
+ let coreConfig = {
2364
+ instrumentationKey: "instrumentationKey",
2365
+ extensionConfig: {
2366
+ "AjaxDependencyPlugin": {
2367
+ appId: "appId",
2368
+ disableFetchTracking: false,
2369
+ enableAjaxPerfTracking: true
2370
+ }
2371
+ }
2372
+ };
2373
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
2374
+ this._ajax["_currentWindowHost"] = "www.example.com";
2375
+
2376
+ let trackSpy = this.sandbox.spy(appInsightsCore, "track")
2377
+ this._context["trackStub"] = trackSpy;
2378
+
2379
+ // Send fetch request that should trigger a track event when complete
2380
+ Assert.ok(trackSpy.notCalled, "No fetch called yet");
2381
+ fetch("https://httpbin.org/status/200", { method: "post" });
2382
+ Assert.equal(true, markSpy.called, "The code should have called been mark()");
2383
+ }]
2384
+ .concat(PollingAssert.createPollingAssert(() => {
2385
+ let trackStub = this._context["trackStub"] as SinonStub;
2386
+ if (trackStub.called) {
2387
+ window.console && window.console.warn("Performance Entries: " + window.performance.getEntries().length);
2388
+
2389
+ Assert.ok(trackStub.calledOnce, "track is called");
2390
+ let data = trackStub.args[0][0].baseData;
2391
+ Assert.equal("Fetch", data.type, "request is Fetch type");
2392
+ let props = data.properties;
2393
+ Assert.notEqual(undefined, props, "Should contain properties");
2394
+ if (props) {
2395
+ Assert.notEqual(undefined, props.ajaxPerf, "Perf detail exists")
2396
+ let perf = props.ajaxPerf || {};
2397
+ if (perf.missing) {
2398
+ Assert.equal(true, !!perf.missing, "Performance was executed but browser did not populate the window.performance entries - " + JSON.stringify(data));
2399
+ } else {
2400
+ Assert.notEqual(undefined, perf.duration, "Duration exists - " + JSON.stringify(data));
2401
+ }
2402
+ }
2403
+ return true;
2404
+ }
2405
+
2406
+ return false;
2407
+ }, 'response received', 600, 1000) as any)
2408
+ });
2409
+
2410
+ this.testCaseAsync({
2411
+ name: "Fetch: should not create and pass correlation header if correlationHeaderExcludePatterns set to exclude all.",
2412
+ stepDelay: 10,
2413
+ timeOut: 10000,
2414
+ steps: [ (testContext) => {
2415
+ let fetchCalls = hookFetch((resolve) => {
2416
+ AITestClass.orgSetTimeout(function() {
2417
+ resolve({
2418
+ headers: new Headers(),
2419
+ ok: true,
2420
+ body: null,
2421
+ bodyUsed: false,
2422
+ redirected: false,
2423
+ status: 200,
2424
+ statusText: "Hello",
2425
+ trailer: null,
2426
+ type: "basic",
2427
+ url: "https://httpbin.org/status/200"
2428
+ });
2429
+ }, 0);
2430
+ });
2431
+
2432
+ this._ajax = new AjaxMonitor();
2433
+ let appInsightsCore = new AppInsightsCore();
2434
+ let coreConfig = {
2435
+ instrumentationKey: "instrumentationKey",
2436
+ disableFetchTracking: false,
2437
+ disableAjaxTracking: false,
2438
+ correlationHeaderExcludePatterns: [/.*/],
2439
+ extensionConfig: {
2440
+ "AjaxDependencyPlugin": {
2441
+ appId: "appId",
2442
+ distributedTracingMode: DistributedTracingModes.AI_AND_W3C
2443
+ }
2444
+ }
2445
+ };
2446
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
2447
+ let trackSpy = this.sandbox.spy(appInsightsCore, "track")
2448
+ this._context["trackStub"] = trackSpy;
2449
+
2450
+ // Use test hook to simulate the correct url location
2451
+ this._ajax["_currentWindowHost"] = "httpbin.org";
2452
+
2453
+ // Setup
2454
+ let headers = new Headers();
2455
+ headers.append('My-Header', 'Header field');
2456
+ let init = {
2457
+ method: 'get',
2458
+ headers
2459
+ };
2460
+ const url = 'https://httpbin.org/status/200';
2461
+
2462
+ // Act
2463
+ Assert.ok(trackSpy.notCalled, "No fetch called yet");
2464
+ fetch(url, init).then(() => {
2465
+ // Assert
2466
+ Assert.ok(trackSpy.called, "The request was not tracked");
2467
+ Assert.equal(1, fetchCalls.length);
2468
+ Assert.notEqual(undefined, fetchCalls[0].init, "Has init param");
2469
+ let headers:Headers = fetchCalls[0].init.headers as Headers;
2470
+ Assert.equal(true, headers.has("My-Header"), "My-Header should be present");
2471
+ Assert.equal(false, headers.has(RequestHeaders.requestIdHeader), "Correlation header - AI header should be excluded"); // AI
2472
+ Assert.equal(false, headers.has(RequestHeaders.traceParentHeader), "Correlation header - W3c header should be excluded"); // W3C
2473
+ }, () => {
2474
+ Assert.ok(false, "fetch failed!");
2475
+ testContext.testDone();
2476
+ });
2477
+ }]
2478
+ .concat(PollingAssert.createPollingAssert(() => {
2479
+ let trackStub = this._context["trackStub"] as SinonStub;
2480
+ if (trackStub.called) {
2481
+ Assert.ok(trackStub.calledOnce, "track is called");
2482
+ let data = trackStub.args[0][0].baseData;
2483
+ Assert.equal("Fetch", data.type, "request is Fatch type");
2484
+ var id = data.id;
2485
+ Assert.equal("|", id[0]);
2486
+ Assert.equal(".", id[id.length - 1]);
2487
+ return true;
2488
+ }
2489
+
2490
+ return false;
2491
+ }, 'response received', 60, 1000) as any)
2492
+ })
2493
+ }
2494
+ }
2495
+
2496
+ export class AjaxFrozenTests extends AITestClass {
2497
+
2498
+ private _fetch:any;
2499
+ private _xmlHttpRequest:XMLHttpRequest;
2500
+ private _ajax:AjaxMonitor;
2501
+ private _context:any;
2502
+
2503
+ constructor(name?: string) {
2504
+ super(name);
2505
+
2506
+ this.useFakeServer = false;
2507
+ this._context = {};
2508
+ }
2509
+
2510
+ public testInitialize() {
2511
+ this._context = {};
2512
+ this._fetch = getGlobalInst("fetch");
2513
+ this._xmlHttpRequest = getGlobalInst("XMLHttpRquest)");
2514
+ }
2515
+
2516
+ public testCleanup() {
2517
+ this._context = {};
2518
+ if (this._ajax) {
2519
+ this._ajax.teardown();
2520
+ this._ajax = null;
2521
+ }
2522
+
2523
+ // Restore the real fetch
2524
+ window.fetch = this._fetch;
2525
+ if (this._xmlHttpRequest) {
2526
+ getGlobal()["XMLHttpRequest"] = this._xmlHttpRequest;
2527
+ }
2528
+ }
2529
+
2530
+ public registerTests() {
2531
+
2532
+ this.testCaseAsync({
2533
+ name: "AjaxFrozenTests: check for prevent extensions",
2534
+ stepDelay: 10,
2535
+ steps: [ () => {
2536
+ Object.preventExtensions(XMLHttpRequest);
2537
+ Object.freeze(XMLHttpRequest);
2538
+ let reflect:any = getGlobalInst("Reflect");
2539
+ if (reflect) {
2540
+ reflect.preventExtensions(XMLHttpRequest);
2541
+ }
2542
+
2543
+ this._ajax = new AjaxMonitor();
2544
+ let appInsightsCore = new AppInsightsCore();
2545
+ let coreConfig = {
2546
+ instrumentationKey: "instrumentationKey",
2547
+ extensionConfig: {
2548
+ "AjaxDependencyPlugin": {
2549
+ appId: "appId"
2550
+ }
2551
+ }
2552
+ };
2553
+ appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
2554
+ this._ajax["_currentWindowHost"] = "httpbin.org";
2555
+
2556
+ // Used to "wait" for App Insights to finish initializing which should complete after the XHR request
2557
+ this._context["trackStub"] = this.sandbox.stub(appInsightsCore, "track");
2558
+ this._context["throwSpy"] = this.sandbox.spy(appInsightsCore.logger, "throwInternal");
2559
+
2560
+ // Act
2561
+ var xhr = new XMLHttpRequest();
2562
+
2563
+ // Make sure the instance can't be changed
2564
+ Object.preventExtensions(xhr);
2565
+ Object.freeze(xhr);
2566
+ if (reflect) {
2567
+ reflect["preventExtensions"](xhr);
2568
+ }
2569
+
2570
+ // trigger the request that should cause a track event once the xhr request is complete
2571
+ xhr.open("GET", "https://httpbin.org/status/200");
2572
+ xhr.send();
2573
+ }]
2574
+ .concat(PollingAssert.createPollingAssert(() => {
2575
+ let throwSpy = this._context["throwSpy"] as SinonStub;
2576
+ if (throwSpy.called) {
2577
+ Assert.ok(throwSpy.calledOnce, "track is called");
2578
+ let message = throwSpy.args[0][2];
2579
+ Assert.notEqual(-1, message.indexOf("Failed to monitor XMLHttpRequest"));
2580
+ let data = throwSpy.args[0][3];
2581
+ Assert.notEqual(-1, data.exception.indexOf("Cannot add property ajaxData"));
2582
+ return true;
2583
+ }
2584
+
2585
+ return false;
2586
+ }, 'response received', 60, 1000) as any)
2587
+ });
2588
+
2589
+ // This is currently a manual test as we don't have hooks / mocks defined to automated this today
2590
+ // this.testCaseAsync({
2591
+ // name: "AjaxFrozenTests: check frozen prototype",
2592
+ // stepDelay: 10,
2593
+ // steps: [ () => {
2594
+ // Object.preventExtensions(XMLHttpRequest.prototype);
2595
+ // Object.freeze(XMLHttpRequest.prototype);
2596
+ // let reflect:any = getGlobalInst("Reflect");
2597
+ // if (reflect) {
2598
+ // reflect.preventExtensions(XMLHttpRequest.prototype);
2599
+ // }
2600
+
2601
+ // this._ajax = new AjaxMonitor();
2602
+ // let appInsightsCore = new AppInsightsCore();
2603
+ // let coreConfig = {
2604
+ // instrumentationKey: "instrumentationKey",
2605
+ // extensionConfig: {
2606
+ // "AjaxDependencyPlugin": {
2607
+ // appId: "appId"
2608
+ // }
2609
+ // }
2610
+ // };
2611
+ // let testThis = this;
2612
+ // appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
2613
+ // appInsightsCore.addNotificationListener({
2614
+ // eventsSent: (events: ITelemetryItem[]) => {
2615
+ // testThis._context["_eventsSent"] = events;
2616
+ // }
2617
+ // });
2618
+ // this._ajax["_currentWindowHost"] = "httpbin.org";
2619
+
2620
+ // // Used to "wait" for App Insights to finish initializing which should complete after the XHR request
2621
+ // this._context["trackStub"] = this.sandbox.stub(appInsightsCore, "track");
2622
+
2623
+ // // Act
2624
+ // var xhr = new XMLHttpRequest();
2625
+
2626
+ // // Make sure the instance can't be changed
2627
+ // Object.preventExtensions(xhr);
2628
+ // Object.freeze(xhr);
2629
+ // if (reflect) {
2630
+ // reflect["preventExtensions"](xhr);
2631
+ // }
2632
+
2633
+ // // trigger the request that should cause a track event once the xhr request is complete
2634
+ // xhr.open("GET", "https://httpbin.org/status/200");
2635
+ // xhr.send();
2636
+ // appInsightsCore.track({
2637
+ // name: "Hello World!"
2638
+ // });
2639
+ // }]
2640
+ // .concat(PollingAssert.createPollingAssert(() => {
2641
+ // let trackStub = this._context["trackStub"] as SinonStub;
2642
+ // if (trackStub.called) {
2643
+ // Assert.ok(trackStub.calledOnce, "track is called");
2644
+ // let data = trackStub.args[0][0].baseData;
2645
+ // Assert.equal("Ajax", data.type, "request is Ajax type");
2646
+ // let props = data.properties || {};
2647
+ // Assert.equal(undefined, props.ajaxPerf, "Should contain properties perf object");
2648
+ // return true;
2649
+ // }
2650
+
2651
+ // return false;
2652
+ // }, 'response received', 600, 1000) as any)
2653
+ // });
2654
+
2655
+ }
2656
+ }
2657
+
2658
+ class TestChannelPlugin implements IChannelControls {
2659
+
2660
+ public isFlushInvoked = false;
2661
+ public isUnloadInvoked = false;
2662
+ public isTearDownInvoked = false;
2663
+ public isResumeInvoked = false;
2664
+ public isPauseInvoked = false;
2665
+
2666
+ constructor() {
2667
+ this.processTelemetry = this._processTelemetry.bind(this);
2668
+ }
2669
+ public pause(): void {
2670
+ this.isPauseInvoked = true;
2671
+ }
2672
+
2673
+ public resume(): void {
2674
+ this.isResumeInvoked = true;
2675
+ }
2676
+
2677
+ public teardown(): void {
2678
+ this.isTearDownInvoked = true;
2679
+ }
2680
+
2681
+ flush(async?: boolean, callBack?: () => void): void {
2682
+ this.isFlushInvoked = true;
2683
+ if (callBack) {
2684
+ callBack();
2685
+ }
2686
+ }
2687
+
2688
+ public processTelemetry;
2689
+
2690
+ public identifier = "Sender";
2691
+
2692
+ setNextPlugin(next: ITelemetryPlugin) {
2693
+ // no next setup
2694
+ }
2695
+
2696
+ public priority: number = 1001;
2697
+
2698
+ public initialize = (config: IConfiguration) => {
2699
+ }
2700
+
2701
+ private _processTelemetry(env: ITelemetryItem) {
2702
+
2703
+ }
2704
+ }
2705
+
2706
+ class TestAjaxMonitor extends AjaxMonitor {
2707
+
2708
+ }