qase-javascript-commons 2.6.2 → 2.6.4

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.
Files changed (44) hide show
  1. package/changelog.md +47 -0
  2. package/dist/client/clientV1.d.ts +6 -83
  3. package/dist/client/clientV1.js +15 -549
  4. package/dist/client/clientV2.d.ts +6 -24
  5. package/dist/client/clientV2.js +8 -255
  6. package/dist/client/services/api-error-handler.d.ts +10 -0
  7. package/dist/client/services/api-error-handler.js +44 -0
  8. package/dist/client/services/attachment-service.d.ts +16 -0
  9. package/dist/client/services/attachment-service.js +209 -0
  10. package/dist/client/services/configuration-service.d.ts +12 -0
  11. package/dist/client/services/configuration-service.js +110 -0
  12. package/dist/client/services/result-transformer.d.ts +23 -0
  13. package/dist/client/services/result-transformer.js +188 -0
  14. package/dist/client/services/run-service.d.ts +17 -0
  15. package/dist/client/services/run-service.js +114 -0
  16. package/dist/client/transport/api-config-builder.d.ts +8 -0
  17. package/dist/client/transport/api-config-builder.js +96 -0
  18. package/dist/formatter/index.d.ts +1 -0
  19. package/dist/formatter/index.js +3 -1
  20. package/dist/formatter/report-serializer.d.ts +20 -0
  21. package/dist/formatter/report-serializer.js +89 -0
  22. package/dist/qase/options-resolver.d.ts +19 -0
  23. package/dist/qase/options-resolver.js +47 -0
  24. package/dist/qase/reporter-factory.d.ts +19 -0
  25. package/dist/qase/reporter-factory.js +67 -0
  26. package/dist/qase/status-processor.d.ts +17 -0
  27. package/dist/qase/status-processor.js +48 -0
  28. package/dist/qase.d.ts +17 -85
  29. package/dist/qase.js +133 -415
  30. package/dist/reporters/report-reporter.d.ts +4 -35
  31. package/dist/reporters/report-reporter.js +6 -130
  32. package/dist/reporters/shared/fallback-coordinator.d.ts +47 -0
  33. package/dist/reporters/shared/fallback-coordinator.js +119 -0
  34. package/dist/reporters/shared/testops-constants.d.ts +5 -0
  35. package/dist/reporters/shared/testops-constants.js +8 -0
  36. package/dist/reporters/shared/testops-url.d.ts +9 -0
  37. package/dist/reporters/shared/testops-url.js +17 -0
  38. package/dist/reporters/testops-multi-reporter.d.ts +0 -1
  39. package/dist/reporters/testops-multi-reporter.js +4 -9
  40. package/dist/reporters/testops-reporter.d.ts +0 -6
  41. package/dist/reporters/testops-reporter.js +7 -17
  42. package/dist/utils/token-masker.d.ts +11 -0
  43. package/dist/utils/token-masker.js +26 -0
  44. package/package.json +1 -1
@@ -1,576 +1,42 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.ClientV1 = void 0;
7
4
  const qase_api_client_1 = require("qase-api-client");
