@appliqation/automation-sdk 2.2.0 → 2.3.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.
@@ -320,6 +320,34 @@ class RunMatrixService {
320
320
  };
321
321
  return normalizeOS(osMap[platform] || 'Unknown');
322
322
  }
323
+
324
+ /**
325
+ * Delete a test run
326
+ * @param {string} runId - Run ID to delete
327
+ * @param {string} reason - Deletion reason
328
+ * @returns {Promise<Object>} Deletion result
329
+ */
330
+ async delete(runId, reason = 'orphan_cleanup') {
331
+ try {
332
+ logger.info('Deleting run...', { runId, reason });
333
+
334
+ const response = await this.http.deleteRun(runId, reason);
335
+
336
+ if (!response.success) {
337
+ throw new Error(response.error || 'Failed to delete run');
338
+ }
339
+
340
+ logger.info('Run deleted successfully', { runId });
341
+
342
+ return response.data || response;
343
+ } catch (error) {
344
+ logger.error('Failed to delete run', {
345
+ error: error.message,
346
+ runId
347
+ });
348
+ throw error;
349
+ }
350
+ }
323
351
  }
324
352
 
325
353
  module.exports = RunMatrixService;
@@ -1,43 +1,240 @@
1
1
  const logger = require('../utils/logger');
2
2
 
