@datadog/datadog-ci 1.7.2 → 1.8.0

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 (49) hide show
  1. package/README.md +26 -0
  2. package/dist/commands/junit/__tests__/upload.test.js +12 -0
  3. package/dist/commands/junit/__tests__/upload.test.js.map +1 -1
  4. package/dist/commands/junit/api.js +1 -1
  5. package/dist/commands/junit/api.js.map +1 -1
  6. package/dist/commands/junit/interfaces.d.ts +1 -0
  7. package/dist/commands/junit/upload.js +2 -0
  8. package/dist/commands/junit/upload.js.map +1 -1
  9. package/dist/commands/synthetics/__tests__/api.test.js +5 -13
  10. package/dist/commands/synthetics/__tests__/api.test.js.map +1 -1
  11. package/dist/commands/synthetics/__tests__/cli.test.js +49 -211
  12. package/dist/commands/synthetics/__tests__/cli.test.js.map +1 -1
  13. package/dist/commands/synthetics/__tests__/fixtures.d.ts +10 -15
  14. package/dist/commands/synthetics/__tests__/fixtures.js +99 -56
  15. package/dist/commands/synthetics/__tests__/fixtures.js.map +1 -1
  16. package/dist/commands/synthetics/__tests__/reporters/default.test.js +21 -24
  17. package/dist/commands/synthetics/__tests__/reporters/default.test.js.map +1 -1
  18. package/dist/commands/synthetics/__tests__/reporters/junit.test.js +1 -2
  19. package/dist/commands/synthetics/__tests__/reporters/junit.test.js.map +1 -1
  20. package/dist/commands/synthetics/__tests__/run-test.test.js +5 -3
  21. package/dist/commands/synthetics/__tests__/run-test.test.js.map +1 -1
  22. package/dist/commands/synthetics/__tests__/utils.test.js +289 -225
  23. package/dist/commands/synthetics/__tests__/utils.test.js.map +1 -1
  24. package/dist/commands/synthetics/__tests__/websocket.test.js +2 -0
  25. package/dist/commands/synthetics/__tests__/websocket.test.js.map +1 -1
  26. package/dist/commands/synthetics/api.d.ts +3 -4
  27. package/dist/commands/synthetics/api.js +13 -3
  28. package/dist/commands/synthetics/api.js.map +1 -1
  29. package/dist/commands/synthetics/command.d.ts +1 -9
  30. package/dist/commands/synthetics/command.js +7 -66
  31. package/dist/commands/synthetics/command.js.map +1 -1
  32. package/dist/commands/synthetics/errors.d.ts +1 -1
  33. package/dist/commands/synthetics/errors.js +1 -1
  34. package/dist/commands/synthetics/errors.js.map +1 -1
  35. package/dist/commands/synthetics/interfaces.d.ts +28 -31
  36. package/dist/commands/synthetics/interfaces.js +1 -7
  37. package/dist/commands/synthetics/interfaces.js.map +1 -1
  38. package/dist/commands/synthetics/reporters/default.d.ts +3 -3
  39. package/dist/commands/synthetics/reporters/default.js +35 -19
  40. package/dist/commands/synthetics/reporters/default.js.map +1 -1
  41. package/dist/commands/synthetics/reporters/junit.js +4 -5
  42. package/dist/commands/synthetics/reporters/junit.js.map +1 -1
  43. package/dist/commands/synthetics/run-test.d.ts +2 -3
  44. package/dist/commands/synthetics/run-test.js +6 -8
  45. package/dist/commands/synthetics/run-test.js.map +1 -1
  46. package/dist/commands/synthetics/utils.d.ts +19 -5
  47. package/dist/commands/synthetics/utils.js +118 -104
  48. package/dist/commands/synthetics/utils.js.map +1 -1
  49. package/package.json +2 -1
@@ -42,7 +42,11 @@ const api_1 = require("../api");
42
42
  const errors_1 = require("../errors");
43
43
  const interfaces_1 = require("../interfaces");
44
44
  const utils = __importStar(require("../utils"));
45
+ const command_1 = require("../command");
45
46
  const fixtures_1 = require("./fixtures");
