@allurereport/plugin-testops 3.14.3 → 3.16.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.
- package/dist/client.js +79 -14
- package/dist/logger.d.ts +3 -11
- package/dist/logger.js +5 -29
- package/dist/model.d.ts +3 -3
- package/dist/plugin.js +201 -62
- package/dist/utils/attachments.d.ts +5 -0
- package/dist/utils/attachments.js +27 -0
- package/dist/utils/categories.d.ts +1 -0
- package/dist/utils/categories.js +18 -0
- package/dist/utils/fixtures.d.ts +4 -0
- package/dist/utils/fixtures.js +30 -0
- package/dist/utils/index.d.ts +7 -7
- package/dist/utils/index.js +7 -68
- package/dist/utils/options.d.ts +2 -0
- package/dist/utils/options.js +18 -0
- package/dist/utils/resolvers.d.ts +6 -0
- package/dist/utils/resolvers.js +58 -0
- package/dist/utils/testResults.d.ts +6 -0
- package/dist/utils/{uploaderDto.js → testResults.js} +31 -58
- package/dist/utils/validation.d.ts +1 -0
- package/dist/utils/validation.js +22 -0
- package/package.json +8 -9
- package/dist/utils/uploadCategory.d.ts +0 -3
- package/dist/utils/uploadCategory.js +0 -20
- package/dist/utils/uploaderDto.d.ts +0 -9
package/dist/client.js
CHANGED
|
@@ -17,12 +17,57 @@ import { chunk } from "lodash-es";
|
|
|
17
17
|
import pLimit from "p-limit";
|
|
18
18
|
import { bold } from "yoctocolors";
|
|
19
19
|
import { Logger } from "./logger.js";
|
|
20
|
+
import { toUploadFixturesResultsDto } from "./utils/fixtures.js";
|
|
20
21
|
import { testStatusToLaunchStatus } from "./utils/launches.js";
|
|
21
|
-
import {
|
|
22
|
+
import { normalizeTestStepsResults, toUploadResultsDto } from "./utils/testResults.js";
|
|
23
|
+
import { validateExecutableName } from "./utils/validation.js";
|
|
22
24
|
class TestOpsClientError extends AxiosError {
|
|
23
25
|
}
|
|
24
26
|
const CHUNK_SIZE = 100;
|
|
25
27
|
const BULK_UPLOAD_CHUNK_SIZE = 1000;
|
|
28
|
+
const MAX_REQUEST_RETRIES = 3;
|
|
29
|
+
const RETRY_BASE_DELAY_MS = 500;
|
|
30
|
+
const getAxiosCause = (error) => {
|
|
31
|
+
if (isAxiosError(error)) {
|
|
32
|
+
return error;
|
|
33
|
+
}
|
|
34
|
+
return error instanceof Error && isAxiosError(error.cause) ? error.cause : undefined;
|
|
35
|
+
};
|
|
36
|
+
const isRetryableRequestError = (error) => {
|
|
37
|
+
const cause = getAxiosCause(error);
|
|
38
|
+
if (!cause || cause.code === "ERR_CANCELED" || cause.name === "CanceledError") {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
const status = cause.response?.status;
|
|
42
|
+
return status === undefined || status === 408 || status === 429 || status >= 500;
|
|
43
|
+
};
|
|
44
|
+
const retryAfterMs = (error) => {
|
|
45
|
+
const headers = getAxiosCause(error)?.response?.headers;
|
|
46
|
+
const value = typeof headers?.get === "function" ? headers.get("retry-after") : headers?.["retry-after"];
|
|
47
|
+
const normalized = Array.isArray(value) ? value[0] : value;
|
|
48
|
+
if (typeof normalized !== "string" && typeof normalized !== "number") {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
const seconds = Number(normalized);
|
|
52
|
+
if (Number.isFinite(seconds)) {
|
|
53
|
+
return Math.max(0, seconds * 1000);
|
|
54
|
+
}
|
|
55
|
+
const date = Date.parse(String(normalized));
|
|
56
|
+
return Number.isNaN(date) ? undefined : Math.max(0, date - Date.now());
|
|
57
|
+
};
|
|
58
|
+
const retryRequest = async (request) => {
|
|
59
|
+
for (let attempt = 0;; attempt += 1) {
|
|
60
|
+
try {
|
|
61
|
+
return await request();
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
if (attempt >= MAX_REQUEST_RETRIES || !isRetryableRequestError(error)) {
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
await new Promise((resolve) => setTimeout(resolve, retryAfterMs(error) ?? RETRY_BASE_DELAY_MS * 2 ** attempt));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
26
71
|
export class TestOpsClient {
|
|
27
72
|
constructor(params) {
|
|
28
73
|
_TestOpsClient_instances.add(this);
|
|
@@ -173,7 +218,7 @@ export class TestOpsClient {
|
|
|
173
218
|
throw new Error("Launch isn't created! Call createLaunch first");
|
|
174
219
|
}
|
|
175
220
|
__classPrivateFieldGet(this, _TestOpsClient_logger, "f").verbose("Retrieving launch progress status…");
|
|
176
|
-
const data = await __classPrivateFieldGet(this, _TestOpsClient_client, "f").get(`/api/launch/${__classPrivateFieldGet(this, _TestOpsClient_launch, "f").id}/progress`);
|
|
221
|
+
const data = await retryRequest(() => __classPrivateFieldGet(this, _TestOpsClient_client, "f").get(`/api/launch/${__classPrivateFieldGet(this, _TestOpsClient_launch, "f").id}/progress`));
|
|
177
222
|
return data.ready;
|
|
178
223
|
}
|
|
179
224
|
async createSession(environment = {}) {
|
|
@@ -245,7 +290,9 @@ export class TestOpsClient {
|
|
|
245
290
|
if (!__classPrivateFieldGet(this, _TestOpsClient_launch, "f")) {
|
|
246
291
|
throw new Error("Launch isn't created! Call createLaunch first");
|
|
247
292
|
}
|
|
293
|
+
const launchId = __classPrivateFieldGet(this, _TestOpsClient_launch, "f").id;
|
|
248
294
|
const formData = new FormData();
|
|
295
|
+
let hasResolvedAttachments = false;
|
|
249
296
|
for (const attachmentLink of attachments) {
|
|
250
297
|
const attachment = await attachmentsResolver(attachmentLink);
|
|
251
298
|
if (!attachment) {
|
|
@@ -255,6 +302,10 @@ export class TestOpsClient {
|
|
|
255
302
|
filename: attachment.originalFileName,
|
|
256
303
|
contentType: attachment.contentType,
|
|
257
304
|
});
|
|
305
|
+
hasResolvedAttachments = true;
|
|
306
|
+
}
|
|
307
|
+
if (!hasResolvedAttachments) {
|
|
308
|
+
return;
|
|
258
309
|
}
|
|
259
310
|
await __classPrivateFieldGet(this, _TestOpsClient_client, "f").post("/api/launch/attachment", {
|
|
260
311
|
body: formData,
|
|
@@ -263,7 +314,7 @@ export class TestOpsClient {
|
|
|
263
314
|
const percent = total > 0 ? Math.min(100, Math.max(0, (progressEvent.loaded / total) * 100)) : 0;
|
|
264
315
|
onProgress?.(percent, total);
|
|
265
316
|
},
|
|
266
|
-
params: { launchId
|
|
317
|
+
params: { launchId },
|
|
267
318
|
headers: formData.getHeaders(),
|
|
268
319
|
});
|
|
269
320
|
}
|
|
@@ -274,9 +325,10 @@ export class TestOpsClient {
|
|
|
274
325
|
if (!__classPrivateFieldGet(this, _TestOpsClient_launch, "f")) {
|
|
275
326
|
throw new Error("Launch isn't created! Call createLaunch first");
|
|
276
327
|
}
|
|
328
|
+
const launchId = __classPrivateFieldGet(this, _TestOpsClient_launch, "f").id;
|
|
277
329
|
await __classPrivateFieldGet(this, _TestOpsClient_client, "f").post("/api/launch/error/bulk", {
|
|
278
330
|
body: {
|
|
279
|
-
launchId
|
|
331
|
+
launchId,
|
|
280
332
|
items: errors,
|
|
281
333
|
},
|
|
282
334
|
onUploadProgress(progressEvent) {
|
|
@@ -291,7 +343,11 @@ export class TestOpsClient {
|
|
|
291
343
|
throw new Error("Session isn't created! Call createSession first");
|
|
292
344
|
}
|
|
293
345
|
const { trs, environments, attachmentsResolver, fixturesResolver, onProgress } = params;
|
|
294
|
-
const
|
|
346
|
+
const projectedTrs = trs.map((tr) => ({
|
|
347
|
+
...tr,
|
|
348
|
+
...(tr.steps ? { steps: normalizeTestStepsResults(tr.steps) } : {}),
|
|
349
|
+
}));
|
|
350
|
+
const trsChunks = chunk(projectedTrs, CHUNK_SIZE);
|
|
295
351
|
const uploadLimitFn = pLimit(__classPrivateFieldGet(this, _TestOpsClient_uploadLimit, "f"));
|
|
296
352
|
const uploadedTrs = [];
|
|
297
353
|
const envNamesById = new Map(environments.map(({ id, name }) => [id, name]));
|
|
@@ -311,8 +367,8 @@ export class TestOpsClient {
|
|
|
311
367
|
await this.createNamedEnvs(Array.from(chunkEnvs.values()));
|
|
312
368
|
}
|
|
313
369
|
const reportIdsToTestOpsIds = await __classPrivateFieldGet(this, _TestOpsClient_instances, "m", _TestOpsClient_postTestResultsChunk).call(this, trsChunk);
|
|
370
|
+
uploadedTrs.push(...trsChunk.filter((tr) => typeof reportIdsToTestOpsIds[tr.id] === "number"));
|
|
314
371
|
await __classPrivateFieldGet(this, _TestOpsClient_instances, "m", _TestOpsClient_uploadChunkAttachmentsAndFixtures).call(this, trsChunk, reportIdsToTestOpsIds, attachmentsResolver, fixturesResolver, uploadLimitFn, onProgress);
|
|
315
|
-
uploadedTrs.push(...trsChunk);
|
|
316
372
|
}
|
|
317
373
|
__classPrivateFieldGet(this, _TestOpsClient_logger, "f").verbose("Test results upload completed");
|
|
318
374
|
}
|
|
@@ -337,6 +393,7 @@ export class TestOpsClient {
|
|
|
337
393
|
if (!__classPrivateFieldGet(this, _TestOpsClient_launch, "f")) {
|
|
338
394
|
throw new Error("Launch isn't created! Call createLaunch first");
|
|
339
395
|
}
|
|
396
|
+
const launchId = __classPrivateFieldGet(this, _TestOpsClient_launch, "f").id;
|
|
340
397
|
const items = results.map((result) => {
|
|
341
398
|
const item = {
|
|
342
399
|
name: result.rule,
|
|
@@ -348,9 +405,9 @@ export class TestOpsClient {
|
|
|
348
405
|
}
|
|
349
406
|
return item;
|
|
350
407
|
});
|
|
351
|
-
await __classPrivateFieldGet(this, _TestOpsClient_client, "f").post("/api/launch/quality-gate/bulk", {
|
|
408
|
+
await retryRequest(() => __classPrivateFieldGet(this, _TestOpsClient_client, "f").post("/api/launch/quality-gate/bulk", {
|
|
352
409
|
body: {
|
|
353
|
-
launchId
|
|
410
|
+
launchId,
|
|
354
411
|
items,
|
|
355
412
|
},
|
|
356
413
|
onUploadProgress(progressEvent) {
|
|
@@ -358,7 +415,7 @@ export class TestOpsClient {
|
|
|
358
415
|
const percent = total > 0 ? Math.min(100, Math.max(0, (progressEvent.loaded / total) * 100)) : 0;
|
|
359
416
|
onProgress?.(percent, total);
|
|
360
417
|
},
|
|
361
|
-
});
|
|
418
|
+
}));
|
|
362
419
|
}
|
|
363
420
|
}
|
|
364
421
|
_TestOpsClient_baseUrl = new WeakMap(), _TestOpsClient_logger = new WeakMap(), _TestOpsClient_accessToken = new WeakMap(), _TestOpsClient_projectId = new WeakMap(), _TestOpsClient_client = new WeakMap(), _TestOpsClient_launch = new WeakMap(), _TestOpsClient_session = new WeakMap(), _TestOpsClient_uploadInProgress = new WeakMap(), _TestOpsClient_uploadLimit = new WeakMap(), _TestOpsClient_namedEnvsIdsByEnv = new WeakMap(), _TestOpsClient_instances = new WeakSet(), _TestOpsClient_postTestResultsChunk = async function _TestOpsClient_postTestResultsChunk(trsChunk) {
|
|
@@ -381,10 +438,10 @@ _TestOpsClient_baseUrl = new WeakMap(), _TestOpsClient_logger = new WeakMap(), _
|
|
|
381
438
|
return extendedTestResult;
|
|
382
439
|
});
|
|
383
440
|
const body = toUploadResultsDto(__classPrivateFieldGet(this, _TestOpsClient_session, "f").id, extendedChunk);
|
|
384
|
-
const data = await __classPrivateFieldGet(this, _TestOpsClient_client, "f").post("/api/upload/test-result", {
|
|
441
|
+
const data = await retryRequest(() => __classPrivateFieldGet(this, _TestOpsClient_client, "f").post("/api/upload/test-result", {
|
|
385
442
|
body,
|
|
386
443
|
headers: { "Content-Type": "application/json" },
|
|
387
|
-
});
|
|
444
|
+
}));
|
|
388
445
|
const reportIdsToTestOpsIds = {};
|
|
389
446
|
for (const { id, uuid } of data.results ?? []) {
|
|
390
447
|
if (typeof uuid === "string" && typeof id === "number") {
|
|
@@ -395,8 +452,16 @@ _TestOpsClient_baseUrl = new WeakMap(), _TestOpsClient_logger = new WeakMap(), _
|
|
|
395
452
|
}, _TestOpsClient_uploadChunkAttachmentsAndFixtures = async function _TestOpsClient_uploadChunkAttachmentsAndFixtures(trsChunk, reportIdsToTestOpsIds, attachmentsResolver, fixturesResolver, uploadLimitFn, onProgress) {
|
|
396
453
|
await Promise.all(trsChunk.map((tr) => uploadLimitFn(async () => {
|
|
397
454
|
const testOpsId = reportIdsToTestOpsIds[tr.id];
|
|
455
|
+
if (typeof testOpsId !== "number") {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
398
458
|
const attachments = await attachmentsResolver(tr);
|
|
399
|
-
const fixtures = await fixturesResolver(tr)
|
|
459
|
+
const fixtures = (await fixturesResolver(tr))
|
|
460
|
+
.filter((fixture) => validateExecutableName(fixture.name))
|
|
461
|
+
.map((fixture) => ({
|
|
462
|
+
...fixture,
|
|
463
|
+
...(fixture.steps ? { steps: normalizeTestStepsResults(fixture.steps) } : {}),
|
|
464
|
+
}));
|
|
400
465
|
await __classPrivateFieldGet(this, _TestOpsClient_instances, "m", _TestOpsClient_uploadAttachmentsForResult).call(this, testOpsId, attachments);
|
|
401
466
|
await __classPrivateFieldGet(this, _TestOpsClient_instances, "m", _TestOpsClient_uploadFixturesForResult).call(this, testOpsId, fixtures);
|
|
402
467
|
onProgress?.();
|
|
@@ -437,7 +502,7 @@ _TestOpsClient_baseUrl = new WeakMap(), _TestOpsClient_logger = new WeakMap(), _
|
|
|
437
502
|
if (fixtures.length === 0)
|
|
438
503
|
return;
|
|
439
504
|
const body = toUploadFixturesResultsDto(fixtures);
|
|
440
|
-
await __classPrivateFieldGet(this, _TestOpsClient_client, "f").post(`/api/upload/test-result/${testOpsResultId}/test-fixture-result`, {
|
|
505
|
+
await retryRequest(() => __classPrivateFieldGet(this, _TestOpsClient_client, "f").post(`/api/upload/test-result/${testOpsResultId}/test-fixture-result`, {
|
|
441
506
|
body,
|
|
442
|
-
});
|
|
507
|
+
}));
|
|
443
508
|
};
|
package/dist/logger.d.ts
CHANGED
|
@@ -1,18 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
type LogLevel = "silent" | "verbose" | "debug" | "info" | "warn" | "error";
|
|
2
2
|
type JSONMessage = Record<string, unknown> | Array<unknown>;
|
|
3
3
|
export declare class Logger {
|
|
4
4
|
#private;
|
|
5
5
|
private readonly loggerName;
|
|
6
|
-
constructor(loggerName: string);
|
|
7
|
-
|
|
8
|
-
tick: () => void;
|
|
9
|
-
update: () => void;
|
|
10
|
-
terminate: () => void;
|
|
11
|
-
};
|
|
12
|
-
progressBar(message: string): ProgressBar | {
|
|
13
|
-
update: () => void;
|
|
14
|
-
terminate: () => void;
|
|
15
|
-
};
|
|
6
|
+
constructor(loggerName: string, logLevel?: LogLevel);
|
|
7
|
+
setLogLevel(logLevel: LogLevel): void;
|
|
16
8
|
verbose(message: string | JSONMessage): void;
|
|
17
9
|
debug(message: string | JSONMessage): void;
|
|
18
10
|
newLine(): void;
|
package/dist/logger.js
CHANGED
|
@@ -13,7 +13,6 @@ var _Logger_instances, _Logger_level, _Logger_prefix, _Logger_isLogLevel, _Logge
|
|
|
13
13
|
import console from "node:console";
|
|
14
14
|
import { env } from "node:process";
|
|
15
15
|
import { inspect } from "node:util";
|
|
16
|
-
import ProgressBar from "progress";
|
|
17
16
|
import { blue, bold, cyan, dim, gray, red, yellow } from "yoctocolors";
|
|
18
17
|
const logLevelsPriority = {
|
|
19
18
|
silent: Number.MAX_SAFE_INTEGER,
|
|
@@ -36,39 +35,16 @@ function getLogLevelFromEnv() {
|
|
|
36
35
|
return "info";
|
|
37
36
|
}
|
|
38
37
|
export class Logger {
|
|
39
|
-
constructor(loggerName) {
|
|
38
|
+
constructor(loggerName, logLevel) {
|
|
40
39
|
_Logger_instances.add(this);
|
|
41
40
|
this.loggerName = loggerName;
|
|
42
|
-
_Logger_level.set(this,
|
|
41
|
+
_Logger_level.set(this, void 0);
|
|
43
42
|
_Logger_prefix.set(this, void 0);
|
|
43
|
+
__classPrivateFieldSet(this, _Logger_level, logLevel ?? getLogLevelFromEnv(), "f");
|
|
44
44
|
__classPrivateFieldSet(this, _Logger_prefix, cyan(bold(`[${this.loggerName}]:`)), "f");
|
|
45
45
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
tick: () => { },
|
|
50
|
-
update: () => { },
|
|
51
|
-
terminate: () => { },
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
const progressBar = new ProgressBar(`${__classPrivateFieldGet(this, _Logger_prefix, "f")} ${blue(message)} [:bar] :current/:total`, {
|
|
55
|
-
total,
|
|
56
|
-
width: 20,
|
|
57
|
-
});
|
|
58
|
-
return progressBar;
|
|
59
|
-
}
|
|
60
|
-
progressBar(message) {
|
|
61
|
-
if (__classPrivateFieldGet(this, _Logger_instances, "m", _Logger_isSilent).call(this)) {
|
|
62
|
-
return {
|
|
63
|
-
update: () => { },
|
|
64
|
-
terminate: () => { },
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
const progressBar = new ProgressBar(`${__classPrivateFieldGet(this, _Logger_prefix, "f")} ${blue(message)} [:bar] :percent`, {
|
|
68
|
-
total: 100,
|
|
69
|
-
width: 20,
|
|
70
|
-
});
|
|
71
|
-
return progressBar;
|
|
46
|
+
setLogLevel(logLevel) {
|
|
47
|
+
__classPrivateFieldSet(this, _Logger_level, logLevel, "f");
|
|
72
48
|
}
|
|
73
49
|
verbose(message) {
|
|
74
50
|
if (!__classPrivateFieldGet(this, _Logger_instances, "m", _Logger_isLogLevel).call(this, "verbose")) {
|
package/dist/model.d.ts
CHANGED
|
@@ -53,7 +53,7 @@ export type UploadLabelDto = {
|
|
|
53
53
|
};
|
|
54
54
|
export type UploadTestResultBodyStepDto = {
|
|
55
55
|
type: "body";
|
|
56
|
-
body
|
|
56
|
+
body: string;
|
|
57
57
|
bodyJson?: unknown;
|
|
58
58
|
status?: UploadTestStatus;
|
|
59
59
|
start?: number;
|
|
@@ -91,7 +91,7 @@ export type UploadTestResultDto = {
|
|
|
91
91
|
uuid?: string;
|
|
92
92
|
historyId?: string;
|
|
93
93
|
testCaseId?: string;
|
|
94
|
-
name
|
|
94
|
+
name: string;
|
|
95
95
|
fullName?: string;
|
|
96
96
|
description?: string;
|
|
97
97
|
descriptionHtml?: string;
|
|
@@ -131,7 +131,7 @@ export type UploadTestFixtureType = "BEFORE" | "AFTER";
|
|
|
131
131
|
export type UploadTestFixtureResultDto = {
|
|
132
132
|
type?: UploadTestFixtureType;
|
|
133
133
|
uuid?: string;
|
|
134
|
-
name
|
|
134
|
+
name: string;
|
|
135
135
|
start?: number;
|
|
136
136
|
stop?: number;
|
|
137
137
|
duration?: number;
|
package/dist/plugin.js
CHANGED
|
@@ -9,9 +9,10 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
9
9
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
10
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
11
|
};
|
|
12
|
-
var _TestOpsPlugin_instances, _TestOpsPlugin_logger, _TestOpsPlugin_ci, _TestOpsPlugin_client, _TestOpsPlugin_launchName, _TestOpsPlugin_launchTags, _TestOpsPlugin_uploadedTestResultsIds, _TestOpsPlugin_autocloseLaunch, _TestOpsPlugin_gitFlow, _TestOpsPlugin_enabledByConfig, _TestOpsPlugin_uploadQualityGateResults, _TestOpsPlugin_uploadGlobalErrors, _TestOpsPlugin_uploadGlobalAttachments, _TestOpsPlugin_uploadTestResults, _TestOpsPlugin_upload, _TestOpsPlugin_trsToUpload, _TestOpsPlugin_enrichWithCategories, _TestOpsPlugin_syncLaunchCategories, _TestOpsPlugin_collectCategoryNamesByExternalId, _TestOpsPlugin_assignCreatedCategoryIds, _TestOpsPlugin_startUpload, _TestOpsPlugin_stopUpload;
|
|
12
|
+
var _TestOpsPlugin_instances, _TestOpsPlugin_logger, _TestOpsPlugin_ci, _TestOpsPlugin_client, _TestOpsPlugin_launchName, _TestOpsPlugin_launchTags, _TestOpsPlugin_uploadedTestResultsIds, _TestOpsPlugin_uploadedGlobalAttachmentIds, _TestOpsPlugin_uploadedGlobalErrors, _TestOpsPlugin_autocloseLaunch, _TestOpsPlugin_launchStarted, _TestOpsPlugin_gitFlow, _TestOpsPlugin_enabledByConfig, _TestOpsPlugin_uploadQualityGateResults, _TestOpsPlugin_uploadGlobalErrors, _TestOpsPlugin_uploadGlobalAttachments, _TestOpsPlugin_uploadTestResults, _TestOpsPlugin_upload, _TestOpsPlugin_trsToUpload, _TestOpsPlugin_enrichWithCategories, _TestOpsPlugin_syncLaunchCategories, _TestOpsPlugin_collectCategoryNamesByExternalId, _TestOpsPlugin_assignCreatedCategoryIds, _TestOpsPlugin_startUpload, _TestOpsPlugin_stopUpload;
|
|
13
13
|
import { env } from "node:process";
|
|
14
14
|
import { detect, isLocalCiDescriptor } from "@allurereport/ci";
|
|
15
|
+
import { createProgressLogger } from "@allurereport/cli-commons";
|
|
15
16
|
import { getWorstStatus } from "@allurereport/core-api";
|
|
16
17
|
import { createPluginSummary, } from "@allurereport/plugin-api";
|
|
17
18
|
import { uniqBy, stubTrue } from "lodash-es";
|
|
@@ -19,9 +20,11 @@ import { bold } from "yoctocolors";
|
|
|
19
20
|
import { TestOpsClient } from "./client.js";
|
|
20
21
|
import { LaunchGitFlow, resolveGitFlowOptions } from "./gitFlow/index.js";
|
|
21
22
|
import { Logger } from "./logger.js";
|
|
22
|
-
import {
|
|
23
|
-
import { toUploadCategory } from "./utils/
|
|
24
|
-
import {
|
|
23
|
+
import { uploadFilenameForLink } from "./utils/attachments.js";
|
|
24
|
+
import { toUploadCategory } from "./utils/categories.js";
|
|
25
|
+
import { resolvePluginOptions } from "./utils/options.js";
|
|
26
|
+
import { attachmentsResolverFactory, fixturesResolverFactory, unwrapStepsAttachments } from "./utils/resolvers.js";
|
|
27
|
+
import { validateExecutableName } from "./utils/validation.js";
|
|
25
28
|
const LAUNCH_PROGRESS_POLL_DELAY_MS = 500;
|
|
26
29
|
const LAUNCH_PROGRESS_ATTEMPTS_LIMIT = 10;
|
|
27
30
|
const categoryDisplayName = (cat) => cat.name ?? cat.grouping?.[0]?.name ?? cat.grouping?.[0]?.value ?? cat.grouping?.[0]?.key ?? cat.externalId;
|
|
@@ -35,7 +38,10 @@ export class TestOpsPlugin {
|
|
|
35
38
|
_TestOpsPlugin_launchName.set(this, "");
|
|
36
39
|
_TestOpsPlugin_launchTags.set(this, []);
|
|
37
40
|
_TestOpsPlugin_uploadedTestResultsIds.set(this, new Set());
|
|
41
|
+
_TestOpsPlugin_uploadedGlobalAttachmentIds.set(this, new Set());
|
|
42
|
+
_TestOpsPlugin_uploadedGlobalErrors.set(this, new Set());
|
|
38
43
|
_TestOpsPlugin_autocloseLaunch.set(this, false);
|
|
44
|
+
_TestOpsPlugin_launchStarted.set(this, false);
|
|
39
45
|
_TestOpsPlugin_gitFlow.set(this, void 0);
|
|
40
46
|
_TestOpsPlugin_enabledByConfig.set(this, false);
|
|
41
47
|
__classPrivateFieldSet(this, _TestOpsPlugin_enabledByConfig, context.enabled === true, "f");
|
|
@@ -102,8 +108,13 @@ export class TestOpsPlugin {
|
|
|
102
108
|
if (!this.enabled) {
|
|
103
109
|
return;
|
|
104
110
|
}
|
|
111
|
+
if (context.realTime) {
|
|
112
|
+
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").setLogLevel("info");
|
|
113
|
+
}
|
|
105
114
|
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").verbose("Starting upload…");
|
|
106
|
-
await __classPrivateFieldGet(this, _TestOpsPlugin_instances, "m", _TestOpsPlugin_startUpload).call(this)
|
|
115
|
+
if (!(await __classPrivateFieldGet(this, _TestOpsPlugin_instances, "m", _TestOpsPlugin_startUpload).call(this))) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
107
118
|
await __classPrivateFieldGet(this, _TestOpsPlugin_instances, "m", _TestOpsPlugin_upload).call(this, store, { context, stage: "start" });
|
|
108
119
|
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").info(`Allure TestOps Launch: ${__classPrivateFieldGet(this, _TestOpsPlugin_client, "f").launchUrl}`);
|
|
109
120
|
}
|
|
@@ -111,6 +122,10 @@ export class TestOpsPlugin {
|
|
|
111
122
|
if (!this.enabled) {
|
|
112
123
|
return;
|
|
113
124
|
}
|
|
125
|
+
if (!__classPrivateFieldGet(this, _TestOpsPlugin_launchStarted, "f")) {
|
|
126
|
+
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").verbose("Skipping update: the TestOps launch was never started");
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
114
129
|
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").verbose("Updating (uploading new results)…");
|
|
115
130
|
await __classPrivateFieldGet(this, _TestOpsPlugin_instances, "m", _TestOpsPlugin_upload).call(this, store, { context, stage: "update" });
|
|
116
131
|
}
|
|
@@ -118,6 +133,10 @@ export class TestOpsPlugin {
|
|
|
118
133
|
if (!this.enabled) {
|
|
119
134
|
return;
|
|
120
135
|
}
|
|
136
|
+
if (!__classPrivateFieldGet(this, _TestOpsPlugin_launchStarted, "f")) {
|
|
137
|
+
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").verbose("Skipping finalization: the TestOps launch was never started");
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
121
140
|
const allTrs = await store.allTestResults({
|
|
122
141
|
filter: this.options.filter,
|
|
123
142
|
includeRetries: false,
|
|
@@ -125,7 +144,21 @@ export class TestOpsPlugin {
|
|
|
125
144
|
const worstStatus = getWorstStatus(allTrs.map(({ status }) => status));
|
|
126
145
|
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").verbose("Finalizing upload…");
|
|
127
146
|
await __classPrivateFieldGet(this, _TestOpsPlugin_instances, "m", _TestOpsPlugin_upload).call(this, store, { context, stage: "done" });
|
|
128
|
-
|
|
147
|
+
try {
|
|
148
|
+
await __classPrivateFieldGet(this, _TestOpsPlugin_instances, "m", _TestOpsPlugin_stopUpload).call(this, worstStatus || "unknown");
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
if (__classPrivateFieldGet(this, _TestOpsPlugin_client, "f").isTestOpsClientError(error)) {
|
|
152
|
+
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").error(`Failed to stop TestOps upload: ${error.response.data.message}`);
|
|
153
|
+
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").debug(error.response?.data);
|
|
154
|
+
}
|
|
155
|
+
else if (error instanceof Error) {
|
|
156
|
+
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").error(`Failed to stop TestOps upload: ${error.message}`);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").error("Failed to stop TestOps upload");
|
|
160
|
+
}
|
|
161
|
+
}
|
|
129
162
|
const launchId = __classPrivateFieldGet(this, _TestOpsPlugin_client, "f").launchId;
|
|
130
163
|
if (typeof launchId !== "number") {
|
|
131
164
|
return;
|
|
@@ -181,24 +214,34 @@ export class TestOpsPlugin {
|
|
|
181
214
|
return summary;
|
|
182
215
|
}
|
|
183
216
|
}
|
|
184
|
-
_TestOpsPlugin_logger = new WeakMap(), _TestOpsPlugin_ci = new WeakMap(), _TestOpsPlugin_client = new WeakMap(), _TestOpsPlugin_launchName = new WeakMap(), _TestOpsPlugin_launchTags = new WeakMap(), _TestOpsPlugin_uploadedTestResultsIds = new WeakMap(), _TestOpsPlugin_autocloseLaunch = new WeakMap(), _TestOpsPlugin_gitFlow = new WeakMap(), _TestOpsPlugin_enabledByConfig = new WeakMap(), _TestOpsPlugin_instances = new WeakSet(), _TestOpsPlugin_uploadQualityGateResults = async function _TestOpsPlugin_uploadQualityGateResults(store) {
|
|
217
|
+
_TestOpsPlugin_logger = new WeakMap(), _TestOpsPlugin_ci = new WeakMap(), _TestOpsPlugin_client = new WeakMap(), _TestOpsPlugin_launchName = new WeakMap(), _TestOpsPlugin_launchTags = new WeakMap(), _TestOpsPlugin_uploadedTestResultsIds = new WeakMap(), _TestOpsPlugin_uploadedGlobalAttachmentIds = new WeakMap(), _TestOpsPlugin_uploadedGlobalErrors = new WeakMap(), _TestOpsPlugin_autocloseLaunch = new WeakMap(), _TestOpsPlugin_launchStarted = new WeakMap(), _TestOpsPlugin_gitFlow = new WeakMap(), _TestOpsPlugin_enabledByConfig = new WeakMap(), _TestOpsPlugin_instances = new WeakSet(), _TestOpsPlugin_uploadQualityGateResults = async function _TestOpsPlugin_uploadQualityGateResults(store) {
|
|
185
218
|
const results = await store.qualityGateResults();
|
|
186
219
|
const uniqueResults = uniqBy(results.filter(({ success }) => !success), ({ rule, environment }) => `${rule}-${environment}`);
|
|
187
220
|
if (uniqueResults.length === 0) {
|
|
188
221
|
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").verbose("No quality gate results to upload");
|
|
189
222
|
return;
|
|
190
223
|
}
|
|
191
|
-
const
|
|
224
|
+
const progressLogger = createProgressLogger({
|
|
225
|
+
total: 1,
|
|
226
|
+
message: "Uploading quality gate results",
|
|
227
|
+
unitLabel: "request uploaded",
|
|
228
|
+
prefix: "[TestOpsPlugin]",
|
|
229
|
+
});
|
|
230
|
+
let completed = false;
|
|
192
231
|
try {
|
|
193
|
-
|
|
194
|
-
await __classPrivateFieldGet(this, _TestOpsPlugin_client, "f").uploadQualityGateResults(uniqueResults, (percent
|
|
195
|
-
|
|
232
|
+
progressLogger.log(true);
|
|
233
|
+
await __classPrivateFieldGet(this, _TestOpsPlugin_client, "f").uploadQualityGateResults(uniqueResults, (percent) => {
|
|
234
|
+
if (!completed && percent >= 100) {
|
|
235
|
+
completed = true;
|
|
236
|
+
progressLogger.increment();
|
|
237
|
+
}
|
|
196
238
|
});
|
|
197
|
-
|
|
198
|
-
|
|
239
|
+
if (!completed) {
|
|
240
|
+
progressLogger.increment();
|
|
241
|
+
}
|
|
242
|
+
progressLogger.log(true);
|
|
199
243
|
}
|
|
200
244
|
catch (error) {
|
|
201
|
-
progressBar.terminate();
|
|
202
245
|
if (__classPrivateFieldGet(this, _TestOpsPlugin_client, "f").isTestOpsClientError(error)) {
|
|
203
246
|
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").error(`Failed to upload quality gate results: ${error.response.data.message}`);
|
|
204
247
|
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").debug(error.response?.data);
|
|
@@ -210,23 +253,38 @@ _TestOpsPlugin_logger = new WeakMap(), _TestOpsPlugin_ci = new WeakMap(), _TestO
|
|
|
210
253
|
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").error("Failed to upload quality gate results");
|
|
211
254
|
}
|
|
212
255
|
}
|
|
213
|
-
|
|
214
|
-
|
|
256
|
+
finally {
|
|
257
|
+
progressLogger.cancel?.();
|
|
258
|
+
}
|
|
259
|
+
}, _TestOpsPlugin_uploadGlobalErrors = async function _TestOpsPlugin_uploadGlobalErrors(results) {
|
|
215
260
|
if (results.length === 0) {
|
|
216
|
-
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").verbose("No global errors to upload");
|
|
261
|
+
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").verbose("No new global errors to upload");
|
|
217
262
|
return;
|
|
218
263
|
}
|
|
219
|
-
const
|
|
264
|
+
const progressLogger = createProgressLogger({
|
|
265
|
+
total: 1,
|
|
266
|
+
message: "Uploading global errors",
|
|
267
|
+
unitLabel: "request uploaded",
|
|
268
|
+
prefix: "[TestOpsPlugin]",
|
|
269
|
+
});
|
|
270
|
+
let completed = false;
|
|
220
271
|
try {
|
|
221
|
-
|
|
222
|
-
await __classPrivateFieldGet(this, _TestOpsPlugin_client, "f").uploadGlobalErrors(results, (percent
|
|
223
|
-
|
|
272
|
+
progressLogger.log(true);
|
|
273
|
+
await __classPrivateFieldGet(this, _TestOpsPlugin_client, "f").uploadGlobalErrors(results, (percent) => {
|
|
274
|
+
if (!completed && percent >= 100) {
|
|
275
|
+
completed = true;
|
|
276
|
+
progressLogger.increment();
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
if (!completed) {
|
|
280
|
+
progressLogger.increment();
|
|
281
|
+
}
|
|
282
|
+
results.forEach((error) => {
|
|
283
|
+
__classPrivateFieldGet(this, _TestOpsPlugin_uploadedGlobalErrors, "f").add(error);
|
|
224
284
|
});
|
|
225
|
-
|
|
226
|
-
progressBar.terminate();
|
|
285
|
+
progressLogger.log(true);
|
|
227
286
|
}
|
|
228
287
|
catch (error) {
|
|
229
|
-
progressBar.terminate();
|
|
230
288
|
if (__classPrivateFieldGet(this, _TestOpsPlugin_client, "f").isTestOpsClientError(error)) {
|
|
231
289
|
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").error(`Failed to upload global errors: ${error.response.data.message}`);
|
|
232
290
|
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").debug(error.response?.data);
|
|
@@ -238,15 +296,23 @@ _TestOpsPlugin_logger = new WeakMap(), _TestOpsPlugin_ci = new WeakMap(), _TestO
|
|
|
238
296
|
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").error("Failed to upload global errors");
|
|
239
297
|
}
|
|
240
298
|
}
|
|
241
|
-
|
|
242
|
-
|
|
299
|
+
finally {
|
|
300
|
+
progressLogger.cancel?.();
|
|
301
|
+
}
|
|
302
|
+
}, _TestOpsPlugin_uploadGlobalAttachments = async function _TestOpsPlugin_uploadGlobalAttachments(store, attachments) {
|
|
243
303
|
if (attachments.length === 0) {
|
|
244
|
-
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").debug("No global attachments to upload");
|
|
304
|
+
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").debug("No new global attachments to upload");
|
|
245
305
|
return;
|
|
246
306
|
}
|
|
247
|
-
const
|
|
307
|
+
const progressLogger = createProgressLogger({
|
|
308
|
+
total: 1,
|
|
309
|
+
message: "Uploading global attachments",
|
|
310
|
+
unitLabel: "request uploaded",
|
|
311
|
+
prefix: "[TestOpsPlugin]",
|
|
312
|
+
});
|
|
313
|
+
let completed = false;
|
|
248
314
|
try {
|
|
249
|
-
|
|
315
|
+
progressLogger.log(true);
|
|
250
316
|
await __classPrivateFieldGet(this, _TestOpsPlugin_client, "f").uploadGlobalAttachments({
|
|
251
317
|
attachments,
|
|
252
318
|
attachmentsResolver: async (attachmentLink) => {
|
|
@@ -262,15 +328,22 @@ _TestOpsPlugin_logger = new WeakMap(), _TestOpsPlugin_ci = new WeakMap(), _TestO
|
|
|
262
328
|
content: body,
|
|
263
329
|
};
|
|
264
330
|
},
|
|
265
|
-
onProgress: (percent
|
|
266
|
-
|
|
331
|
+
onProgress: (percent) => {
|
|
332
|
+
if (!completed && percent >= 100) {
|
|
333
|
+
completed = true;
|
|
334
|
+
progressLogger.increment();
|
|
335
|
+
}
|
|
267
336
|
},
|
|
268
337
|
});
|
|
269
|
-
|
|
270
|
-
|
|
338
|
+
if (!completed) {
|
|
339
|
+
progressLogger.increment();
|
|
340
|
+
}
|
|
341
|
+
attachments.forEach((attachment) => {
|
|
342
|
+
__classPrivateFieldGet(this, _TestOpsPlugin_uploadedGlobalAttachmentIds, "f").add(attachment.id);
|
|
343
|
+
});
|
|
344
|
+
progressLogger.log(true);
|
|
271
345
|
}
|
|
272
346
|
catch (error) {
|
|
273
|
-
progressBar.terminate();
|
|
274
347
|
if (__classPrivateFieldGet(this, _TestOpsPlugin_client, "f").isTestOpsClientError(error)) {
|
|
275
348
|
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").error(`Failed to upload global attachments: ${error.response.data.message}`);
|
|
276
349
|
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").debug(error.response?.data);
|
|
@@ -282,49 +355,99 @@ _TestOpsPlugin_logger = new WeakMap(), _TestOpsPlugin_ci = new WeakMap(), _TestO
|
|
|
282
355
|
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").error("Failed to upload global attachments");
|
|
283
356
|
}
|
|
284
357
|
}
|
|
285
|
-
|
|
358
|
+
finally {
|
|
359
|
+
progressLogger.cancel?.();
|
|
360
|
+
}
|
|
361
|
+
}, _TestOpsPlugin_uploadTestResults = async function _TestOpsPlugin_uploadTestResults(store, trsToUpload, environments, options) {
|
|
286
362
|
const totalCount = trsToUpload.length;
|
|
287
|
-
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
onProgress: () => trsProgressBar.tick(),
|
|
363
|
+
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").verbose(`Preparing to upload ${bold(totalCount.toString())} ${totalCount > 1 ? "test results" : "test result"}`);
|
|
364
|
+
const progressLogger = createProgressLogger({
|
|
365
|
+
total: totalCount,
|
|
366
|
+
message: "Uploading test results",
|
|
367
|
+
unitLabel: totalCount === 1 ? "test result uploaded" : "test results uploaded",
|
|
368
|
+
prefix: "[TestOpsPlugin]",
|
|
369
|
+
silent: !!options?.silent,
|
|
295
370
|
});
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
371
|
+
try {
|
|
372
|
+
progressLogger.log(true);
|
|
373
|
+
const uploadedTrs = await __classPrivateFieldGet(this, _TestOpsPlugin_client, "f").uploadTestResults({
|
|
374
|
+
attachmentsResolver: attachmentsResolverFactory(store),
|
|
375
|
+
fixturesResolver: fixturesResolverFactory(store),
|
|
376
|
+
environments,
|
|
377
|
+
trs: trsToUpload,
|
|
378
|
+
onProgress: () => progressLogger.increment(),
|
|
379
|
+
});
|
|
380
|
+
progressLogger.log(true);
|
|
381
|
+
uploadedTrs.forEach((tr) => {
|
|
382
|
+
__classPrivateFieldGet(this, _TestOpsPlugin_uploadedTestResultsIds, "f").add(tr.id);
|
|
383
|
+
});
|
|
384
|
+
const uploadedCount = uploadedTrs.length;
|
|
385
|
+
if (uploadedCount === 0) {
|
|
386
|
+
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").verbose("No test results were uploaded");
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").verbose(`Uploaded ${uploadedCount} ${uploadedCount > 1 ? "test results" : "test result"}`);
|
|
390
|
+
}
|
|
391
|
+
finally {
|
|
392
|
+
progressLogger.cancel?.();
|
|
305
393
|
}
|
|
306
|
-
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").info(`Uploaded ${uploadedCount} ${uploadedCount > 1 ? "test results" : "test result"}`);
|
|
307
394
|
}, _TestOpsPlugin_upload = async function _TestOpsPlugin_upload(store, options = {}) {
|
|
308
395
|
const { context, stage } = options;
|
|
309
396
|
const trsToUpload = await __classPrivateFieldGet(this, _TestOpsPlugin_instances, "m", _TestOpsPlugin_trsToUpload).call(this, store);
|
|
397
|
+
const shouldUploadGlobalArtifacts = stage === "done" || !context?.realTime;
|
|
398
|
+
let globalErrors = [];
|
|
399
|
+
let globalAttachments = [];
|
|
400
|
+
if (shouldUploadGlobalArtifacts) {
|
|
401
|
+
const [allGlobalErrors, allGlobalAttachments] = await Promise.all([
|
|
402
|
+
store.allGlobalErrors(),
|
|
403
|
+
store.allGlobalAttachments(),
|
|
404
|
+
]);
|
|
405
|
+
globalErrors = allGlobalErrors.filter((error) => !__classPrivateFieldGet(this, _TestOpsPlugin_uploadedGlobalErrors, "f").has(error));
|
|
406
|
+
globalAttachments = allGlobalAttachments.filter((attachment) => !__classPrivateFieldGet(this, _TestOpsPlugin_uploadedGlobalAttachmentIds, "f").has(attachment.id));
|
|
407
|
+
}
|
|
310
408
|
if (trsToUpload.length === 0) {
|
|
311
409
|
if (stage == "update") {
|
|
312
|
-
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").
|
|
410
|
+
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").verbose("No new test results to upload");
|
|
313
411
|
}
|
|
314
412
|
if (stage === "done") {
|
|
315
|
-
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").
|
|
413
|
+
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").verbose("No test results to upload");
|
|
414
|
+
}
|
|
415
|
+
if (globalErrors.length === 0 && globalAttachments.length === 0) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if (stage === "update" && trsToUpload.length > 0) {
|
|
420
|
+
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").verbose(`Found ${bold(trsToUpload.length.toString())} new test ${trsToUpload.length > 1 ? "results" : "result"}, uploading…`);
|
|
421
|
+
}
|
|
422
|
+
try {
|
|
423
|
+
await __classPrivateFieldGet(this, _TestOpsPlugin_client, "f").createSession(env);
|
|
424
|
+
}
|
|
425
|
+
catch (error) {
|
|
426
|
+
if (__classPrivateFieldGet(this, _TestOpsPlugin_client, "f").isTestOpsClientError(error)) {
|
|
427
|
+
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").error(`Failed to create TestOps session: ${error.response.data.message}`);
|
|
428
|
+
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").debug(error.response?.data);
|
|
429
|
+
}
|
|
430
|
+
else if (error instanceof Error) {
|
|
431
|
+
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").error(`Failed to create TestOps session: ${error.message}`);
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").error("Failed to create TestOps session");
|
|
316
435
|
}
|
|
317
436
|
return;
|
|
318
437
|
}
|
|
319
|
-
await __classPrivateFieldGet(this,
|
|
320
|
-
await __classPrivateFieldGet(this, _TestOpsPlugin_instances, "m",
|
|
321
|
-
|
|
438
|
+
await __classPrivateFieldGet(this, _TestOpsPlugin_instances, "m", _TestOpsPlugin_uploadGlobalAttachments).call(this, store, globalAttachments);
|
|
439
|
+
await __classPrivateFieldGet(this, _TestOpsPlugin_instances, "m", _TestOpsPlugin_uploadGlobalErrors).call(this, globalErrors);
|
|
440
|
+
if (trsToUpload.length === 0) {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
322
443
|
await __classPrivateFieldGet(this, _TestOpsPlugin_instances, "m", _TestOpsPlugin_uploadQualityGateResults).call(this, store);
|
|
323
444
|
const environments = await store.allEnvironmentIdentities();
|
|
324
445
|
const contextCategories = context?.categories ?? [];
|
|
325
446
|
const trsEnrichedWithCategories = await __classPrivateFieldGet(this, _TestOpsPlugin_instances, "m", _TestOpsPlugin_enrichWithCategories).call(this, store, trsToUpload, contextCategories);
|
|
326
447
|
await __classPrivateFieldGet(this, _TestOpsPlugin_instances, "m", _TestOpsPlugin_syncLaunchCategories).call(this, trsEnrichedWithCategories, contextCategories);
|
|
327
|
-
await __classPrivateFieldGet(this, _TestOpsPlugin_instances, "m", _TestOpsPlugin_uploadTestResults).call(this, store, trsEnrichedWithCategories, environments
|
|
448
|
+
await __classPrivateFieldGet(this, _TestOpsPlugin_instances, "m", _TestOpsPlugin_uploadTestResults).call(this, store, trsEnrichedWithCategories, environments, {
|
|
449
|
+
silent: !!context?.realTime,
|
|
450
|
+
});
|
|
328
451
|
}, _TestOpsPlugin_trsToUpload = async function _TestOpsPlugin_trsToUpload(store) {
|
|
329
452
|
const filter = this.options.filter ?? stubTrue;
|
|
330
453
|
const filteredTrs = await store.allTestResults({
|
|
@@ -333,7 +456,7 @@ _TestOpsPlugin_logger = new WeakMap(), _TestOpsPlugin_ci = new WeakMap(), _TestO
|
|
|
333
456
|
if (uploaded) {
|
|
334
457
|
return false;
|
|
335
458
|
}
|
|
336
|
-
return filter(tr);
|
|
459
|
+
return validateExecutableName(tr.name) && filter(tr);
|
|
337
460
|
},
|
|
338
461
|
includeRetries: false,
|
|
339
462
|
});
|
|
@@ -425,8 +548,24 @@ _TestOpsPlugin_logger = new WeakMap(), _TestOpsPlugin_ci = new WeakMap(), _TestO
|
|
|
425
548
|
}
|
|
426
549
|
}, _TestOpsPlugin_startUpload = async function _TestOpsPlugin_startUpload() {
|
|
427
550
|
const launchGitContext = __classPrivateFieldGet(this, _TestOpsPlugin_gitFlow, "f").resolve();
|
|
428
|
-
|
|
429
|
-
|
|
551
|
+
try {
|
|
552
|
+
await __classPrivateFieldGet(this, _TestOpsPlugin_client, "f").createLaunch(__classPrivateFieldGet(this, _TestOpsPlugin_launchName, "f"), __classPrivateFieldGet(this, _TestOpsPlugin_launchTags, "f"), launchGitContext);
|
|
553
|
+
await __classPrivateFieldGet(this, _TestOpsPlugin_client, "f").startUpload(__classPrivateFieldGet(this, _TestOpsPlugin_ci, "f"));
|
|
554
|
+
__classPrivateFieldSet(this, _TestOpsPlugin_launchStarted, true, "f");
|
|
555
|
+
}
|
|
556
|
+
catch (error) {
|
|
557
|
+
if (__classPrivateFieldGet(this, _TestOpsPlugin_client, "f").isTestOpsClientError(error)) {
|
|
558
|
+
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").error(`Failed to create TestOps launch: ${error.response.data.message}`);
|
|
559
|
+
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").debug(error.response?.data);
|
|
560
|
+
}
|
|
561
|
+
else if (error instanceof Error) {
|
|
562
|
+
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").error(`Failed to create TestOps launch: ${error.message}`);
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
__classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").error("Failed to create TestOps launch");
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
return __classPrivateFieldGet(this, _TestOpsPlugin_launchStarted, "f");
|
|
430
569
|
}, _TestOpsPlugin_stopUpload = async function _TestOpsPlugin_stopUpload(status) {
|
|
431
570
|
await __classPrivateFieldGet(this, _TestOpsPlugin_client, "f").stopUpload(__classPrivateFieldGet(this, _TestOpsPlugin_ci, "f"), status);
|
|
432
571
|
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AttachmentLink, TestStepResult } from "@allurereport/core-api";
|
|
2
|
+
import type { UploadAttachmentDto } from "../model.js";
|
|
3
|
+
export declare const uploadFilenameForLink: (link: AttachmentLink) => string | undefined;
|
|
4
|
+
export declare const toUploadAttachmentDto: (link: AttachmentLink) => UploadAttachmentDto;
|
|
5
|
+
export declare const getAttachmentIdsFromTestStepsResults: (steps: TestStepResult[] | undefined) => Set<string>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const uploadFilenameForLink = (link) => {
|
|
2
|
+
if ("originalFileName" in link && typeof link.originalFileName === "string")
|
|
3
|
+
return link.originalFileName;
|
|
4
|
+
return undefined;
|
|
5
|
+
};
|
|
6
|
+
export const toUploadAttachmentDto = (link) => ({
|
|
7
|
+
name: "name" in link && typeof link.name === "string" ? link.name : undefined,
|
|
8
|
+
originalFileName: uploadFilenameForLink(link),
|
|
9
|
+
contentType: "contentType" in link && typeof link.contentType === "string" ? link.contentType : undefined,
|
|
10
|
+
contentLength: "contentLength" in link && typeof link.contentLength === "number" ? link.contentLength : undefined,
|
|
11
|
+
optional: "optional" in link && typeof link.optional === "boolean"
|
|
12
|
+
? link.optional
|
|
13
|
+
: undefined,
|
|
14
|
+
});
|
|
15
|
+
export const getAttachmentIdsFromTestStepsResults = (steps) => {
|
|
16
|
+
const ids = new Set();
|
|
17
|
+
for (const step of steps ?? []) {
|
|
18
|
+
if (step.type === "attachment") {
|
|
19
|
+
ids.add(step.link.id);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
for (const id of getAttachmentIdsFromTestStepsResults(step.steps))
|
|
23
|
+
ids.add(id);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return ids;
|
|
27
|
+
};
|
|
@@ -2,3 +2,4 @@ import type { CategoryDefinition } from "@allurereport/core-api";
|
|
|
2
2
|
import type { TestResultWithCategories, UploadCategory } from "../model.js";
|
|
3
3
|
export declare const toUploadCategoryFromContext: (tr: TestResultWithCategories, categories: CategoryDefinition[]) => UploadCategory | undefined;
|
|
4
4
|
export declare const buildUploadCategoryGrouping: (tr: TestResultWithCategories, category: CategoryDefinition) => UploadCategory["grouping"];
|
|
5
|
+
export declare const toUploadCategory: (tr: TestResultWithCategories, contextCategories: CategoryDefinition[]) => UploadCategory | undefined;
|
package/dist/utils/categories.js
CHANGED
|
@@ -80,3 +80,21 @@ export const toUploadCategoryFromContext = (tr, categories) => {
|
|
|
80
80
|
};
|
|
81
81
|
};
|
|
82
82
|
export const buildUploadCategoryGrouping = (tr, category) => buildGrouping(tr, category);
|
|
83
|
+
export const toUploadCategory = (tr, contextCategories) => {
|
|
84
|
+
const c = tr.categories?.[0];
|
|
85
|
+
if (c?.name) {
|
|
86
|
+
const externalId = c.id ?? c.name;
|
|
87
|
+
const groupingFromTestResult = c.grouping?.length ? c.grouping : undefined;
|
|
88
|
+
const contextCategory = groupingFromTestResult === undefined
|
|
89
|
+
? contextCategories.find((category) => category.id === externalId || category.name === c.name)
|
|
90
|
+
: undefined;
|
|
91
|
+
return {
|
|
92
|
+
externalId,
|
|
93
|
+
name: c.name,
|
|
94
|
+
grouping: groupingFromTestResult ?? (contextCategory && buildUploadCategoryGrouping(tr, contextCategory)),
|
|
95
|
+
hide: c.hide,
|
|
96
|
+
expand: c.expand,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return toUploadCategoryFromContext(tr, contextCategories);
|
|
100
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { UploadFixturesResultsDto, UploadTestFixtureResultDto } from "../model.js";
|
|
2
|
+
import type { TestOpsFixtureResult } from "../model.js";
|
|
3
|
+
export declare const toUploadFixtureResultDto: (fxt: TestOpsFixtureResult) => UploadTestFixtureResultDto;
|
|
4
|
+
export declare const toUploadFixturesResultsDto: (fixtures: TestOpsFixtureResult[]) => UploadFixturesResultsDto;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { toUploadAttachmentDto } from "./attachments.js";
|
|
2
|
+
const toUploadStepDto = (step) => step.type === "attachment"
|
|
3
|
+
? { type: "attachment", attachment: toUploadAttachmentDto(step.link) }
|
|
4
|
+
: {
|
|
5
|
+
type: "body",
|
|
6
|
+
body: step.name,
|
|
7
|
+
status: step.status,
|
|
8
|
+
start: step.start,
|
|
9
|
+
stop: step.stop,
|
|
10
|
+
duration: step.duration,
|
|
11
|
+
message: typeof step.message === "string" ? step.message : undefined,
|
|
12
|
+
trace: typeof step.trace === "string" ? step.trace : undefined,
|
|
13
|
+
parameters: step.parameters,
|
|
14
|
+
steps: step.steps?.map(toUploadStepDto),
|
|
15
|
+
};
|
|
16
|
+
export const toUploadFixtureResultDto = (fxt) => ({
|
|
17
|
+
type: fxt.type,
|
|
18
|
+
uuid: fxt.id,
|
|
19
|
+
name: fxt.name,
|
|
20
|
+
start: fxt.start,
|
|
21
|
+
stop: fxt.stop,
|
|
22
|
+
duration: fxt.duration,
|
|
23
|
+
status: fxt.status,
|
|
24
|
+
message: fxt.error?.message,
|
|
25
|
+
trace: fxt.error?.trace,
|
|
26
|
+
steps: fxt.steps?.map(toUploadStepDto),
|
|
27
|
+
});
|
|
28
|
+
export const toUploadFixturesResultsDto = (fixtures) => ({
|
|
29
|
+
fixtures: fixtures.map(toUploadFixtureResultDto),
|
|
30
|
+
});
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export
|
|
5
|
-
export
|
|
6
|
-
export
|
|
7
|
-
export
|
|
1
|
+
export { toUploadAttachmentDto, uploadFilenameForLink } from "./attachments.js";
|
|
2
|
+
export { toUploadCategory, buildUploadCategoryGrouping, toUploadCategoryFromContext } from "./categories.js";
|
|
3
|
+
export { toUploadFixtureResultDto, toUploadFixturesResultsDto } from "./fixtures.js";
|
|
4
|
+
export { resolvePluginOptions } from "./options.js";
|
|
5
|
+
export { attachmentsResolverFactory, fixturesResolverFactory, unwrapStepsAttachments } from "./resolvers.js";
|
|
6
|
+
export { normalizeTestStepsResults, toUploadResultsDto, toUploadTestResultDto } from "./testResults.js";
|
|
7
|
+
export { validateExecutableName } from "./validation.js";
|
package/dist/utils/index.js
CHANGED
|
@@ -1,68 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
attachment: step.link,
|
|
9
|
-
};
|
|
10
|
-
}
|
|
11
|
-
if (step.steps) {
|
|
12
|
-
return {
|
|
13
|
-
...step,
|
|
14
|
-
steps: unwrapStepsAttachments(step.steps),
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
return step;
|
|
18
|
-
});
|
|
19
|
-
};
|
|
20
|
-
export const resolvePluginOptions = (options) => {
|
|
21
|
-
const { ALLURE_TOKEN, ALLURE_ENDPOINT, ALLURE_PROJECT_ID, ALLURE_LAUNCH_TAGS, ALLURE_LAUNCH_NAME } = env;
|
|
22
|
-
const { accessToken = ALLURE_TOKEN, endpoint = ALLURE_ENDPOINT, projectId = ALLURE_PROJECT_ID, launchTags = ALLURE_LAUNCH_TAGS, launchName = ALLURE_LAUNCH_NAME, autocloseLaunch, } = options;
|
|
23
|
-
const tags = !launchTags
|
|
24
|
-
? []
|
|
25
|
-
: Array.isArray(launchTags)
|
|
26
|
-
? launchTags
|
|
27
|
-
: launchTags.split(",").map((tag) => tag.trim());
|
|
28
|
-
return {
|
|
29
|
-
launchName: launchName || "Allure Report",
|
|
30
|
-
launchTags: tags,
|
|
31
|
-
accessToken: accessToken || "",
|
|
32
|
-
endpoint: endpoint || "",
|
|
33
|
-
projectId: projectId || "",
|
|
34
|
-
...(autocloseLaunch !== undefined ? { autocloseLaunch } : {}),
|
|
35
|
-
};
|
|
36
|
-
};
|
|
37
|
-
export function attachmentsResolverFactory(store) {
|
|
38
|
-
const attachmentsResolver = async (tr) => {
|
|
39
|
-
const attachments = await store.attachmentsByTrId(tr.id);
|
|
40
|
-
const result = [];
|
|
41
|
-
await Promise.all(attachments.map(async (attachment) => {
|
|
42
|
-
const content = await store.attachmentContentById(attachment.id);
|
|
43
|
-
const body = await content?.readContent(async (s) => s);
|
|
44
|
-
const filename = uploadFilenameForLink(attachment);
|
|
45
|
-
if (filename === undefined || body === undefined) {
|
|
46
|
-
return undefined;
|
|
47
|
-
}
|
|
48
|
-
result.push({
|
|
49
|
-
originalFileName: filename,
|
|
50
|
-
contentType: attachment.contentType ?? "application/octet-stream",
|
|
51
|
-
content: body,
|
|
52
|
-
});
|
|
53
|
-
}));
|
|
54
|
-
return result;
|
|
55
|
-
};
|
|
56
|
-
return attachmentsResolver;
|
|
57
|
-
}
|
|
58
|
-
export function fixturesResolverFactory(store) {
|
|
59
|
-
const fixturesResolver = async (tr) => {
|
|
60
|
-
const fixtures = await store.fixturesByTrId(tr.id);
|
|
61
|
-
return fixtures.map((fxt) => ({
|
|
62
|
-
...fxt,
|
|
63
|
-
type: fxt.type.toUpperCase(),
|
|
64
|
-
steps: unwrapStepsAttachments(fxt.steps),
|
|
65
|
-
}));
|
|
66
|
-
};
|
|
67
|
-
return fixturesResolver;
|
|
68
|
-
}
|
|
1
|
+
export { toUploadAttachmentDto, uploadFilenameForLink } from "./attachments.js";
|
|
2
|
+
export { toUploadCategory, buildUploadCategoryGrouping, toUploadCategoryFromContext } from "./categories.js";
|
|
3
|
+
export { toUploadFixtureResultDto, toUploadFixturesResultsDto } from "./fixtures.js";
|
|
4
|
+
export { resolvePluginOptions } from "./options.js";
|
|
5
|
+
export { attachmentsResolverFactory, fixturesResolverFactory, unwrapStepsAttachments } from "./resolvers.js";
|
|
6
|
+
export { normalizeTestStepsResults, toUploadResultsDto, toUploadTestResultDto } from "./testResults.js";
|
|
7
|
+
export { validateExecutableName } from "./validation.js";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { env } from "node:process";
|
|
2
|
+
export const resolvePluginOptions = (options) => {
|
|
3
|
+
const { ALLURE_TOKEN, ALLURE_ENDPOINT, ALLURE_PROJECT_ID, ALLURE_LAUNCH_TAGS, ALLURE_LAUNCH_NAME } = env;
|
|
4
|
+
const { accessToken = ALLURE_TOKEN, endpoint = ALLURE_ENDPOINT, projectId = ALLURE_PROJECT_ID, launchTags = ALLURE_LAUNCH_TAGS, launchName = ALLURE_LAUNCH_NAME, autocloseLaunch, } = options;
|
|
5
|
+
const tags = !launchTags
|
|
6
|
+
? []
|
|
7
|
+
: Array.isArray(launchTags)
|
|
8
|
+
? launchTags
|
|
9
|
+
: launchTags.split(",").map((tag) => tag.trim());
|
|
10
|
+
return {
|
|
11
|
+
launchName: launchName || "Allure Report",
|
|
12
|
+
launchTags: tags,
|
|
13
|
+
accessToken: accessToken || "",
|
|
14
|
+
endpoint: endpoint || "",
|
|
15
|
+
projectId: projectId || "",
|
|
16
|
+
...(autocloseLaunch !== undefined ? { autocloseLaunch } : {}),
|
|
17
|
+
};
|
|
18
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { TestStepResult } from "@allurereport/core-api";
|
|
2
|
+
import { AllureStore } from "@allurereport/plugin-api";
|
|
3
|
+
import type { AttachmentsResolver, FixtureResolver } from "../model.js";
|
|
4
|
+
export declare const unwrapStepsAttachments: (steps: TestStepResult[]) => TestStepResult[];
|
|
5
|
+
export declare function attachmentsResolverFactory(store: AllureStore): AttachmentsResolver;
|
|
6
|
+
export declare function fixturesResolverFactory(store: AllureStore): FixtureResolver;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { getAttachmentIdsFromTestStepsResults, uploadFilenameForLink } from "./attachments.js";
|
|
2
|
+
import { normalizeTestStepsResults } from "./testResults.js";
|
|
3
|
+
import { validateExecutableName } from "./validation.js";
|
|
4
|
+
export const unwrapStepsAttachments = (steps) => steps.map((step) => step.type === "attachment"
|
|
5
|
+
? { ...step, attachment: step.link }
|
|
6
|
+
: step.steps
|
|
7
|
+
? { ...step, steps: unwrapStepsAttachments(step.steps) }
|
|
8
|
+
: step);
|
|
9
|
+
export function attachmentsResolverFactory(store) {
|
|
10
|
+
const attachmentsResolver = async (tr) => {
|
|
11
|
+
const attachments = await store.attachmentsByTrId(tr.id);
|
|
12
|
+
const fixtures = await store.fixturesByTrId(tr.id);
|
|
13
|
+
const allowedAttachmentIds = getAttachmentIdsFromTestStepsResults(normalizeTestStepsResults(tr.steps));
|
|
14
|
+
for (const attachment of tr.attachments ?? []) {
|
|
15
|
+
allowedAttachmentIds.add(attachment.id);
|
|
16
|
+
}
|
|
17
|
+
for (const fixture of fixtures) {
|
|
18
|
+
if (!validateExecutableName(fixture.name)) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
for (const id of getAttachmentIdsFromTestStepsResults(normalizeTestStepsResults(fixture.steps))) {
|
|
22
|
+
allowedAttachmentIds.add(id);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const result = [];
|
|
26
|
+
const attachmentsById = new Map(attachments.map((attachment) => [attachment.id, attachment]));
|
|
27
|
+
await Promise.all([...allowedAttachmentIds].map(async (id) => {
|
|
28
|
+
const attachment = (await store.attachmentById(id)) ?? attachmentsById.get(id);
|
|
29
|
+
if (!attachment) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
const content = await store.attachmentContentById(attachment.id);
|
|
33
|
+
const body = await content?.readContent(async (s) => s);
|
|
34
|
+
const filename = uploadFilenameForLink(attachment);
|
|
35
|
+
if (filename === undefined || body === undefined) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
result.push({
|
|
39
|
+
originalFileName: filename,
|
|
40
|
+
contentType: attachment.contentType ?? "application/octet-stream",
|
|
41
|
+
content: body,
|
|
42
|
+
});
|
|
43
|
+
}));
|
|
44
|
+
return result;
|
|
45
|
+
};
|
|
46
|
+
return attachmentsResolver;
|
|
47
|
+
}
|
|
48
|
+
export function fixturesResolverFactory(store) {
|
|
49
|
+
const fixturesResolver = async (tr) => {
|
|
50
|
+
const fixtures = await store.fixturesByTrId(tr.id);
|
|
51
|
+
return fixtures.map((fxt) => ({
|
|
52
|
+
...fxt,
|
|
53
|
+
type: fxt.type.toUpperCase(),
|
|
54
|
+
steps: unwrapStepsAttachments(normalizeTestStepsResults(fxt.steps) ?? []),
|
|
55
|
+
}));
|
|
56
|
+
};
|
|
57
|
+
return fixturesResolver;
|
|
58
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { TestStepResult } from "@allurereport/core-api";
|
|
2
|
+
import type { UploadResultsDto, UploadTestResultDto } from "../model.js";
|
|
3
|
+
import type { TestOpsPluginTestResult } from "../model.js";
|
|
4
|
+
export declare const normalizeTestStepsResults: (steps: TestStepResult[] | undefined) => TestStepResult[] | undefined;
|
|
5
|
+
export declare const toUploadTestResultDto: (tr: TestOpsPluginTestResult) => UploadTestResultDto;
|
|
6
|
+
export declare const toUploadResultsDto: (testSessionId: number, trs: TestOpsPluginTestResult[]) => UploadResultsDto;
|
|
@@ -1,27 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export const
|
|
4
|
-
if (
|
|
5
|
-
return
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
};
|
|
1
|
+
import { toUploadAttachmentDto } from "./attachments.js";
|
|
2
|
+
import { validateExecutableName } from "./validation.js";
|
|
3
|
+
export const normalizeTestStepsResults = (steps) => {
|
|
4
|
+
if (!steps) {
|
|
5
|
+
return undefined;
|
|
6
|
+
}
|
|
7
|
+
const projected = [];
|
|
8
|
+
for (const step of steps) {
|
|
9
|
+
if (step.type === "attachment") {
|
|
10
|
+
projected.push(step);
|
|
11
|
+
}
|
|
12
|
+
else if (validateExecutableName(step.name)) {
|
|
13
|
+
projected.push({ ...step, ...(step.steps ? { steps: normalizeTestStepsResults(step.steps) } : {}) });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return projected;
|
|
18
17
|
};
|
|
19
18
|
const toUploadStepDto = (step) => {
|
|
20
19
|
if (step.type === "attachment") {
|
|
21
|
-
return {
|
|
22
|
-
type: "attachment",
|
|
23
|
-
attachment: toUploadAttachmentDto(step.link),
|
|
24
|
-
};
|
|
20
|
+
return { type: "attachment", attachment: toUploadAttachmentDto(step.link) };
|
|
25
21
|
}
|
|
26
22
|
return {
|
|
27
23
|
type: "body",
|
|
@@ -36,6 +32,8 @@ const toUploadStepDto = (step) => {
|
|
|
36
32
|
steps: step.steps?.map(toUploadStepDto),
|
|
37
33
|
};
|
|
38
34
|
};
|
|
35
|
+
const isObject = (v) => typeof v === "object" && v !== null;
|
|
36
|
+
const hasString = (obj, key) => typeof obj[key] === "string";
|
|
39
37
|
const normalizeCategory = (category) => {
|
|
40
38
|
if (!category)
|
|
41
39
|
return undefined;
|
|
@@ -46,8 +44,9 @@ const normalizeCategory = (category) => {
|
|
|
46
44
|
: typeof externalIdUnknown === "number"
|
|
47
45
|
? String(externalIdUnknown)
|
|
48
46
|
: undefined;
|
|
49
|
-
if (!externalId)
|
|
47
|
+
if (!externalId) {
|
|
50
48
|
return undefined;
|
|
49
|
+
}
|
|
51
50
|
const grouping = Array.isArray(groupingUnknown)
|
|
52
51
|
? groupingUnknown
|
|
53
52
|
.filter(isObject)
|
|
@@ -58,25 +57,18 @@ const normalizeCategory = (category) => {
|
|
|
58
57
|
}))
|
|
59
58
|
.filter((g) => g.key || g.value || g.name)
|
|
60
59
|
: undefined;
|
|
61
|
-
return {
|
|
62
|
-
externalId,
|
|
63
|
-
...(grouping && grouping.length > 0 ? { grouping } : {}),
|
|
64
|
-
};
|
|
60
|
+
return { externalId, ...(grouping && grouping.length > 0 ? { grouping } : {}) };
|
|
65
61
|
};
|
|
66
62
|
const optionalArray = (v, isItem) => {
|
|
67
|
-
if (!Array.isArray(v))
|
|
63
|
+
if (!Array.isArray(v)) {
|
|
68
64
|
return undefined;
|
|
69
|
-
const out = [];
|
|
70
|
-
for (const item of v) {
|
|
71
|
-
if (isItem(item))
|
|
72
|
-
out.push(item);
|
|
73
65
|
}
|
|
74
|
-
return
|
|
66
|
+
return v.filter(isItem);
|
|
75
67
|
};
|
|
76
68
|
const isAttachmentLink = (v) => isObject(v) && hasString(v, "id");
|
|
77
69
|
export const toUploadTestResultDto = (tr) => {
|
|
78
|
-
const
|
|
79
|
-
const
|
|
70
|
+
const attachments = optionalArray(tr.attachments, isAttachmentLink)?.map(toUploadAttachmentDto);
|
|
71
|
+
const category = normalizeCategory(tr.category);
|
|
80
72
|
return {
|
|
81
73
|
uuid: tr.id,
|
|
82
74
|
historyId: tr.historyId,
|
|
@@ -98,7 +90,7 @@ export const toUploadTestResultDto = (tr) => {
|
|
|
98
90
|
hostId: tr.hostId,
|
|
99
91
|
threadId: tr.threadId,
|
|
100
92
|
environment: tr.environment,
|
|
101
|
-
...(
|
|
93
|
+
...(category ? { category } : {}),
|
|
102
94
|
...(typeof tr.namedEnv?.id === "number" ? { namedEnv: { id: tr.namedEnv.id } } : {}),
|
|
103
95
|
steps: tr.steps?.map(toUploadStepDto),
|
|
104
96
|
...(attachments ? { attachments } : {}),
|
|
@@ -107,26 +99,7 @@ export const toUploadTestResultDto = (tr) => {
|
|
|
107
99
|
labels: tr.labels,
|
|
108
100
|
};
|
|
109
101
|
};
|
|
110
|
-
export const toUploadResultsDto = (testSessionId, trs) => {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
};
|
|
115
|
-
};
|
|
116
|
-
export const toUploadFixtureResultDto = (fxt) => {
|
|
117
|
-
return {
|
|
118
|
-
type: fxt.type,
|
|
119
|
-
uuid: fxt.id,
|
|
120
|
-
name: fxt.name,
|
|
121
|
-
start: fxt.start,
|
|
122
|
-
stop: fxt.stop,
|
|
123
|
-
duration: fxt.duration,
|
|
124
|
-
status: fxt.status,
|
|
125
|
-
message: fxt.error?.message,
|
|
126
|
-
trace: fxt.error?.trace,
|
|
127
|
-
steps: fxt.steps?.map(toUploadStepDto),
|
|
128
|
-
};
|
|
129
|
-
};
|
|
130
|
-
export const toUploadFixturesResultsDto = (fixtures) => {
|
|
131
|
-
return { fixtures: fixtures.map(toUploadFixtureResultDto) };
|
|
132
|
-
};
|
|
102
|
+
export const toUploadResultsDto = (testSessionId, trs) => ({
|
|
103
|
+
testSessionId,
|
|
104
|
+
results: trs.map(toUploadTestResultDto),
|
|
105
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const validateExecutableName: (name: unknown) => name is string;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const validateExecutableName = (name) => {
|
|
2
|
+
if (typeof name !== "string" || name.length === 0 || name.trim().length === 0) {
|
|
3
|
+
return false;
|
|
4
|
+
}
|
|
5
|
+
for (let index = 0; index < name.length; index += 1) {
|
|
6
|
+
const code = name.charCodeAt(index);
|
|
7
|
+
if (code <= 0x1f || (code >= 0x7f && code <= 0x9f) || code === 0xfffd) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
if (code >= 0xd800 && code <= 0xdbff) {
|
|
11
|
+
const nextCode = name.charCodeAt(index + 1);
|
|
12
|
+
if (nextCode < 0xdc00 || nextCode > 0xdfff || Number.isNaN(nextCode)) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
index += 1;
|
|
16
|
+
}
|
|
17
|
+
else if (code >= 0xdc00 && code <= 0xdfff) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return true;
|
|
22
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@allurereport/plugin-testops",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.16.0",
|
|
4
4
|
"description": "Allure Plugin TestOps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"allure",
|
|
@@ -32,24 +32,23 @@
|
|
|
32
32
|
"lint:fix": "oxlint --import-plugin --fix src test features stories"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@allurereport/ci": "3.
|
|
36
|
-
"@allurereport/
|
|
37
|
-
"@allurereport/
|
|
38
|
-
"@allurereport/
|
|
39
|
-
"@allurereport/
|
|
40
|
-
"@allurereport/
|
|
35
|
+
"@allurereport/ci": "3.16.0",
|
|
36
|
+
"@allurereport/cli-commons": "3.16.0",
|
|
37
|
+
"@allurereport/core-api": "3.16.0",
|
|
38
|
+
"@allurereport/git": "3.16.0",
|
|
39
|
+
"@allurereport/plugin-api": "3.16.0",
|
|
40
|
+
"@allurereport/reader-api": "3.16.0",
|
|
41
|
+
"@allurereport/service": "3.16.0",
|
|
41
42
|
"axios": "^1.18.1",
|
|
42
43
|
"form-data": "^4.0.6",
|
|
43
44
|
"lodash-es": "^4.18.1",
|
|
44
45
|
"p-limit": "^7.3.0",
|
|
45
|
-
"progress": "^2.0.3",
|
|
46
46
|
"yoctocolors": "^2"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/d3-shape": "^3.1.8",
|
|
50
50
|
"@types/lodash-es": "^4",
|
|
51
51
|
"@types/node": "^20",
|
|
52
|
-
"@types/progress": "^2",
|
|
53
52
|
"@vitest/runner": "^2",
|
|
54
53
|
"@vitest/snapshot": "^2.1.9",
|
|
55
54
|
"allure-js-commons": "^3",
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import type { CategoryDefinition } from "@allurereport/core-api";
|
|
2
|
-
import type { TestResultWithCategories, UploadCategory } from "../model.js";
|
|
3
|
-
export declare const toUploadCategory: (tr: TestResultWithCategories, contextCategories: CategoryDefinition[]) => UploadCategory | undefined;
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { buildUploadCategoryGrouping, toUploadCategoryFromContext } from "./categories.js";
|
|
2
|
-
export const toUploadCategory = (tr, contextCategories) => {
|
|
3
|
-
const c = tr.categories?.[0];
|
|
4
|
-
if (c?.name) {
|
|
5
|
-
const externalId = c.id ?? c.name;
|
|
6
|
-
const { hide, expand } = c || {};
|
|
7
|
-
const groupingFromTestResult = c.grouping?.length ? c.grouping : undefined;
|
|
8
|
-
const contextCategory = groupingFromTestResult === undefined
|
|
9
|
-
? contextCategories.find((category) => category.id === externalId || category.name === c.name)
|
|
10
|
-
: undefined;
|
|
11
|
-
return {
|
|
12
|
-
externalId,
|
|
13
|
-
name: c.name,
|
|
14
|
-
grouping: groupingFromTestResult ?? (contextCategory && buildUploadCategoryGrouping(tr, contextCategory)),
|
|
15
|
-
hide,
|
|
16
|
-
expand,
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
return toUploadCategoryFromContext(tr, contextCategories);
|
|
20
|
-
};
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { AttachmentLink } from "@allurereport/core-api";
|
|
2
|
-
import type { UploadAttachmentDto, UploadFixturesResultsDto, UploadResultsDto, UploadTestFixtureResultDto, UploadTestResultDto } from "../model.js";
|
|
3
|
-
import type { TestOpsFixtureResult, TestOpsPluginTestResult } from "../model.js";
|
|
4
|
-
export declare const uploadFilenameForLink: (link: AttachmentLink) => string | undefined;
|
|
5
|
-
export declare const toUploadAttachmentDto: (link: AttachmentLink) => UploadAttachmentDto;
|
|
6
|
-
export declare const toUploadTestResultDto: (tr: TestOpsPluginTestResult) => UploadTestResultDto;
|
|
7
|
-
export declare const toUploadResultsDto: (testSessionId: number, trs: TestOpsPluginTestResult[]) => UploadResultsDto;
|
|
8
|
-
export declare const toUploadFixtureResultDto: (fxt: TestOpsFixtureResult) => UploadTestFixtureResultDto;
|
|
9
|
-
export declare const toUploadFixturesResultsDto: (fixtures: TestOpsFixtureResult[]) => UploadFixturesResultsDto;
|