3
+ /**
4
+ * Service for auto-tagging test cases after successful runs
5
+ */
3
6
  class TaggingService {
4
7
  constructor(httpClient, config = { options: {} }) {
5
8
  this.http = httpClient;
6
9
  this.config = config;
10
+
11
+ // Configuration resolution: Runtime options > Env vars > Defaults
12
+ this.enabled = this.config?.options?.autoTag !== false &&
13
+ process.env.APPLIQATION_AUTO_TAG_ENABLED !== 'false';
14
+ this.tagName = this.config?.options?.autoTagName ||
15
+ process.env.APPLIQATION_AUTO_TAG_NAME ||
16
+ 'Appq_automated';
17
+ this.batchSize = this.config?.options?.autoTagBatchSize || 50;
18
+ this.retries = this.config?.options?.autoTagRetries || 2;
7
19
  }
8
20
 
9
21
  /**
10
- * Fire-and-forget tagging for accepted UUIDs.
11
- * Does not throw; logs warnings on failure.
12
- * @param {string[]} uuids
13
- * @param {string} tagName
22
+ * Check if tagging is enabled
23
+ * @returns {boolean}
14
24
  */
15
- async tagAccepted(uuids = [], tagName) {
16
- if (!uuids || uuids.length === 0) return;
25
+ isEnabled() {
26
+ return this.enabled;
27
+ }
17
28
 
18
- if (this.config?.options?.enableAppq === false) {
19
- return;
29
+ /**
30
+ * Check which UUIDs already have the tag
31
+ *
32
+ * @param {string[]} uuids - Array of test case UUIDs
33
+ * @param {string} tagName - Tag name (optional, uses config default)
34
+ * @returns {Promise<Object>} { needsTagging: [], alreadyTagged: [], errors: [] }
35
+ */
36
+ async checkTagStatus(uuids, tagName = null) {
37
+ if (!uuids || uuids.length === 0) {
38
+ return { needsTagging: [], alreadyTagged: [], errors: [] };
20
39
  }
21
40
 
41
+ const tag = tagName || this.tagName;
22
42
  const unique = Array.from(new Set(uuids.filter(Boolean)));
23
- if (unique.length === 0) return;
24
43
 
25
- const tag = tagName || this.config?.options?.autoTagName || process.env.APPLIQATION_TAG_NAME || 'Appq_automated';
26
- const endpoint = this.config?.options?.autoTagEndpoint || process.env.APPLIQATION_TAG_ENDPOINT || '/api/automation/testcase/tag';
44
+ try {
45
+ // Build query string: uuids=123-xxx,124-yyy&tag=Appq_automated
46
+ const uuidsParam = unique.join(',');
47
+ const url = `/api/automation/testcases/tags/check?uuids=${encodeURIComponent(uuidsParam)}&tag=${encodeURIComponent(tag)}`;
48
+ const response = await this.http.get(url);
49
+
50
+ if (response.success && response.data) {
51
+ return {
52
+ needsTagging: response.data.needsTagging || [],
53
+ alreadyTagged: response.data.alreadyTagged || [],
54
+ errors: response.data.errors || []
55
+ };
56
+ }
57
+
58
+ throw new Error(response.error || 'Failed to check tag status');
59
+ } catch (error) {
60
+ logger.error('Failed to check tag status', {
61
+ error: error.message,
62
+ count: unique.length,
63
+ tag
64
+ });
65
+ throw error;
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Tag test cases
71
+ *
72
+ * @param {string[]} uuids - Array of test case UUIDs to tag
73
+ * @param {string} tagName - Tag name (optional, uses config default)
74
+ * @returns {Promise<Object>} { tagged: 0, failed: 0, errors: [] }
75
+ */
76
+ async tagTestCases(uuids, tagName = null) {
77
+ if (!uuids || uuids.length === 0) {
78
+ return { tagged: 0, failed: 0, errors: [] };
79
+ }
80
+
81
+ const tag = tagName || this.tagName;
82
+ const unique = Array.from(new Set(uuids.filter(Boolean)));
27
83
 
28
84
  try {
29
- await this.http.post(endpoint, {
85
+ const response = await this.http.post('/api/automation/testcases/tag', {
30
86
  uuids: unique,
31
87
  tag
32
88
  });
33
- logger.debug('Tagging submitted for accepted results', { count: unique.length, tag });
89
+
90
+ if (response.success && response.data) {
91
+ return {
92
+ tagged: response.data.tagged || 0,
93
+ failed: response.data.failed || 0,
94
+ errors: response.data.errors || []
95
+ };
96
+ }
97
+
98
+ throw new Error(response.error || 'Failed to tag test cases');
34
99
  } catch (error) {
35
- logger.warn('Failed to tag accepted results (non-blocking)', {
100
+ logger.error('Failed to tag test cases', {
36
101
  error: error.message,
37
102
  count: unique.length,
38
103
  tag
39
104
  });
105
+ throw error;
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Auto-tag accepted results (main entry point)
111
+ *
112
+ * This is a fire-and-forget operation:
113
+ * 1. Check which UUIDs need tagging
114
+ * 2. Tag only those that don't have the tag yet
115
+ * 3. Process in batches for performance
116
+ * 4. Never throws - logs warnings on failure
117
+ *
118
+ * @param {string[]} acceptedUuids - UUIDs of accepted results
119
+ * @param {Object} options - Optional overrides
120
+ * @param {string} options.tagName - Custom tag name
121
+ * @returns {Promise<Object>} { tagged: 0, skipped: 0, failed: 0 }
122
+ */
123
+ async autoTagAcceptedResults(acceptedUuids = [], options = {}) {
124
+ // Safety checks
125
+ if (!this.enabled) {
126
+ logger.debug('Auto-tagging disabled');
127
+ return { tagged: 0, skipped: 0, failed: 0 };
128
+ }
129
+
130
+ if (!acceptedUuids || acceptedUuids.length === 0) {
131
+ return { tagged: 0, skipped: 0, failed: 0 };
132
+ }
133
+
134
+ // Deduplicate and filter
135
+ const unique = Array.from(new Set(acceptedUuids.filter(Boolean)));
136
+ if (unique.length === 0) {
137
+ return { tagged: 0, skipped: 0, failed: 0 };
138
+ }
139
+
140
+ const tag = options.tagName || this.tagName;
141
+
142
+ try {
143
+ logger.debug('Starting auto-tag process', { count: unique.length, tag });
144
+
145
+ // Step 1: Check which UUIDs need tagging
146
+ const statusCheck = await this.checkTagStatus(unique, tag);
147
+ const needsTagging = statusCheck.needsTagging || [];
148
+ const alreadyTagged = statusCheck.alreadyTagged || [];
149
+ logger.debug('Tag status check complete', {
150
+ needsTagging: needsTagging.length,
151
+ alreadyTagged: alreadyTagged.length,
152
+ errors: statusCheck.errors.length
153
+ });
154
+
155
+ if (needsTagging.length === 0) {
156
+ logger.debug('No test cases need tagging (all already tagged)');
157
+ return {
158
+ tagged: 0,
159
+ skipped: alreadyTagged.length,
160
+ failed: statusCheck.errors.length
161
+ };
162
+ }
163
+
164
+ // Step 2: Tag test cases (in batches if needed)
165
+ let totalTagged = 0;
166
+ let totalFailed = statusCheck.errors.length;
167
+
168
+ const batches = this.chunkArray(needsTagging, this.batchSize);
169
+
170
+ for (let i = 0; i < batches.length; i++) {
171
+ const batch = batches[i];
172
+
173
+ try {
174
+ const result = await this.tagTestCases(batch, tag);
175
+ totalTagged += result.tagged;
176
+ totalFailed += result.failed;
177
+
178
+ logger.debug(`Tagged batch ${i + 1}/${batches.length}`, {
179
+ tagged: result.tagged,
180
+ failed: result.failed
181
+ });
182
+ } catch (error) {
183
+ logger.warn(`Batch ${i + 1} tagging failed`, {
184
+ error: error.message,
185
+ batchSize: batch.length
186
+ });
187
+ totalFailed += batch.length;
188
+ }
189
+ }
190
+
191
+ const summary = {
192
+ tagged: totalTagged,
193
+ skipped: alreadyTagged.length,
194
+ failed: totalFailed
195
+ };
196
+
197
+ logger.info('Auto-tagging complete', summary);
198
+
199
+ return summary;
200
+ } catch (error) {
201
+ // Non-blocking: Log warning and continue
202
+ logger.warn('Auto-tagging failed (non-blocking)', {
203
+ error: error.message,
204
+ count: unique.length,
205
+ tag
206
+ });
207
+
208
+ return {
209
+ tagged: 0,
210
+ skipped: 0,
211
+ failed: unique.length
212
+ };
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Fire-and-forget tagging for accepted UUIDs.
218
+ * Does not throw; logs warnings on failure.
219
+ *
220
+ * @deprecated Use autoTagAcceptedResults() instead
221
+ * @param {string[]} uuids
222
+ * @param {string} tagName
223
+ */
224
+ async tagAccepted(uuids = [], tagName) {
225
+ return this.autoTagAcceptedResults(uuids, { tagName });
226
+ }
227
+
228
+ /**
229
+ * Split array into chunks
230
+ * @private
231
+ */
232
+ chunkArray(array, size) {
233
+ const chunks = [];
234
+ for (let i = 0; i < array.length; i += size) {
235
+ chunks.push(array.slice(i, i + size));
40
236
  }
237
+ return chunks;
41
238
  }
42
239
  }
43
240