@smartbear/mcp 0.8.0 → 0.10.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/README.md +29 -4
- package/dist/api-hub/client/api.js +239 -10
- package/dist/api-hub/client/configuration.js +5 -0
- package/dist/api-hub/client/index.js +1 -0
- package/dist/api-hub/client/portal-types.js +126 -0
- package/dist/api-hub/client/registry-types.js +8 -0
- package/dist/api-hub/client/tools.js +78 -15
- package/dist/api-hub/client/user-management-types.js +24 -0
- package/dist/api-hub/client.js +34 -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 +104 -155
- package/dist/collaborator/client.js +364 -0
- package/dist/common/server.js +73 -23
- package/dist/common/types.js +6 -1
- package/dist/index.js +9 -1
- package/dist/pactflow/client/prompt-utils.js +2 -1
- package/dist/pactflow/client/tools.js +4 -4
- 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 +96 -0
- package/dist/qmetry/client/handlers.js +33 -2
- package/dist/qmetry/client/issues.js +123 -0
- package/dist/qmetry/client/project.js +73 -0
- package/dist/qmetry/client/requirement.js +76 -0
- package/dist/qmetry/client/testcase.js +122 -6
- package/dist/qmetry/client/testsuite.js +272 -0
- package/dist/qmetry/client/tools/index.js +17 -0
- package/dist/qmetry/client/tools/issue-tools.js +545 -0
- package/dist/qmetry/client/tools/project-tools.js +348 -0
- package/dist/qmetry/client/tools/requirement-tools.js +530 -0
- package/dist/qmetry/client/tools/testcase-tools.js +526 -0
- package/dist/qmetry/client/tools/testsuite-tools.js +772 -0
- package/dist/qmetry/client/tools/types.js +1 -0
- package/dist/qmetry/client/utils.js +16 -0
- package/dist/qmetry/client.js +27 -17
- package/dist/qmetry/config/constants.js +28 -0
- package/dist/qmetry/config/rest-endpoints.js +30 -0
- package/dist/qmetry/types/common.js +599 -9
- package/dist/qmetry/types/issues.js +16 -0
- package/dist/qmetry/types/project.js +17 -0
- package/dist/qmetry/types/requirements.js +19 -0
- package/dist/qmetry/types/testcase.js +20 -0
- package/dist/qmetry/types/testsuite.js +44 -0
- package/dist/reflect/client.js +7 -6
- package/dist/zephyr/client.js +7 -1
- package/dist/zephyr/common/api-client.js +8 -0
- package/dist/zephyr/common/auth-service.js +1 -0
- package/dist/zephyr/common/rest-api-schemas.js +5173 -0
- package/dist/zephyr/tool/project/get-project.js +39 -0
- package/dist/zephyr/tool/project/get-projects.js +7 -13
- package/dist/zephyr/tool/test-cycle/get-test-cycles.js +72 -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
- package/dist/qmetry/client/tools.js +0 -222
- package/dist/zephyr/common/types.js +0 -35
package/dist/pactflow/client.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "../common/info.js";
|
|
2
|
+
import { ToolError, } from "../common/types.js";
|
|
2
3
|
import { getOADMatcherRecommendations, getUserMatcherSelection, } from "./client/prompt-utils.js";
|
|
3
4
|
import { PROMPTS } from "./client/prompts.js";
|
|
4
5
|
import { TOOLS } from "./client/tools.js";
|
|
@@ -65,7 +66,7 @@ export class PactflowClient {
|
|
|
65
66
|
body: JSON.stringify(toolInput),
|
|
66
67
|
});
|
|
67
68
|
if (!response.ok) {
|
|
68
|
-
throw new
|
|
69
|
+
throw new ToolError(`HTTP error! status: ${response.status} - ${await response.text()}`);
|
|
69
70
|
}
|
|
70
71
|
const status_response = await response.json();
|
|
71
72
|
return await this.pollForCompletion(status_response, "Generation");
|
|
@@ -93,7 +94,7 @@ export class PactflowClient {
|
|
|
93
94
|
body: JSON.stringify(toolInput),
|
|
94
95
|
});
|
|
95
96
|
if (!response.ok) {
|
|
96
|
-
throw new
|
|
97
|
+
throw new ToolError(`HTTP error! status: ${response.status} - ${await response.text()}`);
|
|
97
98
|
}
|
|
98
99
|
const status_response = await response.json();
|
|
99
100
|
return await this.pollForCompletion(status_response, "Review Pacts");
|
|
@@ -116,7 +117,7 @@ export class PactflowClient {
|
|
|
116
117
|
});
|
|
117
118
|
if (!response.ok) {
|
|
118
119
|
const errorText = await response.text().catch(() => "");
|
|
119
|
-
throw new
|
|
120
|
+
throw new ToolError(`PactFlow AI Entitlements Request Failed - status: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}`);
|
|
120
121
|
}
|
|
121
122
|
return (await response.json());
|
|
122
123
|
}
|
|
@@ -145,7 +146,7 @@ export class PactflowClient {
|
|
|
145
146
|
});
|
|
146
147
|
// Check if the response is OK (status 200)
|
|
147
148
|
if (!response.ok) {
|
|
148
|
-
throw new
|
|
149
|
+
throw new ToolError(`HTTP error! status: ${response.status}`);
|
|
149
150
|
}
|
|
150
151
|
return response.json();
|
|
151
152
|
}
|
|
@@ -161,12 +162,12 @@ export class PactflowClient {
|
|
|
161
162
|
return await this.getResult(status_response.result_url);
|
|
162
163
|
}
|
|
163
164
|
if (statusCheck.status !== 202) {
|
|
164
|
-
throw new
|
|
165
|
+
throw new ToolError(`${operationName} failed with status: ${statusCheck.status}`);
|
|
165
166
|
}
|
|
166
167
|
// Wait before next poll
|
|
167
168
|
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
168
169
|
}
|
|
169
|
-
throw new
|
|
170
|
+
throw new ToolError(`${operationName} timed out after ${timeout / 1000} seconds`);
|
|
170
171
|
}
|
|
171
172
|
// PactFlow / Pact_Broker client methods
|
|
172
173
|
async getProviderStates({ provider, }) {
|
|
@@ -176,7 +177,7 @@ export class PactflowClient {
|
|
|
176
177
|
headers: this.headers,
|
|
177
178
|
});
|
|
178
179
|
if (!response.ok) {
|
|
179
|
-
throw new
|
|
180
|
+
throw new ToolError(`HTTP error! status: ${response.status} - ${await response.text()}`);
|
|
180
181
|
}
|
|
181
182
|
return response.json();
|
|
182
183
|
}
|
|
@@ -206,7 +207,7 @@ export class PactflowClient {
|
|
|
206
207
|
});
|
|
207
208
|
if (!response.ok) {
|
|
208
209
|
const errorText = await response.text().catch(() => "");
|
|
209
|
-
throw new
|
|
210
|
+
throw new ToolError(`Can-I-Deploy Request Failed - status: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}`);
|
|
210
211
|
}
|
|
211
212
|
return (await response.json());
|
|
212
213
|
}
|
|
@@ -265,7 +266,7 @@ export class PactflowClient {
|
|
|
265
266
|
});
|
|
266
267
|
if (!response.ok) {
|
|
267
268
|
const errorText = await response.text().catch(() => "");
|
|
268
|
-
throw new
|
|
269
|
+
throw new ToolError(`Matrix Request Failed - status: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}`);
|
|
269
270
|
}
|
|
270
271
|
return (await response.json());
|
|
271
272
|
}
|
|
@@ -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,96 @@
|
|
|
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.CREATE_TEST_CASE,
|
|
14
|
+
folderIdPath: "rootFolders.TC.id",
|
|
15
|
+
folderIdField: "tcFolderID",
|
|
16
|
+
moduleName: "Test Cases",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
handler: QMetryToolsHandlers.FETCH_REQUIREMENTS,
|
|
20
|
+
viewIdPath: "latestViews.RQ.viewId",
|
|
21
|
+
moduleName: "Requirements",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
handler: QMetryToolsHandlers.FETCH_TEST_SUITES,
|
|
25
|
+
viewIdPath: "latestViews.TS.viewId",
|
|
26
|
+
moduleName: "Test Suites",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
handler: QMetryToolsHandlers.FETCH_TESTSUITES_FOR_TESTCASE,
|
|
30
|
+
viewIdPath: "latestViews.TSFS.viewId",
|
|
31
|
+
folderIdPath: "rootFolders.TS.id",
|
|
32
|
+
folderIdField: "tsFolderID",
|
|
33
|
+
moduleName: "Test Suites",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
handler: QMetryToolsHandlers.CREATE_TEST_SUITE,
|
|
37
|
+
folderIdPath: "rootFolders.TS.id",
|
|
38
|
+
folderIdField: "parentFolderId",
|
|
39
|
+
moduleName: "Test Suites",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
handler: QMetryToolsHandlers.FETCH_ISSUES,
|
|
43
|
+
viewIdPath: "latestViews.IS.viewId",
|
|
44
|
+
moduleName: "Issues",
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
/**
|
|
48
|
+
* Helper function to safely get nested property value using dot notation
|
|
49
|
+
* @param obj - Object to traverse
|
|
50
|
+
* @param path - Dot notation path (e.g., 'latestViews.TC.viewId')
|
|
51
|
+
* @returns The value at the path or undefined if not found
|
|
52
|
+
*/
|
|
53
|
+
export function getNestedProperty(obj, path) {
|
|
54
|
+
return path.split(".").reduce((current, key) => current?.[key], obj);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Generic auto-resolve function for viewId, folderPath, and folderID
|
|
58
|
+
* @param args - Tool arguments that may contain viewId/folderPath/folderID
|
|
59
|
+
* @param projectInfo - Project information from QMetry API
|
|
60
|
+
* @param config - Module configuration for this specific handler
|
|
61
|
+
* @returns Updated args with resolved values
|
|
62
|
+
*/
|
|
63
|
+
export function autoResolveViewIdAndFolderPath(args, projectInfo, config) {
|
|
64
|
+
const updatedArgs = { ...args };
|
|
65
|
+
let viewId = updatedArgs.viewId;
|
|
66
|
+
const folderPath = updatedArgs.folderPath;
|
|
67
|
+
// Auto-resolve viewId if not provided and config has viewIdPath
|
|
68
|
+
if (!viewId && config.viewIdPath) {
|
|
69
|
+
viewId = getNestedProperty(projectInfo, config.viewIdPath);
|
|
70
|
+
if (viewId) {
|
|
71
|
+
updatedArgs.viewId = viewId;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Auto-resolve folderPath if not provided (defaults to root)
|
|
75
|
+
if (folderPath === undefined) {
|
|
76
|
+
updatedArgs.folderPath = "";
|
|
77
|
+
}
|
|
78
|
+
// Auto-resolve folder ID if not provided and config has folderIdPath
|
|
79
|
+
if (config.folderIdPath &&
|
|
80
|
+
config.folderIdField &&
|
|
81
|
+
!updatedArgs[config.folderIdField]) {
|
|
82
|
+
const folderId = getNestedProperty(projectInfo, config.folderIdPath);
|
|
83
|
+
if (folderId) {
|
|
84
|
+
updatedArgs[config.folderIdField] = folderId;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return updatedArgs;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Find the auto-resolve configuration for a given handler
|
|
91
|
+
* @param handler - The handler name to find config for
|
|
92
|
+
* @returns Module configuration or undefined if not found
|
|
93
|
+
*/
|
|
94
|
+
export function findAutoResolveConfig(handler) {
|
|
95
|
+
return AUTO_RESOLVE_MODULES.find((module) => module.handler === handler);
|
|
96
|
+
}
|
|
@@ -1,11 +1,42 @@
|
|
|
1
1
|
import { QMetryToolsHandlers } from "../config/constants.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { createIssue, fetchIssues, fetchIssuesLinkedToTestCase, linkIssuesToTestcaseRun, updateIssue, } from "./issues.js";
|
|
3
|
+
import { getBuilds, getPlatforms, getProjectInfo, getProjects, getReleasesCycles, } from "./project.js";
|
|
4
|
+
import { fetchRequirementDetails, fetchRequirements, fetchRequirementsLinkedToTestCase, } from "./requirement.js";
|
|
5
|
+
import { createTestCases, fetchTestCaseDetails, fetchTestCaseExecutions, fetchTestCaseSteps, fetchTestCases, fetchTestCasesLinkedToRequirement, fetchTestCaseVersionDetails, linkRequirementToTestCase, updateTestCase, } from "./testcase.js";
|
|
6
|
+
import { createTestSuites, fetchExecutionsByTestSuite, fetchLinkedIssuesByTestCaseRun, fetchTestCaseRunsByTestSuiteRun, fetchTestCasesByTestSuite, fetchTestSuites, fetchTestSuitesForTestCase, linkPlatformsToTestSuite, linkTestCasesToTestSuite, reqLinkedTestCasesToTestSuite, updateTestSuite, } from "./testsuite.js";
|
|
4
7
|
export const QMETRY_HANDLER_MAP = {
|
|
8
|
+
[QMetryToolsHandlers.FETCH_PROJECTS]: getProjects,
|
|
5
9
|
[QMetryToolsHandlers.SET_PROJECT_INFO]: getProjectInfo,
|
|
6
10
|
[QMetryToolsHandlers.FETCH_PROJECT_INFO]: getProjectInfo,
|
|
11
|
+
[QMetryToolsHandlers.FETCH_RELEASES_CYCLES]: getReleasesCycles,
|
|
12
|
+
[QMetryToolsHandlers.FETCH_BUILDS]: getBuilds,
|
|
13
|
+
[QMetryToolsHandlers.FETCH_PLATFORMS]: getPlatforms,
|
|
14
|
+
[QMetryToolsHandlers.CREATE_TEST_CASE]: createTestCases,
|
|
15
|
+
[QMetryToolsHandlers.UPDATE_TEST_CASE]: updateTestCase,
|
|
7
16
|
[QMetryToolsHandlers.FETCH_TEST_CASES]: fetchTestCases,
|
|
8
17
|
[QMetryToolsHandlers.FETCH_TEST_CASE_DETAILS]: fetchTestCaseDetails,
|
|
9
18
|
[QMetryToolsHandlers.FETCH_TEST_CASE_VERSION_DETAILS]: fetchTestCaseVersionDetails,
|
|
10
19
|
[QMetryToolsHandlers.FETCH_TEST_CASE_STEPS]: fetchTestCaseSteps,
|
|
20
|
+
[QMetryToolsHandlers.FETCH_TEST_CASE_EXECUTIONS]: fetchTestCaseExecutions,
|
|
21
|
+
[QMetryToolsHandlers.LINK_REQUIREMENT_TO_TESTCASE]: linkRequirementToTestCase,
|
|
22
|
+
[QMetryToolsHandlers.FETCH_REQUIREMENTS]: fetchRequirements,
|
|
23
|
+
[QMetryToolsHandlers.FETCH_REQUIREMENT_DETAILS]: fetchRequirementDetails,
|
|
24
|
+
[QMetryToolsHandlers.FETCH_TESTCASES_LINKED_TO_REQUIREMENT]: fetchTestCasesLinkedToRequirement,
|
|
25
|
+
[QMetryToolsHandlers.FETCH_REQUIREMENTS_LINKED_TO_TESTCASE]: fetchRequirementsLinkedToTestCase,
|
|
26
|
+
[QMetryToolsHandlers.CREATE_TEST_SUITE]: createTestSuites,
|
|
27
|
+
[QMetryToolsHandlers.UPDATE_TEST_SUITE]: updateTestSuite,
|
|
28
|
+
[QMetryToolsHandlers.FETCH_TEST_SUITES]: fetchTestSuites,
|
|
29
|
+
[QMetryToolsHandlers.FETCH_TESTSUITES_FOR_TESTCASE]: fetchTestSuitesForTestCase,
|
|
30
|
+
[QMetryToolsHandlers.LINK_TESTCASES_TO_TESTSUITE]: linkTestCasesToTestSuite,
|
|
31
|
+
[QMetryToolsHandlers.REQUIREMENTS_LINKED_TESTCASES_TO_TESTSUITE]: reqLinkedTestCasesToTestSuite,
|
|
32
|
+
[QMetryToolsHandlers.FETCH_TESTCASES_BY_TESTSUITE]: fetchTestCasesByTestSuite,
|
|
33
|
+
[QMetryToolsHandlers.FETCH_EXECUTIONS_BY_TESTSUITE]: fetchExecutionsByTestSuite,
|
|
34
|
+
[QMetryToolsHandlers.FETCH_TESTCASE_RUNS_BY_TESTSUITE_RUN]: fetchTestCaseRunsByTestSuiteRun,
|
|
35
|
+
[QMetryToolsHandlers.FETCH_LINKED_ISSUES_BY_TESTCASE_RUN]: fetchLinkedIssuesByTestCaseRun,
|
|
36
|
+
[QMetryToolsHandlers.FETCH_ISSUES_LINKED_TO_TESTCASE]: fetchIssuesLinkedToTestCase,
|
|
37
|
+
[QMetryToolsHandlers.CREATE_ISSUE]: createIssue,
|
|
38
|
+
[QMetryToolsHandlers.UPDATE_ISSUE]: updateIssue,
|
|
39
|
+
[QMetryToolsHandlers.FETCH_ISSUES]: fetchIssues,
|
|
40
|
+
[QMetryToolsHandlers.LINK_ISSUES_TO_TESTCASE_RUN]: linkIssuesToTestcaseRun,
|
|
41
|
+
[QMetryToolsHandlers.LINK_PLATFORMS_TO_TESTSUITE]: linkPlatformsToTestSuite,
|
|
11
42
|
};
|