8
- const is_axios_error_1 = require("../utils/is-axios-error");
9
- const qase_error_1 = require("../utils/qase-error");
10
- const chalk_1 = __importDefault(require("chalk"));
11
- const fs_1 = require("fs");
12
- const dateUtils_1 = require("./dateUtils");
13
- const form_data_1 = __importDefault(require("form-data"));
14
- const DEFAULT_API_HOST = 'qase.io';
15
- const API_BASE_URL = 'https://api-';
16
- const APP_BASE_URL = 'https://';
17
- const API_VERSION = '/v1';
18
- // Attachment upload limits
19
- const MAX_FILE_SIZE = 32 * 1024 * 1024; // 32 MB per file
20
- const MAX_REQUEST_SIZE = 128 * 1024 * 1024; // 128 MB per request
21
- const MAX_FILES_PER_REQUEST = 20; // 20 files per request
22
- var ApiErrorCode;
23
- (function (ApiErrorCode) {
24
- ApiErrorCode[ApiErrorCode["UNAUTHORIZED"] = 401] = "UNAUTHORIZED";
25
- ApiErrorCode[ApiErrorCode["FORBIDDEN"] = 403] = "FORBIDDEN";
26
- ApiErrorCode[ApiErrorCode["NOT_FOUND"] = 404] = "NOT_FOUND";
27
- ApiErrorCode[ApiErrorCode["BAD_REQUEST"] = 400] = "BAD_REQUEST";
28
- ApiErrorCode[ApiErrorCode["UNPROCESSABLE_ENTITY"] = 422] = "UNPROCESSABLE_ENTITY";
29
- })(ApiErrorCode || (ApiErrorCode = {}));
5
+ const api_config_builder_1 = require("./transport/api-config-builder");
6
+ const attachment_service_1 = require("./services/attachment-service");
7
+ const configuration_service_1 = require("./services/configuration-service");
8
+ const run_service_1 = require("./services/run-service");
30
9
  class ClientV1 {
31
10
  logger;
32
11
  config;
33
12
  environment;
34
- appUrl;
35
- runClient;
36
- environmentClient;
37
- attachmentClient;
38
- configurationClient;
13
+ attachmentService;
14
+ runService;
39
15
  constructor(logger, config, environment) {
40
16
  this.logger = logger;
41
17
  this.config = config;
42
18
  this.environment = environment;
43
- const { apiConfig, appUrl } = this.createApiConfig();
44
- this.appUrl = appUrl;
45
- this.runClient = new qase_api_client_1.RunsApi(apiConfig);
46
- this.environmentClient = new qase_api_client_1.EnvironmentsApi(apiConfig);
47
- this.attachmentClient = new qase_api_client_1.AttachmentsApi(apiConfig);
48
- this.configurationClient = new qase_api_client_1.ConfigurationsApi(apiConfig);
49
- }
50
- createApiConfig() {
51
- const apiConfig = new qase_api_client_1.Configuration({ apiKey: this.config.api.token, formDataCtor: form_data_1.default });
52
- if (this.config.api.host && this.config.api.host != DEFAULT_API_HOST) {
53
- apiConfig.basePath = `${API_BASE_URL}${this.config.api.host}${API_VERSION}`;
54
- return { apiConfig, appUrl: `${APP_BASE_URL}${this.config.api.host}` };
55
- }
56
- apiConfig.basePath = `https://api.${DEFAULT_API_HOST}${API_VERSION}`;
57
- return { apiConfig, appUrl: `https://app.${DEFAULT_API_HOST}` };
19
+ const apiConfig = (0, api_config_builder_1.createApiConfigV1)(config);
20
+ const appUrl = (0, api_config_builder_1.resolveAppUrl)(config);
21
+ const configurationService = new configuration_service_1.ConfigurationService(logger, new qase_api_client_1.ConfigurationsApi(apiConfig));
22
+ this.runService = new run_service_1.RunService(logger, new qase_api_client_1.RunsApi(apiConfig), new qase_api_client_1.EnvironmentsApi(apiConfig), configurationService, appUrl);
23
+ this.attachmentService = new attachment_service_1.AttachmentService(logger, new qase_api_client_1.AttachmentsApi(apiConfig));
58
24
  }
59
25
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
60
26
  uploadResults(_runId, _results) {
61
27
  throw new Error('Use ClientV2 to upload results');
62
28
  }
63
29
  async createRun() {
64
- if (this.config.run.id) {
65
- return this.config.run.id;
66
- }
67
- try {
68
- // Handle configurations if provided
69
- let configurationIds = [];
70
- if (this.config.configurations) {
71
- configurationIds = await this.handleConfigurations();
72
- }
73
- const environmentId = await this.getEnvironmentId();
74
- const runObject = this.prepareRunObject(environmentId, configurationIds);
75
- this.logger.logDebug(`Creating test run: ${JSON.stringify(runObject)}`);
76
- const { data } = await this.runClient.createRun(this.config.project, runObject);
77
- if (!data.result?.id) {
78
- throw new qase_error_1.QaseError('Failed to create test run');
79
- }
80
- this.logger.logDebug(`Test run created: ${JSON.stringify(data)}`);
81
- if (this.config.run.externalLink && data.result.id) {
82
- // Map our enum values to API enum values
83
- // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
84
- const apiType = this.config.run.externalLink.type === 'jiraCloud'
85
- ? qase_api_client_1.RunExternalIssuesTypeEnum.JIRA_CLOUD
86
- : qase_api_client_1.RunExternalIssuesTypeEnum.JIRA_SERVER;
87
- await this.runClient.runUpdateExternalIssue(this.config.project, {
88
- type: apiType,
89
- links: [
90
- {
91
- run_id: data.result.id,
92
- external_issue: this.config.run.externalLink.link,
93
- },
94
- ],
95
- });
96
- }
97
- return data.result.id;
98
- }
99
- catch (error) {
100
- throw this.processError(error, 'Error creating test run');
101
- }
30
+ return this.runService.createRun(this.config, this.environment);
102
31
  }
103
32
  async completeRun(runId) {
104
- if (!this.config.run.complete) {
105
- return;
106
- }
107
- try {
108
- await this.runClient.completeRun(this.config.project, runId);
109
- }
110
- catch (error) {
111
- throw this.processError(error, 'Error on completing run');
112
- }
113
- if (this.appUrl) {
114
- const runUrl = `${this.appUrl}/run/${this.config.project}/dashboard/${runId}`;
115
- this.logger.log((0, chalk_1.default) `{blue Test run link: ${runUrl}}`);
116
- }
33
+ return this.runService.completeRun(runId, this.config);
117
34
  }
118
35
  async enablePublicReport(runId) {
119
- try {
120
- const { data } = await this.runClient.updateRunPublicity(this.config.project, runId, { status: true });
121
- if (data.result?.url) {
122
- this.logger.log((0, chalk_1.default) `{blue Public report link: ${data.result.url}}`);
123
- }
124
- }
125
- catch (error) {
126
- this.logger.log((0, chalk_1.default) `{yellow Failed to generate public report link: ${this.getErrorMessage(error)}}`);
127
- }
128
- }
129
- getErrorMessage(error) {
130
- if ((0, is_axios_error_1.isAxiosError)(error)) {
131
- const err = error;
132
- const errorData = err.response?.data;
133
- return errorData?.errorMessage ?? errorData?.error ?? errorData?.message ?? 'Unknown API error';
134
- }
135
- return error instanceof Error ? error.message : 'Unknown error';
36
+ return this.runService.enablePublicReport(this.config.project, runId);
136
37
  }
137
38
  async uploadAttachment(attachment) {
138
- try {
139
- const data = this.prepareAttachmentData(attachment);
140
- const response = await this.attachmentClient.uploadAttachment(this.config.project, [data]);
141
- return response.data.result?.[0]?.hash ?? '';
142
- }
143
- catch (error) {
144
- throw this.processError(error, 'Error on uploading attachment');
145
- }
146
- }
147
- async uploadAttachments(attachments) {
148
- if (!this.config.uploadAttachments) {
149
- return [];
150
- }
151
- const uploadedHashes = [];
152
- // Filter out invalid attachments and check file size limits
153
- const validAttachments = [];
154
- for (const attachment of attachments) {
155
- if (!attachment) {
156
- continue;
157
- }
158
- // Ensure attachment size is calculated if not set or is 0
159
- this.ensureAttachmentSize(attachment);
160
- // Skip attachments with unknown size (0)
161
- if (attachment.size === 0) {
162
- this.logger.logError(`Cannot determine size for attachment "${attachment.file_path ?? attachment.file_name}". Skipping.`);
163
- continue;
164
- }
165
- // Check if file exceeds maximum size per file (32 MB)
166
- if (attachment.size > MAX_FILE_SIZE) {
167
- this.logger.logError(`Attachment "${attachment.file_path ?? attachment.file_name}" exceeds maximum file size (32 MB). ` +
168
- `File size: ${(attachment.size / (1024 * 1024)).toFixed(2)} MB. Skipping.`);
169
- continue;
170
- }
171
- validAttachments.push(attachment);
172
- }
173
- if (validAttachments.length === 0) {
174
- return uploadedHashes;
175
- }
176
- // Add initial random delay to spread out requests from different workers/shard
177
- // This helps prevent all workers from hitting the API at the same time
178
- const initialJitter = Math.random() * 500; // 0-500ms random delay
179
- await this.delay(initialJitter);
180
- // Group attachments into batches that respect API limits
181
- const batches = this.groupAttachmentsIntoBatches(validAttachments);
182
- this.logger.logDebug(`Uploading ${validAttachments.length} attachments in ${batches.length} batch(es)`);
183
- // Upload each batch
184
- for (let i = 0; i < batches.length; i++) {
185
- const batch = batches[i];
186
- if (!batch || batch.length === 0) {
187
- continue;
188
- }
189
- try {
190
- const batchNames = batch.map(a => a.file_path ?? a.file_name).join(', ');
191
- this.logger.logDebug(`Uploading batch ${i + 1}/${batches.length} with ${batch.length} file(s): ${batchNames}`);
192
- const batchData = batch.map(attachment => this.prepareAttachmentData(attachment));
193
- const response = await this.uploadAttachmentWithRetry(this.config.project, batchData, batchNames);
194
- // Extract all hashes from the response
195
- if (response.data.result) {
196
- for (const result of response.data.result) {
197
- if (result.hash) {
198
- uploadedHashes.push(result.hash);
199
- }
200
- }
201
- }
202
- }
203
- catch (error) {
204
- this.logger.logError(`Cannot upload batch ${i + 1}:`, error);
205
- // Continue with next batch even if current batch fails
206
- }
207
- // Add delay between batches to avoid rate limiting
208
- // Skip delay after the last batch
209
- if (i < batches.length - 1) {
210
- // Increased delay with random jitter to prevent synchronization
211
- const baseDelay = 1000; // 1000ms (1 second) base delay
212
- const jitter = Math.random() * 300; // 0-300ms random jitter
213
- await this.delay(baseDelay + jitter);
214
- }
215
- }
216
- return uploadedHashes;
217
- }
218
- /**
219
- * Group attachments into batches respecting API limits:
220
- * - Up to 20 files per batch
221
- * - Up to 128 MB per batch
222
- * @param attachments Array of attachments to group
223
- * @returns Array of attachment batches
224
- */
225
- groupAttachmentsIntoBatches(attachments) {
226
- const batches = [];
227
- let currentBatch = [];
228
- let currentBatchSize = 0;
229
- for (const attachment of attachments) {
230
- const attachmentSize = attachment.size;
231
- // Check if adding this attachment would exceed limits
232
- const wouldExceedFileLimit = currentBatch.length >= MAX_FILES_PER_REQUEST;
233
- const wouldExceedSizeLimit = currentBatchSize + attachmentSize > MAX_REQUEST_SIZE;
234
- // If current batch is full or would exceed limits, start a new batch
235
- if (wouldExceedFileLimit || wouldExceedSizeLimit) {
236
- if (currentBatch.length > 0) {
237
- batches.push(currentBatch);
238
- currentBatch = [];
239
- currentBatchSize = 0;
240
- }
241
- }
242
- // If a single file exceeds request size limit, it should have been filtered earlier
243
- // but we check again as a safety measure
244
- if (attachmentSize > MAX_REQUEST_SIZE) {
245
- this.logger.logError(`Attachment "${attachment.file_path ?? attachment.file_name}" exceeds maximum request size (128 MB). ` +
246
- `File size: ${(attachmentSize / (1024 * 1024)).toFixed(2)} MB. Skipping.`);
247
- continue;
248
- }
249
- currentBatch.push(attachment);
250
- currentBatchSize += attachmentSize;
251
- }
252
- // Add the last batch if it's not empty
253
- if (currentBatch.length > 0) {
254
- batches.push(currentBatch);
255
- }
256
- return batches;
257
- }
258
- /**
259
- * Upload attachment with retry logic for 429 errors
260
- * @param project Project code
261
- * @param data Attachment data array (can contain multiple files)
262
- * @param attachmentNames Attachment names for logging (comma-separated for batches)
263
- * @param maxRetries Maximum number of retry attempts
264
- * @param initialDelay Initial delay in milliseconds
265
- * @returns Promise with upload response
266
- */
267
- async uploadAttachmentWithRetry(project, data, attachmentNames, maxRetries = 5, initialDelay = 1000) {
268
- let lastError;
269
- let delay = initialDelay;
270
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
271
- try {
272
- return await this.attachmentClient.uploadAttachment(project, data);
273
- }
274
- catch (error) {
275
- lastError = error;
276
- // Check if it's a 429 error (Too Many Requests)
277
- if ((0, is_axios_error_1.isAxiosError)(error)) {
278
- if (error.response?.status === 429) {
279
- if (attempt < maxRetries) {
280
- const retryAfter = this.getRetryAfter(error);
281
- const baseWaitTime = retryAfter ?? delay;
282
- // Add jitter (random delay) to prevent all workers from retrying simultaneously
283
- // Jitter is 10-30% of the wait time to spread out retry attempts
284
- const jitterPercent = 0.1 + Math.random() * 0.2; // 10-30%
285
- const jitter = baseWaitTime * jitterPercent;
286
- const waitTime = Math.floor(baseWaitTime + jitter);
287
- this.logger.logDebug(`Rate limit exceeded (429) for attachment(s) "${attachmentNames}". ` +
288
- `Retrying in ${waitTime}ms (attempt ${attempt + 1}/${maxRetries})`);
289
- await this.delay(waitTime);
290
- // Exponential backoff: double the delay for next attempt
291
- delay = Math.min(delay * 2, 30000); // Cap at 30 seconds
292
- }
293
- else {
294
- this.logger.logError(`Failed to upload attachment(s) "${attachmentNames}" after ${maxRetries} retries due to rate limiting`);
295
- }
296
- }
297
- else {
298
- // For non-429 errors, throw immediately
299
- throw error;
300
- }
301
- }
302
- else {
303
- // For non-Axios errors, throw immediately
304
- throw error;
305
- }
306
- }
307
- }
308
- // If we exhausted all retries, throw the last error
309
- throw lastError;
310
- }
311
- /**
312
- * Extract Retry-After header value from response or return null
313
- * @param error Axios error
314
- * @returns Retry-After value in milliseconds or null
315
- */
316
- getRetryAfter(error) {
317
- const headers = error.response?.headers;
318
- if (!headers) {
319
- return null;
320
- }
321
- const retryAfterHeader = headers['retry-after'];
322
- if (retryAfterHeader && typeof retryAfterHeader === 'string') {
323
- const retryAfterSeconds = parseInt(retryAfterHeader, 10);
324
- if (!isNaN(retryAfterSeconds)) {
325
- return retryAfterSeconds * 1000; // Convert to milliseconds
326
- }
327
- }
328
- return null;
329
- }
330
- /**
331
- * Delay execution for specified milliseconds
332
- * @param ms Milliseconds to delay
333
- */
334
- delay(ms) {
335
- return new Promise(resolve => setTimeout(resolve, ms));
336
- }
337
- /**
338
- * Ensure attachment size is calculated if not set or is 0
339
- * @param attachment Attachment to ensure size for
340
- */
341
- ensureAttachmentSize(attachment) {
342
- // If size is already set and greater than 0, use it
343
- if (attachment.size > 0) {
344
- return;
345
- }
346
- try {
347
- if (attachment.file_path) {
348
- // Get file size from file system
349
- const stats = (0, fs_1.statSync)(attachment.file_path);
350
- attachment.size = stats.size;
351
- }
352
- else if (attachment.content) {
353
- // Calculate size from content
354
- if (typeof attachment.content === 'string') {
355
- // For strings, check if it's base64 encoded
356
- if (attachment.content.match(/^[A-Za-z0-9+/=]+$/)) {
357
- // Base64 encoded string
358
- attachment.size = Buffer.from(attachment.content, 'base64').length;
359
- }
360
- else {
361
- // Regular string - use byte length
362
- attachment.size = Buffer.byteLength(attachment.content, 'utf8');
363
- }
364
- }
365
- else if (Buffer.isBuffer(attachment.content)) {
366
- // Buffer - use length
367
- attachment.size = attachment.content.length;
368
- }
369
- else {
370
- // Fallback: try to convert to string and get byte length
371
- attachment.size = Buffer.byteLength(String(attachment.content), 'utf8');
372
- }
373
- }
374
- }
375
- catch (error) {
376
- // If we can't determine size, log warning and set to 0
377
- this.logger.logDebug(`Could not determine size for attachment "${attachment.file_path ?? attachment.file_name}": ${error instanceof Error ? error.message : 'Unknown error'}`);
378
- attachment.size = 0;
379
- }
380
- }
381
- prepareAttachmentData(attachment) {
382
- if (attachment.file_path) {
383
- return {
384
- name: attachment.file_name,
385
- value: (0, fs_1.createReadStream)(attachment.file_path),
386
- };
387
- }
388
- return {
389
- name: attachment.file_name,
390
- value: typeof attachment.content === 'string'
391
- ? Buffer.from(attachment.content, attachment.content.match(/^[A-Za-z0-9+/=]+$/) ? 'base64' : undefined)
392
- : attachment.content,
393
- };
394
- }
395
- async getEnvironmentId() {
396
- if (!this.environment)
397
- return undefined;
398
- const { data } = await this.environmentClient.getEnvironments(this.config.project, undefined, this.environment, 100);
399
- return data.result?.entities?.find((env) => env.slug === this.environment)?.id;
400
- }
401
- prepareRunObject(environmentId, configurationIds) {
402
- const runObject = {
403
- title: this.config.run.title ?? `Automated run ${new Date().toISOString()}`,
404
- description: this.config.run.description ?? '',
405
- is_autotest: true,
406
- cases: [],
407
- start_time: (0, dateUtils_1.getStartTime)(),
408
- tags: this.config.run.tags ?? [],
409
- };
410
- if (environmentId !== undefined) {
411
- runObject.environment_id = environmentId;
412
- }
413
- if (this.config.plan.id) {
414
- runObject.plan_id = this.config.plan.id;
415
- }
416
- if (configurationIds && configurationIds.length > 0) {
417
- runObject.configurations = configurationIds;
418
- }
419
- return runObject;
420
- }
421
- /**
422
- * Get all configuration groups with their configurations
423
- * @returns Promise<ConfigurationGroup[]> Array of configuration groups
424
- * @private
425
- */
426
- async getConfigurations() {
427
- try {
428
- const { data } = await this.configurationClient.getConfigurations(this.config.project);
429
- const entities = data.result?.entities ?? [];
430
- // Convert API response to domain model
431
- return entities.map(group => ({
432
- id: group.id ?? 0,
433
- title: group.title ?? '',
434
- configurations: group.configurations?.map(config => ({
435
- id: config.id ?? 0,
436
- title: config.title ?? ''
437
- })) ?? []
438
- }));
439
- }
440
- catch (error) {
441
- throw this.processError(error, 'Error getting configurations');
442
- }
443
- }
444
- /**
445
- * Create a configuration group
446
- * @param title Group title
447
- * @returns Promise<number | undefined> Created group ID
448
- * @private
449
- */
450
- async createConfigurationGroup(title) {
451
- try {
452
- const group = { title };
453
- const { data } = await this.configurationClient.createConfigurationGroup(this.config.project, group);
454
- return data.result?.id;
455
- }
456
- catch (error) {
457
- throw this.processError(error, 'Error creating configuration group');
458
- }
459
- }
460
- /**
461
- * Create a configuration in a group
462
- * @param title Configuration title
463
- * @param groupId Group ID
464
- * @returns Promise<number | undefined> Created configuration ID
465
- * @private
466
- */
467
- async createConfiguration(title, groupId) {
468
- try {
469
- const config = { title, group_id: groupId };
470
- const { data } = await this.configurationClient.createConfiguration(this.config.project, config);
471
- return data.result?.id;
472
- }
473
- catch (error) {
474
- throw this.processError(error, 'Error creating configuration');
475
- }
476
- }
477
- /**
478
- * Handle configuration creation based on config settings
479
- * @returns Promise<number[]> Array of configuration IDs
480
- * @private
481
- */
482
- async handleConfigurations() {
483
- if (!this.config.configurations?.values.length) {
484
- return [];
485
- }
486
- const configurationIds = [];
487
- try {
488
- // Get existing configuration groups
489
- const existingGroups = await this.getConfigurations();
490
- for (const configValue of this.config.configurations.values) {
491
- const { name: groupName, value: configName } = configValue;
492
- // Find existing group or create new one
493
- const group = existingGroups.find(g => g.title === groupName);
494
- let groupId;
495
- if (group) {
496
- groupId = group.id;
497
- this.logger.logDebug(`Found existing configuration group: ${groupName}`);
498
- }
499
- else {
500
- if (this.config.configurations.createIfNotExists) {
501
- const newGroupId = await this.createConfigurationGroup(groupName);
502
- if (newGroupId) {
503
- groupId = newGroupId;
504
- this.logger.logDebug(`Created new configuration group: ${groupName} with ID: ${groupId}`);
505
- }
506
- else {
507
- this.logger.logDebug(`Failed to create configuration group: ${groupName}, skipping`);
508
- continue;
509
- }
510
- }
511
- else {
512
- this.logger.logDebug(`Configuration group not found: ${groupName}, skipping`);
513
- continue;
514
- }
515
- }
516
- if (groupId) {
517
- // Check if configuration already exists in the group
518
- const existingConfig = group?.configurations.find(c => c.title === configName);
519
- if (!existingConfig) {
520
- // Check if we should create configuration if it doesn't exist
521
- if (this.config.configurations.createIfNotExists) {
522
- const configId = await this.createConfiguration(configName, groupId);
523
- if (configId) {
524
- configurationIds.push(configId);
525
- }
526
- this.logger.logDebug(`Created configuration: ${configName} in group: ${groupName}`);
527
- }
528
- else {
529
- this.logger.logDebug(`Configuration not found: ${configName} in group: ${groupName}, skipping`);
530
- }
531
- }
532
- else {
533
- if (existingConfig.id) {
534
- configurationIds.push(existingConfig.id);
535
- }
536
- this.logger.logDebug(`Configuration already exists: ${configName} in group: ${groupName}`);
537
- }
538
- }
539
- }
540
- }
541
- catch (error) {
542
- this.logger.logError('Error handling configurations:', error);
543
- // Don't throw error to avoid blocking test run creation
544
- }
545
- return configurationIds;
546
- }
547
- /**
548
- * Process error and throw QaseError
549
- * @param {Error | AxiosError} error
550
- * @param {string} message
551
- * @param {object} model
552
- * @private
553
- */
554
- processError(error, message, model) {
555
- if (!(0, is_axios_error_1.isAxiosError)(error)) {
556
- return new qase_error_1.QaseError(message, { cause: error });
557
- }
558
- const err = error;
559
- const errorData = err.response?.data;
560
- const status = err.response?.status;
561
- switch (status) {
562
- case ApiErrorCode.UNAUTHORIZED:
563
- return new qase_error_1.QaseError(`${message}: Unauthorized. Please check your API token.`);
564
- case ApiErrorCode.FORBIDDEN:
565
- return new qase_error_1.QaseError(`${message}: ${errorData?.errorMessage ?? 'Forbidden'}`);
566
- case ApiErrorCode.NOT_FOUND:
567
- return new qase_error_1.QaseError(`${message}: Not found.`);
568
- case ApiErrorCode.BAD_REQUEST:
569
- case ApiErrorCode.UNPROCESSABLE_ENTITY:
570
- return new qase_error_1.QaseError(`${message}: Bad request\n${JSON.stringify(errorData)}\nBody: ${JSON.stringify(model)}`);
571
- default:
572
- return new qase_error_1.QaseError(message, { cause: err });
573
- }
39
+ return this.attachmentService.uploadAttachment(this.config.project, attachment);
574
40
  }