47
+ beforeEach(() => {
48
+ jest.restoreAllMocks();
49
+ });
46
50
  describe('utils', () => {
47
51
  const apiConfiguration = {
48
52
  apiKey: '123',
@@ -69,19 +73,11 @@ describe('utils', () => {
69
73
  describe('runTest', () => {
70
74
  const fakeId = '123-456-789';
71
75
  const fakeTrigger = {
72
- results: [],
73
- triggered_check_ids: [fakeId],
76
+ batch_id: 'bid',
77
+ locations: [],
74
78
  };
75
- afterAll(() => {
76
- jest.clearAllMocks();
77
- });
78
79
  test('should run test', () => __awaiter(void 0, void 0, void 0, function* () {
79
- const axiosMock = jest.spyOn(axios_1.default, 'create');
80
- axiosMock.mockImplementation((() => (e) => {
81
- if (e.url === '/synthetics/tests/trigger/ci') {
82
- return { data: fakeTrigger };
83
- }
84
- }));
80
+ jest.spyOn(api, 'triggerTests').mockImplementation(() => __awaiter(void 0, void 0, void 0, function* () { return fakeTrigger; }));
85
81
  const output = yield utils.runTests(api, [{ public_id: fakeId, executionRule: interfaces_1.ExecutionRule.NON_BLOCKING }]);
86
82
  expect(output).toEqual(fakeTrigger);
87
83
  }));
@@ -120,12 +116,7 @@ describe('utils', () => {
120
116
  expect(headersMetadataSpy).toHaveBeenCalledWith(expect.objectContaining({ 'X-Trigger-App': 'unit_test' }));
121
117
  }));
122
118
  test('should run test with publicId from url', () => __awaiter(void 0, void 0, void 0, function* () {
123
- const axiosMock = jest.spyOn(axios_1.default, 'create');
124
- axiosMock.mockImplementation((() => (e) => {
125
- if (e.url === '/synthetics/tests/trigger/ci') {
126
- return { data: fakeTrigger };
127
- }
128
- }));
119
+ jest.spyOn(api, 'triggerTests').mockImplementation(() => __awaiter(void 0, void 0, void 0, function* () { return fakeTrigger; }));
129
120
  const output = yield utils.runTests(api, [
130
121
  {
131
122
  executionRule: interfaces_1.ExecutionRule.NON_BLOCKING,
@@ -143,11 +134,9 @@ describe('utils', () => {
143
134
  status: 502,
144
135
  },
145
136
  });
146
- const requestMock = jest.fn();
147
- requestMock.mockImplementation(() => {
137
+ jest.spyOn(api, 'triggerTests').mockImplementation(() => {
148
138
  throw serverError;
149
139
  });
150
- jest.spyOn(axios_1.default, 'create').mockImplementation((() => requestMock));
151
140
  yield expect(utils.runTests(api, [{ public_id: fakeId, executionRule: interfaces_1.ExecutionRule.NON_BLOCKING }])).rejects.toThrow(/Failed to trigger tests:/);
152
141
  }));
153
142
  });
@@ -167,7 +156,7 @@ describe('utils', () => {
167
156
  suite: 'Suite 3',
168
157
  },
169
158
  };
170
- beforeAll(() => {
159
+ beforeEach(() => {
171
160
  const axiosMock = jest.spyOn(axios_1.default, 'create');
172
161
  axiosMock.mockImplementation((() => (e) => {
173
162
  const publicId = e.url.slice(18);
@@ -179,9 +168,6 @@ describe('utils', () => {
179
168
  throw error;
180
169
  }));
181
170
  });
182
- afterAll(() => {
183
- jest.clearAllMocks();
184
- });
185
171
  test('only existing tests are returned', () => __awaiter(void 0, void 0, void 0, function* () {
186
172
  const triggerConfigs = [
187
173
  { suite: 'Suite 1', config: {}, id: '123-456-789' },
@@ -344,71 +330,59 @@ describe('utils', () => {
344
330
  const result = {
345
331
  device: { height: 0, id: 'laptop_large', width: 0 },
346
332
  duration: 0,
347
- eventType: 'finished',
348
333
  passed: true,
349
334
  startUrl: '',
350
335
  stepDetails: [],
351
336
  };
352
- expect(utils.hasResultPassed(result, false, true)).toBeTruthy();
353
- expect(utils.hasResultPassed(result, true, true)).toBeTruthy();
337
+ expect(utils.hasResultPassed(result, false, false, true)).toBeTruthy();
338
+ expect(utils.hasResultPassed(result, false, true, true)).toBeTruthy();
354
339
  result.passed = false;
355
- expect(utils.hasResultPassed(result, false, true)).toBeFalsy();
356
- expect(utils.hasResultPassed(result, true, true)).toBeFalsy();
340
+ expect(utils.hasResultPassed(result, false, false, true)).toBeFalsy();
341
+ expect(utils.hasResultPassed(result, false, true, true)).toBeFalsy();
357
342
  });
358
343
  test('result with error', () => {
359
344
  const result = {
360
345
  device: { height: 0, id: 'laptop_large', width: 0 },
361
346
  duration: 0,
362
- errorCode: 'ERRABORTED',
363
- eventType: 'finished',
347
+ failure: {
348
+ code: 'ERRABORTED',
349
+ message: 'Connection aborted',
350
+ },
364
351
  passed: false,
365
352
  startUrl: '',
366
353
  stepDetails: [],
367
354
  };
368
- expect(utils.hasResultPassed(result, false, true)).toBeFalsy();
369
- expect(utils.hasResultPassed(result, true, true)).toBeFalsy();
355
+ expect(utils.hasResultPassed(result, false, false, true)).toBeFalsy();
356
+ expect(utils.hasResultPassed(result, false, true, true)).toBeFalsy();
370
357
  });
371
358
  test('result with unhealthy result', () => {
372
359
  const result = {
373
360
  device: { height: 0, id: 'laptop_large', width: 0 },
374
361
  duration: 0,
375
- errorCode: 'ERRABORTED',
376
- eventType: 'finished',
362
+ failure: {
363
+ code: 'ERRABORTED',
364
+ message: 'Connection aborted',
365
+ },
377
366
  passed: false,
378
367
  startUrl: '',
379
368
  stepDetails: [],
380
369
  unhealthy: true,
381
370
  };
382
- expect(utils.hasResultPassed(result, false, true)).toBeTruthy();
383
- expect(utils.hasResultPassed(result, true, true)).toBeFalsy();
371
+ expect(utils.hasResultPassed(result, false, false, true)).toBeTruthy();
372
+ expect(utils.hasResultPassed(result, false, true, true)).toBeFalsy();
384
373
  });
385
374
  test('result with timeout result', () => {
386
375
  const result = {
387
376
  device: { height: 0, id: 'laptop_large', width: 0 },
388
377
  duration: 0,
389
- error: interfaces_1.ERRORS.TIMEOUT,
390
- eventType: 'finished',
391
378
  passed: false,
392
379
  startUrl: '',
393
380
  stepDetails: [],
394
381
  };
395
- expect(utils.hasResultPassed(result, true, true)).toBeFalsy();
396
- expect(utils.hasResultPassed(result, true, false)).toBeTruthy();
382
+ expect(utils.hasResultPassed(result, true, true, true)).toBeFalsy();
383
+ expect(utils.hasResultPassed(result, true, true, false)).toBeTruthy();
397
384
  });
398
385
  });
399
- test('result with endpoint failure result', () => {
400
- const result = {
401
- device: { height: 0, id: 'laptop_large', width: 0 },
402
- duration: 0,
403
- error: interfaces_1.ERRORS.ENDPOINT,
404
- eventType: 'finished',
405
- passed: false,
406
- startUrl: '',
407
- stepDetails: [],
408
- };
409
- expect(utils.hasResultPassed(result, false, true)).toBeTruthy();
410
- expect(utils.hasResultPassed(result, true, true)).toBeFalsy();
411
- });
412
386
  describe('getExecutionRule', () => {
413
387
  const cases = [
414
388
  [undefined, undefined, interfaces_1.ExecutionRule.BLOCKING],
@@ -437,211 +411,162 @@ describe('utils', () => {
437
411
  jest.spyOn(utils, 'getExecutionRule').mockReturnValue(resultRule);
438
412
  const test = fixtures_1.getApiTest('abc-def-ghi');
439
413
  const result = fixtures_1.getApiResult('1', test);
414
+ result.executionRule = resultRule;
440
415
  result.passed = resultPassed;
441
416
  expect(utils.getResultOutcome(result)).toEqual(expectedOutcome);
442
417
  });
443
418
  });
444
419
  describe('waitForResults', () => {
445
- const mockAxiosWithDefaultResult = () => {
446
- jest.spyOn(axios_1.default, 'create').mockImplementation((() => (r) => __awaiter(void 0, void 0, void 0, function* () {
447
- yield utils.wait(100);
448
- const results = JSON.parse(r.params.result_ids)
449
- .filter((resultId) => resultId !== 'timingOutTest')
450
- .map((resultId) => getPassingPollResult(resultId));
451
- return { data: { results } };
452
- })));
453
- };
454
- afterAll(() => {
455
- jest.clearAllMocks();
420
+ beforeAll(() => {
421
+ // We still wait a few milliseconds to avoid the test going crazy on a infinite loop
422
+ // if case of mistakes in the code or test.
423
+ jest.spyOn(utils, 'wait').mockImplementation(() => new Promise((r) => setTimeout(r, 10)));
456
424
  });
457
- const testConfig = fixtures_1.getApiTest('abc-def-ghi');
458
- const getPassingPollResult = (resultId) => {
459
- const result = getPassingResult(resultId);
460
- return {
461
- check: result.test,
462
- enrichment: result.enrichment,
463
- result: result.result,
464
- resultID: result.resultId,
465
- timestamp: result.timestamp,
466
- };
467
- };
468
- const getPassingResult = (resultId) => ({
425
+ const batch = fixtures_1.getBatch();
426
+ const apiTest = fixtures_1.getApiTest(batch.results[0].test_public_id);
427
+ const result = {
428
+ executionRule: interfaces_1.ExecutionRule.BLOCKING,
469
429
  location: fixtures_1.mockLocation.display_name,
470
430
  passed: true,
471
- result: fixtures_1.getBrowserServerResult({ error: interfaces_1.ERRORS.TIMEOUT, passed: false }),
472
- resultId,
473
- test: testConfig,
431
+ result: fixtures_1.getBrowserServerResult({ passed: true }),
432
+ resultId: batch.results[0].result_id,
433
+ test: apiTest,
434
+ timedOut: false,
474
435
  timestamp: 0,
475
- });
476
- const getTestAndResult = (publicId = 'abc-def-ghi', resultId = '0123456789') => {
477
- const triggerResult = {
478
- device: 'laptop_large',
479
- location: fixtures_1.mockLocation.id,
480
- public_id: publicId,
481
- result_id: resultId,
482
- };
483
- const triggerConfig = {
484
- config: {},
485
- id: publicId,
486
- suite: 'Suite 1',
487
- };
488
- const passingResult = getPassingResult(resultId);
489
- return {
490
- passingResult,
491
- test: passingResult.test,
492
- trigger: { locations: [fixtures_1.mockLocation], results: [triggerResult] },
493
- triggerConfig,
494
- triggerResult,
495
- };
436
+ };
437
+ const pollResult = {
438
+ check: result.test,
439
+ result: result.result,
440
+ resultID: result.resultId,
441
+ timestamp: result.timestamp,
442
+ };
443
+ const trigger = { batch_id: 'bid', locations: [fixtures_1.mockLocation] };
444
+ const mockApi = ({ getBatchImplementation, pollResultsImplementation, } = {}) => {
445
+ const getBatchMock = jest
446
+ .spyOn(api, 'getBatch')
447
+ .mockImplementation(getBatchImplementation || (() => __awaiter(void 0, void 0, void 0, function* () { return JSON.parse(JSON.stringify(batch)); })));
448
+ const pollResultsMock = jest
449
+ .spyOn(api, 'pollResults')
450
+ .mockImplementation(pollResultsImplementation || (() => __awaiter(void 0, void 0, void 0, function* () { return JSON.parse(JSON.stringify([pollResult])); })));
451
+ return { getBatchMock, pollResultsMock };
496
452
  };
497
453
  test('should poll result ids', () => __awaiter(void 0, void 0, void 0, function* () {
498
- mockAxiosWithDefaultResult();
499
- const { test, trigger, passingResult, triggerConfig } = getTestAndResult();
500
- const waitMock = jest.spyOn(utils, 'wait');
501
- waitMock.mockImplementation();
502
- const expectedResults = [passingResult];
503
- expect(yield utils.waitForResults(api, trigger, [triggerConfig], [test], { defaultTimeout: 120000, failOnCriticalErrors: false }, fixtures_1.mockReporter)).toEqual(expectedResults);
504
- expect(fixtures_1.mockReporter.resultReceived).toHaveBeenCalledWith(passingResult);
454
+ mockApi();
455
+ expect(yield utils.waitForResults(api, trigger, [result.test], { maxPollingTimeout: 120000, failOnCriticalErrors: false }, fixtures_1.mockReporter)).toEqual([result]);
456
+ expect(fixtures_1.mockReporter.resultReceived).toHaveBeenCalledWith(batch.results[0]);
505
457
  }));
506
- test('results should be timed-out if global pollingTimeout is exceeded', () => __awaiter(void 0, void 0, void 0, function* () {
507
- const { test, trigger, triggerResult } = getTestAndResult();
508
- const expectedResults = [
509
- {
510
- location: fixtures_1.mockLocation.display_name,
511
- passed: true,
512
- result: fixtures_1.getBrowserServerResult({
513
- device: { height: 0, id: triggerResult.device, width: 0 },
514
- error: interfaces_1.ERRORS.TIMEOUT,
515
- passed: false,
516
- }),
517
- resultId: triggerResult.result_id,
518
- test,
519
- timestamp: 0,
520
- },
521
- ];
522
- expect(yield utils.waitForResults(api, trigger, [], [test], { defaultTimeout: 0, failOnCriticalErrors: false }, fixtures_1.mockReporter)).toEqual(expectedResults);
458
+ test('results should be timed out if global pollingTimeout is exceeded', () => __awaiter(void 0, void 0, void 0, function* () {
459
+ mockApi({
460
+ getBatchImplementation: () => __awaiter(void 0, void 0, void 0, function* () {
461
+ return ({
462
+ results: [batch.results[0], Object.assign(Object.assign({}, batch.results[0]), { result_id: '3', timed_out: undefined })],
463
+ status: 'in_progress',
464
+ });
465
+ }),
466
+ pollResultsImplementation: () => __awaiter(void 0, void 0, void 0, function* () {
467
+ return [
468
+ Object.assign(Object.assign({}, pollResult), { result: Object.assign({}, pollResult.result) }),
469
+ Object.assign(Object.assign({}, pollResult), { result: Object.assign({}, pollResult.result), resultID: '3' }),
470
+ ];
471
+ }),
472
+ });
473
+ expect(yield utils.waitForResults(api, trigger, [result.test, result.test], { maxPollingTimeout: 0, failOnCriticalErrors: false }, fixtures_1.mockReporter)).toEqual([
474
+ result,
475
+ Object.assign(Object.assign({}, result), { result: Object.assign(Object.assign({}, result.result), { failure: { code: 'TIMEOUT', message: 'Result timed out' }, passed: false }), resultId: '3', timedOut: true }),
476
+ ]);
523
477
  }));
524
- test('results should be timeout-ed if test pollingTimeout is exceeded', () => __awaiter(void 0, void 0, void 0, function* () {
525
- const { test, trigger, triggerResult } = getTestAndResult();
526
- const expectedResults = [
527
- {
528
- location: fixtures_1.mockLocation.display_name,
529
- passed: true,
530
- result: fixtures_1.getBrowserServerResult({
531
- device: { height: 0, id: triggerResult.device, width: 0 },
532
- error: interfaces_1.ERRORS.TIMEOUT,
533
- passed: false,
534
- }),
535
- resultId: triggerResult.result_id,
536
- test,
537
- timestamp: 0,
538
- },
539
- ];
540
- const testTriggerConfig = {
541
- config: { pollingTimeout: 0 },
542
- id: triggerResult.public_id,
543
- suite: 'Suite 1',
544
- };
545
- expect(yield utils.waitForResults(api, trigger, [testTriggerConfig], [test], { defaultTimeout: 120000, failOnCriticalErrors: false }, fixtures_1.mockReporter)).toEqual(expectedResults);
478
+ test('results failure should ignore if timed-out', () => __awaiter(void 0, void 0, void 0, function* () {
479
+ // The original failure of a result received between timing-out in batch poll
480
+ // and retrieving it should be ignored in favor of timeout.
481
+ mockApi({
482
+ getBatchImplementation: () => __awaiter(void 0, void 0, void 0, function* () {
483
+ return ({
484
+ results: [Object.assign(Object.assign({}, batch.results[0]), { timed_out: undefined })],
485
+ status: 'in_progress',
486
+ });
487
+ }),
488
+ pollResultsImplementation: () => __awaiter(void 0, void 0, void 0, function* () {
489
+ return [
490
+ Object.assign(Object.assign({}, pollResult), { passed: false, result: Object.assign(Object.assign({}, pollResult.result), { failure: { code: 'FAILURE', message: 'Original failure, should be ignored' }, passed: false }) }),
491
+ ];
492
+ }),
493
+ });
494
+ expect(yield utils.waitForResults(api, trigger, [result.test], { maxPollingTimeout: 0, failOnCriticalErrors: false }, fixtures_1.mockReporter)).toStrictEqual([
495
+ Object.assign(Object.assign({}, result), { result: Object.assign(Object.assign({}, result.result), { failure: { code: 'TIMEOUT', message: 'Result timed out' }, passed: false }), timedOut: true }),
496
+ ]);
497
+ }));
498
+ test('results should be timed out if batch result is timed out', () => __awaiter(void 0, void 0, void 0, function* () {
499
+ const batchWithTimeoutResult = Object.assign(Object.assign({}, batch), { results: [Object.assign(Object.assign({}, batch.results[0]), { timed_out: true })] });
500
+ mockApi({ getBatchImplementation: () => __awaiter(void 0, void 0, void 0, function* () { return batchWithTimeoutResult; }) });
501
+ expect(yield utils.waitForResults(api, trigger, [result.test], { maxPollingTimeout: 120000, failOnCriticalErrors: false }, fixtures_1.mockReporter)).toEqual([
502
+ Object.assign(Object.assign({}, result), { result: Object.assign(Object.assign({}, result.result), { failure: { code: 'TIMEOUT', message: 'Result timed out' }, passed: false }), timedOut: true }),
503
+ ]);
504
+ }));
505
+ test('wait between batch polling', () => __awaiter(void 0, void 0, void 0, function* () {
506
+ jest.restoreAllMocks();
507
+ const waitMock = jest.spyOn(utils, 'wait').mockImplementation(() => new Promise((r) => setTimeout(r, 10)));
508
+ let counter = 0;
509
+ mockApi({
510
+ getBatchImplementation: () => __awaiter(void 0, void 0, void 0, function* () {
511
+ counter += 1;
512
+ return counter === 3 ? batch : Object.assign(Object.assign({}, batch), { status: 'in_progress' });
513
+ }),
514
+ });
515
+ expect(yield utils.waitForResults(api, trigger, [result.test], { maxPollingTimeout: 120000, failOnCriticalErrors: false }, fixtures_1.mockReporter)).toEqual([result]);
516
+ expect(counter).toBe(3);
517
+ expect(waitMock).toHaveBeenCalledTimes(2);
546
518
  }));
547
519
  test('correct number of pass and timeout results', () => __awaiter(void 0, void 0, void 0, function* () {
548
- mockAxiosWithDefaultResult();
549
- const { test, trigger, triggerResult, passingResult } = getTestAndResult();
550
- const waitMock = jest.spyOn(utils, 'wait');
551
- waitMock.mockImplementation();
552
- const triggerResultTimeOut = Object.assign(Object.assign({}, triggerResult), { result_id: 'timingOutTest' });
553
- const expectedResults = [
554
- passingResult,
555
- {
556
- location: fixtures_1.mockLocation.display_name,
557
- passed: true,
558
- result: fixtures_1.getBrowserServerResult({
559
- device: { height: 0, id: triggerResultTimeOut.device, width: 0 },
560
- error: interfaces_1.ERRORS.TIMEOUT,
561
- passed: false,
562
- }),
563
- resultId: triggerResultTimeOut.result_id,
564
- test,
565
- timestamp: 0,
566
- },
567
- ];
568
- trigger.results = [triggerResult, triggerResultTimeOut];
569
- expect(yield utils.waitForResults(api, trigger, [], [test], { defaultTimeout: 2000, failOnCriticalErrors: false }, fixtures_1.mockReporter)).toEqual(expectedResults);
520
+ const pollTimeoutResult = Object.assign(Object.assign({}, pollResult), { resultID: 'another-id' });
521
+ const batchWithTimeoutResult = Object.assign(Object.assign({}, batch), { results: [batch.results[0], Object.assign(Object.assign({}, batch.results[0]), { timed_out: true, result_id: pollTimeoutResult.resultID })] });
522
+ mockApi({
523
+ getBatchImplementation: () => __awaiter(void 0, void 0, void 0, function* () { return batchWithTimeoutResult; }),
524
+ pollResultsImplementation: () => __awaiter(void 0, void 0, void 0, function* () { return [pollResult, pollTimeoutResult]; }),
525
+ });
526
+ expect(yield utils.waitForResults(api, trigger, [result.test], { maxPollingTimeout: 2000, failOnCriticalErrors: false }, fixtures_1.mockReporter)).toEqual([result, Object.assign(Object.assign({}, result), { resultId: pollTimeoutResult.resultID, timedOut: true })]);
570
527
  }));
571
528
  test('tunnel failure', () => __awaiter(void 0, void 0, void 0, function* () {
572
- const waitMock = jest.spyOn(utils, 'wait');
573
- waitMock.mockImplementation();
574
- const { test, trigger, triggerResult } = getTestAndResult();
575
- // Fake pollResults to not update results and iterate until the isTunnelConnected is equal to false
576
- jest
577
- .spyOn(axios_1.default, 'create')
578
- .mockImplementation((() => (r) => __awaiter(void 0, void 0, void 0, function* () { return ({ data: { results: [] } }); })));
529
+ mockApi();
579
530
  const mockTunnel = {
580
531
  keepAlive: () => __awaiter(void 0, void 0, void 0, function* () {
581
532
  throw new Error('keepAlive failed');
582
533
  }),
583
534
  };
584
- const expectedResults = [
585
- {
586
- location: 'Tunneled',
587
- passed: false,
588
- result: fixtures_1.getBrowserServerResult({
589
- device: { height: 0, id: triggerResult.device, width: 0 },
590
- error: interfaces_1.ERRORS.TUNNEL,
591
- passed: false,
592
- }),
593
- resultId: triggerResult.result_id,
594
- test,
595
- timestamp: 0,
596
- },
597
- ];
598
- expect(yield utils.waitForResults(api, trigger, [], [test], { defaultTimeout: 2000, failOnCriticalErrors: true }, fixtures_1.mockReporter, mockTunnel)).toEqual(expectedResults);
599
- expect(yield utils.waitForResults(api, trigger, [], [test], { defaultTimeout: 2000, failOnCriticalErrors: false }, fixtures_1.mockReporter, mockTunnel)).toEqual(expectedResults);
535
+ yield utils.waitForResults(api, trigger, [result.test], { maxPollingTimeout: 2000, failOnCriticalErrors: true }, fixtures_1.mockReporter, mockTunnel);
536
+ expect(fixtures_1.mockReporter.error).toBeCalledWith('The tunnel has stopped working, this may have affected the results.');
600
537
  }));
601
538
  test('location when tunnel', () => __awaiter(void 0, void 0, void 0, function* () {
602
- const waitMock = jest.spyOn(utils, 'wait');
603
- waitMock.mockImplementation();
604
- const { test, trigger } = getTestAndResult();
605
- const mockTunnel = {
606
- keepAlive: () => __awaiter(void 0, void 0, void 0, function* () { return true; }),
607
- };
608
- let results = yield utils.waitForResults(api, trigger, [], [test], { defaultTimeout: 2000, failOnCriticalErrors: true }, fixtures_1.mockReporter, mockTunnel);
539
+ mockApi();
540
+ const mockTunnel = { keepAlive: () => __awaiter(void 0, void 0, void 0, function* () { return true; }) };
541
+ let results = yield utils.waitForResults(api, trigger, [result.test], { maxPollingTimeout: 2000, failOnCriticalErrors: true }, fixtures_1.mockReporter, mockTunnel);
609
542
  expect(results[0].location).toBe('Tunneled');
610
- const newTest = Object.assign({}, test);
543
+ const newTest = Object.assign({}, result.test);
611
544
  newTest.type = 'api';
612
545
  newTest.subtype = 'http';
613
- results = yield utils.waitForResults(api, trigger, [], [newTest], { defaultTimeout: 2000, failOnCriticalErrors: true }, fixtures_1.mockReporter, mockTunnel);
546
+ results = yield utils.waitForResults(api, trigger, [newTest], { maxPollingTimeout: 2000, failOnCriticalErrors: true }, fixtures_1.mockReporter, mockTunnel);
614
547
  expect(results[0].location).toBe('Tunneled');
615
548
  newTest.type = 'api';
616
549
  newTest.subtype = 'ssl';
617
- results = yield utils.waitForResults(api, trigger, [], [newTest], { defaultTimeout: 2000, failOnCriticalErrors: true }, fixtures_1.mockReporter, mockTunnel);
550
+ results = yield utils.waitForResults(api, trigger, [newTest], { failOnCriticalErrors: true, maxPollingTimeout: 2000 }, fixtures_1.mockReporter, mockTunnel);
618
551
  expect(results[0].location).toBe('Frankfurt (AWS)');
619
552
  }));
620
553
  test('pollResults throws', () => __awaiter(void 0, void 0, void 0, function* () {
621
- const { test, trigger, triggerResult } = getTestAndResult();
622
- jest.spyOn(utils, 'wait').mockImplementation();
623
- const axiosMock = jest.spyOn(axios_1.default, 'create');
624
- const serverError = new Error('Server Error');
625
- serverError.response = { status: 502 };
626
- axiosMock.mockImplementation((() => (r) => __awaiter(void 0, void 0, void 0, function* () {
627
- throw serverError;
628
- })));
629
- const mockTunnel = { keepAlive: () => __awaiter(void 0, void 0, void 0, function* () { return Promise.reject(); }) };
630
- expect(yield utils.waitForResults(api, trigger, [], [test], { defaultTimeout: 2000, failOnCriticalErrors: false }, fixtures_1.mockReporter, mockTunnel)).toEqual([
631
- {
632
- location: 'Tunneled',
633
- passed: true,
634
- result: fixtures_1.getBrowserServerResult({
635
- device: { height: 0, id: triggerResult.device, width: 0 },
636
- error: interfaces_1.ERRORS.ENDPOINT,
637
- passed: false,
638
- }),
639
- resultId: triggerResult.result_id,
640
- test,
641
- timestamp: 0,
554
+ const { pollResultsMock } = mockApi({
555
+ pollResultsImplementation: () => {
556
+ throw new Error('Poll results server error');
642
557
  },
643
- ]);
644
- yield expect(utils.waitForResults(api, trigger, [], [test], { defaultTimeout: 2000, failOnCriticalErrors: true }, fixtures_1.mockReporter, mockTunnel)).rejects.toThrow();
558
+ });
559
+ yield expect(utils.waitForResults(api, trigger, [result.test], { maxPollingTimeout: 2000 }, fixtures_1.mockReporter)).rejects.toThrowError('Failed to poll results: Poll results server error');
560
+ expect(pollResultsMock).toHaveBeenCalledWith([result.resultId]);
561
+ }));
562
+ test('getBatch throws', () => __awaiter(void 0, void 0, void 0, function* () {
563
+ const { getBatchMock } = mockApi({
564
+ getBatchImplementation: () => {
565
+ throw new Error('Get batch server error');
566
+ },
567
+ });
568
+ yield expect(utils.waitForResults(api, trigger, [result.test], { maxPollingTimeout: 2000 }, fixtures_1.mockReporter)).rejects.toThrowError('Failed to get batch: Get batch server error');
569
+ expect(getBatchMock).toHaveBeenCalledWith(trigger.batch_id);
645
570
  }));
646
571
  });
647
572
  test('getStrictestExecutionRule', () => {
@@ -699,5 +624,144 @@ describe('utils', () => {
699
624
  expect(utils.parseVariablesFromCli([''], mockLogFunction)).toBeUndefined();
700
625
  expect(utils.parseVariablesFromCli(undefined, mockLogFunction)).toBeUndefined();
701
626
  });
627
+ test('getAppBaseURL', () => {
628
+ expect(utils.getAppBaseURL({ datadogSite: 'datadoghq.eu', subdomain: 'custom' })).toBe('https://custom.datadoghq.eu/');
629
+ });
630
+ describe('sortResultsByOutcome', () => {
631
+ const results = fixtures_1.getResults([
632
+ { executionRule: interfaces_1.ExecutionRule.NON_BLOCKING, passed: false },
633
+ { executionRule: interfaces_1.ExecutionRule.BLOCKING, passed: true },
634
+ { executionRule: interfaces_1.ExecutionRule.BLOCKING, passed: false },
635
+ { executionRule: interfaces_1.ExecutionRule.NON_BLOCKING, passed: true },
636
+ ]);
637
+ test('should sort tests with success, non_blocking failures then failures', () => __awaiter(void 0, void 0, void 0, function* () {
638
+ const sortedResults = [...results];
639
+ sortedResults.sort(utils.sortResultsByOutcome());
640
+ expect(sortedResults.map((r) => r.resultId)).toStrictEqual(['3', '1', '0', '2']);
641
+ }));
642
+ });
643
+ describe('Render results', () => {
644
+ const emptySummary = utils.createSummary();
645
+ const cases = [
646
+ {
647
+ description: '1 API test with 1 config override, 1 result (passed)',
648
+ expected: {
649
+ exitCode: 0,
650
+ summary: Object.assign(Object.assign({}, emptySummary), { passed: 1 }),
651
+ },
652
+ failOnCriticalErrors: false,
653
+ failOnTimeout: false,
654
+ results: fixtures_1.getResults([{ passed: true }]),
655
+ summary: Object.assign({}, emptySummary),
656
+ },
657
+ {
658
+ description: '1 API test with 1 config override, 1 result (failed timeout), no fail on timeout, no fail on critical errors',
659
+ expected: {
660
+ exitCode: 0,
661
+ summary: Object.assign(Object.assign({}, emptySummary), { passed: 1, timedOut: 1 }),
662
+ },
663
+ failOnCriticalErrors: false,
664
+ failOnTimeout: false,
665
+ results: fixtures_1.getResults([{ timedOut: true }]),
666
+ summary: Object.assign({}, emptySummary),
667
+ },
668
+ {
669
+ description: '1 API test with 1 config override, 1 result (failed timeout), fail on timeout, no fail on critical errors',
670
+ expected: {
671
+ exitCode: 1,
672
+ summary: Object.assign(Object.assign({}, emptySummary), { failed: 1 }),
673
+ },
674
+ failOnCriticalErrors: false,
675
+ failOnTimeout: true,
676
+ results: fixtures_1.getResults([{ timedOut: true }]),
677
+ summary: Object.assign({}, emptySummary),
678
+ },
679
+ {
680
+ description: '1 API test with 1 config override, 1 result (failed critical error), no fail on timeout, no fail on critical errors',
681
+ expected: {
682
+ exitCode: 0,
683
+ summary: Object.assign(Object.assign({}, emptySummary), { passed: 1, criticalErrors: 1 }),
684
+ },
685
+ failOnCriticalErrors: false,
686
+ failOnTimeout: false,
687
+ results: fixtures_1.getResults([{ unhealthy: true }]),
688
+ summary: Object.assign({}, emptySummary),
689
+ },
690
+ {
691
+ description: '1 API test with 1 config override, 1 result (failed critical error), no fail on timeout, fail on critical errors',
692
+ expected: {
693
+ exitCode: 1,
694
+ summary: Object.assign(Object.assign({}, emptySummary), { criticalErrors: 0, failed: 1 }),
695
+ },
696
+ failOnCriticalErrors: true,
697
+ failOnTimeout: false,
698
+ results: fixtures_1.getResults([{ unhealthy: true }]),
699
+ summary: Object.assign({}, emptySummary),
700
+ },
701
+ {
702
+ description: '1 API test (blocking) with 4 config overrides (1 skipped), 3 results (1 passed, 1 failed, 1 failed non-blocking)',
703
+ expected: {
704
+ exitCode: 1,
705
+ summary: Object.assign(Object.assign({}, emptySummary), { failed: 1, failedNonBlocking: 1, passed: 1, skipped: 1 }),
706
+ },
707
+ failOnCriticalErrors: false,
708
+ failOnTimeout: false,
709
+ results: fixtures_1.getResults([{ passed: true }, { executionRule: interfaces_1.ExecutionRule.NON_BLOCKING }, {}]),
710
+ summary: Object.assign(Object.assign({}, emptySummary), { skipped: 1 }),
711
+ },
712
+ {
713
+ description: '1 API test (non-blocking) with 4 config overrides (1 skipped), 3 results (1 passed, 1 failed, 1 failed non-blocking)',
714
+ expected: {
715
+ exitCode: 0,
716
+ summary: Object.assign(Object.assign({}, emptySummary), { failedNonBlocking: 2, passed: 1, skipped: 1 }),
717
+ },
718
+ failOnCriticalErrors: false,
719
+ failOnTimeout: false,
720
+ results: fixtures_1.getResults([
721
+ {
722
+ executionRule: interfaces_1.ExecutionRule.NON_BLOCKING,
723
+ testExecutionRule: interfaces_1.ExecutionRule.NON_BLOCKING,
724
+ },
725
+ { passed: true, testExecutionRule: interfaces_1.ExecutionRule.NON_BLOCKING },
726
+ {
727
+ testExecutionRule: interfaces_1.ExecutionRule.NON_BLOCKING,
728
+ },
729
+ ]),
730
+ summary: Object.assign(Object.assign({}, emptySummary), { skipped: 1 }),
731
+ },
732
+ {
733
+ description: '3 API tests (blocking) with 1 config override each, 3 results (1 failed non-blocking, 1 failed, 1 passed)',
734
+ expected: {
735
+ exitCode: 1,
736
+ summary: Object.assign(Object.assign({}, emptySummary), { failed: 1, failedNonBlocking: 1, passed: 1 }),
737
+ },
738
+ failOnCriticalErrors: false,
739
+ failOnTimeout: false,
740
+ results: fixtures_1.getResults([{}, { passed: true }, { executionRule: interfaces_1.ExecutionRule.NON_BLOCKING }]),
741
+ summary: Object.assign({}, emptySummary),
742
+ },
743
+ ];
744
+ test.each(cases)('$description', (testCase) => __awaiter(void 0, void 0, void 0, function* () {
745
+ testCase.results.forEach((result) => (result.passed = utils.hasResultPassed(result.result, result.timedOut, testCase.failOnCriticalErrors, testCase.failOnTimeout)));
746
+ const config = Object.assign(Object.assign({}, command_1.DEFAULT_COMMAND_CONFIG), { failOnCriticalErrors: testCase.failOnCriticalErrors, failOnTimeout: testCase.failOnTimeout });
747
+ const startTime = Date.now();
748
+ const exitCode = utils.renderResults({
749
+ config,
750
+ reporter: fixtures_1.mockReporter,
751
+ results: testCase.results,
752
+ startTime,
753
+ summary: testCase.summary,
754
+ });
755
+ expect(fixtures_1.mockReporter.reportStart).toHaveBeenCalledWith({ startTime });
756
+ expect(fixtures_1.mockReporter.resultEnd).toHaveBeenCalledTimes(testCase.results.length);
757
+ const baseUrl = `https://${command_1.DEFAULT_COMMAND_CONFIG.subdomain}.${command_1.DEFAULT_COMMAND_CONFIG.datadogSite}/`;
758
+ for (const result of testCase.results) {
759
+ expect(fixtures_1.mockReporter.resultEnd).toHaveBeenCalledWith(result, baseUrl);
760
+ }
761
+ expect(testCase.summary).toEqual(testCase.expected.summary);
762
+ expect(fixtures_1.mockReporter.runEnd).toHaveBeenCalledWith(testCase.expected.summary, baseUrl);
763
+ expect(exitCode).toBe(testCase.expected.exitCode);
764
+ }));
765
+ });
702
766
  });
703
767
  //# sourceMappingURL=utils.test.js.map