@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.
- package/LICENSE-3rdparty.csv +1 -1
- package/README.md +28 -0
- package/dist/commands/lambda/__tests__/functions/instrument.test.js +2 -2
- package/dist/commands/lambda/__tests__/functions/uninstrument.test.js +2 -2
- package/dist/commands/lambda/__tests__/instrument.test.js +13 -8
- package/dist/commands/lambda/__tests__/prompt.test.js +0 -4
- package/dist/commands/lambda/__tests__/uninstrument.test.js +12 -7
- package/dist/commands/lambda/constants.d.ts +0 -1
- package/dist/commands/lambda/constants.js +2 -27
- package/dist/commands/lambda/functions/commons.js +1 -1
- package/dist/commands/lambda/instrument.js +4 -1
- package/dist/commands/lambda/prompt.d.ts +1 -0
- package/dist/commands/lambda/prompt.js +19 -10
- package/dist/commands/lambda/uninstrument.js +1 -1
- package/dist/commands/synthetics/__tests__/cli.test.js +107 -4
- package/dist/commands/synthetics/__tests__/fixtures.d.ts +2 -1
- package/dist/commands/synthetics/__tests__/fixtures.js +4 -1
- package/dist/commands/synthetics/__tests__/reporters/default.test.js +2 -1
- package/dist/commands/synthetics/__tests__/run-test.test.js +34 -28
- package/dist/commands/synthetics/__tests__/utils.test.js +24 -11
- package/dist/commands/synthetics/api.d.ts +4 -12
- package/dist/commands/synthetics/api.js +8 -3
- package/dist/commands/synthetics/command.js +17 -5
- package/dist/commands/synthetics/errors.d.ts +7 -2
- package/dist/commands/synthetics/errors.js +11 -7
- package/dist/commands/synthetics/index.d.ts +1 -1
- package/dist/commands/synthetics/index.js +2 -1
- package/dist/commands/synthetics/interfaces.d.ts +2 -4
- package/dist/commands/synthetics/reporters/default.d.ts +1 -0
- package/dist/commands/synthetics/reporters/default.js +16 -8
- package/dist/commands/synthetics/run-test.d.ts +1 -11
- package/dist/commands/synthetics/run-test.js +12 -15
- package/dist/commands/synthetics/utils.d.ts +1 -0
- package/dist/commands/synthetics/utils.js +27 -14
- 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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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.
|
|
258
|
-
yield expect(runTests.executeTests(fixtures_1.mockReporter, fixtures_1.ciConfig)).rejects.toMatchError(new errors_1.
|
|
259
|
-
expect(() => runTests.getApiHelper(Object.assign(Object.assign({}, fixtures_1.ciConfig), { appKey: 'fakeappkey' }))).toThrow(new errors_1.
|
|
260
|
-
yield expect(runTests.executeTests(fixtures_1.mockReporter, Object.assign(Object.assign({}, fixtures_1.ciConfig), { appKey: 'fakeappkey' }))).rejects.toMatchError(new errors_1.
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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(
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
|
72
|
-
|
|
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
|
|
2
|
-
declare type
|
|
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
|
|
6
|
-
|
|
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;
|
|
@@ -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?:
|
|
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(['
|
|
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
|
|
258
|
+
return `Skipped test "${chalk_1.default.yellow.dim(test.name)}"`;
|
|
250
259
|
}
|
|
251
260
|
else {
|
|
252
|
-
return
|
|
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 `
|
|
265
|
+
return `Found test "${chalk_1.default.green.bold(test.name)}" (non-blocking)`;
|
|
257
266
|
}
|
|
258
|
-
return `
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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.
|
|
132
|
+
throw new errors_1.CriticalError('MISSING_APP_KEY');
|
|
136
133
|
}
|
|
137
134
|
if (!config.apiKey) {
|
|
138
|
-
throw new errors_1.
|
|
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
|
-
|
|
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 (
|
|
386
|
-
if (api_1.
|
|
387
|
-
|
|
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
|
-
|
|
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
|
|
416
|
+
throw new errors_1.CiError('NO_TESTS_TO_RUN');
|
|
409
417
|
}
|
|
410
|
-
|
|
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
|
-
|
|
417
|
-
|
|
428
|
+
if (ciMetadata) {
|
|
429
|
+
payload.metadata = ciMetadata;
|
|
430
|
+
}
|
|
418
431
|
try {
|
|
419
432
|
return yield api.triggerTests(payload);
|
|
420
433
|
}
|