575
41
  }
576
42
  exports.ClientV1 = ClientV1;
@@ -1,29 +1,11 @@
1
- import { ResultStepStatus } from "qase-api-v2-client";
2
- import { StepStatusEnum, TestResultType, TestStatusEnum } from "../models";
3
- import { LoggerInterface } from "../utils/logger";
4
- import { ClientV1 } from "./clientV1";
5
- import { TestOpsOptionsType } from "../models/config/TestOpsOptionsType";
6
- import { HostData } from "../models/host-data";
1
+ import { TestResultType } from '../models';
2
+ import { LoggerInterface } from '../utils/logger';
3
+ import { TestOpsOptionsType } from '../models/config/TestOpsOptionsType';
4
+ import { HostData } from '../models/host-data';
5
+ import { ClientV1 } from './clientV1';
7
6
  export declare class ClientV2 extends ClientV1 {
8
- private readonly rootSuite;
9
- static statusMap: Record<TestStatusEnum, string>;
10
- static stepStatusMap: Record<StepStatusEnum, ResultStepStatus>;
11
7
  private readonly resultsClient;
8
+ private readonly resultTransformer;
12
9
  constructor(logger: LoggerInterface, config: TestOpsOptionsType, environment: string | undefined, rootSuite: string | undefined, hostData?: HostData, reporterName?: string, frameworkName?: string);
13
- private createApiConfigV2;
14
- private buildHeaders;
15
10
  uploadResults(runId: number, results: TestResultType[]): Promise<void>;
16
- private transformTestResult;
17
- private transformParams;
18
- private transformGroupParams;
19
- private transformSteps;
20
- private transformStep;
21
- private createBaseResultStep;
22
- private processTextStep;
23
- private processGherkinStep;
24
- private processRequestStep;
25
- private getExecution;
26
- private getRelation;
27
- private getDefaultSuiteRelation;
28
- private buildSuiteData;
29
11
  }