@datadog/datadog-ci 1.0.0 → 1.1.2

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 (35) hide show
  1. package/LICENSE-3rdparty.csv +1 -1
  2. package/README.md +28 -0
  3. package/dist/commands/lambda/__tests__/functions/instrument.test.js +2 -2
  4. package/dist/commands/lambda/__tests__/functions/uninstrument.test.js +2 -2
  5. package/dist/commands/lambda/__tests__/instrument.test.js +13 -8
  6. package/dist/commands/lambda/__tests__/prompt.test.js +0 -4
  7. package/dist/commands/lambda/__tests__/uninstrument.test.js +12 -7
  8. package/dist/commands/lambda/constants.d.ts +0 -1
  9. package/dist/commands/lambda/constants.js +2 -27
  10. package/dist/commands/lambda/functions/commons.js +1 -1
  11. package/dist/commands/lambda/instrument.js +4 -1
  12. package/dist/commands/lambda/prompt.d.ts +1 -0
  13. package/dist/commands/lambda/prompt.js +19 -10
  14. package/dist/commands/lambda/uninstrument.js +1 -1
  15. package/dist/commands/synthetics/__tests__/cli.test.js +107 -4
  16. package/dist/commands/synthetics/__tests__/fixtures.d.ts +2 -1
  17. package/dist/commands/synthetics/__tests__/fixtures.js +4 -1
  18. package/dist/commands/synthetics/__tests__/reporters/default.test.js +2 -1
  19. package/dist/commands/synthetics/__tests__/run-test.test.js +34 -28
  20. package/dist/commands/synthetics/__tests__/utils.test.js +24 -11
  21. package/dist/commands/synthetics/api.d.ts +4 -12
  22. package/dist/commands/synthetics/api.js +8 -3
  23. package/dist/commands/synthetics/command.js +17 -5
  24. package/dist/commands/synthetics/errors.d.ts +7 -2
  25. package/dist/commands/synthetics/errors.js +11 -7
  26. package/dist/commands/synthetics/index.d.ts +1 -1
  27. package/dist/commands/synthetics/index.js +2 -1
  28. package/dist/commands/synthetics/interfaces.d.ts +2 -4
  29. package/dist/commands/synthetics/reporters/default.d.ts +1 -0
  30. package/dist/commands/synthetics/reporters/default.js +16 -8
  31. package/dist/commands/synthetics/run-test.d.ts +1 -11
  32. package/dist/commands/synthetics/run-test.js +12 -15
  33. package/dist/commands/synthetics/utils.d.ts +1 -0
  34. package/dist/commands/synthetics/utils.js +27 -14
  35. package/package.json +13 -4
@@ -129,30 +129,36 @@ describe('run-test', () => {
129
129
  expect(startTunnelSpy).toHaveBeenCalledTimes(1);
130
130
  expect(stopTunnelSpy).toHaveBeenCalledTimes(1);
131
131
  }));
