@allurereport/plugin-testops 3.14.3 → 3.15.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 CHANGED
@@ -17,8 +17,10 @@ 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 { toUploadFixturesResultsDto, toUploadResultsDto } from "./utils/uploaderDto.js";
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;
@@ -291,7 +293,11 @@ export class TestOpsClient {
291
293
  throw new Error("Session isn't created! Call createSession first");
292
294
  }
293
295
  const { trs, environments, attachmentsResolver, fixturesResolver, onProgress } = params;
294
- const trsChunks = chunk(trs, CHUNK_SIZE);
296
+ const projectedTrs = trs.map((tr) => ({
297
+ ...tr,
298
+ ...(tr.steps ? { steps: normalizeTestStepsResults(tr.steps) } : {}),
299
+ }));
300
+ const trsChunks = chunk(projectedTrs, CHUNK_SIZE);
295
301
  const uploadLimitFn = pLimit(__classPrivateFieldGet(this, _TestOpsClient_uploadLimit, "f"));
296
302
  const uploadedTrs = [];
297
303
  const envNamesById = new Map(environments.map(({ id, name }) => [id, name]));
@@ -311,8 +317,8 @@ export class TestOpsClient {
311
317
  await this.createNamedEnvs(Array.from(chunkEnvs.values()));
312
318
  }
313
319
  const reportIdsToTestOpsIds = await __classPrivateFieldGet(this, _TestOpsClient_instances, "m", _TestOpsClient_postTestResultsChunk).call(this, trsChunk);
320
+ uploadedTrs.push(...trsChunk.filter((tr) => typeof reportIdsToTestOpsIds[tr.id] === "number"));
314
321
  await __classPrivateFieldGet(this, _TestOpsClient_instances, "m", _TestOpsClient_uploadChunkAttachmentsAndFixtures).call(this, trsChunk, reportIdsToTestOpsIds, attachmentsResolver, fixturesResolver, uploadLimitFn, onProgress);
315
- uploadedTrs.push(...trsChunk);
316
322
  }
317
323
  __classPrivateFieldGet(this, _TestOpsClient_logger, "f").verbose("Test results upload completed");
318
324
  }
