@smartbear/mcp 0.22.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 +28 -4
- package/dist/bearq/client.js +81 -0
- package/dist/bearq/config/constants.js +6 -0
- package/dist/bearq/tool/tasks/chat-with-qa-lead.js +32 -0
- package/dist/bearq/tool/tasks/expand-application-model.js +34 -0
- package/dist/bearq/tool/tasks/get-task-status.js +32 -0
- package/dist/bearq/tool/tasks/get-task.js +29 -0
- package/dist/bearq/tool/tasks/refine-all-draft-tests.js +27 -0
- package/dist/bearq/tool/tasks/refine-test-cases.js +30 -0
- package/dist/bearq/tool/tasks/refine-tests-in-functional-areas.js +34 -0
- package/dist/bearq/tool/tasks/run-regression-tests.js +27 -0
- package/dist/bearq/tool/tasks/run-test-cases.js +30 -0
- package/dist/bearq/tool/tasks/run-tests-in-functional-areas.js +30 -0
- package/dist/bearq/tool/tasks/stop-task.js +29 -0
- package/dist/bearq/tool/tasks/wait-for-task.js +83 -0
- package/dist/common/register-clients.js +2 -0
- 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/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
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import * as zod from "zod";
|
|
2
|
+
import { SORT_DEFAULTS, PAGINATION } from "../config/constants.js";
|
|
3
|
+
const DATE_RANGE_REGEX = /^\d{2}\/[A-Z][a-z]{2}\/\d{4},\d{2}\/[A-Z][a-z]{2}\/\d{4}$/;
|
|
4
|
+
const DATE_RANGE_DESCRIPTION = "Inclusive date range. Format: 'dd/MMM/yyyy,dd/MMM/yyyy' e.g. '02/Apr/2026,15/May/2026'. Month abbreviation is case-sensitive (Apr not apr or APR). Always provide two dates separated by a comma.";
|
|
5
|
+
const SearchTestCycleFilter = zod.object({
|
|
6
|
+
projectId: zod.number().optional().describe(
|
|
7
|
+
"Numeric project ID. Auto-injected from active project context — do not provide."
|
|
8
|
+
),
|
|
9
|
+
status: zod.array(zod.string()).optional().describe(
|
|
10
|
+
"Status names to include (OR logic within array). Examples: ['In Progress', 'To Do']. Common values: 'To Do', 'In Progress', 'Done'."
|
|
11
|
+
),
|
|
12
|
+
priority: zod.array(zod.string()).optional().describe(
|
|
13
|
+
"Priority names to include (OR logic within array). Examples: ['High'], ['High', 'Medium']. Common values: 'High', 'Medium', 'Low'."
|
|
14
|
+
),
|
|
15
|
+
assignee: zod.array(zod.string()).optional().describe(
|
|
16
|
+
"Jira account IDs of assignees (OR logic within array). Example: ['5b10a2844c20165700ede21f']. Multiple IDs match test cycles assigned to any of them."
|
|
17
|
+
),
|
|
18
|
+
reporter: zod.array(zod.string()).optional().describe(
|
|
19
|
+
"Jira account IDs of reporters (OR logic within array). Example: ['5b10a2844c20165700ede21f']."
|
|
20
|
+
),
|
|
21
|
+
folderId: zod.number().optional().describe(
|
|
22
|
+
"Folder ID to restrict results to (numeric). Right-click a folder in QTM4J and select 'Copy Folder Id'."
|
|
23
|
+
),
|
|
24
|
+
labels: zod.array(zod.string()).optional().describe(
|
|
25
|
+
"Label names to include (OR logic within array). Example: ['Release_1', 'Sprint 1']. Use exact label names as configured in the project."
|
|
26
|
+
),
|
|
27
|
+
components: zod.array(zod.string()).optional().describe(
|
|
28
|
+
"Component names to include (OR logic within array). Example: ['UI', 'Cloud']. Use exact component names as configured in the project."
|
|
29
|
+
),
|
|
30
|
+
plannedStartDate: zod.string().regex(
|
|
31
|
+
DATE_RANGE_REGEX,
|
|
32
|
+
"Invalid format. Use dd/MMM/yyyy,dd/MMM/yyyy e.g. 02/Apr/2026,15/May/2026"
|
|
33
|
+
).optional().describe(DATE_RANGE_DESCRIPTION),
|
|
34
|
+
plannedEndDate: zod.string().regex(
|
|
35
|
+
DATE_RANGE_REGEX,
|
|
36
|
+
"Invalid format. Use dd/MMM/yyyy,dd/MMM/yyyy e.g. 02/Apr/2026,15/May/2026"
|
|
37
|
+
).optional().describe(DATE_RANGE_DESCRIPTION),
|
|
38
|
+
searchText: zod.string().optional().describe(
|
|
39
|
+
"Free-text search across key, summary, and description. Case-insensitive. Example: 'regression sprint'"
|
|
40
|
+
),
|
|
41
|
+
createdOn: zod.string().regex(
|
|
42
|
+
DATE_RANGE_REGEX,
|
|
43
|
+
"Invalid format. Use dd/MMM/yyyy,dd/MMM/yyyy e.g. 01/May/2026,20/May/2026"
|
|
44
|
+
).optional().describe(
|
|
45
|
+
`Filter by creation date range (inclusive). ${DATE_RANGE_DESCRIPTION}`
|
|
46
|
+
),
|
|
47
|
+
updatedOn: zod.string().regex(
|
|
48
|
+
DATE_RANGE_REGEX,
|
|
49
|
+
"Invalid format. Use dd/MMM/yyyy,dd/MMM/yyyy e.g. 01/May/2026,21/May/2026"
|
|
50
|
+
).optional().describe(
|
|
51
|
+
"Filter by last-updated date range (inclusive). " + DATE_RANGE_DESCRIPTION
|
|
52
|
+
),
|
|
53
|
+
isAutomated: zod.boolean().optional().describe(
|
|
54
|
+
"Automation status filter: true = automated tests only, false = manual tests only. Omit to include both."
|
|
55
|
+
),
|
|
56
|
+
aiGenerated: zod.boolean().optional().describe(
|
|
57
|
+
"AI generation flag: true = AI-generated test cycles only, false = human-authored only. Omit to include both."
|
|
58
|
+
)
|
|
59
|
+
}).describe(
|
|
60
|
+
"Filter criteria — multiple fields are combined with AND; multiple values within one field use OR."
|
|
61
|
+
);
|
|
62
|
+
const FIELDS_DESCRIPTION = "Fields to include in each result object. If omitted, server returns its default set (NOTE: plannedStartDate and plannedEndDate are NOT in the default response — include them explicitly when needed). Available fields: key, summary, description, status, priority, assignee, reporter, isAutomated, plannedStartDate, plannedEndDate, labels, components, fixVersions, sprint, defectCount, estimatedTime, actualTime, created, updated. Example: ['key', 'summary', 'status', 'assignee', 'plannedStartDate', 'plannedEndDate']";
|
|
63
|
+
const SearchTestCycleBody = zod.object({
|
|
64
|
+
filter: SearchTestCycleFilter.optional(),
|
|
65
|
+
fields: zod.array(zod.string()).optional().describe(FIELDS_DESCRIPTION),
|
|
66
|
+
startAt: zod.number().min(0).optional().default(PAGINATION.DEFAULT_START_AT).describe(
|
|
67
|
+
"Zero-indexed offset for pagination (URL query param). Default: 0."
|
|
68
|
+
),
|
|
69
|
+
maxResults: zod.number().min(PAGINATION.MIN_ALLOWED_RESULTS).max(PAGINATION.MAX_ALLOWED_RESULTS_TEST_CYCLES).optional().default(PAGINATION.DEFAULT_MAX_RESULTS_TEST_CYCLES).describe(
|
|
70
|
+
`Number of results per page (URL query param). Default: ${PAGINATION.DEFAULT_MAX_RESULTS_TEST_CYCLES}. Maximum: ${PAGINATION.MAX_ALLOWED_RESULTS_TEST_CYCLES}. To page through results, increment startAt by ${PAGINATION.DEFAULT_MAX_RESULTS_TEST_CYCLES} until startAt >= total.`
|
|
71
|
+
),
|
|
72
|
+
sort: zod.string().optional().default(SORT_DEFAULTS.TEST_CYCLES).describe(
|
|
73
|
+
`Sort pattern sent as a URL query param. Format: 'fieldName:order'. Default: '${SORT_DEFAULTS.TEST_CYCLES}'. Order values: 'asc' (lowest/oldest first) or 'desc' (highest/newest first). Sortable fields: key, summary, status, plannedStartDate, plannedEndDate, defectCount. Examples: 'key:asc', 'plannedStartDate:desc'`
|
|
74
|
+
)
|
|
75
|
+
});
|
|
76
|
+
const TestCycleStatusSchema = zod.looseObject({
|
|
77
|
+
id: zod.number().optional().describe("Numeric status ID"),
|
|
78
|
+
name: zod.string().optional().describe("Status display name e.g. 'In Progress'"),
|
|
79
|
+
color: zod.string().nullable().optional().describe("Hex color e.g. '#ffd351'")
|
|
80
|
+
});
|
|
81
|
+
const TestCyclePrioritySchema = zod.looseObject({
|
|
82
|
+
id: zod.number().optional().describe("Numeric priority ID"),
|
|
83
|
+
name: zod.string().optional().describe("Priority display name e.g. 'High'"),
|
|
84
|
+
color: zod.string().nullable().optional().describe("Hex color")
|
|
85
|
+
});
|
|
86
|
+
const TestCycleLabelSchema = zod.looseObject({
|
|
87
|
+
id: zod.number().optional(),
|
|
88
|
+
name: zod.string().optional()
|
|
89
|
+
});
|
|
90
|
+
const TestCycleComponentSchema = zod.looseObject({
|
|
91
|
+
id: zod.number().optional(),
|
|
92
|
+
name: zod.string().optional()
|
|
93
|
+
});
|
|
94
|
+
const TestCycleSearchItemSchema = zod.looseObject({
|
|
95
|
+
id: zod.string().describe("Internal UID of the test cycle"),
|
|
96
|
+
key: zod.string().describe("Human-readable key e.g. 'PROJ-TR-212'"),
|
|
97
|
+
projectId: zod.number().optional().describe("Numeric project ID"),
|
|
98
|
+
summary: zod.string().optional().describe("Test cycle name / title"),
|
|
99
|
+
description: zod.string().nullable().optional(),
|
|
100
|
+
status: TestCycleStatusSchema.optional().describe(
|
|
101
|
+
"Status object (empty {} when not requested)"
|
|
102
|
+
),
|
|
103
|
+
priority: TestCyclePrioritySchema.optional().describe(
|
|
104
|
+
"Priority object (empty {} when not requested)"
|
|
105
|
+
),
|
|
106
|
+
assignee: zod.string().nullable().optional().describe("Assignee Jira account ID, or null if unassigned"),
|
|
107
|
+
reporter: zod.string().nullable().optional(),
|
|
108
|
+
isAutomated: zod.boolean().optional(),
|
|
109
|
+
plannedStartDate: zod.string().nullable().optional(),
|
|
110
|
+
plannedEndDate: zod.string().nullable().optional(),
|
|
111
|
+
labels: zod.array(TestCycleLabelSchema).nullable().optional(),
|
|
112
|
+
components: zod.array(TestCycleComponentSchema).nullable().optional(),
|
|
113
|
+
fixVersions: zod.array(zod.any()).nullable().optional(),
|
|
114
|
+
sprint: zod.any().nullable().optional(),
|
|
115
|
+
defectCount: zod.number().nullable().optional(),
|
|
116
|
+
estimatedTime: zod.any().nullable().optional(),
|
|
117
|
+
actualTime: zod.any().nullable().optional(),
|
|
118
|
+
created: zod.any().optional(),
|
|
119
|
+
updated: zod.any().optional(),
|
|
120
|
+
archived: zod.boolean().optional()
|
|
121
|
+
});
|
|
122
|
+
const SearchTestCycleResponse = zod.object({
|
|
123
|
+
startAt: zod.number().describe("Offset of this page"),
|
|
124
|
+
maxResults: zod.number().describe("Page size used for this response"),
|
|
125
|
+
total: zod.number().describe("Total matching test cycles across all pages"),
|
|
126
|
+
data: zod.array(TestCycleSearchItemSchema).describe("Test cycles on this page")
|
|
127
|
+
});
|
|
128
|
+
export {
|
|
129
|
+
SearchTestCycleBody,
|
|
130
|
+
SearchTestCycleFilter,
|
|
131
|
+
SearchTestCycleResponse,
|
|
132
|
+
TestCycleSearchItemSchema
|
|
133
|
+
};
|