132
- test('getTestsList throws', () => __awaiter(void 0, void 0, void 0, function* () {
133
- const serverError = new Error('Server Error');
134
- serverError.response = { data: { errors: ['Bad Gateway'] }, status: 502 };
135
- serverError.config = { baseURL: 'baseURL', url: 'url' };
136
- const apiHelper = {
137
- searchTests: jest.fn(() => {
138
- throw serverError;
139
- }),
140
- };
141
- jest.spyOn(runTests, 'getApiHelper').mockImplementation(() => apiHelper);
142
- yield expect(runTests.executeTests(fixtures_1.mockReporter, Object.assign(Object.assign({}, fixtures_1.ciConfig), { testSearchQuery: 'a-search-query', tunnel: true }))).rejects.toMatchError(new errors_1.CriticalError('UNAVAILABLE_TEST_CONFIG'));
143
- }));
144
- test('getTestsToTrigger throws', () => __awaiter(void 0, void 0, void 0, function* () {
145
- const serverError = new Error('Server Error');
146
- serverError.response = { data: { errors: ['Bad Gateway'] }, status: 502 };
147
- serverError.config = { baseURL: 'baseURL', url: 'url' };
148
- const apiHelper = {
149
- getTest: jest.fn(() => {
150
- throw serverError;
151
- }),
152
- };
153
- jest.spyOn(runTests, 'getApiHelper').mockImplementation(() => apiHelper);
154
- yield expect(runTests.executeTests(fixtures_1.mockReporter, Object.assign(Object.assign({}, fixtures_1.ciConfig), { publicIds: ['public-id-1'], tunnel: true }))).rejects.toMatchError(new errors_1.CriticalError('UNAVAILABLE_TEST_CONFIG'));
155
- }));
132
+ const cases = [
133
+ [403, 'AUTHORIZATION_ERROR'],
134
+ [502, 'UNAVAILABLE_TEST_CONFIG'],
135
+ ];
136
+ describe.each(cases)('%s triggers %s', (status, error) => {
137
+ test(`getTestsList throws - ${status}`, () => __awaiter(void 0, void 0, void 0, function* () {
138
+ const serverError = new Error('Server Error');
139
+ serverError.response = { data: { errors: ['Error'] }, status };
140
+ serverError.config = { baseURL: 'baseURL', url: 'url' };
141
+ const apiHelper = {
142
+ searchTests: jest.fn(() => {
143
+ throw serverError;
144
+ }),
145
+ };
146
+ jest.spyOn(runTests, 'getApiHelper').mockImplementation(() => apiHelper);
147
+ yield expect(runTests.executeTests(fixtures_1.mockReporter, Object.assign(Object.assign({}, fixtures_1.ciConfig), { testSearchQuery: 'a-search-query', tunnel: true }))).rejects.toMatchError(new errors_1.CriticalError(error));
148
+ }));
149
+ test(`getTestsToTrigger throws - ${status}`, () => __awaiter(void 0, void 0, void 0, function* () {
150
+ const serverError = new Error('Server Error');
151
+ serverError.response = { data: { errors: ['Bad Gateway'] }, status };
152
+ serverError.config = { baseURL: 'baseURL', url: 'url' };
153
+ const apiHelper = {
154
+ getTest: jest.fn(() => {
155
+ throw serverError;
156
+ }),
157
+ };
158
+ jest.spyOn(runTests, 'getApiHelper').mockImplementation(() => apiHelper);
159
+ yield expect(runTests.executeTests(fixtures_1.mockReporter, Object.assign(Object.assign({}, fixtures_1.ciConfig), { publicIds: ['public-id-1'], tunnel: true }))).rejects.toMatchError(new errors_1.CriticalError(error));
160
+ }));
161
+ });
156
162
  test('getPresignedURL throws', () => __awaiter(void 0, void 0, void 0, function* () {
157
163
  jest.spyOn(utils, 'getTestsToTrigger').mockReturnValue(Promise.resolve({
158
164
  overriddenTestsToTrigger: [],
@@ -254,10 +260,10 @@ describe('run-test', () => {
254
260
  });
255
261
  test('should throw an error if API or Application key are undefined', () => __awaiter(void 0, void 0, void 0, function* () {
256
262
  process.env = {};
257
- expect(() => runTests.getApiHelper(fixtures_1.ciConfig)).toThrow(new errors_1.CiError('MISSING_APP_KEY'));
258
- yield expect(runTests.executeTests(fixtures_1.mockReporter, fixtures_1.ciConfig)).rejects.toMatchError(new errors_1.CiError('MISSING_APP_KEY'));
259
- expect(() => runTests.getApiHelper(Object.assign(Object.assign({}, fixtures_1.ciConfig), { appKey: 'fakeappkey' }))).toThrow(new errors_1.CiError('MISSING_API_KEY'));
260
- yield expect(runTests.executeTests(fixtures_1.mockReporter, Object.assign(Object.assign({}, fixtures_1.ciConfig), { appKey: 'fakeappkey' }))).rejects.toMatchError(new errors_1.CiError('MISSING_API_KEY'));
263
+ expect(() => runTests.getApiHelper(fixtures_1.ciConfig)).toThrow(new errors_1.CriticalError('MISSING_APP_KEY'));
264
+ yield expect(runTests.executeTests(fixtures_1.mockReporter, fixtures_1.ciConfig)).rejects.toMatchError(new errors_1.CriticalError('MISSING_APP_KEY'));
265
+ expect(() => runTests.getApiHelper(Object.assign(Object.assign({}, fixtures_1.ciConfig), { appKey: 'fakeappkey' }))).toThrow(new errors_1.CriticalError('MISSING_API_KEY'));
266
+ yield expect(runTests.executeTests(fixtures_1.mockReporter, Object.assign(Object.assign({}, fixtures_1.ciConfig), { appKey: 'fakeappkey' }))).rejects.toMatchError(new errors_1.CriticalError('MISSING_API_KEY'));
261
267
  }));
262
268
  });
263
269
  describe('getTestsList', () => {
@@ -38,6 +38,7 @@ const axios_1 = __importDefault(require("axios"));
38
38
  const glob_1 = __importDefault(require("glob"));
39
39
  const ciHelpers = __importStar(require("../../../helpers/ci"));
40
40
  const api_1 = require("../api");
41
+ const errors_1 = require("../errors");
41
42
  const interfaces_1 = require("../interfaces");
42
43
  const utils = __importStar(require("../utils"));
43
44
  const fixtures_1 = require("./fixtures");
@@ -86,27 +87,36 @@ describe('utils', () => {
86
87
  test('runTests sends batch metadata', () => __awaiter(void 0, void 0, void 0, function* () {
87
88
  jest.spyOn(ciHelpers, 'getCIMetadata').mockImplementation(() => undefined);
88
89
  const payloadMetadataSpy = jest.fn();
89
- const axiosMock = jest.spyOn(axios_1.default, 'create');
90
- axiosMock.mockImplementation((() => (e) => {
91
- payloadMetadataSpy(e.data.metadata);
92
- if (e.url === '/synthetics/tests/trigger/ci') {
90
+ jest.spyOn(axios_1.default, 'create').mockImplementation((() => (request) => {
91
+ payloadMetadataSpy(request.data.metadata);
92
+ if (request.url === '/synthetics/tests/trigger/ci') {
93
93
  return { data: fakeTrigger };
94
94
  }
95
95
  }));
96
96
  yield utils.runTests(api, [{ public_id: fakeId, executionRule: interfaces_1.ExecutionRule.NON_BLOCKING }]);
97
- expect(payloadMetadataSpy).toHaveBeenCalledWith({
98
- ci: { job: {}, pipeline: {}, provider: {}, stage: {} },
99
- git: { commit: { author: {}, committer: {} } },
100
- trigger_app: 'npm_package',
101
- });
97
+ expect(payloadMetadataSpy).toHaveBeenCalledWith(undefined);
102
98
  const metadata = {
103
99
  ci: { job: { name: 'job' }, pipeline: {}, provider: { name: 'jest' }, stage: {} },
104
100
  git: { commit: { author: {}, committer: {}, message: 'test' } },
105
101
  };
106
102
  jest.spyOn(ciHelpers, 'getCIMetadata').mockImplementation(() => metadata);
103
+ yield utils.runTests(api, [{ public_id: fakeId, executionRule: interfaces_1.ExecutionRule.NON_BLOCKING }]);
104
+ expect(payloadMetadataSpy).toHaveBeenCalledWith(metadata);
105
+ }));
106
+ test('runTests api call includes trigger app header', () => __awaiter(void 0, void 0, void 0, function* () {
107
+ jest.spyOn(ciHelpers, 'getCIMetadata').mockImplementation(() => undefined);
108
+ const headersMetadataSpy = jest.fn();
109
+ jest.spyOn(axios_1.default, 'create').mockImplementation((() => (request) => {
110
+ headersMetadataSpy(request.headers);
111
+ if (request.url === '/synthetics/tests/trigger/ci') {
112
+ return { data: fakeTrigger };
113
+ }
114
+ }));
115
+ yield utils.runTests(api, [{ public_id: fakeId, executionRule: interfaces_1.ExecutionRule.NON_BLOCKING }]);
116
+ expect(headersMetadataSpy).toHaveBeenCalledWith(expect.objectContaining({ 'X-Trigger-App': 'npm_package' }));
107
117
  utils.setCiTriggerApp('unit_test');
108
118
  yield utils.runTests(api, [{ public_id: fakeId, executionRule: interfaces_1.ExecutionRule.NON_BLOCKING }]);
109
- expect(payloadMetadataSpy).toHaveBeenCalledWith(Object.assign(Object.assign({}, metadata), { trigger_app: 'unit_test' }));
119
+ expect(headersMetadataSpy).toHaveBeenCalledWith(expect.objectContaining({ 'X-Trigger-App': 'unit_test' }));
110
120
  }));
111
121
  test('should run test with publicId from url', () => __awaiter(void 0, void 0, void 0, function* () {
112
122
  const axiosMock = jest.spyOn(axios_1.default, 'create');
@@ -163,6 +173,9 @@ describe('utils', () => {
163
173
  if (fakeTests[publicId]) {
164
174
  return { data: fakeTests[publicId] };
165
175
  }
176
+ const error = new Error('Not found');
177
+ error.status = 404;
178
+ throw error;
166
179
  }));
167
180
  });
168
181
  afterAll(() => {
@@ -191,7 +204,7 @@ describe('utils', () => {
191
204
  expect(summary).toEqual(expectedSummary);
192
205
  }));
193
206
  test('no tests triggered throws an error', () => __awaiter(void 0, void 0, void 0, function* () {
194
- yield expect(utils.getTestsToTrigger(api, [], fixtures_1.mockReporter)).rejects.toEqual(new Error('No tests to trigger'));
207
+ yield expect(utils.getTestsToTrigger(api, [], fixtures_1.mockReporter)).rejects.toEqual(new errors_1.CiError('NO_TESTS_TO_RUN'));
195
208
  }));
196
209
  });
197
210
  describe('handleConfig', () => {
@@ -1,5 +1,5 @@
1
1
  import { AxiosError } from 'axios';
2
- import { APIConfiguration, Payload, PollResult, Test, TestSearchResult, Trigger } from './interfaces';
2
+ import { APIConfiguration, APIHelper } from './interfaces';
3
3
  interface BackendError {
4
4
  errors: string[];
5
5
  }
@@ -9,16 +9,8 @@ export declare class EndpointError extends Error {
9
9
  constructor(message: string, status: number);
10
10
  }
11
11
  export declare const formatBackendErrors: (requestError: AxiosError<BackendError>) => string;
12
+ export declare const isForbiddenError: (error: AxiosError | EndpointError) => boolean;
13
+ export declare const isNotFoundError: (error: AxiosError | EndpointError) => boolean;
12
14
  export declare const is5xxError: (error: AxiosError | EndpointError) => boolean | 0 | undefined;
13
- export declare const apiConstructor: (configuration: APIConfiguration) => {
14
- getPresignedURL: (testIds: string[]) => Promise<{
15
- url: string;
16
- }>;
17
- getTest: (testId: string) => Promise<Test>;
18
- pollResults: (resultIds: string[]) => Promise<{
19
- results: PollResult[];
20
- }>;
21
- searchTests: (query: string) => Promise<TestSearchResult>;
22
- triggerTests: (data: Payload) => Promise<Trigger>;
23
- };
15
+ export declare const apiConstructor: (configuration: APIConfiguration) => APIHelper;
24
16
  export {};
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.apiConstructor = exports.is5xxError = exports.formatBackendErrors = exports.EndpointError = void 0;
12
+ exports.apiConstructor = exports.is5xxError = exports.isNotFoundError = exports.isForbiddenError = exports.formatBackendErrors = exports.EndpointError = void 0;
13
13
  const querystring_1 = require("querystring");
14
14
  const utils_1 = require("../../helpers/utils");
15
15
  const utils_2 = require("./utils");
@@ -43,6 +43,7 @@ exports.formatBackendErrors = formatBackendErrors;
43
43
  const triggerTests = (request) => (data) => __awaiter(void 0, void 0, void 0, function* () {
44
44
  const resp = yield retryRequest({
45
45
  data,
46
+ headers: { 'X-Trigger-App': utils_2.ciTriggerApp },
46
47
  method: 'POST',
47
48
  url: '/synthetics/tests/trigger/ci',
48
49
  }, request);
@@ -87,9 +88,13 @@ const retryOn5xxErrors = (retries, error) => {
87
88
  return 500;
88
89
  }
89
90
  };
91
+ const getErrorHttpStatus = (error) => { var _a; return 'status' in error ? error.status : (_a = error.response) === null || _a === void 0 ? void 0 : _a.status; };
92
+ const isForbiddenError = (error) => getErrorHttpStatus(error) === 403;
93
+ exports.isForbiddenError = isForbiddenError;
94
+ const isNotFoundError = (error) => getErrorHttpStatus(error) === 404;
95
+ exports.isNotFoundError = isNotFoundError;
90
96
  const is5xxError = (error) => {
91
- var _a;
92
- const statusCode = 'status' in error ? error.status : (_a = error.response) === null || _a === void 0 ? void 0 : _a.status;
97
+ const statusCode = getErrorHttpStatus(error);
93
98
  return statusCode && statusCode >= 500 && statusCode <= 599;
94
99
  };
95
100
  exports.is5xxError = is5xxError;
@@ -68,8 +68,14 @@ class RunTestCommand extends clipanion_1.Command {
68
68
  catch (error) {
69
69
  if (error instanceof errors_1.CiError) {
70
70
  this.reportCiError(error, this.reporter);
71
- if (error instanceof errors_1.CriticalError && this.config.failOnCriticalErrors) {
72
- return 1;
71
+ if (error instanceof errors_1.CriticalError) {
72
+ if (this.config.failOnCriticalErrors) {
73
+ return 1;
74
+ }
75
+ else {
76
+ this.reporter.error(chalk_1.default.yellow('Because `failOnCriticalErrors` is not set or disabled, the command will exit with an error code 0. ' +
77
+ 'Use `failOnCriticalErrors: true` to exit with an error code 1.\n'));
78
+ }
73
79
  }
74
80
  }
75
81
  return 0;
@@ -128,12 +134,18 @@ class RunTestCommand extends clipanion_1.Command {
128
134
  }
129
135
  reportCiError(error, reporter) {
130
136
  switch (error.code) {
137
+ // Non critical errors
131
138
  case 'NO_RESULTS_TO_POLL':
132
139
  reporter.log('No results to poll.\n');
133
140
  break;
134
141
  case 'NO_TESTS_TO_RUN':
135
142
  reporter.log('No test to run.\n');
136
143
  break;
144
+ // Critical command errors
145
+ case 'AUTHORIZATION_ERROR':
146
+ reporter.error(`\n${chalk_1.default.bgRed.bold(' ERROR: authorization error ')}\n${error.message}\n\n`);
147
+ reporter.log('Credentials refused, make sure `apiKey`, `appKey` and `datadogSite` are correct.\n');
148
+ break;
137
149
  case 'MISSING_APP_KEY':
138
150
  reporter.error(`Missing ${chalk_1.default.red.bold('DATADOG_APP_KEY')} in your environment.\n`);
139
151
  break;
@@ -144,16 +156,16 @@ class RunTestCommand extends clipanion_1.Command {
144
156
  reporter.error(`\n${chalk_1.default.bgRed.bold(' ERROR: unable to poll test results ')}\n${error.message}\n\n`);
145
157
  break;
146
158
  case 'TUNNEL_START_FAILED':
147
- reporter.error(`\n${chalk_1.default.bgRed.bold(' ERROR: unable to start tunnel')}\n${error.message}\n\n`);
159
+ reporter.error(`\n${chalk_1.default.bgRed.bold(' ERROR: unable to start tunnel ')}\n${error.message}\n\n`);
148
160
  break;
149
161
  case 'TRIGGER_TESTS_FAILED':
150
- reporter.error(`\n${chalk_1.default.bgRed.bold(' ERROR: unable to trigger tests')}\n${error.message}\n\n`);
162
+ reporter.error(`\n${chalk_1.default.bgRed.bold(' ERROR: unable to trigger tests ')}\n${error.message}\n\n`);
151
163
  break;
152
164
  case 'UNAVAILABLE_TEST_CONFIG':
153
165
  reporter.error(`\n${chalk_1.default.bgRed.bold(' ERROR: unable to obtain test configurations with search query ')}\n${error.message}\n\n`);
154
166
  break;
155
167
  case 'UNAVAILABLE_TUNNEL_CONFIG':
156
- reporter.error(`\n${chalk_1.default.bgRed.bold(' ERROR: unable to get tunnel configuration')}\n${error.message}\n\n`);
168
+ reporter.error(`\n${chalk_1.default.bgRed.bold(' ERROR: unable to get tunnel configuration ')}\n${error.message}\n\n`);
157
169
  }
158
170
  }
159
171
  resolveConfig() {
@@ -1,9 +1,14 @@
1
- declare const ciErrorCodes: readonly ["UNAVAILABLE_TEST_CONFIG", "MISSING_API_KEY", "MISSING_APP_KEY", "NO_RESULTS_TO_POLL", "NO_TESTS_TO_RUN", "UNAVAILABLE_TUNNEL_CONFIG", "TUNNEL_START_FAILED", "TRIGGER_TESTS_FAILED", "POLL_RESULTS_FAILED"];
2
- declare type CiErrorCode = typeof ciErrorCodes[number];
1
+ declare const nonCriticalErrorCodes: readonly ["NO_TESTS_TO_RUN", "NO_RESULTS_TO_POLL"];
2
+ export declare type NonCriticalCiErrorCode = typeof nonCriticalErrorCodes[number];
3
+ declare const criticalErrorCodes: readonly ["AUTHORIZATION_ERROR", "MISSING_API_KEY", "MISSING_APP_KEY", "POLL_RESULTS_FAILED", "TRIGGER_TESTS_FAILED", "TUNNEL_START_FAILED", "UNAVAILABLE_TEST_CONFIG", "UNAVAILABLE_TUNNEL_CONFIG"];
4
+ export declare type CriticalCiErrorCode = typeof criticalErrorCodes[number];
5
+ export declare type CiErrorCode = NonCriticalCiErrorCode | CriticalCiErrorCode;
3
6
  export declare class CiError extends Error {
4
7
  code: CiErrorCode;
5
8
  constructor(code: CiErrorCode);
6
9
  }
7
10
  export declare class CriticalError extends CiError {
11
+ code: CriticalCiErrorCode;
12
+ constructor(code: CriticalCiErrorCode);
8
13
  }
9
14
  export {};
@@ -2,16 +2,16 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CriticalError = exports.CiError = void 0;
4
4
  /* tslint:disable:max-classes-per-file */
5
- const ciErrorCodes = [
6
- 'UNAVAILABLE_TEST_CONFIG',
5
+ const nonCriticalErrorCodes = ['NO_TESTS_TO_RUN', 'NO_RESULTS_TO_POLL'];
6
+ const criticalErrorCodes = [
7
+ 'AUTHORIZATION_ERROR',
7
8
  'MISSING_API_KEY',
8
9
  'MISSING_APP_KEY',
9
- 'NO_RESULTS_TO_POLL',
10
- 'NO_TESTS_TO_RUN',
11
- 'UNAVAILABLE_TUNNEL_CONFIG',
12
- 'TUNNEL_START_FAILED',
13
- 'TRIGGER_TESTS_FAILED',
14
10
  'POLL_RESULTS_FAILED',
11
+ 'TRIGGER_TESTS_FAILED',
12
+ 'TUNNEL_START_FAILED',
13
+ 'UNAVAILABLE_TEST_CONFIG',
14
+ 'UNAVAILABLE_TUNNEL_CONFIG',
15
15
  ];
16
16
  class CiError extends Error {
17
17
  constructor(code) {
@@ -21,5 +21,9 @@ class CiError extends Error {
21
21
  }
22
22
  exports.CiError = CiError;
23
23
  class CriticalError extends CiError {
24
+ constructor(code) {
25
+ super(code);
26
+ this.code = code;
27
+ }
24
28
  }
25
29
  exports.CriticalError = CriticalError;
@@ -1,4 +1,4 @@
1
- export { CiError } from './errors';
1
+ export { CiError, CriticalError } from './errors';
2
2
  export * from './interfaces';
3
3
  export { DefaultReporter } from './reporters/default';
4
4
  export { executeTests } from './run-test';
@@ -22,9 +22,10 @@ var __importStar = (this && this.__importStar) || function (mod) {
22
22
  return result;
23
23
  };
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.utils = exports.executeTests = exports.DefaultReporter = exports.CiError = void 0;
25
+ exports.utils = exports.executeTests = exports.DefaultReporter = exports.CriticalError = exports.CiError = void 0;
26
26
  var errors_1 = require("./errors");
27
27
  Object.defineProperty(exports, "CiError", { enumerable: true, get: function () { return errors_1.CiError; } });
28
+ Object.defineProperty(exports, "CriticalError", { enumerable: true, get: function () { return errors_1.CriticalError; } });
28
29
  __exportStar(require("./interfaces"), exports);
29
30
  var default_1 = require("./reporters/default");
30
31
  Object.defineProperty(exports, "DefaultReporter", { enumerable: true, get: function () { return default_1.DefaultReporter; } });
@@ -11,6 +11,7 @@ export interface MainReporter {
11
11
  }): void;
12
12
  runEnd(summary: Summary): void;
13
13
  testEnd(test: Test, results: PollResult[], baseUrl: string, locationNames: LocationsMapping, failOnCriticalErrors: boolean, failOnTimeout: boolean): void;
14
+ testsWait(tests: Test[]): void;
14
15
  testTrigger(test: Test, testId: string, executionRule: ExecutionRule, config: ConfigOverride): void;
15
16
  testWait(test: Test): void;
16
17
  }
@@ -252,12 +253,9 @@ export interface ConfigOverride {
252
253
  };
253
254
  }
254
255
  export interface Payload {
255
- metadata?: SyntheticsMetadata;
256
+ metadata?: Metadata;
256
257
  tests: TestPayload[];
257
258
  }
258
- export declare type SyntheticsMetadata = Metadata & {
259
- trigger_app: string;
260
- };
261
259
  export interface TestPayload extends ConfigOverride {
262
260
  executionRule: ExecutionRule;
263
261
  public_id: string;
@@ -13,6 +13,7 @@ export declare class DefaultReporter implements MainReporter {
13
13
  }): void;
14
14
  runEnd(summary: Summary): void;
15
15
  testEnd(test: Test, results: PollResult[], baseUrl: string, locationNames: LocationsMapping, failOnCriticalErrors: boolean, failOnTimeout: boolean): void;
16
+ testsWait(tests: Test[]): void;
16
17
  testTrigger(test: Test, testId: string, executionRule: ExecutionRule, config: ConfigOverride): void;
17
18
  testWait(test: Test): void;
18
19
  }
@@ -195,14 +195,14 @@ class DefaultReporter {
195
195
  this.write(error);
196
196
  }
197
197
  initErrors(errors) {
198
- this.write(errors.join('\n'));
198
+ this.write(errors.join('\n') + '\n\n');
199
199
  }
200
200
  log(log) {
201
201
  this.write(log);
202
202
  }
203
203
  reportStart(timings) {
204
204
  const delay = (Date.now() - timings.startTime).toString();
205
- this.write(['\n', chalk_1.default.bold.cyan('=== REPORT ==='), `Took ${chalk_1.default.bold(delay)}ms`, '\n'].join('\n'));
205
+ this.write(['', chalk_1.default.bold.cyan('=== REPORT ==='), `Took ${chalk_1.default.bold(delay)}ms`, '\n'].join('\n'));
206
206
  }
207
207
  runEnd(summary) {
208
208
  const summaries = [
@@ -239,6 +239,15 @@ class DefaultReporter {
239
239
  .concat('\n\n');
240
240
  this.write([`${icon} ${idDisplay}${nonBlockingText} | ${nameColor(test.name)}`, testResultsText].join('\n'));
241
241
  }
242
+ testsWait(tests) {
243
+ const testsList = tests.map((t) => t.public_id);
244
+ if (testsList.length > 10) {
245
+ testsList.splice(10);
246
+ testsList.push('…');
247
+ }
248
+ const testsDisplay = chalk_1.default.gray(`(${testsList.join(', ')})`);
249
+ this.write(`Waiting for ${chalk_1.default.bold.cyan(tests.length)} test result${tests.length > 1 ? 's' : ''} ${testsDisplay}…\n`);
250
+ }
242
251
  testTrigger(test, testId, executionRule, config) {
243
252
  const idDisplay = `[${chalk_1.default.bold.dim(testId)}]`;
244
253
  const getMessage = () => {
@@ -246,22 +255,21 @@ class DefaultReporter {
246
255
  // Test is either skipped from datadog-ci config or from test config
247
256
  const isSkippedByCIConfig = config.executionRule === interfaces_1.ExecutionRule.SKIPPED;
248
257
  if (isSkippedByCIConfig) {
249
- return `>> Skipped test "${chalk_1.default.yellow.dim(test.name)}"`;
258
+ return `Skipped test "${chalk_1.default.yellow.dim(test.name)}"`;
250
259
  }
251
260
  else {
252
- return `>> Skipped test "${chalk_1.default.yellow.dim(test.name)}" because of execution rule configuration in Datadog`;
261
+ return `Skipped test "${chalk_1.default.yellow.dim(test.name)}" because of execution rule configuration in Datadog`;
253
262
  }
254
263
  }
255
264
  if (executionRule === interfaces_1.ExecutionRule.NON_BLOCKING) {
256
- return `Trigger test "${chalk_1.default.green.bold(test.name)}" (non-blocking)`;
265
+ return `Found test "${chalk_1.default.green.bold(test.name)}" (non-blocking)`;
257
266
  }
258
- return `Trigger test "${chalk_1.default.green.bold(test.name)}"`;
267
+ return `Found test "${chalk_1.default.green.bold(test.name)}"`;
259
268
  };
260
269
  this.write(`${idDisplay} ${getMessage()}\n`);
261
270
  }
262
271
  testWait(test) {
263
- const idDisplay = `[${chalk_1.default.bold.dim(test.public_id)}]`;
264
- this.write(`${idDisplay} Waiting results for "${chalk_1.default.green.bold(test.name)}"\n`);
272
+ return;
265
273
  }
266
274
  }
267
275
  exports.DefaultReporter = DefaultReporter;
@@ -62,15 +62,5 @@ export declare const getTestsList: (api: APIHelper, config: SyntheticsCIConfig,
62
62
  id: string;
63
63
  suite: string | undefined;
64
64
  }[]>;
65
- export declare const getApiHelper: (config: SyntheticsCIConfig) => {
66
- getPresignedURL: (testIds: string[]) => Promise<{
67
- url: string;
68
- }>;
69
- getTest: (testId: string) => Promise<Test>;
70
- pollResults: (resultIds: string[]) => Promise<{
71
- results: PollResult[];
72
- }>;
73
- searchTests: (query: string) => Promise<import("./interfaces").TestSearchResult>;
74
- triggerTests: (data: import("./interfaces").Payload) => Promise<Trigger>;
75
- };
65
+ export declare const getApiHelper: (config: SyntheticsCIConfig) => APIHelper;
76
66
  export declare const getDatadogHost: (useIntake: boolean | undefined, config: SyntheticsCIConfig) => string;
@@ -32,8 +32,7 @@ const executeTests = (reporter, config) => __awaiter(void 0, void 0, void 0, fun
32
32
  testsToTrigger = yield exports.getTestsList(api, config, reporter);
33
33
  }
34
34
  catch (error) {
35
- const isCriticalError = api_1.is5xxError(error);
36
- throw new (isCriticalError ? errors_1.CriticalError : errors_1.CiError)('UNAVAILABLE_TEST_CONFIG');
35
+ throw new errors_1.CriticalError(api_1.isForbiddenError(error) ? 'AUTHORIZATION_ERROR' : 'UNAVAILABLE_TEST_CONFIG');
37
36
  }
38
37
  }
39
38
  if (!testsToTrigger.length) {
@@ -44,8 +43,10 @@ const executeTests = (reporter, config) => __awaiter(void 0, void 0, void 0, fun
44
43
  testsToTriggerResult = yield utils_1.getTestsToTrigger(api, testsToTrigger, reporter);
45
44
  }
46
45
  catch (error) {
47
- const isCriticalError = api_1.is5xxError(error);
48
- throw new (isCriticalError ? errors_1.CriticalError : errors_1.CiError)('UNAVAILABLE_TEST_CONFIG');
46
+ if (error instanceof errors_1.CiError) {
47
+ throw error;
48
+ }
49
+ throw new errors_1.CriticalError(api_1.isForbiddenError(error) ? 'AUTHORIZATION_ERROR' : 'UNAVAILABLE_TEST_CONFIG');
49
50
  }
50
51
  const { tests, overriddenTestsToTrigger, summary } = testsToTriggerResult;
51
52
  // All tests have been skipped or are missing.
@@ -60,8 +61,7 @@ const executeTests = (reporter, config) => __awaiter(void 0, void 0, void 0, fun
60
61
  presignedURL = (yield api.getPresignedURL(publicIdsToTrigger)).url;
61
62
  }
62
63
  catch (error) {
63
- const isCriticalError = api_1.is5xxError(error);
64
- throw new (isCriticalError ? errors_1.CriticalError : errors_1.CiError)('UNAVAILABLE_TUNNEL_CONFIG');
64
+ throw new errors_1.CriticalError('UNAVAILABLE_TUNNEL_CONFIG');
65
65
  }
66
66
  // Open a tunnel to Datadog
67
67
  try {
@@ -72,9 +72,8 @@ const executeTests = (reporter, config) => __awaiter(void 0, void 0, void 0, fun
72
72
  });
73
73
  }
74
74
  catch (error) {
75
- const isCriticalError = api_1.is5xxError(error);
76
75
  yield stopTunnel();
77
- throw new (isCriticalError ? errors_1.CriticalError : errors_1.CiError)('TUNNEL_START_FAILED');
76
+ throw new errors_1.CriticalError('TUNNEL_START_FAILED');
78
77
  }
79
78
  }
80
79
  let triggers;
@@ -82,13 +81,12 @@ const executeTests = (reporter, config) => __awaiter(void 0, void 0, void 0, fun
82
81
  triggers = yield utils_1.runTests(api, overriddenTestsToTrigger);
83
82
  }
84
83
  catch (error) {
85
- const isCriticalError = api_1.is5xxError(error);
86
84
  yield stopTunnel();
87
- throw new (isCriticalError ? errors_1.CriticalError : errors_1.CiError)('TRIGGER_TESTS_FAILED');
85
+ throw new errors_1.CriticalError('TRIGGER_TESTS_FAILED');
88
86
  }
89
87
  if (!triggers.results) {
90
88
  yield stopTunnel();
91
- throw new errors_1.CriticalError('NO_RESULTS_TO_POLL');
89
+ throw new errors_1.CiError('NO_RESULTS_TO_POLL');
92
90
  }
93
91
  const results = {};
94
92
  try {
@@ -97,8 +95,7 @@ const executeTests = (reporter, config) => __awaiter(void 0, void 0, void 0, fun
97
95
  Object.assign(results, resultPolled);
98
96
  }
99
97
  catch (error) {
100
- const isCriticalError = api_1.is5xxError(error);
101
- throw new (isCriticalError ? errors_1.CriticalError : errors_1.CiError)('POLL_RESULTS_FAILED');
98
+ throw new errors_1.CriticalError('POLL_RESULTS_FAILED');
102
99
  }
103
100
  finally {
104
101
  yield stopTunnel();
@@ -132,10 +129,10 @@ const getTestsList = (api, config, reporter) => __awaiter(void 0, void 0, void 0
132
129
  exports.getTestsList = getTestsList;
133
130
  const getApiHelper = (config) => {
134
131
  if (!config.appKey) {
135
- throw new errors_1.CiError('MISSING_APP_KEY');
132
+ throw new errors_1.CriticalError('MISSING_APP_KEY');
136
133
  }
137
134
  if (!config.apiKey) {
138
- throw new errors_1.CiError('MISSING_API_KEY');
135
+ throw new errors_1.CriticalError('MISSING_API_KEY');
139
136
  }
140
137
  return api_1.apiConstructor({
141
138
  apiKey: config.apiKey,
@@ -1,5 +1,6 @@
1
1
  import { APIHelper, ConfigOverride, ExecutionRule, InternalTest, MainReporter, PollResult, Reporter, Result, Suite, Summary, TestPayload, Trigger, TriggerConfig, TriggerResponse, TriggerResult } from './interfaces';
2
2
  import { Tunnel } from './tunnel';
3
+ export declare let ciTriggerApp: string;
3
4
  export declare const handleConfig: (test: InternalTest, publicId: string, reporter: MainReporter, config?: ConfigOverride | undefined) => TestPayload;
4
5
  export declare const setCiTriggerApp: (source: string) => void;
5
6
  export declare const getExecutionRule: (test: InternalTest, configOverride?: ConfigOverride | undefined) => ExecutionRule;
@@ -31,7 +31,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
31
31
  return (mod && mod.__esModule) ? mod : { "default": mod };
32
32
  };
33
33
  Object.defineProperty(exports, "__esModule", { value: true });
34
- exports.parseVariablesFromCli = exports.retry = exports.runTests = exports.getTestsToTrigger = exports.getReporter = exports.getResultDuration = exports.createSummary = exports.createTriggerResultMap = exports.waitForResults = exports.wait = exports.getSuites = exports.hasTestSucceeded = exports.hasResultPassed = exports.isCriticalError = exports.getStrictestExecutionRule = exports.getExecutionRule = exports.setCiTriggerApp = exports.handleConfig = void 0;
34
+ exports.parseVariablesFromCli = exports.retry = exports.runTests = exports.getTestsToTrigger = exports.getReporter = exports.getResultDuration = exports.createSummary = exports.createTriggerResultMap = exports.waitForResults = exports.wait = exports.getSuites = exports.hasTestSucceeded = exports.hasResultPassed = exports.isCriticalError = exports.getStrictestExecutionRule = exports.getExecutionRule = exports.setCiTriggerApp = exports.handleConfig = exports.ciTriggerApp = void 0;
35
35
  const fs = __importStar(require("fs"));
36
36
  const path = __importStar(require("path"));
37
37
  const url_1 = require("url");
@@ -41,13 +41,14 @@ const glob_1 = __importDefault(require("glob"));
41
41
  const ci_1 = require("../../helpers/ci");
42
42
  const utils_1 = require("../../helpers/utils");
43
43
  const api_1 = require("./api");
44
+ const errors_1 = require("./errors");
44
45
  const interfaces_1 = require("./interfaces");
45
46
  const POLLING_INTERVAL = 5000; // In ms
46
47
  const PUBLIC_ID_REGEX = /^[\d\w]{3}-[\d\w]{3}-[\d\w]{3}$/;
47
48
  const SUBDOMAIN_REGEX = /(.*?)\.(?=[^\/]*\..{2,5})/;
48
49
  const TEMPLATE_REGEX = /{{\s*([^{}]*?)\s*}}/g;
49
50
  const template = (st, context) => st.replace(TEMPLATE_REGEX, (match, p1) => (p1 in context ? context[p1] : match));
50
- let ciTriggerApp = 'npm_package';
51
+ exports.ciTriggerApp = 'npm_package';
51
52
  const handleConfig = (test, publicId, reporter, config) => {
52
53
  const executionRule = exports.getExecutionRule(test, config);
53
54
  let handledConfig = {
@@ -85,7 +86,7 @@ const handleConfig = (test, publicId, reporter, config) => {
85
86
  };
86
87
  exports.handleConfig = handleConfig;
87
88
  const setCiTriggerApp = (source) => {
88
- ciTriggerApp = source;
89
+ exports.ciTriggerApp = source;
89
90
  };
90
91
  exports.setCiTriggerApp = setCiTriggerApp;
91
92
  const parseUrlVariables = (url, reporter) => {
@@ -370,6 +371,13 @@ const getReporter = (reporters) => ({
370
371
  }
371
372
  }
372
373
  },
374
+ testsWait: (tests) => {
375
+ for (const reporter of reporters) {
376
+ if (typeof reporter.testsWait === 'function') {
377
+ reporter.testsWait(tests);
378
+ }
379
+ }
380
+ },
373
381
  });
374
382
  exports.getReporter = getReporter;
375
383
  const getTestsToTrigger = (api, triggerConfigs, reporter) => __awaiter(void 0, void 0, void 0, function* () {
@@ -382,14 +390,14 @@ const getTestsToTrigger = (api, triggerConfigs, reporter) => __awaiter(void 0, v
382
390
  try {
383
391
  test = Object.assign(Object.assign({}, (yield api.getTest(id))), { suite });
384
392
  }
385
- catch (e) {
386
- if (api_1.is5xxError(e)) {
387
- throw e;
393
+ catch (error) {
394
+ if (api_1.isNotFoundError(error)) {
395
+ summary.testsNotFound.add(id);
396
+ const errorMessage = api_1.formatBackendErrors(error);
397
+ errorMessages.push(`[${chalk_1.default.bold.dim(id)}] ${chalk_1.default.yellow.bold('Test not found')}: ${errorMessage}`);
398
+ return;
388
399
  }
389
- summary.testsNotFound.add(id);
390
- const errorMessage = api_1.formatBackendErrors(e);
391
- errorMessages.push(`[${chalk_1.default.bold.dim(id)}] ${chalk_1.default.yellow.bold('Test not found')}: ${errorMessage}\n`);
392
- return;
400
+ throw error;
393
401
  }
394
402
  const overriddenConfig = exports.handleConfig(test, id, reporter, config);
395
403
  overriddenTestsToTrigger.push(overriddenConfig);
@@ -405,16 +413,21 @@ const getTestsToTrigger = (api, triggerConfigs, reporter) => __awaiter(void 0, v
405
413
  // Display errors at the end of all tests for better visibility.
406
414
  reporter.initErrors(errorMessages);
407
415
  if (!overriddenTestsToTrigger.length) {
408
- throw new Error('No tests to trigger');
416
+ throw new errors_1.CiError('NO_TESTS_TO_RUN');
409
417
  }
410
- return { tests: tests.filter(definedTypeGuard), overriddenTestsToTrigger, summary };
418
+ const waitedTests = tests.filter(definedTypeGuard);
419
+ if (waitedTests.length > 0) {
420
+ reporter.testsWait(waitedTests);
421
+ }
422
+ return { tests: waitedTests, overriddenTestsToTrigger, summary };
411
423
  });
412
424
  exports.getTestsToTrigger = getTestsToTrigger;
413
425
  const runTests = (api, testsToTrigger) => __awaiter(void 0, void 0, void 0, function* () {
414
426
  const payload = { tests: testsToTrigger };
415
427
  const ciMetadata = ci_1.getCIMetadata();
416
- const syntheticsMetadata = Object.assign(Object.assign({ ci: { job: {}, pipeline: {}, provider: {}, stage: {} }, git: { commit: { author: {}, committer: {} } } }, ciMetadata), { trigger_app: ciTriggerApp });
417
- payload.metadata = syntheticsMetadata;
428
+ if (ciMetadata) {
429
+ payload.metadata = ciMetadata;
430
+ }
418
431
  try {
419
432
  return yield api.triggerTests(payload);
420
433
  }