@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 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 { 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;
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: __classPrivateFieldGet(this, _TestOpsClient_launch, "f").id },
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: __classPrivateFieldGet(this, _TestOpsClient_launch, "f").id,
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 trsChunks = chunk(trs, CHUNK_SIZE);
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: __classPrivateFieldGet(this, _TestOpsClient_launch, "f").id,
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
- import ProgressBar from "progress";
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
- 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
- };
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, getLogLevelFromEnv());
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
- 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;
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?: 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_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 { 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;
@@ -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
- await __classPrivateFieldGet(this, _TestOpsPlugin_instances, "m", _TestOpsPlugin_stopUpload).call(this, worstStatus || "unknown");
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 progressBar = __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").progressBar("Uploading quality gate results");
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
- progressBar.update(0);
194
- await __classPrivateFieldGet(this, _TestOpsPlugin_client, "f").uploadQualityGateResults(uniqueResults, (percent, total) => {
195
- progressBar.update(percent / total);
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
- progressBar.update(1);
198
- progressBar.terminate();
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
- }, _TestOpsPlugin_uploadGlobalErrors = async function _TestOpsPlugin_uploadGlobalErrors(store) {
214
- const results = await store.allGlobalErrors();
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 progressBar = __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").progressBar("Uploading global errors");
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
- progressBar.update(0);
222
- await __classPrivateFieldGet(this, _TestOpsPlugin_client, "f").uploadGlobalErrors(results, (percent, total) => {
223
- progressBar.update(percent / total);
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
- progressBar.update(1);
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
- }, _TestOpsPlugin_uploadGlobalAttachments = async function _TestOpsPlugin_uploadGlobalAttachments(store) {
242
- const attachments = await store.allGlobalAttachments();
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 progressBar = __classPrivateFieldGet(this, _TestOpsPlugin_logger, "f").progressBar("Uploading global attachments");
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
- progressBar.update(0);
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, total) => {
266
- progressBar.update(percent / total);
331
+ onProgress: (percent) => {
332
+ if (!completed && percent >= 100) {
333
+ completed = true;
334
+ progressLogger.increment();
335
+ }
267
336
  },
268
337
  });
269
- progressBar.update(1);
270
- progressBar.terminate();
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
- }, _TestOpsPlugin_uploadTestResults = async function _TestOpsPlugin_uploadTestResults(store, trsToUpload, environments) {
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").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(),
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
- uploadedTrs.forEach((tr) => {
297
- __classPrivateFieldGet(this, _TestOpsPlugin_uploadedTestResultsIds, "f").add(tr.id);
298
- });
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;
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").info("No new test results to upload");
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").info("No test results to upload");
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, _TestOpsPlugin_client, "f").createSession(env);
320
- await __classPrivateFieldGet(this, _TestOpsPlugin_instances, "m", _TestOpsPlugin_uploadGlobalAttachments).call(this, store);
321
- await __classPrivateFieldGet(this, _TestOpsPlugin_instances, "m", _TestOpsPlugin_uploadGlobalErrors).call(this, store);
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
- 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"));
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;
@@ -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.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.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.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;