@smartbear/mcp 0.8.0 → 0.9.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/api-hub/client/api.js +51 -10
- package/dist/api-hub/client/registry-types.js +8 -0
- package/dist/api-hub/client/tools.js +7 -1
- package/dist/api-hub/client.js +3 -0
- package/dist/bugsnag/client/api/CurrentUser.js +12 -49
- package/dist/bugsnag/client/api/Error.js +29 -142
- package/dist/bugsnag/client/api/Project.js +52 -113
- package/dist/bugsnag/client/api/api.js +3743 -0
- package/dist/bugsnag/client/api/base.js +97 -34
- package/dist/bugsnag/client/api/configuration.js +26 -0
- package/dist/bugsnag/client/api/index.js +2 -0
- package/dist/bugsnag/client/filters.js +28 -0
- package/dist/bugsnag/client.js +100 -151
- package/dist/common/server.js +25 -3
- package/dist/common/types.js +6 -1
- package/dist/pactflow/client/prompt-utils.js +2 -1
- package/dist/pactflow/client/utils.js +5 -4
- package/dist/pactflow/client.js +10 -9
- package/dist/qmetry/client/api/client-api.js +21 -16
- package/dist/qmetry/client/api/error-handler.js +329 -0
- package/dist/qmetry/client/auto-resolve.js +74 -0
- package/dist/qmetry/client/handlers.js +19 -2
- package/dist/qmetry/client/issues.js +26 -0
- package/dist/qmetry/client/project.js +56 -0
- package/dist/qmetry/client/requirement.js +76 -0
- package/dist/qmetry/client/testcase.js +46 -8
- package/dist/qmetry/client/testsuite.js +117 -0
- package/dist/qmetry/client/tools.js +1455 -4
- package/dist/qmetry/client/utils.js +16 -0
- package/dist/qmetry/client.js +19 -16
- package/dist/qmetry/config/constants.js +14 -0
- package/dist/qmetry/config/rest-endpoints.js +20 -0
- package/dist/qmetry/types/common.js +313 -8
- package/dist/qmetry/types/issues.js +6 -0
- package/dist/qmetry/types/project.js +10 -0
- package/dist/qmetry/types/requirements.js +19 -0
- package/dist/qmetry/types/testcase.js +14 -0
- package/dist/qmetry/types/testsuite.js +26 -0
- package/dist/reflect/client.js +7 -6
- package/dist/zephyr/common/auth-service.js +1 -0
- package/package.json +1 -1
- package/dist/bugsnag/client/api/filters.js +0 -167
- package/dist/bugsnag/client/configuration.js +0 -10
- package/dist/bugsnag/client/index.js +0 -2
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "../../../common/info.js";
|
|
2
2
|
import { QMETRY_DEFAULTS } from "../../config/constants.js";
|
|
3
|
+
import { handleQMetryApiError, handleQMetryFetchError, } from "./error-handler.js";
|
|
4
|
+
/**
|
|
5
|
+
* QMetry API request function with centralized error handling.
|
|
6
|
+
*
|
|
7
|
+
* Handles authentication, project access, CORS, and generic API errors with
|
|
8
|
+
* user-friendly messages and troubleshooting guidance.
|
|
9
|
+
*
|
|
10
|
+
* @param options Request configuration including authentication token
|
|
11
|
+
* @returns Parsed JSON response from QMetry API
|
|
12
|
+
* @throws User-friendly errors with detailed troubleshooting steps for various scenarios
|
|
13
|
+
*/
|
|
3
14
|
export async function qmetryRequest({ method = "GET", path, token, project, baseUrl, body, }) {
|
|
4
15
|
const url = `${baseUrl}${path}`;
|
|
5
16
|
const headers = {
|
|
@@ -17,23 +28,17 @@ export async function qmetryRequest({ method = "GET", path, token, project, base
|
|
|
17
28
|
if (body && ["POST", "PUT", "PATCH"].includes(method)) {
|
|
18
29
|
init.body = JSON.stringify(body);
|
|
19
30
|
}
|
|
20
|
-
|
|
31
|
+
let res;
|
|
32
|
+
try {
|
|
33
|
+
res = await fetch(url, init);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
// Handle fetch errors (CORS, network issues, SSL certificate issues, etc.)
|
|
37
|
+
handleQMetryFetchError(error instanceof Error ? error : new Error(String(error)), baseUrl, project, path);
|
|
38
|
+
}
|
|
21
39
|
if (!res.ok) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const contentType = res.headers.get("content-type");
|
|
25
|
-
if (contentType?.includes("application/json")) {
|
|
26
|
-
const json = await res.json();
|
|
27
|
-
errorText = JSON.stringify(json);
|
|
28
|
-
}
|
|
29
|
-
else {
|
|
30
|
-
errorText = await res.text();
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
errorText = res.statusText;
|
|
35
|
-
}
|
|
36
|
-
throw new Error(`QMetry API request failed (${res.status}): ${errorText}`);
|
|
40
|
+
// Use centralized error handling
|
|
41
|
+
await handleQMetryApiError(res, baseUrl, project, path);
|
|
37
42
|
}
|
|
38
43
|
return (await res.json());
|
|
39
44
|
}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QMetry API Error Handling Utilities
|
|
3
|
+
*
|
|
4
|
+
* This module provides centralized error handling for QMetry API operations,
|
|
5
|
+
* including user-friendly error messages and troubleshooting guidance.
|
|
6
|
+
*
|
|
7
|
+
* Handles common error scenarios:
|
|
8
|
+
* - SSL/TLS Certificate errors (corporate proxies like Zscaler)
|
|
9
|
+
* - Authentication/Authorization failures
|
|
10
|
+
* - CORS (Cross-Origin Resource Sharing) issues
|
|
11
|
+
* - Project access errors
|
|
12
|
+
* - Network connectivity problems
|
|
13
|
+
* - Generic API errors with helpful troubleshooting
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Error message templates for common QMetry API issues
|
|
17
|
+
*/
|
|
18
|
+
const ERROR_TEMPLATES = {
|
|
19
|
+
AUTHENTICATION_FAILED: (baseUrl, errorText) => `QMetry API Authentication Failed: Invalid or expired API key.\n\n` +
|
|
20
|
+
`To resolve this issue:\n` +
|
|
21
|
+
`1. Log into your QMetry Test Management instance: ${baseUrl}\n` +
|
|
22
|
+
`2. Search Open API → Go To Open API Page\n` +
|
|
23
|
+
`3. Generate a new API key OR copy an existing valid key\n` +
|
|
24
|
+
`4. Copy the API key to your clipboard\n` +
|
|
25
|
+
`5. Restart VS Code or reload the MCP server\n` +
|
|
26
|
+
`6. When prompted, paste the new API key for 'QMetry Open API Key'\n\n` +
|
|
27
|
+
`Note: API keys may expire or be revoked. Always use the latest key from your QMetry instance.\n` +
|
|
28
|
+
`Original error: ${errorText}`,
|
|
29
|
+
AUTHORIZATION_ERROR: (errorText) => `QMetry Authorization Error: Insufficient permissions.\n\n` +
|
|
30
|
+
`Your API key is valid but lacks the necessary permissions for this operation.\n\n` +
|
|
31
|
+
`Possible causes:\n` +
|
|
32
|
+
`1. Your user role doesn't have access to this resource\n` +
|
|
33
|
+
`2. The project permissions are restricted\n` +
|
|
34
|
+
`3. The specific operation requires higher privileges\n\n` +
|
|
35
|
+
`To resolve:\n` +
|
|
36
|
+
`1. Contact your QMetry administrator to review your permissions\n` +
|
|
37
|
+
`2. Verify you have the correct project access\n` +
|
|
38
|
+
`3. Check if your user role includes the required privileges\n\n` +
|
|
39
|
+
`Original error: ${errorText}`,
|
|
40
|
+
PROJECT_ACCESS_ERROR: (project, errorText) => `QMetry Project Access Error: Cannot access project '${project}'.\n\n` +
|
|
41
|
+
`Possible causes:\n` +
|
|
42
|
+
`1. API key lacks permissions for this project\n` +
|
|
43
|
+
`2. Project key '${project}' doesn't exist or is archived\n` +
|
|
44
|
+
`3. Your user account doesn't have access to this project\n\n` +
|
|
45
|
+
`To resolve:\n` +
|
|
46
|
+
`1. Verify the project key is correct\n` +
|
|
47
|
+
`2. Check your QMetry permissions for this project\n` +
|
|
48
|
+
`3. Contact your QMetry administrator if needed\n\n` +
|
|
49
|
+
`Original error: ${errorText}`,
|
|
50
|
+
GENERIC_API_ERROR: (status, baseUrl, errorText) => `QMetry API request failed (${status}): ${errorText}\n\n` +
|
|
51
|
+
`Troubleshooting tips:\n` +
|
|
52
|
+
`Verify your QMetry instance URL: ${baseUrl}\n` +
|
|
53
|
+
`Check if your API key is valid and not expired\n` +
|
|
54
|
+
`Ensure you have the necessary permissions\n` +
|
|
55
|
+
`Try accessing QMetry directly in your browser to confirm connectivity`,
|
|
56
|
+
CORS_ERROR: (baseUrl, errorText) => `QMetry API CORS Error: Cross-Origin Request Blocked.\n\n` +
|
|
57
|
+
`CORS (Cross-Origin Resource Sharing) issue detected:\n` +
|
|
58
|
+
`Your browser is blocking the request due to CORS policy\n` +
|
|
59
|
+
`The QMetry server may not be configured to allow requests from this origin\n\n` +
|
|
60
|
+
`Possible solutions:\n` +
|
|
61
|
+
`1. Contact your QMetry administrator to configure CORS headers\n` +
|
|
62
|
+
`2. Ensure the QMetry instance URL is correct: ${baseUrl}\n` +
|
|
63
|
+
`3. If using a proxy, verify proxy CORS configuration\n` +
|
|
64
|
+
`4. Try accessing QMetry from the same domain/protocol\n` +
|
|
65
|
+
`5. Check if browser extensions are blocking the request\n\n` +
|
|
66
|
+
`Technical details: ${errorText}`,
|
|
67
|
+
SSL_CERTIFICATE_ERROR: (baseUrl, errorText) => `QMetry API SSL Certificate Error: Unable to verify certificate.\n\n` +
|
|
68
|
+
`SSL/TLS Certificate verification failed - Common in corporate networks:\n` +
|
|
69
|
+
`• Corporate proxy/firewall (Zscaler, Forcepoint, etc.) is intercepting HTTPS traffic\n` +
|
|
70
|
+
`• Self-signed or corporate certificates are being used\n` +
|
|
71
|
+
`• Certificate chain validation is failing\n\n` +
|
|
72
|
+
`Solution for corporate network environments:\n` +
|
|
73
|
+
`Contact your IT administrator to:\n` +
|
|
74
|
+
`• Add QMetry domain to certificate bypass list\n` +
|
|
75
|
+
`• Configure corporate certificates properly\n` +
|
|
76
|
+
`• Whitelist the QMetry API endpoints\n` +
|
|
77
|
+
`Target URL: ${baseUrl}\n` +
|
|
78
|
+
`Technical details: ${errorText}`,
|
|
79
|
+
INVALID_URL_ERROR: (baseUrl, path, errorText) => `QMetry API Invalid URL Error: The API endpoint appears to be incorrect.\n\n` +
|
|
80
|
+
`Request details:\n` +
|
|
81
|
+
`• Base URL: ${baseUrl}\n` +
|
|
82
|
+
`• API Path: ${path}\n` +
|
|
83
|
+
`• Full URL: ${baseUrl}${path}\n\n` +
|
|
84
|
+
`Common URL issues:\n` +
|
|
85
|
+
`1. Wrong API endpoint path (check QMetry API documentation)\n` +
|
|
86
|
+
`2. Typo in the URL path (missing '/', wrong spelling)\n` +
|
|
87
|
+
`3. API version mismatch (v1, v2, etc.)\n` +
|
|
88
|
+
`4. Incorrect base URL (should end with QMetry instance domain)\n` +
|
|
89
|
+
`5. Body stream errors (often caused by malformed URLs or wrong HTTP method)\n\n` +
|
|
90
|
+
`Troubleshooting steps:\n` +
|
|
91
|
+
`1. Verify the API endpoint in QMetry documentation\n` +
|
|
92
|
+
`2. Check the QMetry instance URL is correct\n` +
|
|
93
|
+
`3. Test the endpoint manually using a REST client\n` +
|
|
94
|
+
`4. Ensure the API path matches the expected format\n` +
|
|
95
|
+
`5. If you see 'Body is unusable' errors, check for URL typos or wrong endpoints\n\n` +
|
|
96
|
+
`Expected URL format: https://your-qmetry-instance.com/rest/...\n` +
|
|
97
|
+
`Server response: ${errorText}`,
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Checks if the error is related to authentication (invalid/expired API key)
|
|
101
|
+
*/
|
|
102
|
+
function isAuthenticationError(context) {
|
|
103
|
+
const { status, errorData } = context;
|
|
104
|
+
return (status === 401 ||
|
|
105
|
+
errorData?.code === "CO.INVALID_API_KEY" ||
|
|
106
|
+
errorData?.message?.toLowerCase().includes("invalid api key"));
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Checks if the error is related to authorization (insufficient permissions)
|
|
110
|
+
*/
|
|
111
|
+
function isAuthorizationError(context) {
|
|
112
|
+
const { status } = context;
|
|
113
|
+
return status === 403;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Checks if the error is related to project access
|
|
117
|
+
*/
|
|
118
|
+
function isProjectAccessError(context) {
|
|
119
|
+
const { status, path } = context;
|
|
120
|
+
return status === 404 && (path?.includes("project") ?? false);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Checks if the error is related to CORS (Cross-Origin Resource Sharing)
|
|
124
|
+
*/
|
|
125
|
+
function isCorsError(context) {
|
|
126
|
+
const { status, errorText, isCorsError } = context;
|
|
127
|
+
// Explicit CORS flag from fetch error
|
|
128
|
+
if (isCorsError) {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
// Common CORS indicators
|
|
132
|
+
const corsIndicators = [
|
|
133
|
+
"cors",
|
|
134
|
+
"cross-origin",
|
|
135
|
+
"cross origin",
|
|
136
|
+
"preflight",
|
|
137
|
+
"access-control-allow-origin",
|
|
138
|
+
"network error when attempting to fetch resource",
|
|
139
|
+
"failed to fetch",
|
|
140
|
+
];
|
|
141
|
+
const lowercaseErrorText = errorText.toLowerCase();
|
|
142
|
+
// Status 0 often indicates CORS issues
|
|
143
|
+
if (status === 0) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
// Check error text for CORS-related keywords
|
|
147
|
+
return corsIndicators.some((indicator) => lowercaseErrorText.includes(indicator));
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Checks if the error is related to SSL/TLS certificate issues
|
|
151
|
+
*/
|
|
152
|
+
function isSslCertificateError(context) {
|
|
153
|
+
const { errorText } = context;
|
|
154
|
+
// Common SSL certificate error indicators
|
|
155
|
+
const sslErrorIndicators = [
|
|
156
|
+
"unable to get local issuer certificate",
|
|
157
|
+
"unable_to_get_issuer_cert_locally",
|
|
158
|
+
"self signed certificate",
|
|
159
|
+
"certificate verify failed",
|
|
160
|
+
"ssl certificate problem",
|
|
161
|
+
"certificate has expired",
|
|
162
|
+
"certificate authority is invalid",
|
|
163
|
+
"untrusted certificate",
|
|
164
|
+
"cert authority invalid",
|
|
165
|
+
"tls certificate verification failed",
|
|
166
|
+
"certificate validation error",
|
|
167
|
+
];
|
|
168
|
+
const lowercaseErrorText = errorText.toLowerCase();
|
|
169
|
+
// Check for specific error codes that indicate SSL issues
|
|
170
|
+
if (lowercaseErrorText.includes("unable_to_get_issuer_cert_locally") ||
|
|
171
|
+
lowercaseErrorText.includes("cert_untrusted") ||
|
|
172
|
+
lowercaseErrorText.includes("cert_authority_invalid") ||
|
|
173
|
+
lowercaseErrorText.includes("certificate verify failed")) {
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
// Check error text for SSL-related keywords
|
|
177
|
+
return sslErrorIndicators.some((indicator) => lowercaseErrorText.includes(indicator));
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Checks if the error is related to invalid/wrong API URL
|
|
181
|
+
*/
|
|
182
|
+
function isInvalidUrlError(context) {
|
|
183
|
+
const { status, errorText, errorData } = context;
|
|
184
|
+
const lowercaseErrorText = errorText.toLowerCase();
|
|
185
|
+
// HTTP 404 is the most common indicator of wrong URL/endpoint
|
|
186
|
+
if (status === 404) {
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
// Check for specific error messages that indicate wrong URL
|
|
190
|
+
const urlErrorIndicators = [
|
|
191
|
+
"not found",
|
|
192
|
+
"resource not found",
|
|
193
|
+
"endpoint not found",
|
|
194
|
+
"path not found",
|
|
195
|
+
"url not found",
|
|
196
|
+
"route not found",
|
|
197
|
+
"invalid endpoint",
|
|
198
|
+
"unknown endpoint",
|
|
199
|
+
"method not allowed",
|
|
200
|
+
"no handler found",
|
|
201
|
+
"404",
|
|
202
|
+
// Body stream errors that often indicate wrong URL/malformed requests
|
|
203
|
+
"body is unusable",
|
|
204
|
+
"body has already been read",
|
|
205
|
+
"request body already read",
|
|
206
|
+
"body stream already consumed",
|
|
207
|
+
"invalid request body",
|
|
208
|
+
"malformed request",
|
|
209
|
+
];
|
|
210
|
+
// Check error text for URL-related keywords
|
|
211
|
+
if (urlErrorIndicators.some((indicator) => lowercaseErrorText.includes(indicator))) {
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
// Check if error data indicates invalid endpoint
|
|
215
|
+
if (errorData?.error?.toLowerCase().includes("not found") ||
|
|
216
|
+
errorData?.message?.toLowerCase().includes("not found")) {
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Creates an appropriate error message based on the error context
|
|
223
|
+
*/
|
|
224
|
+
export function createQMetryError(context) {
|
|
225
|
+
const { status, errorText, baseUrl, project } = context;
|
|
226
|
+
// SSL certificate errors should be checked first as they're network-level issues
|
|
227
|
+
if (isSslCertificateError(context)) {
|
|
228
|
+
return new Error(ERROR_TEMPLATES.SSL_CERTIFICATE_ERROR(baseUrl, errorText));
|
|
229
|
+
}
|
|
230
|
+
if (isCorsError(context)) {
|
|
231
|
+
return new Error(ERROR_TEMPLATES.CORS_ERROR(baseUrl, errorText));
|
|
232
|
+
}
|
|
233
|
+
if (isAuthenticationError(context)) {
|
|
234
|
+
return new Error(ERROR_TEMPLATES.AUTHENTICATION_FAILED(baseUrl, errorText));
|
|
235
|
+
}
|
|
236
|
+
if (isAuthorizationError(context)) {
|
|
237
|
+
return new Error(ERROR_TEMPLATES.AUTHORIZATION_ERROR(errorText));
|
|
238
|
+
}
|
|
239
|
+
if (isProjectAccessError(context) && project) {
|
|
240
|
+
return new Error(ERROR_TEMPLATES.PROJECT_ACCESS_ERROR(project, errorText));
|
|
241
|
+
}
|
|
242
|
+
// Check for invalid URL/endpoint errors (404, wrong paths, etc.)
|
|
243
|
+
if (isInvalidUrlError(context)) {
|
|
244
|
+
const path = context.path || "";
|
|
245
|
+
return new Error(ERROR_TEMPLATES.INVALID_URL_ERROR(baseUrl, path, errorText));
|
|
246
|
+
}
|
|
247
|
+
return new Error(ERROR_TEMPLATES.GENERIC_API_ERROR(status, baseUrl, errorText));
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Handles QMetry API errors in a standardized way
|
|
251
|
+
*
|
|
252
|
+
* @param response - The failed HTTP response
|
|
253
|
+
* @param baseUrl - The QMetry base URL
|
|
254
|
+
* @param project - Optional project context
|
|
255
|
+
* @param path - Optional API path context
|
|
256
|
+
*/
|
|
257
|
+
export async function handleQMetryApiError(response, baseUrl, project, path) {
|
|
258
|
+
let errorData;
|
|
259
|
+
let errorText;
|
|
260
|
+
try {
|
|
261
|
+
errorData = await response.json();
|
|
262
|
+
errorText = JSON.stringify(errorData);
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
errorText = (await response.text()) || `HTTP ${response.status}`;
|
|
266
|
+
}
|
|
267
|
+
const context = {
|
|
268
|
+
status: response.status,
|
|
269
|
+
errorData,
|
|
270
|
+
errorText,
|
|
271
|
+
baseUrl,
|
|
272
|
+
project,
|
|
273
|
+
path,
|
|
274
|
+
};
|
|
275
|
+
throw createQMetryError(context);
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Handles fetch errors that occur before receiving a response (e.g., CORS, network issues)
|
|
279
|
+
*
|
|
280
|
+
* @param error - The fetch error
|
|
281
|
+
* @param baseUrl - The QMetry base URL
|
|
282
|
+
* @param project - Optional project context
|
|
283
|
+
* @param path - Optional API path context
|
|
284
|
+
*/
|
|
285
|
+
export function handleQMetryFetchError(error, baseUrl, project, path) {
|
|
286
|
+
// Extract comprehensive error information
|
|
287
|
+
let errorText = error.message || error.toString();
|
|
288
|
+
// Check if error has a cause property with more details (common for SSL errors)
|
|
289
|
+
if (error.cause && typeof error.cause === "object") {
|
|
290
|
+
const cause = error.cause;
|
|
291
|
+
if (cause.message) {
|
|
292
|
+
errorText += ` | Cause: ${cause.message}`;
|
|
293
|
+
}
|
|
294
|
+
if (cause.code) {
|
|
295
|
+
errorText += ` | Code: ${cause.code}`;
|
|
296
|
+
}
|
|
297
|
+
// Add the full cause details for better debugging
|
|
298
|
+
if (cause.toString && typeof cause.toString === "function") {
|
|
299
|
+
errorText += ` | Details: ${cause.toString()}`;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// Check for specific SSL error patterns in the full error
|
|
303
|
+
const fullErrorString = JSON.stringify(error, Object.getOwnPropertyNames(error));
|
|
304
|
+
if (fullErrorString.includes("unable to get local issuer certificate") ||
|
|
305
|
+
fullErrorString.includes("UNABLE_TO_GET_ISSUER_CERT_LOCALLY")) {
|
|
306
|
+
errorText +=
|
|
307
|
+
" | SSL Certificate Error: Corporate proxy/firewall intercepting HTTPS";
|
|
308
|
+
}
|
|
309
|
+
// Check if this is an SSL certificate error first
|
|
310
|
+
const tempContext = {
|
|
311
|
+
status: 0,
|
|
312
|
+
errorText,
|
|
313
|
+
baseUrl,
|
|
314
|
+
project,
|
|
315
|
+
path,
|
|
316
|
+
};
|
|
317
|
+
const isSSLError = isSslCertificateError(tempContext);
|
|
318
|
+
const isURLError = isInvalidUrlError(tempContext);
|
|
319
|
+
const context = {
|
|
320
|
+
status: 0, // Status 0 typically indicates network/CORS/SSL issues
|
|
321
|
+
errorText,
|
|
322
|
+
baseUrl,
|
|
323
|
+
project,
|
|
324
|
+
path,
|
|
325
|
+
// Only assume CORS if it's not an SSL or URL error
|
|
326
|
+
isCorsError: !isSSLError && !isURLError,
|
|
327
|
+
};
|
|
328
|
+
throw createQMetryError(context);
|
|
329
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { QMetryToolsHandlers } from "../config/constants.js";
|
|
2
|
+
/**
|
|
3
|
+
* Registry of all modules that support auto-resolution
|
|
4
|
+
* Add new modules here to automatically support viewId/folderPath resolution
|
|
5
|
+
*/
|
|
6
|
+
export const AUTO_RESOLVE_MODULES = [
|
|
7
|
+
{
|
|
8
|
+
handler: QMetryToolsHandlers.FETCH_TEST_CASES,
|
|
9
|
+
viewIdPath: "latestViews.TC.viewId",
|
|
10
|
+
moduleName: "Test Cases",
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
handler: QMetryToolsHandlers.FETCH_REQUIREMENTS,
|
|
14
|
+
viewIdPath: "latestViews.RQ.viewId",
|
|
15
|
+
moduleName: "Requirements",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
handler: QMetryToolsHandlers.FETCH_TESTSUITES_FOR_TESTCASE,
|
|
19
|
+
viewIdPath: "latestViews.TSFS.viewId",
|
|
20
|
+
folderIdPath: "rootFolders.TS.id",
|
|
21
|
+
folderIdField: "tsFolderID",
|
|
22
|
+
moduleName: "Test Suites",
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
/**
|
|
26
|
+
* Helper function to safely get nested property value using dot notation
|
|
27
|
+
* @param obj - Object to traverse
|
|
28
|
+
* @param path - Dot notation path (e.g., 'latestViews.TC.viewId')
|
|
29
|
+
* @returns The value at the path or undefined if not found
|
|
30
|
+
*/
|
|
31
|
+
export function getNestedProperty(obj, path) {
|
|
32
|
+
return path.split(".").reduce((current, key) => current?.[key], obj);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Generic auto-resolve function for viewId, folderPath, and folderID
|
|
36
|
+
* @param args - Tool arguments that may contain viewId/folderPath/folderID
|
|
37
|
+
* @param projectInfo - Project information from QMetry API
|
|
38
|
+
* @param config - Module configuration for this specific handler
|
|
39
|
+
* @returns Updated args with resolved values
|
|
40
|
+
*/
|
|
41
|
+
export function autoResolveViewIdAndFolderPath(args, projectInfo, config) {
|
|
42
|
+
const updatedArgs = { ...args };
|
|
43
|
+
let viewId = updatedArgs.viewId;
|
|
44
|
+
const folderPath = updatedArgs.folderPath;
|
|
45
|
+
// Auto-resolve viewId if not provided and config has viewIdPath
|
|
46
|
+
if (!viewId && config.viewIdPath) {
|
|
47
|
+
viewId = getNestedProperty(projectInfo, config.viewIdPath);
|
|
48
|
+
if (viewId) {
|
|
49
|
+
updatedArgs.viewId = viewId;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Auto-resolve folderPath if not provided (defaults to root)
|
|
53
|
+
if (folderPath === undefined) {
|
|
54
|
+
updatedArgs.folderPath = "";
|
|
55
|
+
}
|
|
56
|
+
// Auto-resolve folder ID if not provided and config has folderIdPath
|
|
57
|
+
if (config.folderIdPath &&
|
|
58
|
+
config.folderIdField &&
|
|
59
|
+
!updatedArgs[config.folderIdField]) {
|
|
60
|
+
const folderId = getNestedProperty(projectInfo, config.folderIdPath);
|
|
61
|
+
if (folderId) {
|
|
62
|
+
updatedArgs[config.folderIdField] = folderId;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return updatedArgs;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Find the auto-resolve configuration for a given handler
|
|
69
|
+
* @param handler - The handler name to find config for
|
|
70
|
+
* @returns Module configuration or undefined if not found
|
|
71
|
+
*/
|
|
72
|
+
export function findAutoResolveConfig(handler) {
|
|
73
|
+
return AUTO_RESOLVE_MODULES.find((module) => module.handler === handler);
|
|
74
|
+
}
|
|
@@ -1,11 +1,28 @@
|
|
|
1
1
|
import { QMetryToolsHandlers } from "../config/constants.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { fetchIssuesLinkedToTestCase } from "./issues.js";
|
|
3
|
+
import { getBuilds, getPlatforms, getProjectInfo, getReleasesCycles, } from "./project.js";
|
|
4
|
+
import { fetchRequirementDetails, fetchRequirements, fetchRequirementsLinkedToTestCase, } from "./requirement.js";
|
|
5
|
+
import { fetchTestCaseDetails, fetchTestCaseExecutions, fetchTestCaseSteps, fetchTestCases, fetchTestCasesLinkedToRequirement, fetchTestCaseVersionDetails, } from "./testcase.js";
|
|
6
|
+
import { fetchExecutionsByTestSuite, fetchLinkedIssuesByTestCaseRun, fetchTestCaseRunsByTestSuiteRun, fetchTestCasesByTestSuite, fetchTestSuitesForTestCase, } from "./testsuite.js";
|
|
4
7
|
export const QMETRY_HANDLER_MAP = {
|
|
5
8
|
[QMetryToolsHandlers.SET_PROJECT_INFO]: getProjectInfo,
|
|
6
9
|
[QMetryToolsHandlers.FETCH_PROJECT_INFO]: getProjectInfo,
|
|
10
|
+
[QMetryToolsHandlers.FETCH_RELEASES_CYCLES]: getReleasesCycles,
|
|
11
|
+
[QMetryToolsHandlers.FETCH_BUILDS]: getBuilds,
|
|
12
|
+
[QMetryToolsHandlers.FETCH_PLATFORMS]: getPlatforms,
|
|
7
13
|
[QMetryToolsHandlers.FETCH_TEST_CASES]: fetchTestCases,
|
|
8
14
|
[QMetryToolsHandlers.FETCH_TEST_CASE_DETAILS]: fetchTestCaseDetails,
|
|
9
15
|
[QMetryToolsHandlers.FETCH_TEST_CASE_VERSION_DETAILS]: fetchTestCaseVersionDetails,
|
|
10
16
|
[QMetryToolsHandlers.FETCH_TEST_CASE_STEPS]: fetchTestCaseSteps,
|
|
17
|
+
[QMetryToolsHandlers.FETCH_TEST_CASE_EXECUTIONS]: fetchTestCaseExecutions,
|
|
18
|
+
[QMetryToolsHandlers.FETCH_REQUIREMENTS]: fetchRequirements,
|
|
19
|
+
[QMetryToolsHandlers.FETCH_REQUIREMENT_DETAILS]: fetchRequirementDetails,
|
|
20
|
+
[QMetryToolsHandlers.FETCH_TESTCASES_LINKED_TO_REQUIREMENT]: fetchTestCasesLinkedToRequirement,
|
|
21
|
+
[QMetryToolsHandlers.FETCH_REQUIREMENTS_LINKED_TO_TESTCASE]: fetchRequirementsLinkedToTestCase,
|
|
22
|
+
[QMetryToolsHandlers.FETCH_TESTSUITES_FOR_TESTCASE]: fetchTestSuitesForTestCase,
|
|
23
|
+
[QMetryToolsHandlers.FETCH_TESTCASES_BY_TESTSUITE]: fetchTestCasesByTestSuite,
|
|
24
|
+
[QMetryToolsHandlers.FETCH_EXECUTIONS_BY_TESTSUITE]: fetchExecutionsByTestSuite,
|
|
25
|
+
[QMetryToolsHandlers.FETCH_TESTCASE_RUNS_BY_TESTSUITE_RUN]: fetchTestCaseRunsByTestSuiteRun,
|
|
26
|
+
[QMetryToolsHandlers.FETCH_LINKED_ISSUES_BY_TESTCASE_RUN]: fetchLinkedIssuesByTestCaseRun,
|
|
27
|
+
[QMetryToolsHandlers.FETCH_ISSUES_LINKED_TO_TESTCASE]: fetchIssuesLinkedToTestCase,
|
|
11
28
|
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { QMETRY_PATHS } from "../config/rest-endpoints.js";
|
|
2
|
+
import { DEFAULT_FETCH_ISSUES_LINKED_TO_TESTCASE_PAYLOAD, } from "../types/issues.js";
|
|
3
|
+
import { qmetryRequest } from "./api/client-api.js";
|
|
4
|
+
import { resolveDefaults } from "./utils.js";
|
|
5
|
+
/**
|
|
6
|
+
* Fetches issues linked to a specific test case.
|
|
7
|
+
* @throws If `tcID` is missing/invalid.
|
|
8
|
+
*/
|
|
9
|
+
export async function fetchIssuesLinkedToTestCase(token, baseUrl, project, payload) {
|
|
10
|
+
const { resolvedBaseUrl, resolvedProject } = resolveDefaults(baseUrl, project);
|
|
11
|
+
const body = {
|
|
12
|
+
...DEFAULT_FETCH_ISSUES_LINKED_TO_TESTCASE_PAYLOAD,
|
|
13
|
+
...payload,
|
|
14
|
+
};
|
|
15
|
+
if (typeof body.tcID !== "number") {
|
|
16
|
+
throw new Error("[fetchIssuesLinkedToTestCase] Missing or invalid required parameter: 'tcID'.");
|
|
17
|
+
}
|
|
18
|
+
return qmetryRequest({
|
|
19
|
+
method: "POST",
|
|
20
|
+
path: QMETRY_PATHS.ISSUES.GET_ISSUES_LINKED_TO_TC,
|
|
21
|
+
token,
|
|
22
|
+
project: resolvedProject,
|
|
23
|
+
baseUrl: resolvedBaseUrl,
|
|
24
|
+
body,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { QMETRY_DEFAULTS } from "../config/constants.js";
|
|
2
2
|
import { QMETRY_PATHS } from "../config/rest-endpoints.js";
|
|
3
|
+
import { DEFAULT_FETCH_BUILD_PAYLOAD, DEFAULT_FETCH_PLATFORMS_PAYLOAD, } from "../types/project.js";
|
|
3
4
|
import { qmetryRequest } from "./api/client-api.js";
|
|
4
5
|
/**
|
|
5
6
|
* Retrieves project information from QMetry
|
|
@@ -25,3 +26,58 @@ export async function getProjectInfo(token, baseUrl, project) {
|
|
|
25
26
|
project: project || QMETRY_DEFAULTS.PROJECT_KEY,
|
|
26
27
|
});
|
|
27
28
|
}
|
|
29
|
+
export async function getReleasesCycles(token, baseUrl, project, payload = {}) {
|
|
30
|
+
let showArchiveValue;
|
|
31
|
+
if (payload.showArchive !== undefined) {
|
|
32
|
+
showArchiveValue = payload.showArchive;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
showArchiveValue = false;
|
|
36
|
+
}
|
|
37
|
+
const body = {
|
|
38
|
+
showArchive: showArchiveValue,
|
|
39
|
+
};
|
|
40
|
+
return qmetryRequest({
|
|
41
|
+
method: "POST",
|
|
42
|
+
path: QMETRY_PATHS.PROJECT.GET_RELEASES_CYCLES,
|
|
43
|
+
token,
|
|
44
|
+
baseUrl: baseUrl || QMETRY_DEFAULTS.BASE_URL,
|
|
45
|
+
project: project || QMETRY_DEFAULTS.PROJECT_KEY,
|
|
46
|
+
body,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Fetches a List of Builds of the current project.
|
|
51
|
+
*/
|
|
52
|
+
export async function getBuilds(token, baseUrl, project, payload) {
|
|
53
|
+
const body = {
|
|
54
|
+
...DEFAULT_FETCH_BUILD_PAYLOAD,
|
|
55
|
+
...payload,
|
|
56
|
+
};
|
|
57
|
+
return qmetryRequest({
|
|
58
|
+
method: "POST",
|
|
59
|
+
path: QMETRY_PATHS.PROJECT.GET_BUILD,
|
|
60
|
+
token,
|
|
61
|
+
baseUrl: baseUrl || QMETRY_DEFAULTS.BASE_URL,
|
|
62
|
+
project: project || QMETRY_DEFAULTS.PROJECT_KEY,
|
|
63
|
+
body,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Fetches platforms from the current QMetry project
|
|
68
|
+
*
|
|
69
|
+
*/
|
|
70
|
+
export async function getPlatforms(token, baseUrl, project, payload) {
|
|
71
|
+
const body = {
|
|
72
|
+
...DEFAULT_FETCH_PLATFORMS_PAYLOAD,
|
|
73
|
+
...payload,
|
|
74
|
+
};
|
|
75
|
+
return qmetryRequest({
|
|
76
|
+
method: "POST",
|
|
77
|
+
path: QMETRY_PATHS.PROJECT.GET_PLATFORMS,
|
|
78
|
+
token,
|
|
79
|
+
baseUrl: baseUrl || QMETRY_DEFAULTS.BASE_URL,
|
|
80
|
+
project: project || QMETRY_DEFAULTS.PROJECT_KEY,
|
|
81
|
+
body,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { QMETRY_PATHS } from "../config/rest-endpoints.js";
|
|
2
|
+
import { DEFAULT_FETCH_REQUIREMENT_DETAILS_PAYLOAD, DEFAULT_FETCH_REQUIREMENTS_LINKED_TO_TESTCASE_PAYLOAD, DEFAULT_FETCH_REQUIREMENTS_PAYLOAD, } from "../types/requirements.js";
|
|
3
|
+
import { qmetryRequest } from "./api/client-api.js";
|
|
4
|
+
import { resolveDefaults } from "./utils.js";
|
|
5
|
+
/**
|
|
6
|
+
* Fetches a list of requirements.
|
|
7
|
+
* @throws If `viewId` or `folderPath` are missing/invalid.
|
|
8
|
+
*/
|
|
9
|
+
export async function fetchRequirements(token, baseUrl, project, payload) {
|
|
10
|
+
const { resolvedBaseUrl, resolvedProject } = resolveDefaults(baseUrl, project);
|
|
11
|
+
const body = {
|
|
12
|
+
...DEFAULT_FETCH_REQUIREMENTS_PAYLOAD,
|
|
13
|
+
...payload,
|
|
14
|
+
};
|
|
15
|
+
if (typeof body.viewId !== "number") {
|
|
16
|
+
throw new Error("[fetchRequirements] Missing or invalid required parameter: 'viewId'.");
|
|
17
|
+
}
|
|
18
|
+
if (typeof body.folderPath !== "string") {
|
|
19
|
+
throw new Error("[fetchRequirements] Missing or invalid required parameter: 'folderPath'.");
|
|
20
|
+
}
|
|
21
|
+
return qmetryRequest({
|
|
22
|
+
method: "POST",
|
|
23
|
+
path: QMETRY_PATHS.REQUIREMENT.GET_RQ_LIST,
|
|
24
|
+
token,
|
|
25
|
+
project: resolvedProject,
|
|
26
|
+
baseUrl: resolvedBaseUrl,
|
|
27
|
+
body,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Fetches requirement details by numeric ID.
|
|
32
|
+
* @throws If `id` or `version` are missing/invalid.
|
|
33
|
+
*/
|
|
34
|
+
export async function fetchRequirementDetails(token, baseUrl, project, payload) {
|
|
35
|
+
const { resolvedBaseUrl, resolvedProject } = resolveDefaults(baseUrl, project);
|
|
36
|
+
const body = {
|
|
37
|
+
...DEFAULT_FETCH_REQUIREMENT_DETAILS_PAYLOAD,
|
|
38
|
+
...payload,
|
|
39
|
+
};
|
|
40
|
+
if (typeof body.id !== "number") {
|
|
41
|
+
throw new Error("[fetchRequirementDetails] Missing or invalid required parameter: 'id'.");
|
|
42
|
+
}
|
|
43
|
+
if (typeof body.version !== "number") {
|
|
44
|
+
throw new Error("[fetchRequirementDetails] Missing or invalid required parameter: 'version'.");
|
|
45
|
+
}
|
|
46
|
+
return qmetryRequest({
|
|
47
|
+
method: "POST",
|
|
48
|
+
path: QMETRY_PATHS.REQUIREMENT.GET_RQ_DETAILS,
|
|
49
|
+
token,
|
|
50
|
+
project: resolvedProject,
|
|
51
|
+
baseUrl: resolvedBaseUrl,
|
|
52
|
+
body,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Fetches requirements linked to a specific test case.
|
|
57
|
+
* @throws If `tcID` is missing/invalid.
|
|
58
|
+
*/
|
|
59
|
+
export async function fetchRequirementsLinkedToTestCase(token, baseUrl, project, payload) {
|
|
60
|
+
const { resolvedBaseUrl, resolvedProject } = resolveDefaults(baseUrl, project);
|
|
61
|
+
const body = {
|
|
62
|
+
...DEFAULT_FETCH_REQUIREMENTS_LINKED_TO_TESTCASE_PAYLOAD,
|
|
63
|
+
...payload,
|
|
64
|
+
};
|
|
65
|
+
if (typeof body.tcID !== "number") {
|
|
66
|
+
throw new Error("[fetchRequirementsLinkedToTestCase] Missing or invalid required parameter: 'tcID'.");
|
|
67
|
+
}
|
|
68
|
+
return qmetryRequest({
|
|
69
|
+
method: "POST",
|
|
70
|
+
path: QMETRY_PATHS.REQUIREMENT.GET_RQ_LINKED_TO_TC,
|
|
71
|
+
token,
|
|
72
|
+
project: resolvedProject,
|
|
73
|
+
baseUrl: resolvedBaseUrl,
|
|
74
|
+
body,
|
|
75
|
+
});
|
|
76
|
+
}
|