@@ -395,8 +401,16 @@ _TestOpsClient_baseUrl = new WeakMap(), _TestOpsClient_logger = new WeakMap(), _
395
401
  }, _TestOpsClient_uploadChunkAttachmentsAndFixtures = async function _TestOpsClient_uploadChunkAttachmentsAndFixtures(trsChunk, reportIdsToTestOpsIds, attachmentsResolver, fixturesResolver, uploadLimitFn, onProgress) {
396
402
  await Promise.all(trsChunk.map((tr) => uploadLimitFn(async () => {
397
403
  const testOpsId = reportIdsToTestOpsIds[tr.id];
404
+ if (typeof testOpsId !== "number") {
405
+ return;
406
+ }
398
407
  const attachments = await attachmentsResolver(tr);
399
- const fixtures = await fixturesResolver(tr);
408
+ const fixtures = (await fixturesResolver(tr))
409
+ .filter((fixture) => validateExecutableName(fixture.name))
410
+ .map((fixture) => ({
411
+ ...fixture,
412
+ ...(fixture.steps ? { steps: normalizeTestStepsResults(fixture.steps) } : {}),
413
+ }));
400
414
  await __classPrivateFieldGet(this, _TestOpsClient_instances, "m", _TestOpsClient_uploadAttachmentsForResult).call(this, testOpsId, attachments);
401
415
  await __classPrivateFieldGet(this, _TestOpsClient_instances, "m", _TestOpsClient_uploadFixturesForResult).call(this, testOpsId, fixtures);
402
416
  onProgress?.();
package/dist/logger.d.ts CHANGED
@@ -1,18 +1,8 @@
1
- import ProgressBar from "progress";
2
1
  type JSONMessage = Record<string, unknown> | Array<unknown>;
3
2
  export declare class Logger {
4
3
  #private;
5
4
  private readonly loggerName;
6
5
  constructor(loggerName: string);
7
- progressBarCounter(message: string, total: number): ProgressBar | {
8
- tick: () => void;
9
- update: () => void;
10
- terminate: () => void;
11
- };
12
- progressBar(message: string): ProgressBar | {
13
- update: () => void;
14
- terminate: () => void;
15
- };
16
6
  verbose(message: string | JSONMessage): void;
17
7
  debug(message: string | JSONMessage): void;
18
8
  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,
@@ -43,33 +42,6 @@ export class Logger {
43
42
  _Logger_prefix.set(this, void 0);
44
43
  __classPrivateFieldSet(this, _Logger_prefix, cyan(bold(`[${this.loggerName}]:`)), "f");
45
44
  }
46
- progressBarCounter(message, total) {
47
- if (__classPrivateFieldGet(this, _Logger_instances, "m", _Logger_isSilent).call(this)) {
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;
72
- }
73
45
  verbose(message) {
74
46
  if (!__classPrivateFieldGet(this, _Logger_instances, "m", _Logger_isLogLevel).call(this, "verbose")) {
75
47
  return;
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?: string;
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?: string;
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?: string;
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_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 { attachmentsResolverFactory, fixturesResolverFactory, resolvePluginOptions, unwrapStepsAttachments, } from "./utils/index.js";
23
- import { toUploadCategory } from "./utils/uploadCategory.js";
24
- import { uploadFilenameForLink } from "./utils/uploaderDto.js";
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;
@@ -36,6 +39,7 @@ export class TestOpsPlugin {
36
39
  _TestOpsPlugin_launchTags.set(this, []);
37
40
  _TestOpsPlugin_uploadedTestResultsIds.set(this, new Set());
38
41
  _TestOpsPlugin_autocloseLaunch.set(this, false);
42
+ _TestOpsPlugin_launchStarted.set(this, false);
39
43
  _TestOpsPlugin_gitFlow.set(this, void 0);
40
44
  _TestOpsPlugin_enabledByConfig.set(this, false);
41
45
  __classPrivateFieldSet(this, _TestOpsPlugin_enabledByConfig, context.enabled === true, "f");
@@ -103,7 +107,9 @@ export class TestOpsPlugin {
103
107
  return;
104
108
  }
105
109
  __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").verbose("Starting upload…");
106
- await __classPrivateFieldGet(this, _TestOpsPlugin_instances, "m", _TestOpsPlugin_startUpload).call(this);
110
+ if (!(await __classPrivateFieldGet(this, _TestOpsPlugin_instances, "m", _TestOpsPlugin_startUpload).call(this))) {
111
+ return;
112
+ }
107
113
  await __classPrivateFieldGet(this, _TestOpsPlugin_instances, "m", _TestOpsPlugin_upload).call(this, store, { context, stage: "start" });
108
114
  __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").info(`Allure TestOps Launch: ${__classPrivateFieldGet(this, _TestOpsPlugin_client, "f").launchUrl}`);
109
115
  }
@@ -111,6 +117,10 @@ export class TestOpsPlugin {
111
117
  if (!this.enabled) {
112
118
  return;
113
119
  }
120
+ if (!__classPrivateFieldGet(this, _TestOpsPlugin_launchStarted, "f")) {
121
+ __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").verbose("Skipping update: the TestOps launch was never started");
122
+ return;
123
+ }
114
124
  __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").verbose("Updating (uploading new results)…");
115
125
  await __classPrivateFieldGet(this, _TestOpsPlugin_instances, "m", _TestOpsPlugin_upload).call(this, store, { context, stage: "update" });
116
126
  }
@@ -118,6 +128,10 @@ export class TestOpsPlugin {
118
128
  if (!this.enabled) {
119
129
  return;
120
130
  }
131
+ if (!__classPrivateFieldGet(this, _TestOpsPlugin_launchStarted, "f")) {
132
+ __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").verbose("Skipping finalization: the TestOps launch was never started");
133
+ return;
134
+ }
121
135
  const allTrs = await store.allTestResults({
122
136
  filter: this.options.filter,
123
137
  includeRetries: false,
@@ -125,7 +139,21 @@ export class TestOpsPlugin {
125
139
  const worstStatus = getWorstStatus(allTrs.map(({ status }) => status));
126
140
  __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").verbose("Finalizing upload…");
127
141
  await __classPrivateFieldGet(this, _TestOpsPlugin_instances, "m", _TestOpsPlugin_upload).call(this, store, { context, stage: "done" });
128
- await __classPrivateFieldGet(this, _TestOpsPlugin_instances, "m", _TestOpsPlugin_stopUpload).call(this, worstStatus || "unknown");
142
+ try {
143
+ await __classPrivateFieldGet(this, _TestOpsPlugin_instances, "m", _TestOpsPlugin_stopUpload).call(this, worstStatus || "unknown");
144
+ }
145
+ catch (error) {
146
+ if (__classPrivateFieldGet(this, _TestOpsPlugin_client, "f").isTestOpsClientError(error)) {
147
+ __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").error(`Failed to stop TestOps upload: ${error.response.data.message}`);
148
+ __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").debug(error.response?.data);
149
+ }
150
+ else if (error instanceof Error) {
151
+ __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").error(`Failed to stop TestOps upload: ${error.message}`);
152
+ }
153
+ else {
154
+ __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").error("Failed to stop TestOps upload");
155
+ }
156
+ }
129
157
  const launchId = __classPrivateFieldGet(this, _TestOpsPlugin_client, "f").launchId;
130
158
  if (typeof launchId !== "number") {
131
159
  return;
@@ -181,24 +209,34 @@ export class TestOpsPlugin {
181
209
  return summary;
182
210
  }
183
211
  }
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) {
212
+ _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_launchStarted = new WeakMap(), _TestOpsPlugin_gitFlow = new WeakMap(), _TestOpsPlugin_enabledByConfig = new WeakMap(), _TestOpsPlugin_instances = new WeakSet(), _TestOpsPlugin_uploadQualityGateResults = async function _TestOpsPlugin_uploadQualityGateResults(store) {
185
213
  const results = await store.qualityGateResults();
186
214
  const uniqueResults = uniqBy(results.filter(({ success }) => !success), ({ rule, environment }) => `${rule}-${environment}`);
187
215
  if (uniqueResults.length === 0) {
188
216
  __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").verbose("No quality gate results to upload");
189
217
  return;
190
218
  }
191
- const progressBar = __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").progressBar("Uploading quality gate results");
219
+ const progressLogger = createProgressLogger({
220
+ total: 1,
221
+ message: "Uploading quality gate results",
222
+ unitLabel: "request uploaded",
223
+ prefix: "[TestOpsPlugin]",
224
+ });
225
+ let completed = false;
192
226
  try {
193
- progressBar.update(0);
194
- await __classPrivateFieldGet(this, _TestOpsPlugin_client, "f").uploadQualityGateResults(uniqueResults, (percent, total) => {
195
- progressBar.update(percent / total);
227
+ progressLogger.log(true);
228
+ await __classPrivateFieldGet(this, _TestOpsPlugin_client, "f").uploadQualityGateResults(uniqueResults, (percent) => {
229
+ if (!completed && percent >= 100) {
230
+ completed = true;
231
+ progressLogger.increment();
232
+ }
196
233
  });
197
- progressBar.update(1);
198
- progressBar.terminate();
234
+ if (!completed) {
235
+ progressLogger.increment();
236
+ }
237
+ progressLogger.log(true);
199
238
  }
200
239
  catch (error) {
201
- progressBar.terminate();
202
240
  if (__classPrivateFieldGet(this, _TestOpsPlugin_client, "f").isTestOpsClientError(error)) {
203
241
  __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").error(`Failed to upload quality gate results: ${error.response.data.message}`);
204
242
  __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").debug(error.response?.data);
@@ -210,23 +248,36 @@ _TestOpsPlugin_logger = new WeakMap(), _TestOpsPlugin_ci = new WeakMap(), _TestO
210
248
  __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").error("Failed to upload quality gate results");
211
249
  }
212
250
  }
251
+ finally {
252
+ progressLogger.cancel?.();
253
+ }
213
254
  }, _TestOpsPlugin_uploadGlobalErrors = async function _TestOpsPlugin_uploadGlobalErrors(store) {
214
255
  const results = await store.allGlobalErrors();
215
256
  if (results.length === 0) {
216
257
  __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").verbose("No global errors to upload");
217
258
  return;
218
259
  }
219
- const progressBar = __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").progressBar("Uploading global errors");
260
+ const progressLogger = createProgressLogger({
261
+ total: 1,
262
+ message: "Uploading global errors",
263
+ unitLabel: "request uploaded",
264
+ prefix: "[TestOpsPlugin]",
265
+ });
266
+ let completed = false;
220
267
  try {
221
- progressBar.update(0);
222
- await __classPrivateFieldGet(this, _TestOpsPlugin_client, "f").uploadGlobalErrors(results, (percent, total) => {
223
- progressBar.update(percent / total);
268
+ progressLogger.log(true);
269
+ await __classPrivateFieldGet(this, _TestOpsPlugin_client, "f").uploadGlobalErrors(results, (percent) => {
270
+ if (!completed && percent >= 100) {
271
+ completed = true;
272
+ progressLogger.increment();
273
+ }
224
274
  });
225
- progressBar.update(1);
226
- progressBar.terminate();
275
+ if (!completed) {
276
+ progressLogger.increment();
277
+ }
278
+ progressLogger.log(true);
227
279
  }
228
280
  catch (error) {
229
- progressBar.terminate();
230
281
  if (__classPrivateFieldGet(this, _TestOpsPlugin_client, "f").isTestOpsClientError(error)) {
231
282
  __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").error(`Failed to upload global errors: ${error.response.data.message}`);
232
283
  __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").debug(error.response?.data);
@@ -238,15 +289,24 @@ _TestOpsPlugin_logger = new WeakMap(), _TestOpsPlugin_ci = new WeakMap(), _TestO
238
289
  __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").error("Failed to upload global errors");
239
290
  }
240
291
  }
292
+ finally {
293
+ progressLogger.cancel?.();
294
+ }
241
295
  }, _TestOpsPlugin_uploadGlobalAttachments = async function _TestOpsPlugin_uploadGlobalAttachments(store) {
242
296
  const attachments = await store.allGlobalAttachments();
243
297
  if (attachments.length === 0) {
244
298
  __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").debug("No global attachments to upload");
245
299
  return;
246
300
  }
247
- const progressBar = __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").progressBar("Uploading global attachments");
301
+ const progressLogger = createProgressLogger({
302
+ total: 1,
303
+ message: "Uploading global attachments",
304
+ unitLabel: "request uploaded",
305
+ prefix: "[TestOpsPlugin]",
306
+ });
307
+ let completed = false;
248
308
  try {
249
- progressBar.update(0);
309
+ progressLogger.log(true);
250
310
  await __classPrivateFieldGet(this, _TestOpsPlugin_client, "f").uploadGlobalAttachments({
251
311
  attachments,
252
312
  attachmentsResolver: async (attachmentLink) => {
@@ -262,15 +322,19 @@ _TestOpsPlugin_logger = new WeakMap(), _TestOpsPlugin_ci = new WeakMap(), _TestO
262
322
  content: body,
263
323
  };
264
324
  },
265
- onProgress: (percent, total) => {
266
- progressBar.update(percent / total);
325
+ onProgress: (percent) => {
326
+ if (!completed && percent >= 100) {
327
+ completed = true;
328
+ progressLogger.increment();
329
+ }
267
330
  },
268
331
  });
269
- progressBar.update(1);
270
- progressBar.terminate();
332
+ if (!completed) {
333
+ progressLogger.increment();
334
+ }
335
+ progressLogger.log(true);
271
336
  }
272
337
  catch (error) {
273
- progressBar.terminate();
274
338
  if (__classPrivateFieldGet(this, _TestOpsPlugin_client, "f").isTestOpsClientError(error)) {
275
339
  __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").error(`Failed to upload global attachments: ${error.response.data.message}`);
276
340
  __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").debug(error.response?.data);
@@ -282,28 +346,43 @@ _TestOpsPlugin_logger = new WeakMap(), _TestOpsPlugin_ci = new WeakMap(), _TestO
282
346
  __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").error("Failed to upload global attachments");
283
347
  }
284
348
  }
349
+ finally {
350
+ progressLogger.cancel?.();
351
+ }
285
352
  }, _TestOpsPlugin_uploadTestResults = async function _TestOpsPlugin_uploadTestResults(store, trsToUpload, environments) {
286
353
  const totalCount = trsToUpload.length;
287
354
  __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").info(`Preparing to upload ${bold(totalCount.toString())} ${totalCount > 1 ? "test results" : "test result"}`);
288
- const trsProgressBar = __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").progressBarCounter("Uploading test results", totalCount);
289
- const uploadedTrs = await __classPrivateFieldGet(this, _TestOpsPlugin_client, "f").uploadTestResults({
290
- attachmentsResolver: attachmentsResolverFactory(store),
291
- fixturesResolver: fixturesResolverFactory(store),
292
- environments,
293
- trs: trsToUpload,
294
- onProgress: () => trsProgressBar.tick(),
295
- });
296
- uploadedTrs.forEach((tr) => {
297
- __classPrivateFieldGet(this, _TestOpsPlugin_uploadedTestResultsIds, "f").add(tr.id);
355
+ const progressLogger = createProgressLogger({
356
+ total: totalCount,
357
+ message: "Uploading test results",
358
+ unitLabel: totalCount === 1 ? "test result uploaded" : "test results uploaded",
359
+ prefix: "[TestOpsPlugin]",
298
360
  });
299
- const uploadedCount = uploadedTrs.length;
300
- trsProgressBar.update(uploadedCount / totalCount);
301
- trsProgressBar.terminate();
302
- if (uploadedCount === 0) {
303
- __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").warn("No test results were uploaded");
304
- return;
361
+ const logProgress = progressLogger.log;
362
+ const incrementProgress = progressLogger.increment;
363
+ try {
364
+ logProgress(true);
365
+ const uploadedTrs = await __classPrivateFieldGet(this, _TestOpsPlugin_client, "f").uploadTestResults({
366
+ attachmentsResolver: attachmentsResolverFactory(store),
367
+ fixturesResolver: fixturesResolverFactory(store),
368
+ environments,
369
+ trs: trsToUpload,
370
+ onProgress: () => incrementProgress(),
371
+ });
372
+ logProgress(true);
373
+ uploadedTrs.forEach((tr) => {
374
+ __classPrivateFieldGet(this, _TestOpsPlugin_uploadedTestResultsIds, "f").add(tr.id);
375
+ });
376
+ const uploadedCount = uploadedTrs.length;
377
+ if (uploadedCount === 0) {
378
+ __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").warn("No test results were uploaded");
379
+ return;
380
+ }
381
+ __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").info(`Uploaded ${uploadedCount} ${uploadedCount > 1 ? "test results" : "test result"}`);
382
+ }
383
+ finally {
384
+ progressLogger.cancel?.();
305
385
  }
306
- __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").info(`Uploaded ${uploadedCount} ${uploadedCount > 1 ? "test results" : "test result"}`);
307
386
  }, _TestOpsPlugin_upload = async function _TestOpsPlugin_upload(store, options = {}) {
308
387
  const { context, stage } = options;
309
388
  const trsToUpload = await __classPrivateFieldGet(this, _TestOpsPlugin_instances, "m", _TestOpsPlugin_trsToUpload).call(this, store);
@@ -316,7 +395,22 @@ _TestOpsPlugin_logger = new WeakMap(), _TestOpsPlugin_ci = new WeakMap(), _TestO
316
395
  }
317
396
  return;
318
397
  }
319
- await __classPrivateFieldGet(this, _TestOpsPlugin_client, "f").createSession(env);
398
+ try {
399
+ await __classPrivateFieldGet(this, _TestOpsPlugin_client, "f").createSession(env);
400
+ }
401
+ catch (error) {
402
+ if (__classPrivateFieldGet(this, _TestOpsPlugin_client, "f").isTestOpsClientError(error)) {
403
+ __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").error(`Failed to create TestOps session: ${error.response.data.message}`);
404
+ __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").debug(error.response?.data);
405
+ }
406
+ else if (error instanceof Error) {
407
+ __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").error(`Failed to create TestOps session: ${error.message}`);
408
+ }
409
+ else {
410
+ __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").error("Failed to create TestOps session");
411
+ }
412
+ return;
413
+ }
320
414
  await __classPrivateFieldGet(this, _TestOpsPlugin_instances, "m", _TestOpsPlugin_uploadGlobalAttachments).call(this, store);
321
415
  await __classPrivateFieldGet(this, _TestOpsPlugin_instances, "m", _TestOpsPlugin_uploadGlobalErrors).call(this, store);
322
416
  await __classPrivateFieldGet(this, _TestOpsPlugin_instances, "m", _TestOpsPlugin_uploadQualityGateResults).call(this, store);
@@ -333,7 +427,7 @@ _TestOpsPlugin_logger = new WeakMap(), _TestOpsPlugin_ci = new WeakMap(), _TestO
333
427
  if (uploaded) {
334
428
  return false;
335
429
  }
336
- return filter(tr);
430
+ return validateExecutableName(tr.name) && filter(tr);
337
431
  },
338
432
  includeRetries: false,
339
433
  });
@@ -425,8 +519,24 @@ _TestOpsPlugin_logger = new WeakMap(), _TestOpsPlugin_ci = new WeakMap(), _TestO
425
519
  }
426
520
  }, _TestOpsPlugin_startUpload = async function _TestOpsPlugin_startUpload() {
427
521
  const launchGitContext = __classPrivateFieldGet(this, _TestOpsPlugin_gitFlow, "f").resolve();
428
- await __classPrivateFieldGet(this, _TestOpsPlugin_client, "f").createLaunch(__classPrivateFieldGet(this, _TestOpsPlugin_launchName, "f"), __classPrivateFieldGet(this, _TestOpsPlugin_launchTags, "f"), launchGitContext);
429
- await __classPrivateFieldGet(this, _TestOpsPlugin_client, "f").startUpload(__classPrivateFieldGet(this, _TestOpsPlugin_ci, "f"));
522
+ try {
523
+ await __classPrivateFieldGet(this, _TestOpsPlugin_client, "f").createLaunch(__classPrivateFieldGet(this, _TestOpsPlugin_launchName, "f"), __classPrivateFieldGet(this, _TestOpsPlugin_launchTags, "f"), launchGitContext);
524
+ await __classPrivateFieldGet(this, _TestOpsPlugin_client, "f").startUpload(__classPrivateFieldGet(this, _TestOpsPlugin_ci, "f"));
525
+ __classPrivateFieldSet(this, _TestOpsPlugin_launchStarted, true, "f");
526
+ }
527
+ catch (error) {
528
+ if (__classPrivateFieldGet(this, _TestOpsPlugin_client, "f").isTestOpsClientError(error)) {
529
+ __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").error(`Failed to create TestOps launch: ${error.response.data.message}`);
530
+ __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").debug(error.response?.data);
531
+ }
532
+ else if (error instanceof Error) {
533
+ __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").error(`Failed to create TestOps launch: ${error.message}`);
534
+ }
535
+ else {
536
+ __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").error("Failed to create TestOps launch");
537
+ }
538
+ }
539
+ return __classPrivateFieldGet(this, _TestOpsPlugin_launchStarted, "f");
430
540
  }, _TestOpsPlugin_stopUpload = async function _TestOpsPlugin_stopUpload(status) {
431
541
  await __classPrivateFieldGet(this, _TestOpsPlugin_client, "f").stopUpload(__classPrivateFieldGet(this, _TestOpsPlugin_ci, "f"), status);
432
542
  };
@@ -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;
@@ -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
+ });
@@ -1,7 +1,7 @@
1
- import type { TestStepResult } from "@allurereport/core-api";
2
- import { AllureStore } from "@allurereport/plugin-api";
3
- import type { AttachmentsResolver, FixtureResolver, TestOpsPluginOptions } from "../model.js";
4
- export declare const unwrapStepsAttachments: (steps: TestStepResult[]) => TestStepResult[];
5
- export declare const resolvePluginOptions: (options: TestOpsPluginOptions) => Omit<TestOpsPluginOptions, "filter">;
6
- export declare function attachmentsResolverFactory(store: AllureStore): AttachmentsResolver;
7
- export declare function fixturesResolverFactory(store: AllureStore): FixtureResolver;
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";
@@ -1,68 +1,7 @@
1
- import { env } from "node:process";
2
- import { uploadFilenameForLink } from "./uploaderDto.js";
3
- export const unwrapStepsAttachments = (steps) => {
4
- return steps.map((step) => {
5
- if (step.type === "attachment") {
6
- return {
7
- ...step,
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,2 @@
1
+ import type { TestOpsPluginOptions } from "../model.js";
2
+ export declare const resolvePluginOptions: (options: TestOpsPluginOptions) => Omit<TestOpsPluginOptions, "filter">;
@@ -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
- const isObject = (v) => typeof v === "object" && v !== null;
2
- const hasString = (obj, key) => typeof obj[key] === "string";
3
- export const uploadFilenameForLink = (link) => {
4
- if ("originalFileName" in link && typeof link.originalFileName === "string")
5
- return link.originalFileName;
6
- return undefined;
7
- };
8
- export const toUploadAttachmentDto = (link) => {
9
- return {
10
- name: "name" in link && typeof link.name === "string" ? link.name : undefined,
11
- originalFileName: uploadFilenameForLink(link),
12
- contentType: "contentType" in link && typeof link.contentType === "string" ? link.contentType : undefined,
13
- contentLength: "contentLength" in link && typeof link.contentLength === "number" ? link.contentLength : undefined,
14
- optional: "optional" in link && typeof link.optional === "boolean"
15
- ? link.optional
16
- : undefined,
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 out;
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 attachmentsUnknown = tr.attachments;
79
- const attachments = optionalArray(attachmentsUnknown, isAttachmentLink)?.map(toUploadAttachmentDto);
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
- ...(normalizeCategory(tr.category) ? { category: normalizeCategory(tr.category) } : {}),
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
- return {
112
- testSessionId,
113
- results: trs.map(toUploadTestResultDto),
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.14.3",
3
+ "version": "3.15.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.14.3",
36
- "@allurereport/core-api": "3.14.3",
37
- "@allurereport/git": "3.14.3",
38
- "@allurereport/plugin-api": "3.14.3",
39
- "@allurereport/reader-api": "3.14.3",
40
- "@allurereport/service": "3.14.3",
35
+ "@allurereport/ci": "3.15.0",
36
+ "@allurereport/cli-commons": "3.15.0",
37
+ "@allurereport/core-api": "3.15.0",
38
+ "@allurereport/git": "3.15.0",
39
+ "@allurereport/plugin-api": "3.15.0",
40
+ "@allurereport/reader-api": "3.15.0",
41
+ "@allurereport/service": "3.15.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;