@smartbear/mcp 0.23.0 → 0.24.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 +10 -2
- package/dist/package.json.js +1 -1
- package/dist/pactflow/client/base.js +3 -3
- package/dist/qtm4j/client.js +26 -2
- package/dist/qtm4j/config/constants.js +96 -4
- package/dist/qtm4j/config/field-resolution.types.js +2 -1
- package/dist/qtm4j/http/api-client.js +70 -1
- package/dist/qtm4j/resolver/cache/cache.js +1 -1
- package/dist/qtm4j/resolver/resolvers/common-attribute-resolver.js +1 -0
- package/dist/qtm4j/resolver/resolvers/component-resolver.js +2 -0
- package/dist/qtm4j/resolver/resolvers/label-resolver.js +2 -0
- package/dist/qtm4j/schema/automation.schema.js +107 -0
- package/dist/qtm4j/schema/search-test-cycle.schema.js +133 -0
- package/dist/qtm4j/schema/test-cycle.schema.js +39 -0
- package/dist/qtm4j/schema/update-test-cycle.schema.js +54 -0
- package/dist/qtm4j/tool/test-automation/get-automation-history.js +69 -0
- package/dist/qtm4j/tool/test-automation/upload-automation-result.js +143 -0
- package/dist/qtm4j/tool/test-cycle/create-test-cycle.js +80 -0
- package/dist/qtm4j/tool/test-cycle/search-test-cycle.js +136 -0
- package/dist/qtm4j/tool/test-cycle/update-test-cycle.js +161 -0
- package/dist/swagger/client/portal-types.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -107,7 +107,8 @@ Alternatively, you can use `npx` (or globally install) the `@smartbear/mcp` pack
|
|
|
107
107
|
"COLLABORATOR_USERNAME": "${input:collab_username}",
|
|
108
108
|
"COLLABORATOR_LOGIN_TICKET": "${input:collab_login_ticket}",
|
|
109
109
|
"QTM4J_API_KEY": "${input:qtm4j_api_key}",
|
|
110
|
-
"QTM4J_BASE_URL": "${input:qtm4j_base_url}"
|
|
110
|
+
"QTM4J_BASE_URL": "${input:qtm4j_base_url}",
|
|
111
|
+
"QTM4J_AUTOMATION_API_KEY": "${input:qtm4j_automation_api_key}"
|
|
111
112
|
}
|
|
112
113
|
}
|
|
113
114
|
},
|
|
@@ -243,6 +244,12 @@ Alternatively, you can use `npx` (or globally install) the `@smartbear/mcp` pack
|
|
|
243
244
|
"type": "promptString",
|
|
244
245
|
"description": "US region (default): https://qtmcloud.qmetry.com. Australia region: https://syd-qtmcloud.qmetry.com.",
|
|
245
246
|
"password": false
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
"id": "qtm4j_automation_api_key",
|
|
250
|
+
"type": "promptString",
|
|
251
|
+
"description": "QTM4J Automation API Key - required for automation tools, leave blank to disable them",
|
|
252
|
+
"password": true
|
|
246
253
|
}
|
|
247
254
|
]
|
|
248
255
|
}
|
|
@@ -283,7 +290,8 @@ Add the following configuration to your `claude_desktop_config.json` to launch t
|
|
|
283
290
|
"COLLABORATOR_USERNAME": "your collab user name",
|
|
284
291
|
"COLLABORATOR_LOGIN_TICKET": "your collab login ticket",
|
|
285
292
|
"QTM4J_API_KEY": "your_qtm4j_key",
|
|
286
|
-
"QTM4J_BASE_URL": "https://qtmcloud.qmetry.com"
|
|
293
|
+
"QTM4J_BASE_URL": "https://qtmcloud.qmetry.com",
|
|
294
|
+
"QTM4J_AUTOMATION_API_KEY": "your_qtm4j_automation_api_key"
|
|
287
295
|
}
|
|
288
296
|
}
|
|
289
297
|
}
|
package/dist/package.json.js
CHANGED
|
@@ -308,7 +308,7 @@ const AdminUserIdSchema = z.object({
|
|
|
308
308
|
userId: z.string().describe("UUID of the user")
|
|
309
309
|
});
|
|
310
310
|
const CreateAdminUserSchema = z.object({
|
|
311
|
-
email: z.string().
|
|
311
|
+
email: z.string().describe("Email address of the new user"),
|
|
312
312
|
name: z.string().describe("Display name of the new user"),
|
|
313
313
|
firstName: z.string().optional().describe("First name"),
|
|
314
314
|
lastName: z.string().optional().describe("Last name"),
|
|
@@ -318,7 +318,7 @@ const CreateAdminUserSchema = z.object({
|
|
|
318
318
|
const UpdateAdminUserSchema = z.object({
|
|
319
319
|
userId: z.string().describe("UUID of the user to update"),
|
|
320
320
|
active: z.boolean().optional().describe("Whether the user is active"),
|
|
321
|
-
email: z.string().
|
|
321
|
+
email: z.string().optional().describe("New email address"),
|
|
322
322
|
firstName: z.string().optional().describe("First name"),
|
|
323
323
|
lastName: z.string().optional().describe("Last name"),
|
|
324
324
|
name: z.string().optional().describe("Display name")
|
|
@@ -326,7 +326,7 @@ const UpdateAdminUserSchema = z.object({
|
|
|
326
326
|
const InviteUsersSchema = z.object({
|
|
327
327
|
users: z.array(
|
|
328
328
|
z.object({
|
|
329
|
-
email: z.string().
|
|
329
|
+
email: z.string().describe("Email address"),
|
|
330
330
|
name: z.string().min(1).describe("Display name")
|
|
331
331
|
})
|
|
332
332
|
).min(1).describe("List of users to invite")
|
package/dist/qtm4j/client.js
CHANGED
|
@@ -5,6 +5,7 @@ import { ApiClient } from "./http/api-client.js";
|
|
|
5
5
|
import { ResolverRegistry } from "./resolver/resolver-registry.js";
|
|
6
6
|
const ConfigurationSchema = zod__default.object({
|
|
7
7
|
[CONFIG_KEYS.API_KEY]: zod__default.string().describe(SCHEMA_DESCRIPTIONS.API_KEY),
|
|
8
|
+
[CONFIG_KEYS.AUTOMATION_API_KEY]: zod__default.string().optional().describe(SCHEMA_DESCRIPTIONS.AUTOMATION_API_KEY),
|
|
8
9
|
[CONFIG_KEYS.BASE_URL]: zod__default.string().url().optional().default(API_CONFIG.DEFAULT_BASE_URL).describe(SCHEMA_DESCRIPTIONS.BASE_URL)
|
|
9
10
|
});
|
|
10
11
|
class Qtm4jClient {
|
|
@@ -13,6 +14,7 @@ class Qtm4jClient {
|
|
|
13
14
|
configPrefix = CLIENT_CONFIG.CONFIG_PREFIX;
|
|
14
15
|
config = ConfigurationSchema;
|
|
15
16
|
_apiKey;
|
|
17
|
+
_automationApiKey;
|
|
16
18
|
baseUrl = API_CONFIG.DEFAULT_BASE_URL;
|
|
17
19
|
apiClient;
|
|
18
20
|
resolverRegistry;
|
|
@@ -23,10 +25,15 @@ class Qtm4jClient {
|
|
|
23
25
|
*/
|
|
24
26
|
async configure(server, config) {
|
|
25
27
|
this._apiKey = config[CONFIG_KEYS.API_KEY];
|
|
28
|
+
this._automationApiKey = config[CONFIG_KEYS.AUTOMATION_API_KEY];
|
|
26
29
|
if (config[CONFIG_KEYS.BASE_URL]) {
|
|
27
30
|
this.baseUrl = config[CONFIG_KEYS.BASE_URL];
|
|
28
31
|
}
|
|
29
|
-
this.apiClient = new ApiClient(
|
|
32
|
+
this.apiClient = new ApiClient(
|
|
33
|
+
() => this.getAuthToken(),
|
|
34
|
+
this.baseUrl,
|
|
35
|
+
() => this.getAutomationApiKey()
|
|
36
|
+
);
|
|
30
37
|
this.resolverRegistry = new ResolverRegistry(
|
|
31
38
|
this.apiClient,
|
|
32
39
|
server.getCache()
|
|
@@ -48,6 +55,13 @@ class Qtm4jClient {
|
|
|
48
55
|
}
|
|
49
56
|
return this._apiKey || null;
|
|
50
57
|
}
|
|
58
|
+
getAutomationApiKey() {
|
|
59
|
+
const headerKey = getRequestHeader("Qtm4j-Automation-Api-Key");
|
|
60
|
+
if (headerKey) {
|
|
61
|
+
return Array.isArray(headerKey) ? headerKey[0] : headerKey;
|
|
62
|
+
}
|
|
63
|
+
return this._automationApiKey || null;
|
|
64
|
+
}
|
|
51
65
|
/**
|
|
52
66
|
* Check if the client is properly configured
|
|
53
67
|
* @returns true if API key is set and client is ready
|
|
@@ -91,13 +105,23 @@ class Qtm4jClient {
|
|
|
91
105
|
const { GetTestCases } = await import("./tool/test-case/get-test-cases.js");
|
|
92
106
|
const { GetTestSteps } = await import("./tool/test-case/get-test-steps.js");
|
|
93
107
|
const { UpdateTestCase } = await import("./tool/test-case/update-test-case.js");
|
|
108
|
+
const { UploadAutomationResult } = await import("./tool/test-automation/upload-automation-result.js");
|
|
109
|
+
const { GetAutomationHistory } = await import("./tool/test-automation/get-automation-history.js");
|
|
110
|
+
const { CreateTestCycle } = await import("./tool/test-cycle/create-test-cycle.js");
|
|
111
|
+
const { SearchTestCycles } = await import("./tool/test-cycle/search-test-cycle.js");
|
|
112
|
+
const { UpdateTestCycle } = await import("./tool/test-cycle/update-test-cycle.js");
|
|
94
113
|
const tools = [
|
|
95
114
|
new GetProjects(this),
|
|
96
115
|
new SetProjectContext(this),
|
|
97
116
|
new CreateTestCase(this),
|
|
98
117
|
new GetTestCases(this),
|
|
99
118
|
new GetTestSteps(this),
|
|
100
|
-
new UpdateTestCase(this)
|
|
119
|
+
new UpdateTestCase(this),
|
|
120
|
+
new CreateTestCycle(this),
|
|
121
|
+
new SearchTestCycles(this),
|
|
122
|
+
new UpdateTestCycle(this),
|
|
123
|
+
new UploadAutomationResult(this),
|
|
124
|
+
new GetAutomationHistory(this)
|
|
101
125
|
];
|
|
102
126
|
for (const tool of tools) {
|
|
103
127
|
register(tool.specification, tool.handle);
|
|
@@ -13,6 +13,12 @@ const ENDPOINTS = {
|
|
|
13
13
|
SEARCH_TEST_CASES: `${API_CONFIG.API_VERSION}/testcases/search`,
|
|
14
14
|
/** Resolve test case keys → internal UIDs for a given project */
|
|
15
15
|
RESOLVE_TEST_CASE_IDS: (projectId) => `${API_CONFIG.API_VERSION}/projects/${projectId}/mcp/testcases/resolve-ids`,
|
|
16
|
+
/** Update test cycle endpoint */
|
|
17
|
+
UPDATE_TEST_CYCLE: (id) => `${API_CONFIG.API_VERSION}/testcycles/${id}`,
|
|
18
|
+
/** Create test cycle endpoint */
|
|
19
|
+
CREATE_TEST_CYCLE: `${API_CONFIG.API_VERSION}/testcycles`,
|
|
20
|
+
/** Search test cycles endpoint */
|
|
21
|
+
SEARCH_TEST_CYCLES: `${API_CONFIG.API_VERSION}/testcycles/search`,
|
|
16
22
|
/** Update test case endpoint */
|
|
17
23
|
UPDATE_TEST_CASE: (id, versionNo) => `${API_CONFIG.API_VERSION}/testcases/${id}/versions/${versionNo}`,
|
|
18
24
|
/** Test steps search endpoint */
|
|
@@ -22,7 +28,13 @@ const ENDPOINTS = {
|
|
|
22
28
|
/** Labels search endpoint */
|
|
23
29
|
LABELS: (projectId) => `${API_CONFIG.API_VERSION}/projects/${projectId}/mcp/labels`,
|
|
24
30
|
/** Components search endpoint */
|
|
25
|
-
COMPONENTS: (projectId) => `${API_CONFIG.API_VERSION}/projects/${projectId}/mcp/components
|
|
31
|
+
COMPONENTS: (projectId) => `${API_CONFIG.API_VERSION}/projects/${projectId}/mcp/components`,
|
|
32
|
+
/** Automation: initiate result file import — returns pre-signed S3 upload URL + trackingId */
|
|
33
|
+
AUTOMATION_IMPORT: "/rest/api/automation/importresult",
|
|
34
|
+
/** Automation: poll import progress by trackingId */
|
|
35
|
+
AUTOMATION_IMPORT_TRACK: "/rest/api/automation/importresult/track",
|
|
36
|
+
/** Automation: paginated history of past automation result uploads */
|
|
37
|
+
AUTOMATION_HISTORY: "/rest/api/automation/importresult/history"
|
|
26
38
|
};
|
|
27
39
|
const HTTP_HEADERS = {
|
|
28
40
|
/** API key header name */
|
|
@@ -38,7 +50,9 @@ const HTTP_HEADERS = {
|
|
|
38
50
|
};
|
|
39
51
|
const CONTENT_TYPES = {
|
|
40
52
|
/** JSON content type */
|
|
41
|
-
JSON: "application/json"
|
|
53
|
+
JSON: "application/json",
|
|
54
|
+
/** Multipart form-data content type (used for S3 automation file uploads) */
|
|
55
|
+
MULTIPART: "multipart/form-data"
|
|
42
56
|
};
|
|
43
57
|
const HTTP_METHODS = {
|
|
44
58
|
GET: "GET",
|
|
@@ -72,14 +86,24 @@ const PAGINATION = {
|
|
|
72
86
|
DEFAULT_MAX_RESULTS_TEST_STEPS: 50,
|
|
73
87
|
/** Maximum allowed results per request for test steps */
|
|
74
88
|
MAX_ALLOWED_RESULTS_TEST_STEPS: 100,
|
|
89
|
+
/** Default maximum results for test cycles */
|
|
90
|
+
DEFAULT_MAX_RESULTS_TEST_CYCLES: 20,
|
|
91
|
+
/** Maximum allowed results per request for test cycles */
|
|
92
|
+
MAX_ALLOWED_RESULTS_TEST_CYCLES: 100,
|
|
75
93
|
/** Minimum allowed results per request */
|
|
76
94
|
MIN_ALLOWED_RESULTS: 1
|
|
77
95
|
};
|
|
96
|
+
const AUTOMATION_LIMITS = {
|
|
97
|
+
/** Maximum allowed upload file size in bytes (10 MB) */
|
|
98
|
+
MAX_FILE_SIZE_BYTES: 10 * 1024 * 1024
|
|
99
|
+
};
|
|
78
100
|
const ERROR_MESSAGES = {
|
|
79
101
|
/** Client not configured error */
|
|
80
102
|
CLIENT_NOT_CONFIGURED: "QTM4J client not configured. Please set API key.",
|
|
81
103
|
/** Request failed template */
|
|
82
|
-
REQUEST_FAILED: (status, errorText) => `Request failed with status ${status}: ${errorText}
|
|
104
|
+
REQUEST_FAILED: (status, errorText) => `Request failed with status ${status}: ${errorText}`,
|
|
105
|
+
/** Automation API key not configured */
|
|
106
|
+
AUTOMATION_API_KEY_NOT_CONFIGURED: "QTM4J Automation API key not configured. Set the QTM4J_AUTOMATION_API_KEY environment variable or pass the Qtm4j-Automation-Api-Key header."
|
|
83
107
|
};
|
|
84
108
|
const TOOL_NAMES = {
|
|
85
109
|
/** Get Projects tool */
|
|
@@ -111,17 +135,46 @@ const TOOL_NAMES = {
|
|
|
111
135
|
UPDATE_TEST_CASE: {
|
|
112
136
|
TITLE: "Update Test Case",
|
|
113
137
|
SUMMARY: "Update an existing test case in QTM4J. Supports auto-resolving human-readable names for priority, status, labels, and components. Labels and components support add/delete operations."
|
|
138
|
+
},
|
|
139
|
+
/** Upload Automation Result tool */
|
|
140
|
+
UPLOAD_AUTOMATION_RESULT: {
|
|
141
|
+
TITLE: "Upload Automation Result",
|
|
142
|
+
SUMMARY: "Upload an automation result file to QTM4J and map the results to a test cycle. Supports JUnit XML, TestNG XML, Cucumber JSON, QAF, HP UFT, and SpecFlow formats."
|
|
143
|
+
},
|
|
144
|
+
/** Get Automation History tool */
|
|
145
|
+
GET_AUTOMATION_HISTORY: {
|
|
146
|
+
TITLE: "Get Automation History",
|
|
147
|
+
SUMMARY: "Retrieve a paginated history of past automation result uploads for a QTM4J project."
|
|
148
|
+
},
|
|
149
|
+
/** Search Test Cycles tool */
|
|
150
|
+
SEARCH_TEST_CYCLES: {
|
|
151
|
+
TITLE: "Search Test Cycles",
|
|
152
|
+
SUMMARY: "Search for test cycles in a QTM4J project by status, owner, folder, date range, or keyword. projectId is injected automatically from the active project context."
|
|
153
|
+
},
|
|
154
|
+
/** Create Test Cycle tool */
|
|
155
|
+
CREATE_TEST_CYCLE: {
|
|
156
|
+
TITLE: "Create Test Cycle",
|
|
157
|
+
SUMMARY: "Create a new test cycle in a QTM4J project. Supports auto-resolving human-readable names for priority and status. Always creates in the 'MCP Generated' folder. projectId is injected automatically from the active project context."
|
|
158
|
+
},
|
|
159
|
+
/** Update Test Cycle tool */
|
|
160
|
+
UPDATE_TEST_CYCLE: {
|
|
161
|
+
TITLE: "Update Test Cycle",
|
|
162
|
+
SUMMARY: "Update an existing test cycle in QTM4J by its human-readable key (e.g. 'SCRUM-TR-101'). Supports auto-resolving human-readable names for status and priority. Labels and components support add/delete operations. Only the fields you provide are changed — omitted fields are left as-is. projectId is injected automatically from the active project context."
|
|
114
163
|
}
|
|
115
164
|
};
|
|
116
165
|
const CONFIG_KEYS = {
|
|
117
166
|
/** API key configuration key */
|
|
118
167
|
API_KEY: "api_key",
|
|
168
|
+
/** Automation API key configuration key */
|
|
169
|
+
AUTOMATION_API_KEY: "automation_api_key",
|
|
119
170
|
/** Base URL configuration key */
|
|
120
171
|
BASE_URL: "base_url"
|
|
121
172
|
};
|
|
122
173
|
const SCHEMA_DESCRIPTIONS = {
|
|
123
174
|
/** API key description */
|
|
124
175
|
API_KEY: "QTM4J API key for authentication",
|
|
176
|
+
/** Automation API key description */
|
|
177
|
+
AUTOMATION_API_KEY: "QTM4J Automation API key for uploading automation result files. This is a separate key from the regular API key and can be found in QTM4J under Automation settings.",
|
|
125
178
|
/** Base URL description */
|
|
126
179
|
BASE_URL: "QTM4J base URL (default: https://qtmcloud.qmetry.com). Can be customized for on-premise installations.",
|
|
127
180
|
/** Start at description */
|
|
@@ -133,8 +186,40 @@ const SCHEMA_DESCRIPTIONS = {
|
|
|
133
186
|
/** QMetry enabled description */
|
|
134
187
|
QMETRY_ENABLED: "Filter by QMetry enabled status",
|
|
135
188
|
/** Project object description */
|
|
136
|
-
PROJECT_OBJECT: "Project object"
|
|
189
|
+
PROJECT_OBJECT: "Project object",
|
|
190
|
+
/** Automation result file path */
|
|
191
|
+
AUTOMATION_FILE_PATH: "Path to the automation result file on disk. Filesystem contents can change between turns — always resolve this from a fresh scan, never from a previously seen path. Supported extensions: .xml, .json, .zip",
|
|
192
|
+
/** Automation result format */
|
|
193
|
+
AUTOMATION_FORMAT: "Format of the result file. Supported values: cucumber, testng, junit, qaf, hpuft, specflow",
|
|
194
|
+
/** Test cycle to reuse */
|
|
195
|
+
AUTOMATION_TEST_CYCLE_TO_REUSE: "Work key of an existing test cycle to reuse (e.g. 'TR-PRJ-1'). If omitted, a new test cycle is created.",
|
|
196
|
+
/** Automation environment */
|
|
197
|
+
AUTOMATION_ENVIRONMENT: "Name of the environment on which the test cycle was executed (e.g. 'Chrome', 'Staging'). Defaults to 'No Environment'.",
|
|
198
|
+
/** Automation build */
|
|
199
|
+
AUTOMATION_BUILD: "Build name or version for the test cycle execution (e.g. '1.0.0-beta'). Defaults to blank.",
|
|
200
|
+
/** isZip flag */
|
|
201
|
+
AUTOMATION_IS_ZIP: "Set to true when uploading a ZIP archive containing result files. Required for QAF format.",
|
|
202
|
+
/** attachFile flag */
|
|
203
|
+
AUTOMATION_ATTACH_FILE: "Set to true to upload attachments referenced in execution results.",
|
|
204
|
+
/** matchTestSteps flag */
|
|
205
|
+
AUTOMATION_MATCH_TEST_STEPS: "true — match test cases by summary AND test steps. false — match by summary or key only.",
|
|
206
|
+
/** appendTestName flag */
|
|
207
|
+
AUTOMATION_APPEND_TEST_NAME: "Applicable to JUnit/TestNG only. Appends suite/test name to method name in test case summary.",
|
|
208
|
+
/** fields object */
|
|
209
|
+
AUTOMATION_FIELDS: "Additional fields to set on the test cycle, test case, and/or test case execution created during import.",
|
|
210
|
+
/** getAutomationHistory — pagination */
|
|
211
|
+
AUTOMATION_HISTORY_START_AT: "Zero-indexed starting position for pagination (default: 0).",
|
|
212
|
+
AUTOMATION_HISTORY_MAX_RESULTS: "Maximum number of records to return per page (default: 20, max: 100)."
|
|
137
213
|
};
|
|
214
|
+
const AUTOMATION_RESULT_DIRS = [
|
|
215
|
+
"target/surefire-reports",
|
|
216
|
+
"target/failsafe-reports",
|
|
217
|
+
"build/reports/tests",
|
|
218
|
+
"build/test-results",
|
|
219
|
+
"test-results",
|
|
220
|
+
"reports",
|
|
221
|
+
"cucumber-reports"
|
|
222
|
+
];
|
|
138
223
|
const RESPONSE_FIELDS = {
|
|
139
224
|
/** Start at field */
|
|
140
225
|
START_AT: "startAt",
|
|
@@ -143,6 +228,10 @@ const RESPONSE_FIELDS = {
|
|
|
143
228
|
FIELDS: "fields",
|
|
144
229
|
SORT: "sort"
|
|
145
230
|
};
|
|
231
|
+
const SORT_DEFAULTS = {
|
|
232
|
+
/** Default sort expression for test cycle search */
|
|
233
|
+
TEST_CYCLES: "key:asc"
|
|
234
|
+
};
|
|
146
235
|
const EMPTY_VALUES = {
|
|
147
236
|
/** Empty object */
|
|
148
237
|
OBJECT: {},
|
|
@@ -153,6 +242,8 @@ const EMPTY_VALUES = {
|
|
|
153
242
|
};
|
|
154
243
|
export {
|
|
155
244
|
API_CONFIG,
|
|
245
|
+
AUTOMATION_LIMITS,
|
|
246
|
+
AUTOMATION_RESULT_DIRS,
|
|
156
247
|
CLIENT_CONFIG,
|
|
157
248
|
CONFIG_KEYS,
|
|
158
249
|
CONTENT_TYPES,
|
|
@@ -165,5 +256,6 @@ export {
|
|
|
165
256
|
PAGINATION,
|
|
166
257
|
RESPONSE_FIELDS,
|
|
167
258
|
SCHEMA_DESCRIPTIONS,
|
|
259
|
+
SORT_DEFAULTS,
|
|
168
260
|
TOOL_NAMES
|
|
169
261
|
};
|
|
@@ -8,7 +8,8 @@ const ResolverKeys = {
|
|
|
8
8
|
TEST_PLAN_STATUS: "testplan_status",
|
|
9
9
|
TEST_CYCLE_STATUS: "testcycle_status",
|
|
10
10
|
PRIORITY: "priority",
|
|
11
|
-
TESTCASE_FOLDER: "testcase_folder"
|
|
11
|
+
TESTCASE_FOLDER: "testcase_folder",
|
|
12
|
+
TEST_CYCLE_FOLDER: "testcycle_folder"
|
|
12
13
|
},
|
|
13
14
|
/**
|
|
14
15
|
* Fields with dedicated search APIs (fetched on-demand, not batch-loaded).
|
|
@@ -4,13 +4,15 @@ import { AuthService } from "./auth-service.js";
|
|
|
4
4
|
class ApiClient {
|
|
5
5
|
baseUrl;
|
|
6
6
|
tokenProvider;
|
|
7
|
-
|
|
7
|
+
automationTokenProvider;
|
|
8
|
+
constructor(tokenOrProvider, baseUrl, automationTokenProvider) {
|
|
8
9
|
this.baseUrl = baseUrl.trim().replace(/\/$/, EMPTY_VALUES.STRING);
|
|
9
10
|
if (typeof tokenOrProvider === "string") {
|
|
10
11
|
this.tokenProvider = () => tokenOrProvider;
|
|
11
12
|
} else {
|
|
12
13
|
this.tokenProvider = tokenOrProvider;
|
|
13
14
|
}
|
|
15
|
+
this.automationTokenProvider = automationTokenProvider;
|
|
14
16
|
}
|
|
15
17
|
/**
|
|
16
18
|
* Get authentication headers for current request
|
|
@@ -25,6 +27,17 @@ class ApiClient {
|
|
|
25
27
|
}
|
|
26
28
|
return new AuthService(token).getAuthHeaders();
|
|
27
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Get authentication headers using the automation API key.
|
|
32
|
+
* @throws ToolError if automation API key is not configured
|
|
33
|
+
*/
|
|
34
|
+
getAutomationHeaders() {
|
|
35
|
+
const token = this.automationTokenProvider?.();
|
|
36
|
+
if (!token) {
|
|
37
|
+
throw new ToolError(ERROR_MESSAGES.AUTOMATION_API_KEY_NOT_CONFIGURED);
|
|
38
|
+
}
|
|
39
|
+
return new AuthService(token).getAuthHeaders();
|
|
40
|
+
}
|
|
28
41
|
/**
|
|
29
42
|
* Construct full URL with query parameters
|
|
30
43
|
* @param endpoint - API endpoint path
|
|
@@ -89,6 +102,62 @@ class ApiClient {
|
|
|
89
102
|
});
|
|
90
103
|
return await this.validateAndGetResponseBody(response);
|
|
91
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Perform GET request using the automation API key (QTM4J_AUTOMATION_API_KEY).
|
|
107
|
+
* Used for automation endpoints that require the automation key instead of the regular API key.
|
|
108
|
+
* @param endpoint - API endpoint path
|
|
109
|
+
* @param params - Optional query parameters
|
|
110
|
+
* @returns Parsed response data
|
|
111
|
+
*/
|
|
112
|
+
async getAutomation(endpoint, params) {
|
|
113
|
+
const response = await fetch(this.getUrl(endpoint, params), {
|
|
114
|
+
method: HTTP_METHODS.GET,
|
|
115
|
+
headers: this.getAutomationHeaders()
|
|
116
|
+
});
|
|
117
|
+
return await this.validateAndGetResponseBody(response);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Perform POST request using the automation API key (QTM4J_AUTOMATION_API_KEY).
|
|
121
|
+
* Used for automation import endpoints — same apiKey header as all other APIs,
|
|
122
|
+
* but the value is the automation key instead of the regular API key.
|
|
123
|
+
* @param endpoint - API endpoint path
|
|
124
|
+
* @param body - Request body object
|
|
125
|
+
* @returns Parsed response data
|
|
126
|
+
*/
|
|
127
|
+
async postAutomation(endpoint, body) {
|
|
128
|
+
const url = this.getUrl(endpoint);
|
|
129
|
+
const requestHeaders = {
|
|
130
|
+
...this.getAutomationHeaders(),
|
|
131
|
+
[HTTP_HEADERS.CONTENT_TYPE]: CONTENT_TYPES.JSON
|
|
132
|
+
};
|
|
133
|
+
const response = await fetch(url, {
|
|
134
|
+
method: HTTP_METHODS.POST,
|
|
135
|
+
headers: requestHeaders,
|
|
136
|
+
body: JSON.stringify(body)
|
|
137
|
+
});
|
|
138
|
+
return await this.validateAndGetResponseBody(response);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Upload a file as multipart/form-data using the automation API key.
|
|
142
|
+
* Content-Type is not set manually — fetch sets it with the correct multipart boundary.
|
|
143
|
+
* @param uploadUrl - Full URL returned by the automation import initiation step
|
|
144
|
+
* @param fileBuffer - Raw file contents as a Buffer
|
|
145
|
+
*/
|
|
146
|
+
async uploadFileMultipart(uploadUrl, fileBuffer) {
|
|
147
|
+
const response = await fetch(uploadUrl, {
|
|
148
|
+
method: HTTP_METHODS.PUT,
|
|
149
|
+
headers: {
|
|
150
|
+
[HTTP_HEADERS.CONTENT_TYPE]: CONTENT_TYPES.MULTIPART
|
|
151
|
+
},
|
|
152
|
+
body: new Uint8Array(fileBuffer)
|
|
153
|
+
});
|
|
154
|
+
if (!response.ok) {
|
|
155
|
+
const errorText = await response.text();
|
|
156
|
+
throw new ToolError(
|
|
157
|
+
ERROR_MESSAGES.REQUEST_FAILED(response.status, errorText)
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
92
161
|
/**
|
|
93
162
|
* Validate HTTP response and extract body
|
|
94
163
|
* Handles various response types: JSON, text, empty responses
|
|
@@ -19,7 +19,7 @@ class Cache {
|
|
|
19
19
|
if (!this.trackedKeys.has(projectKey)) {
|
|
20
20
|
this.trackedKeys.set(projectKey, /* @__PURE__ */ new Set());
|
|
21
21
|
}
|
|
22
|
-
this.trackedKeys.get(projectKey)
|
|
22
|
+
this.trackedKeys.get(projectKey)?.add(key);
|
|
23
23
|
}
|
|
24
24
|
has(projectKey, fieldKey) {
|
|
25
25
|
return this.cacheService.get(this.compositeKey(projectKey, fieldKey)) !== void 0;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import zod__default from "zod";
|
|
2
|
+
import { SCHEMA_DESCRIPTIONS } from "../config/constants.js";
|
|
3
|
+
const TestCycleFields = zod__default.object({
|
|
4
|
+
labels: zod__default.array(zod__default.string()).nullable().optional(),
|
|
5
|
+
components: zod__default.array(zod__default.string()).nullable().optional(),
|
|
6
|
+
priority: zod__default.string().nullable().optional(),
|
|
7
|
+
status: zod__default.string().nullable().optional(),
|
|
8
|
+
summary: zod__default.string().nullable().optional(),
|
|
9
|
+
description: zod__default.string().nullable().optional(),
|
|
10
|
+
assignee: zod__default.string().nullable().optional().describe(
|
|
11
|
+
"Jira Account ID of the assignee. Ask the user to provide their Account ID directly."
|
|
12
|
+
),
|
|
13
|
+
reporter: zod__default.string().nullable().optional().describe(
|
|
14
|
+
"Jira Account ID of the reporter. Ask the user to provide their Account ID directly."
|
|
15
|
+
),
|
|
16
|
+
folderId: zod__default.number().int().nullable().optional().describe(
|
|
17
|
+
"Numeric folder ID to place the test cycle in. Right-click a folder in QTM4J and select 'Copy Folder Id' to get this value."
|
|
18
|
+
),
|
|
19
|
+
plannedStartDate: zod__default.string().nullable().optional().describe("Date in 'dd/MMM/yyyy HH:mm' format, e.g. '14/May/2026 10:30'"),
|
|
20
|
+
plannedEndDate: zod__default.string().nullable().optional().describe("Date in 'dd/MMM/yyyy HH:mm' format, e.g. '14/May/2026 10:30'")
|
|
21
|
+
}).nullable().optional();
|
|
22
|
+
const TestCaseFields = zod__default.object({
|
|
23
|
+
labels: zod__default.array(zod__default.string()).nullable().optional(),
|
|
24
|
+
components: zod__default.array(zod__default.string()).nullable().optional(),
|
|
25
|
+
priority: zod__default.string().nullable().optional(),
|
|
26
|
+
status: zod__default.string().nullable().optional(),
|
|
27
|
+
description: zod__default.string().nullable().optional(),
|
|
28
|
+
precondition: zod__default.string().nullable().optional(),
|
|
29
|
+
assignee: zod__default.string().nullable().optional().describe(
|
|
30
|
+
"Jira Account ID of the assignee. Ask the user to provide their Account ID directly."
|
|
31
|
+
),
|
|
32
|
+
reporter: zod__default.string().nullable().optional().describe(
|
|
33
|
+
"Jira Account ID of the reporter. Ask the user to provide their Account ID directly."
|
|
34
|
+
),
|
|
35
|
+
estimatedTime: zod__default.string().nullable().optional(),
|
|
36
|
+
folderId: zod__default.number().int().nullable().optional().describe(
|
|
37
|
+
"Numeric folder ID to place the test cases in. Right-click a folder in QTM4J and select 'Copy Folder Id' to get this value."
|
|
38
|
+
)
|
|
39
|
+
}).nullable().optional();
|
|
40
|
+
const TestCaseExecutionFields = zod__default.object({
|
|
41
|
+
comment: zod__default.string().nullable().optional(),
|
|
42
|
+
actualTime: zod__default.string().nullable().optional(),
|
|
43
|
+
executionPlannedDate: zod__default.string().nullable().optional(),
|
|
44
|
+
assignee: zod__default.string().nullable().optional()
|
|
45
|
+
}).nullable().optional();
|
|
46
|
+
const UploadAutomationResultBody = zod__default.object({
|
|
47
|
+
filePath: zod__default.string().describe(SCHEMA_DESCRIPTIONS.AUTOMATION_FILE_PATH),
|
|
48
|
+
format: zod__default.enum(["cucumber", "testng", "junit", "qaf", "hpuft", "specflow"]).describe(SCHEMA_DESCRIPTIONS.AUTOMATION_FORMAT),
|
|
49
|
+
testCycleToReuse: zod__default.string().optional().describe(SCHEMA_DESCRIPTIONS.AUTOMATION_TEST_CYCLE_TO_REUSE),
|
|
50
|
+
environment: zod__default.string().optional().describe(SCHEMA_DESCRIPTIONS.AUTOMATION_ENVIRONMENT),
|
|
51
|
+
build: zod__default.string().optional().describe(SCHEMA_DESCRIPTIONS.AUTOMATION_BUILD),
|
|
52
|
+
isZip: zod__default.boolean().optional().default(false).describe(SCHEMA_DESCRIPTIONS.AUTOMATION_IS_ZIP),
|
|
53
|
+
attachFile: zod__default.boolean().optional().default(false).describe(SCHEMA_DESCRIPTIONS.AUTOMATION_ATTACH_FILE),
|
|
54
|
+
matchTestSteps: zod__default.boolean().optional().default(true).describe(SCHEMA_DESCRIPTIONS.AUTOMATION_MATCH_TEST_STEPS),
|
|
55
|
+
appendTestName: zod__default.boolean().optional().describe(SCHEMA_DESCRIPTIONS.AUTOMATION_APPEND_TEST_NAME),
|
|
56
|
+
fields: zod__default.object({
|
|
57
|
+
testCycle: TestCycleFields,
|
|
58
|
+
testCase: TestCaseFields,
|
|
59
|
+
testCaseExecution: TestCaseExecutionFields
|
|
60
|
+
}).optional().describe(SCHEMA_DESCRIPTIONS.AUTOMATION_FIELDS)
|
|
61
|
+
});
|
|
62
|
+
const UploadAutomationResultResponse = zod__default.object({
|
|
63
|
+
trackingId: zod__default.string(),
|
|
64
|
+
message: zod__default.string(),
|
|
65
|
+
filePath: zod__default.string(),
|
|
66
|
+
format: zod__default.enum(["cucumber", "testng", "junit", "qaf", "hpuft", "specflow"])
|
|
67
|
+
});
|
|
68
|
+
const GetAutomationHistoryBody = zod__default.object({
|
|
69
|
+
startAt: zod__default.number().int().min(0).optional().default(0).describe(SCHEMA_DESCRIPTIONS.AUTOMATION_HISTORY_START_AT),
|
|
70
|
+
maxResults: zod__default.number().int().min(1).max(100).optional().default(20).describe(SCHEMA_DESCRIPTIONS.AUTOMATION_HISTORY_MAX_RESULTS)
|
|
71
|
+
});
|
|
72
|
+
const AutomationHistorySummary = zod__default.object({
|
|
73
|
+
testCycle: zod__default.string().nullable().optional(),
|
|
74
|
+
testCasesCreated: zod__default.number().nullable().optional(),
|
|
75
|
+
testCaseVersionsCreated: zod__default.number().nullable().optional(),
|
|
76
|
+
testCaseVersionsReused: zod__default.number().nullable().optional(),
|
|
77
|
+
testStepsCreated: zod__default.number().nullable().optional(),
|
|
78
|
+
testCycleIssueKey: zod__default.string().nullable().optional(),
|
|
79
|
+
testCycleSummary: zod__default.string().nullable().optional()
|
|
80
|
+
});
|
|
81
|
+
const AutomationHistoryRecord = zod__default.object({
|
|
82
|
+
trackingId: zod__default.string().nullable().optional(),
|
|
83
|
+
fileName: zod__default.string().nullable().optional(),
|
|
84
|
+
format: zod__default.string().nullable().optional(),
|
|
85
|
+
processStatus: zod__default.string().nullable().optional(),
|
|
86
|
+
importStatus: zod__default.string().nullable().optional(),
|
|
87
|
+
startTime: zod__default.string().nullable().optional(),
|
|
88
|
+
endTime: zod__default.string().nullable().optional(),
|
|
89
|
+
fileSize: zod__default.number().nullable().optional(),
|
|
90
|
+
detailedMessage: zod__default.string().nullable().optional(),
|
|
91
|
+
extraAttributes: zod__default.record(zod__default.string(), zod__default.any()).nullable().optional(),
|
|
92
|
+
childFileSummaryResponses: zod__default.array(zod__default.any()).nullable().optional(),
|
|
93
|
+
summary: zod__default.array(AutomationHistorySummary).nullable().optional()
|
|
94
|
+
});
|
|
95
|
+
const GetAutomationHistoryResponse = zod__default.object({
|
|
96
|
+
startAt: zod__default.number().nullable().optional(),
|
|
97
|
+
maxResults: zod__default.number().nullable().optional(),
|
|
98
|
+
total: zod__default.number().nullable().optional(),
|
|
99
|
+
data: zod__default.array(AutomationHistoryRecord).nullable().optional()
|
|
100
|
+
});
|
|
101
|
+
export {
|
|
102
|
+
AutomationHistoryRecord,
|
|
103
|
+
GetAutomationHistoryBody,
|
|
104
|
+
GetAutomationHistoryResponse,
|
|
105
|
+
UploadAutomationResultBody,
|
|
106
|
+
UploadAutomationResultResponse
|
|
107
|
+
};
|