@rbaileysr/zephyr-managed-api 1.3.2 → 1.3.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.
- package/README.md +104 -40
- package/dist/README.md +104 -40
- package/dist/groups/All.d.ts.map +1 -1
- package/dist/groups/Private/PrivateAttachments.d.ts +2 -0
- package/dist/groups/Private/PrivateAttachments.d.ts.map +1 -1
- package/dist/groups/Private/PrivateAttachments.js +13 -4
- package/dist/groups/Private/PrivateComments.d.ts +1 -0
- package/dist/groups/Private/PrivateComments.d.ts.map +1 -1
- package/dist/groups/Private/PrivateComments.js +11 -4
- package/dist/groups/Private/PrivateConfig.d.ts +47 -0
- package/dist/groups/Private/PrivateConfig.d.ts.map +1 -1
- package/dist/groups/Private/PrivateConfig.js +100 -0
- package/dist/groups/Private/PrivateVersionControl.d.ts +169 -0
- package/dist/groups/Private/PrivateVersionControl.d.ts.map +1 -0
- package/dist/groups/Private/PrivateVersionControl.js +709 -0
- package/dist/groups/Private.d.ts +5 -0
- package/dist/groups/Private.d.ts.map +1 -1
- package/dist/groups/Private.js +2 -0
- package/dist/groups/TestCase.d.ts +1 -1
- package/dist/groups/TestCase.js +1 -1
- package/dist/package.json +1 -1
- package/dist/types.d.ts +129 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,709 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright Adaptavist 2025 (c) All rights reserved
|
|
3
|
+
*/
|
|
4
|
+
import { PrivateBase } from './PrivateBase';
|
|
5
|
+
import { BadRequestError, UnauthorizedError, ForbiddenError, NotFoundError, ServerError, UnexpectedError, } from '../../utils';
|
|
6
|
+
export class PrivateVersionControl extends PrivateBase {
|
|
7
|
+
constructor(apiConnection) {
|
|
8
|
+
super(apiConnection);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Get issue links for a test case using private API (with version support)
|
|
12
|
+
*
|
|
13
|
+
* Retrieves all issue links associated with a test case, optionally for a specific version.
|
|
14
|
+
* The response format matches the public API `getTestCaseLinks().issues` format.
|
|
15
|
+
*
|
|
16
|
+
* ⚠️ WARNING: This uses a private Zephyr API endpoint that is not officially supported.
|
|
17
|
+
* The endpoint may change or be removed at any time without notice.
|
|
18
|
+
*
|
|
19
|
+
* @param credentials - Private API credentials
|
|
20
|
+
* @param request - Get issue links request
|
|
21
|
+
* @param request.testCaseKey - Test case key (e.g., 'PROJ-T1'). The numeric ID will be looked up automatically if Zephyr Connector is available.
|
|
22
|
+
* @param request.projectId - Jira project ID (numeric, not the project key)
|
|
23
|
+
* @param request.version - Optional version number (absolute, 1-based: 1 = first version ever created, 2 = second version, etc. The highest number is the current/latest version). If not provided, uses the latest version.
|
|
24
|
+
* @returns Array of issue links matching the public API format
|
|
25
|
+
* @throws {BadRequestError} If the request is invalid
|
|
26
|
+
* @throws {UnauthorizedError} If authentication fails
|
|
27
|
+
* @throws {ForbiddenError} If the user doesn't have permission
|
|
28
|
+
* @throws {NotFoundError} If the test case is not found
|
|
29
|
+
* @throws {ServerError} If the server returns an error
|
|
30
|
+
* @throws {UnexpectedError} If test case ID cannot be looked up from key and Zephyr Connector is not available
|
|
31
|
+
*/
|
|
32
|
+
async getTestCaseIssueLinks(credentials, request) {
|
|
33
|
+
// Get test case ID from key if we have API connection
|
|
34
|
+
let testCaseId;
|
|
35
|
+
if (this.testCaseGroup) {
|
|
36
|
+
try {
|
|
37
|
+
// If version is specified, get the versioned test case; otherwise get the current version
|
|
38
|
+
const testCase = request.version !== undefined && request.version > 0
|
|
39
|
+
? await this.testCaseGroup.getTestCaseVersion({
|
|
40
|
+
testCaseKey: request.testCaseKey,
|
|
41
|
+
version: request.version,
|
|
42
|
+
})
|
|
43
|
+
: await this.testCaseGroup.getTestCase({ testCaseKey: request.testCaseKey });
|
|
44
|
+
if (!testCase) {
|
|
45
|
+
throw new NotFoundError(`Test case with key '${request.testCaseKey}'${request.version !== undefined && request.version > 0 ? ` version ${request.version}` : ''} not found.`);
|
|
46
|
+
}
|
|
47
|
+
testCaseId = testCase.id;
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
if (error instanceof NotFoundError) {
|
|
51
|
+
throw new NotFoundError(`Test case with key '${request.testCaseKey}'${request.version !== undefined && request.version > 0 ? ` version ${request.version}` : ''} not found.`);
|
|
52
|
+
}
|
|
53
|
+
throw new UnexpectedError(`Failed to look up test case ID from key '${request.testCaseKey}'. Ensure Zephyr Connector is configured.`, error);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
throw new UnexpectedError('Cannot look up test case ID from key. This method requires Zephyr Connector to be configured. Use createZephyrApi() with an API Connection.');
|
|
58
|
+
}
|
|
59
|
+
// Get Context JWT
|
|
60
|
+
const contextJwt = await this.getContextJwt(credentials);
|
|
61
|
+
const fields = 'id,issueId,url,urlDescription,type(id,index,name,i18nKey,systemKey),testCaseId,testRunId,testPlanId';
|
|
62
|
+
const url = `${this.privateApiBaseUrl}/testcase/${testCaseId}/tracelinks/issue?fields=${encodeURIComponent(fields)}`;
|
|
63
|
+
const headers = {
|
|
64
|
+
accept: 'application/json',
|
|
65
|
+
authorization: `JWT ${contextJwt}`,
|
|
66
|
+
'jira-project-id': String(request.projectId),
|
|
67
|
+
};
|
|
68
|
+
try {
|
|
69
|
+
const response = await fetch(url, {
|
|
70
|
+
method: 'GET',
|
|
71
|
+
headers,
|
|
72
|
+
});
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
if (response.status === 400) {
|
|
75
|
+
throw new BadRequestError('Invalid request parameters for getting issue links.');
|
|
76
|
+
}
|
|
77
|
+
if (response.status === 401) {
|
|
78
|
+
throw new UnauthorizedError('Failed to authenticate. Please check your credentials.');
|
|
79
|
+
}
|
|
80
|
+
if (response.status === 403) {
|
|
81
|
+
throw new ForbiddenError('Insufficient permissions to get issue links.');
|
|
82
|
+
}
|
|
83
|
+
if (response.status === 404) {
|
|
84
|
+
throw new NotFoundError(`Test case with key '${request.testCaseKey}'${request.version !== undefined && request.version > 0 ? ` version ${request.version}` : ''} not found.`);
|
|
85
|
+
}
|
|
86
|
+
throw new ServerError(`Failed to get test case issue links. Status: ${response.status}`, response.status, response.statusText);
|
|
87
|
+
}
|
|
88
|
+
const privateLinks = (await response.json());
|
|
89
|
+
// Transform to public API format
|
|
90
|
+
const issueLinks = privateLinks.map((link) => {
|
|
91
|
+
// Build self URL (approximate format based on public API pattern)
|
|
92
|
+
const self = `/testcases/${request.testCaseKey}/links/issues/${link.id}`;
|
|
93
|
+
return {
|
|
94
|
+
id: link.id,
|
|
95
|
+
self,
|
|
96
|
+
issueId: Number(link.issueId),
|
|
97
|
+
target: `/issues/${link.issueId}`,
|
|
98
|
+
type: link.type.systemKey,
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
return issueLinks;
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
if (error instanceof BadRequestError ||
|
|
105
|
+
error instanceof UnauthorizedError ||
|
|
106
|
+
error instanceof ForbiddenError ||
|
|
107
|
+
error instanceof NotFoundError ||
|
|
108
|
+
error instanceof ServerError ||
|
|
109
|
+
error instanceof UnexpectedError) {
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
throw new UnexpectedError('Unexpected error while getting test case issue links', error);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get web links for a test case using private API (with version support)
|
|
117
|
+
*
|
|
118
|
+
* Retrieves all web links associated with a test case, optionally for a specific version.
|
|
119
|
+
* The response format matches the public API `getTestCaseLinks().webLinks` format.
|
|
120
|
+
*
|
|
121
|
+
* ⚠️ WARNING: This uses a private Zephyr API endpoint that is not officially supported.
|
|
122
|
+
* The endpoint may change or be removed at any time without notice.
|
|
123
|
+
*
|
|
124
|
+
* @param credentials - Private API credentials
|
|
125
|
+
* @param request - Get web links request
|
|
126
|
+
* @param request.testCaseKey - Test case key (e.g., 'PROJ-T1'). The numeric ID will be looked up automatically if Zephyr Connector is available.
|
|
127
|
+
* @param request.projectId - Jira project ID (numeric, not the project key)
|
|
128
|
+
* @param request.version - Optional version number (absolute, 1-based: 1 = first version ever created, 2 = second version, etc. The highest number is the current/latest version). If not provided, uses the latest version.
|
|
129
|
+
* @returns Array of web links matching the public API format
|
|
130
|
+
* @throws {BadRequestError} If the request is invalid
|
|
131
|
+
* @throws {UnauthorizedError} If authentication fails
|
|
132
|
+
* @throws {ForbiddenError} If the user doesn't have permission
|
|
133
|
+
* @throws {NotFoundError} If the test case is not found
|
|
134
|
+
* @throws {ServerError} If the server returns an error
|
|
135
|
+
* @throws {UnexpectedError} If test case ID cannot be looked up from key and Zephyr Connector is not available
|
|
136
|
+
*/
|
|
137
|
+
async getTestCaseWebLinks(credentials, request) {
|
|
138
|
+
// Get test case ID from key if we have API connection
|
|
139
|
+
let testCaseId;
|
|
140
|
+
if (this.testCaseGroup) {
|
|
141
|
+
try {
|
|
142
|
+
// If version is specified, get the versioned test case; otherwise get the current version
|
|
143
|
+
const testCase = request.version !== undefined && request.version > 0
|
|
144
|
+
? await this.testCaseGroup.getTestCaseVersion({
|
|
145
|
+
testCaseKey: request.testCaseKey,
|
|
146
|
+
version: request.version,
|
|
147
|
+
})
|
|
148
|
+
: await this.testCaseGroup.getTestCase({ testCaseKey: request.testCaseKey });
|
|
149
|
+
if (!testCase) {
|
|
150
|
+
throw new NotFoundError(`Test case with key '${request.testCaseKey}'${request.version !== undefined && request.version > 0 ? ` version ${request.version}` : ''} not found.`);
|
|
151
|
+
}
|
|
152
|
+
testCaseId = testCase.id;
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
if (error instanceof NotFoundError) {
|
|
156
|
+
throw new NotFoundError(`Test case with key '${request.testCaseKey}'${request.version !== undefined && request.version > 0 ? ` version ${request.version}` : ''} not found.`);
|
|
157
|
+
}
|
|
158
|
+
throw new UnexpectedError(`Failed to look up test case ID from key '${request.testCaseKey}'. Ensure Zephyr Connector is configured.`, error);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
throw new UnexpectedError('Cannot look up test case ID from key. This method requires Zephyr Connector to be configured. Use createZephyrApi() with an API Connection.');
|
|
163
|
+
}
|
|
164
|
+
// Get Context JWT
|
|
165
|
+
const contextJwt = await this.getContextJwt(credentials);
|
|
166
|
+
const fields = 'id,url,urlDescription,type(id,index,name,i18nKey,systemKey),testCaseId,testRunId,testPlanId';
|
|
167
|
+
const url = `${this.privateApiBaseUrl}/testcase/${testCaseId}/tracelinks/weblink?fields=${encodeURIComponent(fields)}`;
|
|
168
|
+
const headers = {
|
|
169
|
+
accept: 'application/json',
|
|
170
|
+
authorization: `JWT ${contextJwt}`,
|
|
171
|
+
'jira-project-id': String(request.projectId),
|
|
172
|
+
};
|
|
173
|
+
try {
|
|
174
|
+
const response = await fetch(url, {
|
|
175
|
+
method: 'GET',
|
|
176
|
+
headers,
|
|
177
|
+
});
|
|
178
|
+
if (!response.ok) {
|
|
179
|
+
if (response.status === 400) {
|
|
180
|
+
throw new BadRequestError('Invalid request parameters for getting web links.');
|
|
181
|
+
}
|
|
182
|
+
if (response.status === 401) {
|
|
183
|
+
throw new UnauthorizedError('Failed to authenticate. Please check your credentials.');
|
|
184
|
+
}
|
|
185
|
+
if (response.status === 403) {
|
|
186
|
+
throw new ForbiddenError('Insufficient permissions to get web links.');
|
|
187
|
+
}
|
|
188
|
+
if (response.status === 404) {
|
|
189
|
+
throw new NotFoundError(`Test case with key '${request.testCaseKey}'${request.version !== undefined && request.version > 0 ? ` version ${request.version}` : ''} not found.`);
|
|
190
|
+
}
|
|
191
|
+
throw new ServerError(`Failed to get test case web links. Status: ${response.status}`, response.status, response.statusText);
|
|
192
|
+
}
|
|
193
|
+
const privateLinks = (await response.json());
|
|
194
|
+
// Transform to public API format
|
|
195
|
+
const webLinks = privateLinks.map((link) => {
|
|
196
|
+
// Build self URL (approximate format based on public API pattern)
|
|
197
|
+
const self = `/testcases/${request.testCaseKey}/links/weblinks/${link.id}`;
|
|
198
|
+
return {
|
|
199
|
+
id: link.id,
|
|
200
|
+
self,
|
|
201
|
+
url: link.url,
|
|
202
|
+
description: link.urlDescription || undefined,
|
|
203
|
+
type: link.type.systemKey,
|
|
204
|
+
};
|
|
205
|
+
});
|
|
206
|
+
return webLinks;
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
if (error instanceof BadRequestError ||
|
|
210
|
+
error instanceof UnauthorizedError ||
|
|
211
|
+
error instanceof ForbiddenError ||
|
|
212
|
+
error instanceof NotFoundError ||
|
|
213
|
+
error instanceof ServerError ||
|
|
214
|
+
error instanceof UnexpectedError) {
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
217
|
+
throw new UnexpectedError('Unexpected error while getting test case web links', error);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Get test script for a test case using private API (with version support)
|
|
222
|
+
*
|
|
223
|
+
* Retrieves the test script associated with a test case, optionally for a specific version.
|
|
224
|
+
* The response format matches the public API `getTestCaseTestScript()` format.
|
|
225
|
+
*
|
|
226
|
+
* ⚠️ WARNING: This uses a private Zephyr API endpoint that is not officially supported.
|
|
227
|
+
* The endpoint may change or be removed at any time without notice.
|
|
228
|
+
*
|
|
229
|
+
* @param credentials - Private API credentials
|
|
230
|
+
* @param request - Get test script request
|
|
231
|
+
* @param request.testCaseKey - Test case key (e.g., 'PROJ-T1')
|
|
232
|
+
* @param request.projectId - Jira project ID (numeric, not the project key)
|
|
233
|
+
* @param request.version - Optional version number (absolute, 1-based: 1 = first version ever created, 2 = second version, etc. The highest number is the current/latest version). If not provided, uses the latest version.
|
|
234
|
+
* @returns Test script matching the public API format, or null if no test script exists
|
|
235
|
+
* @throws {BadRequestError} If the request is invalid
|
|
236
|
+
* @throws {UnauthorizedError} If authentication fails
|
|
237
|
+
* @throws {ForbiddenError} If the user doesn't have permission
|
|
238
|
+
* @throws {NotFoundError} If the test case is not found
|
|
239
|
+
* @throws {ServerError} If the server returns an error
|
|
240
|
+
* @throws {UnexpectedError} If test case cannot be retrieved
|
|
241
|
+
*/
|
|
242
|
+
async getTestCaseTestScript(credentials, request) {
|
|
243
|
+
// Get test case ID from key if we have API connection (for version support)
|
|
244
|
+
let testCaseId = request.testCaseKey;
|
|
245
|
+
if (this.testCaseGroup) {
|
|
246
|
+
try {
|
|
247
|
+
// If version is specified, get the versioned test case; otherwise get the current version
|
|
248
|
+
const testCase = request.version !== undefined && request.version > 0
|
|
249
|
+
? await this.testCaseGroup.getTestCaseVersion({
|
|
250
|
+
testCaseKey: request.testCaseKey,
|
|
251
|
+
version: request.version,
|
|
252
|
+
})
|
|
253
|
+
: await this.testCaseGroup.getTestCase({ testCaseKey: request.testCaseKey });
|
|
254
|
+
if (!testCase) {
|
|
255
|
+
throw new NotFoundError(`Test case with key '${request.testCaseKey}'${request.version !== undefined && request.version > 0 ? ` version ${request.version}` : ''} not found.`);
|
|
256
|
+
}
|
|
257
|
+
// Use the ID for the private API call (works with both key and ID)
|
|
258
|
+
testCaseId = testCase.id;
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
if (error instanceof NotFoundError) {
|
|
262
|
+
throw new NotFoundError(`Test case with key '${request.testCaseKey}'${request.version !== undefined && request.version > 0 ? ` version ${request.version}` : ''} not found.`);
|
|
263
|
+
}
|
|
264
|
+
throw new UnexpectedError(`Failed to look up test case ID from key '${request.testCaseKey}'. Ensure Zephyr Connector is configured.`, error);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// Get Context JWT
|
|
268
|
+
const contextJwt = await this.getContextJwt(credentials);
|
|
269
|
+
// Build fields query to get test script
|
|
270
|
+
const fields = 'testScript(id,text,steps(index,reflectRef,description,text,expectedResult,testData,customFieldValues,attachments,id,stepParameters(id,testCaseParameterId,value),testCase(id,key,name,archived,majorVersion,latestVersion,projectKey,parameters(id,name,defaultValue,index))))';
|
|
271
|
+
const url = `${this.privateApiBaseUrl}/testcase/${testCaseId}?fields=${encodeURIComponent(fields)}`;
|
|
272
|
+
const headers = {
|
|
273
|
+
accept: 'application/json',
|
|
274
|
+
authorization: `JWT ${contextJwt}`,
|
|
275
|
+
'jira-project-id': String(request.projectId),
|
|
276
|
+
};
|
|
277
|
+
try {
|
|
278
|
+
const response = await fetch(url, {
|
|
279
|
+
method: 'GET',
|
|
280
|
+
headers,
|
|
281
|
+
});
|
|
282
|
+
if (!response.ok) {
|
|
283
|
+
if (response.status === 400) {
|
|
284
|
+
throw new BadRequestError('Invalid request parameters for getting test script.');
|
|
285
|
+
}
|
|
286
|
+
if (response.status === 401) {
|
|
287
|
+
throw new UnauthorizedError('Failed to authenticate. Please check your credentials.');
|
|
288
|
+
}
|
|
289
|
+
if (response.status === 403) {
|
|
290
|
+
throw new ForbiddenError('Insufficient permissions to get test script.');
|
|
291
|
+
}
|
|
292
|
+
if (response.status === 404) {
|
|
293
|
+
throw new NotFoundError(`Test case with key '${request.testCaseKey}'${request.version !== undefined && request.version > 0 ? ` version ${request.version}` : ''} not found.`);
|
|
294
|
+
}
|
|
295
|
+
throw new ServerError(`Failed to get test case test script. Status: ${response.status}`, response.status, response.statusText);
|
|
296
|
+
}
|
|
297
|
+
const testCase = (await response.json());
|
|
298
|
+
// If no test script exists, return null
|
|
299
|
+
if (!testCase.testScript) {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
// If it's a step-by-step script (has steps), it's not a plain/BDD script, return null
|
|
303
|
+
if (testCase.testScript.stepByStepScript) {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
// If it has text, it's a plain text script
|
|
307
|
+
if (testCase.testScript.text) {
|
|
308
|
+
return {
|
|
309
|
+
id: testCase.testScript.id,
|
|
310
|
+
type: 'plain',
|
|
311
|
+
text: testCase.testScript.text,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
// No script found
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
if (error instanceof BadRequestError ||
|
|
319
|
+
error instanceof UnauthorizedError ||
|
|
320
|
+
error instanceof ForbiddenError ||
|
|
321
|
+
error instanceof NotFoundError ||
|
|
322
|
+
error instanceof ServerError ||
|
|
323
|
+
error instanceof UnexpectedError) {
|
|
324
|
+
throw error;
|
|
325
|
+
}
|
|
326
|
+
throw new UnexpectedError('Unexpected error while getting test case test script', error);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Get test steps for a test case using private API (with version support)
|
|
331
|
+
*
|
|
332
|
+
* Retrieves the test steps associated with a test case, optionally for a specific version.
|
|
333
|
+
* The response format matches the public API `getTestCaseTestSteps()` format.
|
|
334
|
+
*
|
|
335
|
+
* ⚠️ WARNING: This uses a private Zephyr API endpoint that is not officially supported.
|
|
336
|
+
* The endpoint may change or be removed at any time without notice.
|
|
337
|
+
*
|
|
338
|
+
* @param credentials - Private API credentials
|
|
339
|
+
* @param request - Get test steps request
|
|
340
|
+
* @param request.testCaseKey - Test case key (e.g., 'PROJ-T1')
|
|
341
|
+
* @param request.projectId - Jira project ID (numeric, not the project key)
|
|
342
|
+
* @param request.version - Optional version number (absolute, 1-based: 1 = first version ever created, 2 = second version, etc. The highest number is the current/latest version). If not provided, uses the latest version.
|
|
343
|
+
* @returns Test steps list matching the public API format
|
|
344
|
+
* @throws {BadRequestError} If the request is invalid
|
|
345
|
+
* @throws {UnauthorizedError} If authentication fails
|
|
346
|
+
* @throws {ForbiddenError} If the user doesn't have permission
|
|
347
|
+
* @throws {NotFoundError} If the test case is not found
|
|
348
|
+
* @throws {ServerError} If the server returns an error
|
|
349
|
+
* @throws {UnexpectedError} If test case cannot be retrieved
|
|
350
|
+
*/
|
|
351
|
+
async getTestCaseTestSteps(credentials, request) {
|
|
352
|
+
// Get test case ID from key if we have API connection (for version support)
|
|
353
|
+
let testCaseId = request.testCaseKey;
|
|
354
|
+
if (this.testCaseGroup) {
|
|
355
|
+
try {
|
|
356
|
+
// If version is specified, get the versioned test case; otherwise get the current version
|
|
357
|
+
const testCase = request.version !== undefined && request.version > 0
|
|
358
|
+
? await this.testCaseGroup.getTestCaseVersion({
|
|
359
|
+
testCaseKey: request.testCaseKey,
|
|
360
|
+
version: request.version,
|
|
361
|
+
})
|
|
362
|
+
: await this.testCaseGroup.getTestCase({ testCaseKey: request.testCaseKey });
|
|
363
|
+
if (!testCase) {
|
|
364
|
+
throw new NotFoundError(`Test case with key '${request.testCaseKey}'${request.version !== undefined && request.version > 0 ? ` version ${request.version}` : ''} not found.`);
|
|
365
|
+
}
|
|
366
|
+
// Use the ID for the private API call (works with both key and ID)
|
|
367
|
+
testCaseId = testCase.id;
|
|
368
|
+
}
|
|
369
|
+
catch (error) {
|
|
370
|
+
if (error instanceof NotFoundError) {
|
|
371
|
+
throw new NotFoundError(`Test case with key '${request.testCaseKey}'${request.version !== undefined && request.version > 0 ? ` version ${request.version}` : ''} not found.`);
|
|
372
|
+
}
|
|
373
|
+
throw new UnexpectedError(`Failed to look up test case ID from key '${request.testCaseKey}'. Ensure Zephyr Connector is configured.`, error);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
// Get Context JWT
|
|
377
|
+
const contextJwt = await this.getContextJwt(credentials);
|
|
378
|
+
// Build fields query to get test steps
|
|
379
|
+
const fields = 'testScript(id,text,steps(index,reflectRef,description,text,expectedResult,testData,customFieldValues,attachments,id,stepParameters(id,testCaseParameterId,value),testCase(id,key,name,archived,majorVersion,latestVersion,projectKey,parameters(id,name,defaultValue,index))))';
|
|
380
|
+
const url = `${this.privateApiBaseUrl}/testcase/${testCaseId}?fields=${encodeURIComponent(fields)}`;
|
|
381
|
+
const headers = {
|
|
382
|
+
accept: 'application/json',
|
|
383
|
+
authorization: `JWT ${contextJwt}`,
|
|
384
|
+
'jira-project-id': String(request.projectId),
|
|
385
|
+
};
|
|
386
|
+
try {
|
|
387
|
+
const response = await fetch(url, {
|
|
388
|
+
method: 'GET',
|
|
389
|
+
headers,
|
|
390
|
+
});
|
|
391
|
+
if (!response.ok) {
|
|
392
|
+
if (response.status === 400) {
|
|
393
|
+
throw new BadRequestError('Invalid request parameters for getting test steps.');
|
|
394
|
+
}
|
|
395
|
+
if (response.status === 401) {
|
|
396
|
+
throw new UnauthorizedError('Failed to authenticate. Please check your credentials.');
|
|
397
|
+
}
|
|
398
|
+
if (response.status === 403) {
|
|
399
|
+
throw new ForbiddenError('Insufficient permissions to get test steps.');
|
|
400
|
+
}
|
|
401
|
+
if (response.status === 404) {
|
|
402
|
+
throw new NotFoundError(`Test case with key '${request.testCaseKey}'${request.version !== undefined && request.version > 0 ? ` version ${request.version}` : ''} not found.`);
|
|
403
|
+
}
|
|
404
|
+
throw new ServerError(`Failed to get test case test steps. Status: ${response.status}`, response.status, response.statusText);
|
|
405
|
+
}
|
|
406
|
+
const testCase = (await response.json());
|
|
407
|
+
// If no test script or no step-by-step script exists, return empty list
|
|
408
|
+
if (!testCase.testScript || !testCase.testScript.stepByStepScript || !testCase.testScript.stepByStepScript.steps) {
|
|
409
|
+
return {
|
|
410
|
+
values: [],
|
|
411
|
+
total: 0,
|
|
412
|
+
maxResults: 0,
|
|
413
|
+
startAt: 0,
|
|
414
|
+
isLast: true,
|
|
415
|
+
next: null,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
const privateSteps = testCase.testScript.stepByStepScript.steps;
|
|
419
|
+
// Transform to public API format
|
|
420
|
+
const testSteps = privateSteps.map((step) => {
|
|
421
|
+
const inline = {
|
|
422
|
+
description: step.description || '',
|
|
423
|
+
testData: step.testData || undefined,
|
|
424
|
+
expectedResult: step.expectedResult || '',
|
|
425
|
+
reflectRef: step.reflectRef || undefined,
|
|
426
|
+
// Note: customFieldValues transformation would require more complex logic
|
|
427
|
+
// For now, we'll skip it as it's not in the public API TestStepInline type
|
|
428
|
+
};
|
|
429
|
+
return {
|
|
430
|
+
inline,
|
|
431
|
+
};
|
|
432
|
+
});
|
|
433
|
+
return {
|
|
434
|
+
values: testSteps,
|
|
435
|
+
total: testSteps.length,
|
|
436
|
+
maxResults: testSteps.length,
|
|
437
|
+
startAt: 0,
|
|
438
|
+
isLast: true,
|
|
439
|
+
next: null,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
catch (error) {
|
|
443
|
+
if (error instanceof BadRequestError ||
|
|
444
|
+
error instanceof UnauthorizedError ||
|
|
445
|
+
error instanceof ForbiddenError ||
|
|
446
|
+
error instanceof NotFoundError ||
|
|
447
|
+
error instanceof ServerError ||
|
|
448
|
+
error instanceof UnexpectedError) {
|
|
449
|
+
throw error;
|
|
450
|
+
}
|
|
451
|
+
throw new UnexpectedError('Unexpected error while getting test case test steps', error);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Map LinkType to numeric typeId for private API
|
|
456
|
+
*/
|
|
457
|
+
mapLinkTypeToTypeId(type) {
|
|
458
|
+
switch (type) {
|
|
459
|
+
case 'COVERAGE':
|
|
460
|
+
return 1;
|
|
461
|
+
case 'BLOCKS':
|
|
462
|
+
return 2;
|
|
463
|
+
case 'RELATED':
|
|
464
|
+
return 3;
|
|
465
|
+
default:
|
|
466
|
+
return 1; // Default to COVERAGE
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Map numeric typeId to LinkType for private API
|
|
471
|
+
*/
|
|
472
|
+
mapTypeIdToLinkType(typeId) {
|
|
473
|
+
switch (typeId) {
|
|
474
|
+
case 1:
|
|
475
|
+
return 'COVERAGE';
|
|
476
|
+
case 2:
|
|
477
|
+
return 'BLOCKS';
|
|
478
|
+
case 3:
|
|
479
|
+
return 'RELATED';
|
|
480
|
+
default:
|
|
481
|
+
return 'COVERAGE'; // Default to COVERAGE
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Get issue links for a test execution step using private API
|
|
486
|
+
*
|
|
487
|
+
* Retrieves all issue links associated with a specific test execution step. This is useful for
|
|
488
|
+
* migration scenarios where you need to extract issue links from a source test execution and
|
|
489
|
+
* recreate them in a target instance.
|
|
490
|
+
*
|
|
491
|
+
* ⚠️ WARNING: This uses a private Zephyr API endpoint that is not officially supported.
|
|
492
|
+
* The endpoint may change or be removed at any time without notice.
|
|
493
|
+
*
|
|
494
|
+
* @param credentials - Private API credentials
|
|
495
|
+
* @param request - Get issue links request
|
|
496
|
+
* @param request.testExecutionKey - Test execution key (e.g., 'PROJ-E1')
|
|
497
|
+
* @param request.stepIndex - Zero-based index of the test execution step
|
|
498
|
+
* @param request.projectId - Jira project ID (numeric, not the project key)
|
|
499
|
+
* @returns List of issue links for the test execution step
|
|
500
|
+
* @throws {BadRequestError} If the request is invalid
|
|
501
|
+
* @throws {UnauthorizedError} If authentication fails
|
|
502
|
+
* @throws {ForbiddenError} If the user doesn't have permission
|
|
503
|
+
* @throws {NotFoundError} If the test execution or step is not found
|
|
504
|
+
* @throws {ServerError} If the server returns an error
|
|
505
|
+
* @throws {UnexpectedError} If an unexpected error occurs
|
|
506
|
+
*/
|
|
507
|
+
async getTestExecutionStepIssueLinks(credentials, request) {
|
|
508
|
+
// Get Context JWT
|
|
509
|
+
const contextJwt = await this.getContextJwt(credentials);
|
|
510
|
+
// Fetch test execution with traceLinks in testScriptResults
|
|
511
|
+
const url = `${this.privateApiBaseUrl}/testresult/${request.testExecutionKey}?fields=testScriptResults(id,index,traceLinks(id,type(id,systemKey),issueId)),attachments&itemId=${request.testExecutionKey}`;
|
|
512
|
+
const headers = {
|
|
513
|
+
accept: 'application/json',
|
|
514
|
+
authorization: `JWT ${contextJwt}`,
|
|
515
|
+
'jira-project-id': String(request.projectId),
|
|
516
|
+
};
|
|
517
|
+
try {
|
|
518
|
+
const response = await fetch(url, {
|
|
519
|
+
method: 'GET',
|
|
520
|
+
headers,
|
|
521
|
+
});
|
|
522
|
+
if (!response.ok) {
|
|
523
|
+
if (response.status === 404) {
|
|
524
|
+
throw new NotFoundError(`Test execution with key '${request.testExecutionKey}' not found.`);
|
|
525
|
+
}
|
|
526
|
+
if (response.status === 401) {
|
|
527
|
+
throw new UnauthorizedError('Failed to authenticate. Please check your credentials.');
|
|
528
|
+
}
|
|
529
|
+
if (response.status === 403) {
|
|
530
|
+
throw new ForbiddenError('Insufficient permissions to get test execution step issue links.');
|
|
531
|
+
}
|
|
532
|
+
throw new ServerError(`Failed to get test execution step issue links. Status: ${response.status}`, response.status, response.statusText);
|
|
533
|
+
}
|
|
534
|
+
const executionData = (await response.json());
|
|
535
|
+
const steps = executionData.testScriptResults || [];
|
|
536
|
+
if (request.stepIndex < 0 || request.stepIndex >= steps.length) {
|
|
537
|
+
throw new NotFoundError(`Test execution step at index ${request.stepIndex} not found in test execution '${request.testExecutionKey}'. Test execution has ${steps.length} step(s).`);
|
|
538
|
+
}
|
|
539
|
+
const step = steps[request.stepIndex];
|
|
540
|
+
const traceLinks = step.traceLinks || [];
|
|
541
|
+
// Transform private API response to migration-friendly format
|
|
542
|
+
return traceLinks.map((link) => ({
|
|
543
|
+
id: link.id,
|
|
544
|
+
issueId: link.issueId,
|
|
545
|
+
type: this.mapSystemKeyToLinkType(link.type.systemKey),
|
|
546
|
+
}));
|
|
547
|
+
}
|
|
548
|
+
catch (error) {
|
|
549
|
+
if (error instanceof NotFoundError ||
|
|
550
|
+
error instanceof UnauthorizedError ||
|
|
551
|
+
error instanceof ForbiddenError ||
|
|
552
|
+
error instanceof ServerError ||
|
|
553
|
+
error instanceof UnexpectedError) {
|
|
554
|
+
throw error;
|
|
555
|
+
}
|
|
556
|
+
throw new UnexpectedError('Unexpected error while getting test execution step issue links', error);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Map system key to LinkType
|
|
561
|
+
*/
|
|
562
|
+
mapSystemKeyToLinkType(systemKey) {
|
|
563
|
+
switch (systemKey.toUpperCase()) {
|
|
564
|
+
case 'COVERAGE':
|
|
565
|
+
return 'COVERAGE';
|
|
566
|
+
case 'BLOCKS':
|
|
567
|
+
return 'BLOCKS';
|
|
568
|
+
case 'RELATED':
|
|
569
|
+
return 'RELATED';
|
|
570
|
+
default:
|
|
571
|
+
return 'COVERAGE'; // Default to COVERAGE
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Create an issue link for a test execution step using private API
|
|
576
|
+
*
|
|
577
|
+
* Creates a link between a test execution step and a Jira issue. The link type can be COVERAGE, BLOCKS, or RELATED.
|
|
578
|
+
* This functionality is not available in the public API.
|
|
579
|
+
*
|
|
580
|
+
* ⚠️ WARNING: This uses a private Zephyr API endpoint that is not officially supported.
|
|
581
|
+
* The endpoint may change or be removed at any time without notice.
|
|
582
|
+
*
|
|
583
|
+
* @param credentials - Private API credentials
|
|
584
|
+
* @param request - Create issue link request
|
|
585
|
+
* @param request.testExecutionKey - Test execution key (e.g., 'PROJ-E1')
|
|
586
|
+
* @param request.stepIndex - Zero-based index of the test execution step
|
|
587
|
+
* @param request.issueId - Jira issue ID (numeric)
|
|
588
|
+
* @param request.type - Link type: 'COVERAGE', 'BLOCKS', or 'RELATED' (optional, defaults to 'COVERAGE')
|
|
589
|
+
* @param request.projectId - Jira project ID (numeric, not the project key)
|
|
590
|
+
* @returns Created link response with ID
|
|
591
|
+
* @throws {BadRequestError} If the request is invalid
|
|
592
|
+
* @throws {UnauthorizedError} If authentication fails
|
|
593
|
+
* @throws {ForbiddenError} If the user doesn't have permission
|
|
594
|
+
* @throws {NotFoundError} If the test execution or step is not found
|
|
595
|
+
* @throws {ServerError} If the server returns an error
|
|
596
|
+
* @throws {UnexpectedError} If test execution ID cannot be looked up
|
|
597
|
+
*/
|
|
598
|
+
async createTestExecutionStepIssueLink(credentials, request) {
|
|
599
|
+
// Get Context JWT
|
|
600
|
+
const contextJwt = await this.getContextJwt(credentials);
|
|
601
|
+
// Step 1: Get test execution to find testResultId (numeric ID)
|
|
602
|
+
let testResultId;
|
|
603
|
+
if (this.testExecutionGroup) {
|
|
604
|
+
try {
|
|
605
|
+
const testExecution = await this.testExecutionGroup.getTestExecution({
|
|
606
|
+
testExecutionIdOrKey: request.testExecutionKey,
|
|
607
|
+
});
|
|
608
|
+
testResultId = testExecution.id;
|
|
609
|
+
}
|
|
610
|
+
catch (error) {
|
|
611
|
+
if (error instanceof NotFoundError) {
|
|
612
|
+
throw new NotFoundError(`Test execution with key '${request.testExecutionKey}' not found.`);
|
|
613
|
+
}
|
|
614
|
+
throw new UnexpectedError(`Failed to look up test execution ID from key '${request.testExecutionKey}'. Ensure Zephyr Connector is configured.`, error);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
else {
|
|
618
|
+
throw new UnexpectedError('Cannot look up test execution ID from key. This method requires Zephyr Connector to be configured. Use createZephyrApi() with an API Connection.');
|
|
619
|
+
}
|
|
620
|
+
// Step 2: Get test execution steps to find testScriptResultId from stepIndex
|
|
621
|
+
const url = `${this.privateApiBaseUrl}/testresult/${request.testExecutionKey}?fields=testScriptResults(id,index),attachments&itemId=${request.testExecutionKey}`;
|
|
622
|
+
const headers = {
|
|
623
|
+
accept: 'application/json',
|
|
624
|
+
authorization: `JWT ${contextJwt}`,
|
|
625
|
+
'jira-project-id': String(request.projectId),
|
|
626
|
+
};
|
|
627
|
+
let testScriptResultId;
|
|
628
|
+
try {
|
|
629
|
+
const response = await fetch(url, {
|
|
630
|
+
method: 'GET',
|
|
631
|
+
headers,
|
|
632
|
+
});
|
|
633
|
+
if (!response.ok) {
|
|
634
|
+
if (response.status === 404) {
|
|
635
|
+
throw new NotFoundError(`Test execution with key '${request.testExecutionKey}' not found.`);
|
|
636
|
+
}
|
|
637
|
+
throw new ServerError(`Failed to get test execution steps. Status: ${response.status}`, response.status, response.statusText);
|
|
638
|
+
}
|
|
639
|
+
const executionData = (await response.json());
|
|
640
|
+
const steps = executionData.testScriptResults || [];
|
|
641
|
+
if (request.stepIndex < 0 || request.stepIndex >= steps.length) {
|
|
642
|
+
throw new NotFoundError(`Test execution step at index ${request.stepIndex} not found in test execution '${request.testExecutionKey}'. Test execution has ${steps.length} step(s).`);
|
|
643
|
+
}
|
|
644
|
+
testScriptResultId = steps[request.stepIndex].id;
|
|
645
|
+
}
|
|
646
|
+
catch (error) {
|
|
647
|
+
if (error instanceof NotFoundError ||
|
|
648
|
+
error instanceof ServerError ||
|
|
649
|
+
error instanceof UnexpectedError) {
|
|
650
|
+
throw error;
|
|
651
|
+
}
|
|
652
|
+
throw new UnexpectedError('Unexpected error while getting test execution steps', error);
|
|
653
|
+
}
|
|
654
|
+
// Step 3: Create the issue link using bulk endpoint
|
|
655
|
+
const typeId = this.mapLinkTypeToTypeId(request.type);
|
|
656
|
+
const bulkUrl = `${this.privateApiBaseUrl}/tracelink/testresult/bulk/create`;
|
|
657
|
+
const bulkHeaders = {
|
|
658
|
+
'Content-Type': 'application/json',
|
|
659
|
+
authorization: `JWT ${contextJwt}`,
|
|
660
|
+
'jira-project-id': String(request.projectId),
|
|
661
|
+
};
|
|
662
|
+
const requestBody = [
|
|
663
|
+
{
|
|
664
|
+
testResultId,
|
|
665
|
+
issueId: String(request.issueId),
|
|
666
|
+
testScriptResultId,
|
|
667
|
+
typeId,
|
|
668
|
+
},
|
|
669
|
+
];
|
|
670
|
+
try {
|
|
671
|
+
const response = await fetch(bulkUrl, {
|
|
672
|
+
method: 'POST',
|
|
673
|
+
headers: bulkHeaders,
|
|
674
|
+
body: JSON.stringify(requestBody),
|
|
675
|
+
});
|
|
676
|
+
if (!response.ok) {
|
|
677
|
+
if (response.status === 400) {
|
|
678
|
+
throw new BadRequestError('Invalid request parameters for creating test execution step issue link.');
|
|
679
|
+
}
|
|
680
|
+
if (response.status === 401) {
|
|
681
|
+
throw new UnauthorizedError('Failed to authenticate. Please check your credentials.');
|
|
682
|
+
}
|
|
683
|
+
if (response.status === 403) {
|
|
684
|
+
throw new ForbiddenError('Insufficient permissions to create test execution step issue link.');
|
|
685
|
+
}
|
|
686
|
+
if (response.status === 404) {
|
|
687
|
+
throw new NotFoundError(`Test execution '${request.testExecutionKey}' or step at index ${request.stepIndex} not found.`);
|
|
688
|
+
}
|
|
689
|
+
throw new ServerError(`Failed to create test execution step issue link. Status: ${response.status}`, response.status, response.statusText);
|
|
690
|
+
}
|
|
691
|
+
const results = (await response.json());
|
|
692
|
+
if (results.length === 0) {
|
|
693
|
+
throw new UnexpectedError('No link ID returned from API response.');
|
|
694
|
+
}
|
|
695
|
+
return results[0];
|
|
696
|
+
}
|
|
697
|
+
catch (error) {
|
|
698
|
+
if (error instanceof BadRequestError ||
|
|
699
|
+
error instanceof UnauthorizedError ||
|
|
700
|
+
error instanceof ForbiddenError ||
|
|
701
|
+
error instanceof NotFoundError ||
|
|
702
|
+
error instanceof ServerError ||
|
|
703
|
+
error instanceof UnexpectedError) {
|
|
704
|
+
throw error;
|
|
705
|
+
}
|
|
706
|
+
throw new UnexpectedError('Unexpected error while creating test execution step issue link', error);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|