@datadog/datadog-ci 1.1.1 → 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/dist/commands/synthetics/__tests__/run-test.test.js +30 -24
- package/dist/commands/synthetics/__tests__/utils.test.js +19 -10
- package/dist/commands/synthetics/api.d.ts +3 -12
- package/dist/commands/synthetics/api.js +4 -1
- package/dist/commands/synthetics/command.js +4 -0
- package/dist/commands/synthetics/errors.d.ts +4 -4
- package/dist/commands/synthetics/errors.js +5 -4
- package/dist/commands/synthetics/interfaces.d.ts +1 -4
- package/dist/commands/synthetics/reporters/default.js +2 -2
- package/dist/commands/synthetics/run-test.d.ts +1 -11
- package/dist/commands/synthetics/run-test.js +5 -2
- package/dist/commands/synthetics/utils.d.ts +1 -0
- package/dist/commands/synthetics/utils.js +6 -5
- package/package.json +1 -1
|
@@ -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: [],
|
|
@@ -87,27 +87,36 @@ describe('utils', () => {
|
|
|
87
87
|
test('runTests sends batch metadata', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
88
88
|
jest.spyOn(ciHelpers, 'getCIMetadata').mockImplementation(() => undefined);
|
|
89
89
|
const payloadMetadataSpy = jest.fn();
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
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') {
|
|
94
93
|
return { data: fakeTrigger };
|
|
95
94
|
}
|
|
96
95
|
}));
|
|
97
96
|
yield utils.runTests(api, [{ public_id: fakeId, executionRule: interfaces_1.ExecutionRule.NON_BLOCKING }]);
|
|
98
|
-
expect(payloadMetadataSpy).toHaveBeenCalledWith(
|
|
99
|
-
ci: { job: {}, pipeline: {}, provider: {}, stage: {} },
|
|
100
|
-
git: { commit: { author: {}, committer: {} } },
|
|
101
|
-
trigger_app: 'npm_package',
|
|
102
|
-
});
|
|
97
|
+
expect(payloadMetadataSpy).toHaveBeenCalledWith(undefined);
|
|
103
98
|
const metadata = {
|
|
104
99
|
ci: { job: { name: 'job' }, pipeline: {}, provider: { name: 'jest' }, stage: {} },
|
|
105
100
|
git: { commit: { author: {}, committer: {}, message: 'test' } },
|
|
106
101
|
};
|
|
107
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' }));
|
|
108
117
|
utils.setCiTriggerApp('unit_test');
|
|
109
118
|
yield utils.runTests(api, [{ public_id: fakeId, executionRule: interfaces_1.ExecutionRule.NON_BLOCKING }]);
|
|
110
|
-
expect(
|
|
119
|
+
expect(headersMetadataSpy).toHaveBeenCalledWith(expect.objectContaining({ 'X-Trigger-App': 'unit_test' }));
|
|
111
120
|
}));
|
|
112
121
|
test('should run test with publicId from url', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
113
122
|
const axiosMock = jest.spyOn(axios_1.default, 'create');
|
|
@@ -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,17 +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;
|
|
12
13
|
export declare const isNotFoundError: (error: AxiosError | EndpointError) => boolean;
|
|
13
14
|
export declare const is5xxError: (error: AxiosError | EndpointError) => boolean | 0 | undefined;
|
|
14
|
-
export declare const apiConstructor: (configuration: APIConfiguration) =>
|
|
15
|
-
getPresignedURL: (testIds: string[]) => Promise<{
|
|
16
|
-
url: string;
|
|
17
|
-
}>;
|
|
18
|
-
getTest: (testId: string) => Promise<Test>;
|
|
19
|
-
pollResults: (resultIds: string[]) => Promise<{
|
|
20
|
-
results: PollResult[];
|
|
21
|
-
}>;
|
|
22
|
-
searchTests: (query: string) => Promise<TestSearchResult>;
|
|
23
|
-
triggerTests: (data: Payload) => Promise<Trigger>;
|
|
24
|
-
};
|
|
15
|
+
export declare const apiConstructor: (configuration: APIConfiguration) => APIHelper;
|
|
25
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.isNotFoundError = 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);
|
|
@@ -88,6 +89,8 @@ const retryOn5xxErrors = (retries, error) => {
|
|
|
88
89
|
}
|
|
89
90
|
};
|
|
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;
|
|
91
94
|
const isNotFoundError = (error) => getErrorHttpStatus(error) === 404;
|
|
92
95
|
exports.isNotFoundError = isNotFoundError;
|
|
93
96
|
const is5xxError = (error) => {
|
|
@@ -142,6 +142,10 @@ class RunTestCommand extends clipanion_1.Command {
|
|
|
142
142
|
reporter.log('No test to run.\n');
|
|
143
143
|
break;
|
|
144
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;
|
|
145
149
|
case 'MISSING_APP_KEY':
|
|
146
150
|
reporter.error(`Missing ${chalk_1.default.red.bold('DATADOG_APP_KEY')} in your environment.\n`);
|
|
147
151
|
break;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
declare const nonCriticalErrorCodes: readonly ["NO_TESTS_TO_RUN", "NO_RESULTS_TO_POLL"];
|
|
2
|
-
declare type NonCriticalCiErrorCode = typeof nonCriticalErrorCodes[number];
|
|
3
|
-
declare const criticalErrorCodes: readonly ["
|
|
4
|
-
declare type CriticalCiErrorCode = typeof criticalErrorCodes[number];
|
|
5
|
-
declare type CiErrorCode = NonCriticalCiErrorCode | CriticalCiErrorCode;
|
|
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;
|
|
6
6
|
export declare class CiError extends Error {
|
|
7
7
|
code: CiErrorCode;
|
|
8
8
|
constructor(code: CiErrorCode);
|
|
@@ -4,13 +4,14 @@ exports.CriticalError = exports.CiError = void 0;
|
|
|
4
4
|
/* tslint:disable:max-classes-per-file */
|
|
5
5
|
const nonCriticalErrorCodes = ['NO_TESTS_TO_RUN', 'NO_RESULTS_TO_POLL'];
|
|
6
6
|
const criticalErrorCodes = [
|
|
7
|
-
'
|
|
7
|
+
'AUTHORIZATION_ERROR',
|
|
8
8
|
'MISSING_API_KEY',
|
|
9
9
|
'MISSING_APP_KEY',
|
|
10
|
-
'UNAVAILABLE_TUNNEL_CONFIG',
|
|
11
|
-
'TUNNEL_START_FAILED',
|
|
12
|
-
'TRIGGER_TESTS_FAILED',
|
|
13
10
|
'POLL_RESULTS_FAILED',
|
|
11
|
+
'TRIGGER_TESTS_FAILED',
|
|
12
|
+
'TUNNEL_START_FAILED',
|
|
13
|
+
'UNAVAILABLE_TEST_CONFIG',
|
|
14
|
+
'UNAVAILABLE_TUNNEL_CONFIG',
|
|
14
15
|
];
|
|
15
16
|
class CiError extends Error {
|
|
16
17
|
constructor(code) {
|
|
@@ -253,12 +253,9 @@ export interface ConfigOverride {
|
|
|
253
253
|
};
|
|
254
254
|
}
|
|
255
255
|
export interface Payload {
|
|
256
|
-
metadata?:
|
|
256
|
+
metadata?: Metadata;
|
|
257
257
|
tests: TestPayload[];
|
|
258
258
|
}
|
|
259
|
-
export declare type SyntheticsMetadata = Metadata & {
|
|
260
|
-
trigger_app: string;
|
|
261
|
-
};
|
|
262
259
|
export interface TestPayload extends ConfigOverride {
|
|
263
260
|
executionRule: ExecutionRule;
|
|
264
261
|
public_id: string;
|
|
@@ -195,7 +195,7 @@ class DefaultReporter {
|
|
|
195
195
|
this.write(error);
|
|
196
196
|
}
|
|
197
197
|
initErrors(errors) {
|
|
198
|
-
this.write(errors.join('\n') + '\n');
|
|
198
|
+
this.write(errors.join('\n') + '\n\n');
|
|
199
199
|
}
|
|
200
200
|
log(log) {
|
|
201
201
|
this.write(log);
|
|
@@ -246,7 +246,7 @@ class DefaultReporter {
|
|
|
246
246
|
testsList.push('…');
|
|
247
247
|
}
|
|
248
248
|
const testsDisplay = chalk_1.default.gray(`(${testsList.join(', ')})`);
|
|
249
|
-
this.write(
|
|
249
|
+
this.write(`Waiting for ${chalk_1.default.bold.cyan(tests.length)} test result${tests.length > 1 ? 's' : ''} ${testsDisplay}…\n`);
|
|
250
250
|
}
|
|
251
251
|
testTrigger(test, testId, executionRule, config) {
|
|
252
252
|
const idDisplay = `[${chalk_1.default.bold.dim(testId)}]`;
|
|
@@ -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,7 +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
|
-
throw new errors_1.CriticalError('UNAVAILABLE_TEST_CONFIG');
|
|
35
|
+
throw new errors_1.CriticalError(api_1.isForbiddenError(error) ? 'AUTHORIZATION_ERROR' : 'UNAVAILABLE_TEST_CONFIG');
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
if (!testsToTrigger.length) {
|
|
@@ -43,7 +43,10 @@ const executeTests = (reporter, config) => __awaiter(void 0, void 0, void 0, fun
|
|
|
43
43
|
testsToTriggerResult = yield utils_1.getTestsToTrigger(api, testsToTrigger, reporter);
|
|
44
44
|
}
|
|
45
45
|
catch (error) {
|
|
46
|
-
|
|
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');
|
|
47
50
|
}
|
|
48
51
|
const { tests, overriddenTestsToTrigger, summary } = testsToTriggerResult;
|
|
49
52
|
// All tests have been skipped or are missing.
|
|
@@ -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");
|
|
@@ -48,7 +48,7 @@ const PUBLIC_ID_REGEX = /^[\d\w]{3}-[\d\w]{3}-[\d\w]{3}$/;
|
|
|
48
48
|
const SUBDOMAIN_REGEX = /(.*?)\.(?=[^\/]*\..{2,5})/;
|
|
49
49
|
const TEMPLATE_REGEX = /{{\s*([^{}]*?)\s*}}/g;
|
|
50
50
|
const template = (st, context) => st.replace(TEMPLATE_REGEX, (match, p1) => (p1 in context ? context[p1] : match));
|
|
51
|
-
|
|
51
|
+
exports.ciTriggerApp = 'npm_package';
|
|
52
52
|
const handleConfig = (test, publicId, reporter, config) => {
|
|
53
53
|
const executionRule = exports.getExecutionRule(test, config);
|
|
54
54
|
let handledConfig = {
|
|
@@ -86,7 +86,7 @@ const handleConfig = (test, publicId, reporter, config) => {
|
|
|
86
86
|
};
|
|
87
87
|
exports.handleConfig = handleConfig;
|
|
88
88
|
const setCiTriggerApp = (source) => {
|
|
89
|
-
ciTriggerApp = source;
|
|
89
|
+
exports.ciTriggerApp = source;
|
|
90
90
|
};
|
|
91
91
|
exports.setCiTriggerApp = setCiTriggerApp;
|
|
92
92
|
const parseUrlVariables = (url, reporter) => {
|
|
@@ -425,8 +425,9 @@ exports.getTestsToTrigger = getTestsToTrigger;
|
|
|
425
425
|
const runTests = (api, testsToTrigger) => __awaiter(void 0, void 0, void 0, function* () {
|
|
426
426
|
const payload = { tests: testsToTrigger };
|
|
427
427
|
const ciMetadata = ci_1.getCIMetadata();
|
|
428
|
-
|
|
429
|
-
|
|
428
|
+
if (ciMetadata) {
|
|
429
|
+
payload.metadata = ciMetadata;
|
|
430
|
+
}
|
|
430
431
|
try {
|
|
431
432
|
return yield api.triggerTests(payload);
|
|
432
433
|
}
|