@smartbear/mcp 0.20.0 → 0.22.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 +23 -6
- package/dist/common/register-clients.js +2 -0
- package/dist/common/server.js +1 -4
- package/dist/package.json.js +1 -1
- package/dist/qtm4j/client.js +109 -0
- package/dist/qtm4j/config/constants.js +169 -0
- package/dist/qtm4j/config/field-resolution.types.js +34 -0
- package/dist/qtm4j/http/api-client.js +123 -0
- package/dist/qtm4j/http/auth-service.js +23 -0
- package/dist/qtm4j/resolver/cache/cache.js +52 -0
- package/dist/qtm4j/resolver/resolver-registry.js +70 -0
- package/dist/qtm4j/resolver/resolvers/common-attribute-resolver.js +56 -0
- package/dist/qtm4j/resolver/resolvers/component-resolver.js +56 -0
- package/dist/qtm4j/resolver/resolvers/label-resolver.js +56 -0
- package/dist/qtm4j/resolver/resolvers/resolver.js +6 -0
- package/dist/qtm4j/resolver/resolvers/test-case-uid-resolver.js +28 -0
- package/dist/qtm4j/schema/get-test-case.schema.js +153 -0
- package/dist/qtm4j/schema/get-test-steps.schema.js +74 -0
- package/dist/qtm4j/schema/project.schema.js +43 -0
- package/dist/qtm4j/schema/test-case.schema.js +41 -0
- package/dist/qtm4j/schema/update-test-case.schema.js +45 -0
- package/dist/qtm4j/tool/project/get-projects.js +111 -0
- package/dist/qtm4j/tool/project/set-project-context.js +99 -0
- package/dist/qtm4j/tool/test-case/create-test-case.js +113 -0
- package/dist/qtm4j/tool/test-case/get-test-cases.js +295 -0
- package/dist/qtm4j/tool/test-case/get-test-steps.js +111 -0
- package/dist/qtm4j/tool/test-case/update-test-case.js +158 -0
- package/package.json +5 -4
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { CommonAttributeResolver } from "./resolvers/common-attribute-resolver.js";
|
|
2
|
+
import { ComponentResolver } from "./resolvers/component-resolver.js";
|
|
3
|
+
import { LabelResolver } from "./resolvers/label-resolver.js";
|
|
4
|
+
import { TestCaseUidResolver } from "./resolvers/test-case-uid-resolver.js";
|
|
5
|
+
const ERROR_NO_PROJECT_CONTEXT = "No active project set. Please call set_project_context before performing this operation.";
|
|
6
|
+
class ResolverRegistry {
|
|
7
|
+
resolverByKey;
|
|
8
|
+
commonAttributes;
|
|
9
|
+
testCaseUidResolver;
|
|
10
|
+
projectContext;
|
|
11
|
+
constructor(apiClient, cacheService) {
|
|
12
|
+
this.commonAttributes = new CommonAttributeResolver(
|
|
13
|
+
apiClient,
|
|
14
|
+
cacheService
|
|
15
|
+
);
|
|
16
|
+
this.testCaseUidResolver = new TestCaseUidResolver(apiClient);
|
|
17
|
+
const labelResolver = new LabelResolver(apiClient, cacheService);
|
|
18
|
+
const componentResolver = new ComponentResolver(apiClient, cacheService);
|
|
19
|
+
this.resolverByKey = /* @__PURE__ */ new Map();
|
|
20
|
+
for (const resolver of [
|
|
21
|
+
this.commonAttributes,
|
|
22
|
+
labelResolver,
|
|
23
|
+
componentResolver,
|
|
24
|
+
this.testCaseUidResolver
|
|
25
|
+
]) {
|
|
26
|
+
for (const key of resolver.fieldKeys) {
|
|
27
|
+
this.resolverByKey.set(key, resolver);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Returns the resolver responsible for the given resolver key.
|
|
33
|
+
* Tools use this with their FIELD_CONFIG map to dispatch field resolution.
|
|
34
|
+
*/
|
|
35
|
+
getResolver(resolverKey) {
|
|
36
|
+
const resolver = this.resolverByKey.get(resolverKey);
|
|
37
|
+
if (!resolver)
|
|
38
|
+
throw new Error(`No resolver registered for key '${resolverKey}'`);
|
|
39
|
+
return resolver;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Direct typed access to the CommonAttributeResolver, used by set_project_context to
|
|
43
|
+
* eagerly load all common attribute fields (priority, statuses) for LLM NLP mapping.
|
|
44
|
+
*/
|
|
45
|
+
getCommonAttributeResolver() {
|
|
46
|
+
return this.commonAttributes;
|
|
47
|
+
}
|
|
48
|
+
setProjectContext(context) {
|
|
49
|
+
this.projectContext = context;
|
|
50
|
+
}
|
|
51
|
+
requireProjectContext() {
|
|
52
|
+
if (!this.projectContext) {
|
|
53
|
+
throw new Error(ERROR_NO_PROJECT_CONTEXT);
|
|
54
|
+
}
|
|
55
|
+
return this.projectContext;
|
|
56
|
+
}
|
|
57
|
+
clearCache() {
|
|
58
|
+
for (const resolver of new Set(this.resolverByKey.values())) {
|
|
59
|
+
resolver.clearCache();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
clearProjectCache(projectKey) {
|
|
63
|
+
for (const resolver of new Set(this.resolverByKey.values())) {
|
|
64
|
+
resolver.clearCache(projectKey);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export {
|
|
69
|
+
ResolverRegistry
|
|
70
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { ENDPOINTS } from "../../config/constants.js";
|
|
2
|
+
import { ResolverKeys } from "../../config/field-resolution.types.js";
|
|
3
|
+
import { Cache } from "../cache/cache.js";
|
|
4
|
+
import { Resolver } from "./resolver.js";
|
|
5
|
+
class CommonAttributeResolver extends Resolver {
|
|
6
|
+
fieldKeys = Object.values(
|
|
7
|
+
ResolverKeys.CommonAttribute
|
|
8
|
+
);
|
|
9
|
+
cache;
|
|
10
|
+
apiClient;
|
|
11
|
+
constructor(apiClient, cacheService) {
|
|
12
|
+
super();
|
|
13
|
+
this.apiClient = apiClient;
|
|
14
|
+
this.cache = new Cache(cacheService);
|
|
15
|
+
}
|
|
16
|
+
// Priority and status are always single values — no array handling needed.
|
|
17
|
+
async resolve(inputField, resolverKey, body, context, warnings) {
|
|
18
|
+
const name = body[inputField];
|
|
19
|
+
if (name == null) return;
|
|
20
|
+
const id = await this.resolveAndReturn(
|
|
21
|
+
context.projectKey,
|
|
22
|
+
context.projectId,
|
|
23
|
+
resolverKey,
|
|
24
|
+
name
|
|
25
|
+
);
|
|
26
|
+
if (id !== void 0) {
|
|
27
|
+
body[inputField] = Number(id);
|
|
28
|
+
} else {
|
|
29
|
+
warnings.push(
|
|
30
|
+
`Skipped ${inputField} '${name}' — not available in the current project.`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async resolveAndReturn(projectKey, projectId, resolverKey, name) {
|
|
35
|
+
const cached = this.cache.matchValue(projectKey, resolverKey, name);
|
|
36
|
+
if (cached !== void 0) return cached;
|
|
37
|
+
await this.preload(projectKey, projectId);
|
|
38
|
+
return this.cache.matchValue(projectKey, resolverKey, name);
|
|
39
|
+
}
|
|
40
|
+
async preload(projectKey, projectId) {
|
|
41
|
+
const response = await this.apiClient.get(
|
|
42
|
+
ENDPOINTS.COMMON_ATTRIBUTES(projectId)
|
|
43
|
+
);
|
|
44
|
+
const attributes = response;
|
|
45
|
+
for (const [key, values] of Object.entries(attributes)) {
|
|
46
|
+
this.cache.set(projectKey, key, values);
|
|
47
|
+
}
|
|
48
|
+
return attributes;
|
|
49
|
+
}
|
|
50
|
+
clearCache(projectKey) {
|
|
51
|
+
this.cache.clear(projectKey);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export {
|
|
55
|
+
CommonAttributeResolver
|
|
56
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { ENDPOINTS } from "../../config/constants.js";
|
|
2
|
+
import { ResolverKeys } from "../../config/field-resolution.types.js";
|
|
3
|
+
import { Cache } from "../cache/cache.js";
|
|
4
|
+
import { Resolver } from "./resolver.js";
|
|
5
|
+
class ComponentResolver extends Resolver {
|
|
6
|
+
fieldKeys = [
|
|
7
|
+
ResolverKeys.SearchableField.COMPONENTS
|
|
8
|
+
];
|
|
9
|
+
cache;
|
|
10
|
+
apiClient;
|
|
11
|
+
constructor(apiClient, cacheService) {
|
|
12
|
+
super();
|
|
13
|
+
this.apiClient = apiClient;
|
|
14
|
+
this.cache = new Cache(cacheService);
|
|
15
|
+
}
|
|
16
|
+
async resolve(inputField, resolverKey, body, context, warnings) {
|
|
17
|
+
const raw = body[inputField];
|
|
18
|
+
if (raw == null) return;
|
|
19
|
+
const isArray = Array.isArray(raw);
|
|
20
|
+
const names = isArray ? raw : [raw];
|
|
21
|
+
const ids = [];
|
|
22
|
+
for (const name of names) {
|
|
23
|
+
const id = await this.resolveAndReturn(
|
|
24
|
+
context.projectKey,
|
|
25
|
+
context.projectId,
|
|
26
|
+
resolverKey,
|
|
27
|
+
name
|
|
28
|
+
);
|
|
29
|
+
if (id !== void 0) {
|
|
30
|
+
ids.push(Number(id));
|
|
31
|
+
} else {
|
|
32
|
+
warnings.push(
|
|
33
|
+
`Skipped ${inputField} '${name}' — not available in the current project.`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (ids.length > 0) {
|
|
38
|
+
body[inputField] = isArray ? ids : ids[0];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async resolveAndReturn(projectKey, projectId, resolverKey, name) {
|
|
42
|
+
const cached = this.cache.matchValue(projectKey, resolverKey, name);
|
|
43
|
+
if (cached !== void 0) return cached;
|
|
44
|
+
const response = await this.apiClient.get(ENDPOINTS.COMPONENTS(projectId), {
|
|
45
|
+
search: name
|
|
46
|
+
});
|
|
47
|
+
this.cache.set(projectKey, resolverKey, response);
|
|
48
|
+
return this.cache.matchValue(projectKey, resolverKey, name);
|
|
49
|
+
}
|
|
50
|
+
clearCache(projectKey) {
|
|
51
|
+
this.cache.clear(projectKey);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export {
|
|
55
|
+
ComponentResolver
|
|
56
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { ENDPOINTS } from "../../config/constants.js";
|
|
2
|
+
import { ResolverKeys } from "../../config/field-resolution.types.js";
|
|
3
|
+
import { Cache } from "../cache/cache.js";
|
|
4
|
+
import { Resolver } from "./resolver.js";
|
|
5
|
+
class LabelResolver extends Resolver {
|
|
6
|
+
fieldKeys = [
|
|
7
|
+
ResolverKeys.SearchableField.LABEL
|
|
8
|
+
];
|
|
9
|
+
cache;
|
|
10
|
+
apiClient;
|
|
11
|
+
constructor(apiClient, cacheService) {
|
|
12
|
+
super();
|
|
13
|
+
this.apiClient = apiClient;
|
|
14
|
+
this.cache = new Cache(cacheService);
|
|
15
|
+
}
|
|
16
|
+
async resolve(inputField, resolverKey, body, context, warnings) {
|
|
17
|
+
const raw = body[inputField];
|
|
18
|
+
if (raw == null) return;
|
|
19
|
+
const isArray = Array.isArray(raw);
|
|
20
|
+
const names = isArray ? raw : [raw];
|
|
21
|
+
const ids = [];
|
|
22
|
+
for (const name of names) {
|
|
23
|
+
const id = await this.resolveAndReturn(
|
|
24
|
+
context.projectKey,
|
|
25
|
+
context.projectId,
|
|
26
|
+
resolverKey,
|
|
27
|
+
name
|
|
28
|
+
);
|
|
29
|
+
if (id !== void 0) {
|
|
30
|
+
ids.push(Number(id));
|
|
31
|
+
} else {
|
|
32
|
+
warnings.push(
|
|
33
|
+
`Skipped ${inputField} '${name}' — not available in the current project.`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (ids.length > 0) {
|
|
38
|
+
body[inputField] = isArray ? ids : ids[0];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async resolveAndReturn(projectKey, projectId, resolverKey, name) {
|
|
42
|
+
const cached = this.cache.matchValue(projectKey, resolverKey, name);
|
|
43
|
+
if (cached !== void 0) return cached;
|
|
44
|
+
const response = await this.apiClient.get(ENDPOINTS.LABELS(projectId), {
|
|
45
|
+
search: name
|
|
46
|
+
});
|
|
47
|
+
this.cache.set(projectKey, resolverKey, response);
|
|
48
|
+
return this.cache.matchValue(projectKey, resolverKey, name);
|
|
49
|
+
}
|
|
50
|
+
clearCache(projectKey) {
|
|
51
|
+
this.cache.clear(projectKey);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export {
|
|
55
|
+
LabelResolver
|
|
56
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ENDPOINTS } from "../../config/constants.js";
|
|
2
|
+
import { ResolverKeys } from "../../config/field-resolution.types.js";
|
|
3
|
+
import { Resolver } from "./resolver.js";
|
|
4
|
+
class TestCaseUidResolver extends Resolver {
|
|
5
|
+
fieldKeys = [
|
|
6
|
+
ResolverKeys.SearchableField.TEST_CASE_KEY_TO_UID
|
|
7
|
+
];
|
|
8
|
+
apiClient;
|
|
9
|
+
constructor(apiClient) {
|
|
10
|
+
super();
|
|
11
|
+
this.apiClient = apiClient;
|
|
12
|
+
}
|
|
13
|
+
async resolve(_inputField, _resolverKey, _body, _context, _warnings) {
|
|
14
|
+
}
|
|
15
|
+
async resolveAndReturn(projectId, keys) {
|
|
16
|
+
if (keys.length === 0) return {};
|
|
17
|
+
const response = await this.apiClient.get(
|
|
18
|
+
ENDPOINTS.RESOLVE_TEST_CASE_IDS(projectId),
|
|
19
|
+
{ keys: keys.join(",") }
|
|
20
|
+
);
|
|
21
|
+
return response;
|
|
22
|
+
}
|
|
23
|
+
clearCache(_projectKey) {
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export {
|
|
27
|
+
TestCaseUidResolver
|
|
28
|
+
};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import * as zod from "zod";
|
|
2
|
+
import { PAGINATION } from "../config/constants.js";
|
|
3
|
+
const SearchTestCaseFilter = zod.object({
|
|
4
|
+
projectId: zod.string().optional().describe(
|
|
5
|
+
"Project ID as a numeric string (e.g. '10000'). Auto-populated from active project context if omitted."
|
|
6
|
+
),
|
|
7
|
+
searchText: zod.string().optional().describe(
|
|
8
|
+
"Free-text search across summary and description. Case-insensitive. Example: 'login functionality'"
|
|
9
|
+
),
|
|
10
|
+
status: zod.array(zod.string()).optional().describe(
|
|
11
|
+
"Status names to include (OR logic within array). Examples: ['Done'], ['To Do', 'In Progress']. Common values: 'Done', 'To Do', 'In Progress'."
|
|
12
|
+
),
|
|
13
|
+
priority: zod.array(zod.string()).optional().describe(
|
|
14
|
+
"Priority names to include (OR logic within array). Examples: ['High'], ['High', 'Medium']. Common values: 'High', 'Medium', 'Low'."
|
|
15
|
+
),
|
|
16
|
+
labels: zod.array(zod.string()).optional().describe(
|
|
17
|
+
"Label names to include (OR logic within array). Example: ['Release_1', 'Sprint 1']. Use exact label names as configured in the project."
|
|
18
|
+
),
|
|
19
|
+
components: zod.array(zod.string()).optional().describe(
|
|
20
|
+
"Component names to include (OR logic within array). Example: ['UI', 'Cloud', 'API']. Use exact component names as configured in the project."
|
|
21
|
+
),
|
|
22
|
+
folders: zod.array(zod.number()).optional().describe(
|
|
23
|
+
"Folder IDs (numeric, OR logic within array). Example: [123, 456]. Retrieve folder IDs from the project's folder structure."
|
|
24
|
+
),
|
|
25
|
+
assignee: zod.array(zod.string()).optional().describe(
|
|
26
|
+
"Jira account IDs of assignees (OR logic within array). Example: ['712020:ddc8e24b-2de7-404b-b9ed-3d7b241e2ced']. Multiple IDs match test cases assigned to any of them."
|
|
27
|
+
),
|
|
28
|
+
reporter: zod.array(zod.string()).optional().describe(
|
|
29
|
+
"Jira account IDs of reporters (OR logic within array). Example: ['712020:b8479b55-6d23-478c-a2ad-4c8ce176e1fc']."
|
|
30
|
+
),
|
|
31
|
+
isAutomated: zod.boolean().optional().describe(
|
|
32
|
+
"Automation status filter: true = automated tests only, false = manual tests only. Omit to include both."
|
|
33
|
+
),
|
|
34
|
+
createdOnFrom: zod.string().optional().describe(
|
|
35
|
+
"Lower bound for creation date (inclusive). Format: 'dd/MMM/yyyy', e.g. '01/Jan/2026'."
|
|
36
|
+
),
|
|
37
|
+
createdOnTo: zod.string().optional().describe(
|
|
38
|
+
"Upper bound for creation date (inclusive). Format: 'dd/MMM/yyyy', e.g. '31/Dec/2026'."
|
|
39
|
+
),
|
|
40
|
+
updatedOnFrom: zod.string().optional().describe(
|
|
41
|
+
"Lower bound for last-updated date (inclusive). Format: 'dd/MMM/yyyy', e.g. '01/Apr/2026'."
|
|
42
|
+
),
|
|
43
|
+
updatedOnTo: zod.string().optional().describe(
|
|
44
|
+
"Upper bound for last-updated date (inclusive). Format: 'dd/MMM/yyyy', e.g. '30/Apr/2026'."
|
|
45
|
+
),
|
|
46
|
+
executedOnFrom: zod.string().optional().describe(
|
|
47
|
+
"Lower bound for last-execution date (inclusive). Format: 'dd/MMM/yyyy', e.g. '27/Apr/2026'."
|
|
48
|
+
),
|
|
49
|
+
executedOnTo: zod.string().optional().describe(
|
|
50
|
+
"Upper bound for last-execution date (inclusive). Format: 'dd/MMM/yyyy', e.g. '03/May/2026'."
|
|
51
|
+
),
|
|
52
|
+
fixVersions: zod.array(zod.number()).optional().describe(
|
|
53
|
+
"Fix version IDs (numeric, OR logic within array). Example: [789]. Retrieve IDs from the project's fix version list."
|
|
54
|
+
),
|
|
55
|
+
sprint: zod.array(zod.number()).optional().describe(
|
|
56
|
+
"Sprint IDs (numeric, OR logic within array). Example: [10, 11]. Retrieve IDs from the project's sprint list."
|
|
57
|
+
),
|
|
58
|
+
aiGenerated: zod.boolean().optional().describe(
|
|
59
|
+
"AI generation flag: true = AI-generated test cases only, false = human-authored only. Omit to include both."
|
|
60
|
+
)
|
|
61
|
+
}).describe(
|
|
62
|
+
"Filter criteria — multiple fields are combined with AND; multiple values within one field use OR."
|
|
63
|
+
);
|
|
64
|
+
const FIELDS_DESCRIPTION = "Fields to include in each result object. If omitted, all fields are returned. Available fields: key, summary, description, priority, status, assignee, isAutomated, reporter, estimatedTime, labels, components, fixVersions, sprint, folders, updated, created, executed, flakyScore, passRateScore, aiGenerated, precondition, orderNo, seqNo, version. Example: ['key', 'summary', 'status', 'priority', 'assignee']";
|
|
65
|
+
const SearchTestCaseBody = zod.object({
|
|
66
|
+
filter: SearchTestCaseFilter.optional(),
|
|
67
|
+
fields: zod.array(zod.string()).optional().describe(FIELDS_DESCRIPTION),
|
|
68
|
+
startAt: zod.number().min(0).optional().default(PAGINATION.DEFAULT_START_AT).describe(
|
|
69
|
+
"Zero-indexed offset for pagination (URL query param). First page: 0. Second page: 50 (when maxResults=50). Default: 0."
|
|
70
|
+
),
|
|
71
|
+
maxResults: zod.number().min(PAGINATION.MIN_ALLOWED_RESULTS).max(PAGINATION.MAX_ALLOWED_RESULTS_TEST_CASES).optional().default(PAGINATION.DEFAULT_MAX_RESULTS_TEST_CASES).describe(
|
|
72
|
+
"Number of results per page (URL query param). Default: 50. Maximum: 50 (backend enforced). To page through results, increment startAt by 50 until startAt >= total."
|
|
73
|
+
),
|
|
74
|
+
sort: zod.string().optional().describe(
|
|
75
|
+
"Sort pattern sent as a URL query param. Format: 'fieldName:order'. For multiple fields, comma-separate: 'priority:asc,created:desc'. Order values: 'asc' (oldest/lowest first) or 'desc' (newest/highest first). Sortable fields: key, summary, created, updated, status, priority, executed. Examples: 'created:desc', 'key:asc', 'priority:desc,created:asc'"
|
|
76
|
+
)
|
|
77
|
+
});
|
|
78
|
+
zod.object({
|
|
79
|
+
createdBy: zod.string().optional(),
|
|
80
|
+
createdOn: zod.string().optional(),
|
|
81
|
+
updatedBy: zod.string().optional(),
|
|
82
|
+
updatedOn: zod.string().optional()
|
|
83
|
+
});
|
|
84
|
+
const VersionSchema = zod.object({
|
|
85
|
+
isLatestVersion: zod.boolean().optional(),
|
|
86
|
+
versionNo: zod.number().optional()
|
|
87
|
+
});
|
|
88
|
+
const StatusSchema = zod.object({
|
|
89
|
+
isArchive: zod.boolean().optional(),
|
|
90
|
+
color: zod.string().optional(),
|
|
91
|
+
name: zod.string().optional(),
|
|
92
|
+
id: zod.number().optional()
|
|
93
|
+
});
|
|
94
|
+
const PrioritySchema = zod.object({
|
|
95
|
+
name: zod.string().optional(),
|
|
96
|
+
id: zod.number().optional()
|
|
97
|
+
});
|
|
98
|
+
const LabelSchema = zod.object({
|
|
99
|
+
name: zod.string().optional(),
|
|
100
|
+
id: zod.number().optional()
|
|
101
|
+
});
|
|
102
|
+
const ComponentSchema = zod.object({
|
|
103
|
+
name: zod.string().optional(),
|
|
104
|
+
id: zod.number().optional()
|
|
105
|
+
});
|
|
106
|
+
const TestCaseSchema = zod.object({
|
|
107
|
+
id: zod.string().describe("Internal test case ID"),
|
|
108
|
+
key: zod.string().describe("Test case key, e.g. 'SCRUM-TC-145'"),
|
|
109
|
+
summary: zod.string().describe("Test case title"),
|
|
110
|
+
description: zod.string().nullable().optional(),
|
|
111
|
+
priority: PrioritySchema.optional(),
|
|
112
|
+
status: StatusSchema.optional(),
|
|
113
|
+
assignee: zod.string().nullable().optional(),
|
|
114
|
+
reporter: zod.string().optional(),
|
|
115
|
+
isAutomated: zod.boolean().optional(),
|
|
116
|
+
automated: zod.boolean().optional(),
|
|
117
|
+
estimatedTime: zod.string().nullable().optional(),
|
|
118
|
+
labels: zod.array(LabelSchema).nullable().optional(),
|
|
119
|
+
components: zod.array(ComponentSchema).nullable().optional(),
|
|
120
|
+
fixVersions: zod.array(zod.any()).nullable().optional(),
|
|
121
|
+
sprint: zod.any().nullable().optional(),
|
|
122
|
+
folders: zod.array(zod.any()).nullable().optional(),
|
|
123
|
+
created: zod.any().optional(),
|
|
124
|
+
updated: zod.any().optional(),
|
|
125
|
+
executed: zod.string().nullable().optional(),
|
|
126
|
+
flakyScore: zod.number().nullable().optional(),
|
|
127
|
+
passRateScore: zod.number().nullable().optional(),
|
|
128
|
+
aiGenerated: zod.boolean().optional(),
|
|
129
|
+
precondition: zod.string().nullable().optional(),
|
|
130
|
+
orderNo: zod.number().nullable().optional(),
|
|
131
|
+
seqNo: zod.number().nullable().optional(),
|
|
132
|
+
version: VersionSchema.optional(),
|
|
133
|
+
projectId: zod.number().optional(),
|
|
134
|
+
shareable: zod.boolean().optional(),
|
|
135
|
+
archived: zod.boolean().optional()
|
|
136
|
+
}).passthrough();
|
|
137
|
+
const SearchTestCaseResponse = zod.object({
|
|
138
|
+
total: zod.number().describe("Total test cases matching the filter (across all pages)"),
|
|
139
|
+
startAt: zod.number().describe("Offset of this page"),
|
|
140
|
+
maxResults: zod.number().describe("Page size used for this response"),
|
|
141
|
+
data: zod.array(TestCaseSchema).describe("Test cases on this page")
|
|
142
|
+
});
|
|
143
|
+
export {
|
|
144
|
+
ComponentSchema,
|
|
145
|
+
LabelSchema,
|
|
146
|
+
PrioritySchema,
|
|
147
|
+
SearchTestCaseBody,
|
|
148
|
+
SearchTestCaseFilter,
|
|
149
|
+
SearchTestCaseResponse,
|
|
150
|
+
StatusSchema,
|
|
151
|
+
TestCaseSchema,
|
|
152
|
+
VersionSchema
|
|
153
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as zod from "zod";
|
|
2
|
+
import { PAGINATION } from "../config/constants.js";
|
|
3
|
+
const GetTestStepsFilter = zod.object({
|
|
4
|
+
stepDetails: zod.string().optional().describe(
|
|
5
|
+
"Substring match on step details (action text). Example: 'Open the application'"
|
|
6
|
+
),
|
|
7
|
+
testData: zod.string().optional().describe(
|
|
8
|
+
"Substring match on test data. Example: 'Username: user1, Password: pass123'"
|
|
9
|
+
),
|
|
10
|
+
expectedResult: zod.string().optional().describe(
|
|
11
|
+
"Substring match on expected result. Example: 'User should be logged in successfully'"
|
|
12
|
+
)
|
|
13
|
+
}).describe(
|
|
14
|
+
"Text filters for test steps — each field performs a substring match. Multiple fields are combined with AND."
|
|
15
|
+
);
|
|
16
|
+
const GetTestStepsBody = zod.object({
|
|
17
|
+
key: zod.string().describe(
|
|
18
|
+
"Test case key in the format '{PROJECT_KEY}-TC-{number}', e.g. 'SCRUM-TC-145'. PROJECT_KEY is the Jira project key (e.g. 'SCRUM'). The number is the test case counter within that project (auto-incremented, not related to seqNo). Obtain keys from the search_test_cases tool or directly from QTM4J."
|
|
19
|
+
),
|
|
20
|
+
versionNo: zod.number().int().min(1).optional().describe(
|
|
21
|
+
"Test case version number. Defaults to the latest version if omitted. Obtain from search_test_cases response field: version.versionNo"
|
|
22
|
+
),
|
|
23
|
+
filter: GetTestStepsFilter.optional(),
|
|
24
|
+
startAt: zod.number().min(0).optional().default(PAGINATION.DEFAULT_START_AT).describe(
|
|
25
|
+
"Zero-indexed offset for pagination (URL query param). Default: 0."
|
|
26
|
+
),
|
|
27
|
+
maxResults: zod.number().min(PAGINATION.MIN_ALLOWED_RESULTS).max(PAGINATION.MAX_ALLOWED_RESULTS_TEST_STEPS).optional().default(PAGINATION.DEFAULT_MAX_RESULTS_TEST_STEPS).describe(
|
|
28
|
+
"Number of steps per page (URL query param). Default: 50. Maximum: 100."
|
|
29
|
+
),
|
|
30
|
+
sort: zod.string().optional().describe(
|
|
31
|
+
"Sort pattern (URL query param). Format: 'fieldName:order'. Sortable fields: stepDetails, testData, seqNo, expectedResult. Order values: 'asc' or 'desc'. Example: 'seqNo:asc'"
|
|
32
|
+
)
|
|
33
|
+
});
|
|
34
|
+
const ShareableTestStepSchema = zod.object({
|
|
35
|
+
id: zod.number().optional(),
|
|
36
|
+
seqNo: zod.union([zod.string(), zod.number()]).optional().describe("Sequence number within the shared test case, e.g. '1.1', '1.3'"),
|
|
37
|
+
stepDetails: zod.string().describe("Step action / description — always required"),
|
|
38
|
+
testData: zod.string().nullish().describe("Input data for this step"),
|
|
39
|
+
expectedResult: zod.string().nullish().describe("Expected outcome of this step"),
|
|
40
|
+
testcase_version_id: zod.number().optional()
|
|
41
|
+
});
|
|
42
|
+
const ShareableSchema = zod.object({
|
|
43
|
+
shareableTestcaseUID: zod.string().optional(),
|
|
44
|
+
shareableVersionNo: zod.number().optional(),
|
|
45
|
+
archived: zod.boolean().optional(),
|
|
46
|
+
projectId: zod.number().optional(),
|
|
47
|
+
shareableTestSteps: zod.array(ShareableTestStepSchema).optional().describe("Embedded steps from the shared test case")
|
|
48
|
+
}).describe(
|
|
49
|
+
"Present when this step is a reference to a shared (reusable) test case. Contains its embedded sub-steps."
|
|
50
|
+
);
|
|
51
|
+
const TestStepSchema = zod.object({
|
|
52
|
+
id: zod.number().describe("Internal test step ID"),
|
|
53
|
+
seqNo: zod.union([zod.string(), zod.number()]).optional().describe("Step sequence number within the test case (e.g. 1, 2, 3)"),
|
|
54
|
+
stepDetails: zod.string().describe("Step action / description — always required"),
|
|
55
|
+
testData: zod.string().nullish().describe("Input data for this step"),
|
|
56
|
+
expectedResult: zod.string().nullish().describe("Expected outcome of this step"),
|
|
57
|
+
testcase_version_id: zod.number().optional(),
|
|
58
|
+
attachmentCount: zod.number().optional().describe("Number of attachments on this step"),
|
|
59
|
+
shareable: ShareableSchema.nullable().optional()
|
|
60
|
+
}).passthrough();
|
|
61
|
+
const GetTestStepsResponse = zod.object({
|
|
62
|
+
total: zod.number().describe("Total steps matching the filter (across all pages)"),
|
|
63
|
+
startAt: zod.number().describe("Offset of this page"),
|
|
64
|
+
maxResults: zod.number().describe("Page size used for this response"),
|
|
65
|
+
data: zod.array(TestStepSchema).describe("Test steps on this page")
|
|
66
|
+
});
|
|
67
|
+
export {
|
|
68
|
+
GetTestStepsBody,
|
|
69
|
+
GetTestStepsFilter,
|
|
70
|
+
GetTestStepsResponse,
|
|
71
|
+
ShareableSchema,
|
|
72
|
+
ShareableTestStepSchema,
|
|
73
|
+
TestStepSchema
|
|
74
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as zod from "zod";
|
|
2
|
+
import { PAGINATION, SCHEMA_DESCRIPTIONS } from "../config/constants.js";
|
|
3
|
+
const ProjectSchema = zod.object({
|
|
4
|
+
id: zod.number().describe("Project ID"),
|
|
5
|
+
key: zod.string().describe("Project key (e.g., 'SCRUM', 'AD')"),
|
|
6
|
+
name: zod.string().describe("Project name"),
|
|
7
|
+
favorite: zod.boolean().optional().describe("Whether project is marked as favorite"),
|
|
8
|
+
avatarUrl: zod.string().optional().nullable().describe("Project avatar URL"),
|
|
9
|
+
projectTypeKey: zod.string().optional().nullable().describe("Project type key (e.g., 'software')"),
|
|
10
|
+
qmetryEnabled: zod.boolean().optional().describe("Whether QMetry is enabled for this project")
|
|
11
|
+
}).passthrough().describe(SCHEMA_DESCRIPTIONS.PROJECT_OBJECT);
|
|
12
|
+
const GetProjectsBody = zod.object({
|
|
13
|
+
projectId: zod.number().optional().describe("Filter by specific project ID"),
|
|
14
|
+
search: zod.string().optional().describe(SCHEMA_DESCRIPTIONS.SEARCH_TEXT),
|
|
15
|
+
qmetryEnabled: zod.boolean().optional().describe(SCHEMA_DESCRIPTIONS.QMETRY_ENABLED),
|
|
16
|
+
startAt: zod.number().min(PAGINATION.MIN_ALLOWED_RESULTS - 1).default(PAGINATION.DEFAULT_START_AT).describe(SCHEMA_DESCRIPTIONS.START_AT),
|
|
17
|
+
maxResults: zod.number().min(PAGINATION.MIN_ALLOWED_RESULTS).max(PAGINATION.MAX_ALLOWED_RESULTS).default(PAGINATION.DEFAULT_MAX_RESULTS_PROJECTS).describe(SCHEMA_DESCRIPTIONS.MAX_RESULTS_PROJECTS)
|
|
18
|
+
});
|
|
19
|
+
const GetProjectsResponse = zod.object({
|
|
20
|
+
total: zod.number().min(PAGINATION.MIN_ALLOWED_RESULTS - 1).describe("Total number of projects"),
|
|
21
|
+
data: zod.array(ProjectSchema).describe("List of projects")
|
|
22
|
+
});
|
|
23
|
+
const SetProjectContextBody = zod.object({
|
|
24
|
+
projectKey: zod.string().describe(
|
|
25
|
+
"Project key (e.g., 'SCRUM'). Use the get_projects tool to discover available project keys."
|
|
26
|
+
)
|
|
27
|
+
});
|
|
28
|
+
const SetProjectContextResponse = zod.object({
|
|
29
|
+
projectId: zod.number().describe("Numeric project ID"),
|
|
30
|
+
projectKey: zod.string().describe("Project key"),
|
|
31
|
+
projectName: zod.string().describe("Project name"),
|
|
32
|
+
message: zod.string().describe("Confirmation message"),
|
|
33
|
+
availableFields: zod.record(zod.string(), zod.record(zod.string(), zod.string())).optional().describe(
|
|
34
|
+
"Available field values keyed by field name (e.g. 'priority', 'testcase_status'). Use these to map user input via NLP (e.g. user says 'Major' → send 'High')."
|
|
35
|
+
)
|
|
36
|
+
});
|
|
37
|
+
export {
|
|
38
|
+
GetProjectsBody,
|
|
39
|
+
GetProjectsResponse,
|
|
40
|
+
ProjectSchema,
|
|
41
|
+
SetProjectContextBody,
|
|
42
|
+
SetProjectContextResponse
|
|
43
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as zod from "zod";
|
|
2
|
+
const TestStepSchema = zod.object({
|
|
3
|
+
stepDetails: zod.string().describe("Step description/action — always required for each step"),
|
|
4
|
+
testData: zod.string().optional().describe(
|
|
5
|
+
"Test data for this step. Always include this field for each step — use empty string if no test data is applicable."
|
|
6
|
+
),
|
|
7
|
+
expectedResult: zod.string().optional().describe(
|
|
8
|
+
"Expected result after performing the step. Always include this field for each step — describe what should happen."
|
|
9
|
+
)
|
|
10
|
+
});
|
|
11
|
+
const CreateTestCaseBody = zod.object({
|
|
12
|
+
summary: zod.string().describe("Test case summary/title"),
|
|
13
|
+
description: zod.string().optional().describe("Test case description"),
|
|
14
|
+
folderId: zod.number().optional().describe("Folder ID to place the test case in"),
|
|
15
|
+
priority: zod.string().optional().describe(
|
|
16
|
+
"Priority name (e.g., 'High', 'Medium', 'Low'). Auto-resolved to ID."
|
|
17
|
+
),
|
|
18
|
+
status: zod.string().optional().describe(
|
|
19
|
+
"Status name (e.g., 'To Do', 'In Progress', 'Done'). Auto-resolved to ID."
|
|
20
|
+
),
|
|
21
|
+
assignee: zod.string().optional().describe("Assignee account ID"),
|
|
22
|
+
reporter: zod.string().optional().describe("Reporter account ID"),
|
|
23
|
+
components: zod.array(zod.string()).optional().describe(
|
|
24
|
+
"List of component names (e.g., ['UI', 'Cloud']). Auto-resolved to IDs."
|
|
25
|
+
),
|
|
26
|
+
labels: zod.array(zod.string()).optional().describe(
|
|
27
|
+
"List of label names (e.g., ['Release_1', 'Sprint 1']). Auto-resolved to IDs."
|
|
28
|
+
),
|
|
29
|
+
steps: zod.array(TestStepSchema).optional().describe("List of test steps")
|
|
30
|
+
});
|
|
31
|
+
const CreateTestCaseResponse = zod.object({
|
|
32
|
+
id: zod.string().describe("Unique test case ID"),
|
|
33
|
+
key: zod.string().describe("Test case key (e.g., 'SCRUM-TC-190')"),
|
|
34
|
+
versionNo: zod.number().describe("Version number"),
|
|
35
|
+
summary: zod.string().describe("Test case summary")
|
|
36
|
+
});
|
|
37
|
+
export {
|
|
38
|
+
CreateTestCaseBody,
|
|
39
|
+
CreateTestCaseResponse,
|
|
40
|
+
TestStepSchema
|
|
41
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as zod from "zod";
|
|
2
|
+
const MetadataAddDelete = zod.object({
|
|
3
|
+
add: zod.array(zod.string()).optional().describe(
|
|
4
|
+
"Names to add (e.g., ['Release_1', 'Sprint 1']). Auto-resolved to IDs."
|
|
5
|
+
),
|
|
6
|
+
delete: zod.array(zod.string()).optional().describe("Names to remove (e.g., ['Sprint 1']). Auto-resolved to IDs.")
|
|
7
|
+
}).describe(
|
|
8
|
+
"Add or remove entries by name. Both add and delete are optional — omit either to skip that operation."
|
|
9
|
+
);
|
|
10
|
+
const UpdateTestCaseBody = zod.object({
|
|
11
|
+
key: zod.string().describe(
|
|
12
|
+
"Test case key in the format '{PROJECT_KEY}-TC-{number}', e.g. 'SCRUM-TC-145'. Automatically resolved to the internal ID and latest version."
|
|
13
|
+
),
|
|
14
|
+
versionNo: zod.number().int().min(1).optional().describe(
|
|
15
|
+
"Test case version number to update. Defaults to the latest version if omitted."
|
|
16
|
+
),
|
|
17
|
+
summary: zod.string().min(1).optional().describe("Updated test case summary/title."),
|
|
18
|
+
description: zod.string().optional().describe("Updated test case description."),
|
|
19
|
+
precondition: zod.string().optional().describe(
|
|
20
|
+
"Updated precondition — conditions that must be true before the test is executed."
|
|
21
|
+
),
|
|
22
|
+
priority: zod.string().optional().describe(
|
|
23
|
+
"Priority name (e.g., 'High', 'Medium', 'Low'). Auto-resolved to ID. Use values from set_project_context response."
|
|
24
|
+
),
|
|
25
|
+
status: zod.string().optional().describe(
|
|
26
|
+
"Status name (e.g., 'To Do', 'In Progress', 'Done'). Auto-resolved to ID. Use values from set_project_context response."
|
|
27
|
+
),
|
|
28
|
+
assignee: zod.string().optional().describe("Assignee Jira account ID (e.g., '5b10a2844c20165700ede21f')."),
|
|
29
|
+
estimatedTime: zod.string().regex(/^\d{2}:\d{2}:\d{2}$/, "Must be in HH:MM:SS format").optional().describe("Estimated time in HH:MM:SS format (e.g., '02:30:00')."),
|
|
30
|
+
labels: MetadataAddDelete.optional().describe(
|
|
31
|
+
"Labels to add or remove by name. Each name is auto-resolved to its ID."
|
|
32
|
+
),
|
|
33
|
+
components: MetadataAddDelete.optional().describe(
|
|
34
|
+
"Components to add or remove by name. Each name is auto-resolved to its ID."
|
|
35
|
+
)
|
|
36
|
+
});
|
|
37
|
+
const UpdateTestCaseResponse = zod.object({
|
|
38
|
+
key: zod.string().describe("Test case key that was updated"),
|
|
39
|
+
versionNo: zod.number().describe("Version number that was updated"),
|
|
40
|
+
updated: zod.literal(true).describe("Confirms the update was applied")
|
|
41
|
+
});
|
|
42
|
+
export {
|
|
43
|
+
UpdateTestCaseBody,
|
|
44
|
+
UpdateTestCaseResponse
|
|
45
|
+
};
|