@alternative-path/qa-path-mcp 1.0.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.
Files changed (97) hide show
  1. package/LICENSE +23 -0
  2. package/QUICK_INSTALL.md +133 -0
  3. package/README.md +226 -0
  4. package/TOOLS_DOCUMENTATION.md +675 -0
  5. package/dist/__tests__/tools/module-tools.test.d.ts +2 -0
  6. package/dist/__tests__/tools/module-tools.test.d.ts.map +1 -0
  7. package/dist/__tests__/tools/module-tools.test.js +145 -0
  8. package/dist/__tests__/tools/module-tools.test.js.map +1 -0
  9. package/dist/__tests__/tools/project-tools.test.d.ts +2 -0
  10. package/dist/__tests__/tools/project-tools.test.d.ts.map +1 -0
  11. package/dist/__tests__/tools/project-tools.test.js +674 -0
  12. package/dist/__tests__/tools/project-tools.test.js.map +1 -0
  13. package/dist/__tests__/tools/query-tools.test.d.ts +2 -0
  14. package/dist/__tests__/tools/query-tools.test.d.ts.map +1 -0
  15. package/dist/__tests__/tools/query-tools.test.js +225 -0
  16. package/dist/__tests__/tools/query-tools.test.js.map +1 -0
  17. package/dist/__tests__/tools/testgroup-launch-tools.test.d.ts +2 -0
  18. package/dist/__tests__/tools/testgroup-launch-tools.test.d.ts.map +1 -0
  19. package/dist/__tests__/tools/testgroup-launch-tools.test.js +553 -0
  20. package/dist/__tests__/tools/testgroup-launch-tools.test.js.map +1 -0
  21. package/dist/__tests__/utils/mcp-error-mapper.test.d.ts +2 -0
  22. package/dist/__tests__/utils/mcp-error-mapper.test.d.ts.map +1 -0
  23. package/dist/__tests__/utils/mcp-error-mapper.test.js +240 -0
  24. package/dist/__tests__/utils/mcp-error-mapper.test.js.map +1 -0
  25. package/dist/__tests__/utils/mcp-response.test.d.ts +2 -0
  26. package/dist/__tests__/utils/mcp-response.test.d.ts.map +1 -0
  27. package/dist/__tests__/utils/mcp-response.test.js +72 -0
  28. package/dist/__tests__/utils/mcp-response.test.js.map +1 -0
  29. package/dist/agents/test-planner-context.d.ts +7 -0
  30. package/dist/agents/test-planner-context.d.ts.map +1 -0
  31. package/dist/agents/test-planner-context.js +283 -0
  32. package/dist/agents/test-planner-context.js.map +1 -0
  33. package/dist/agents/test-planner-prompt.d.ts +34 -0
  34. package/dist/agents/test-planner-prompt.d.ts.map +1 -0
  35. package/dist/agents/test-planner-prompt.js +82 -0
  36. package/dist/agents/test-planner-prompt.js.map +1 -0
  37. package/dist/api-client.d.ts +52 -0
  38. package/dist/api-client.d.ts.map +1 -0
  39. package/dist/api-client.js +285 -0
  40. package/dist/api-client.js.map +1 -0
  41. package/dist/constants.d.ts +7 -0
  42. package/dist/constants.d.ts.map +1 -0
  43. package/dist/constants.js +7 -0
  44. package/dist/constants.js.map +1 -0
  45. package/dist/index.d.ts +3 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +175 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/services/project-context-service.d.ts +15 -0
  50. package/dist/services/project-context-service.d.ts.map +1 -0
  51. package/dist/services/project-context-service.js +36 -0
  52. package/dist/services/project-context-service.js.map +1 -0
  53. package/dist/tools/auth-tools.d.ts +16 -0
  54. package/dist/tools/auth-tools.d.ts.map +1 -0
  55. package/dist/tools/auth-tools.js +66 -0
  56. package/dist/tools/auth-tools.js.map +1 -0
  57. package/dist/tools/automation-tools.d.ts +28 -0
  58. package/dist/tools/automation-tools.d.ts.map +1 -0
  59. package/dist/tools/automation-tools.js +541 -0
  60. package/dist/tools/automation-tools.js.map +1 -0
  61. package/dist/tools/export-import-tools.d.ts +18 -0
  62. package/dist/tools/export-import-tools.d.ts.map +1 -0
  63. package/dist/tools/export-import-tools.js +61 -0
  64. package/dist/tools/export-import-tools.js.map +1 -0
  65. package/dist/tools/module-tools.d.ts +43 -0
  66. package/dist/tools/module-tools.d.ts.map +1 -0
  67. package/dist/tools/module-tools.js +289 -0
  68. package/dist/tools/module-tools.js.map +1 -0
  69. package/dist/tools/project-context-tools.d.ts +19 -0
  70. package/dist/tools/project-context-tools.d.ts.map +1 -0
  71. package/dist/tools/project-context-tools.js +133 -0
  72. package/dist/tools/project-context-tools.js.map +1 -0
  73. package/dist/tools/project-tools.d.ts +47 -0
  74. package/dist/tools/project-tools.d.ts.map +1 -0
  75. package/dist/tools/project-tools.js +362 -0
  76. package/dist/tools/project-tools.js.map +1 -0
  77. package/dist/tools/query-tools.d.ts +22 -0
  78. package/dist/tools/query-tools.d.ts.map +1 -0
  79. package/dist/tools/query-tools.js +127 -0
  80. package/dist/tools/query-tools.js.map +1 -0
  81. package/dist/tools/testcase-tools.d.ts +135 -0
  82. package/dist/tools/testcase-tools.d.ts.map +1 -0
  83. package/dist/tools/testcase-tools.js +845 -0
  84. package/dist/tools/testcase-tools.js.map +1 -0
  85. package/dist/tools/testgroup-launch-tools.d.ts +37 -0
  86. package/dist/tools/testgroup-launch-tools.d.ts.map +1 -0
  87. package/dist/tools/testgroup-launch-tools.js +727 -0
  88. package/dist/tools/testgroup-launch-tools.js.map +1 -0
  89. package/dist/utils/mcp-error-mapper.d.ts +27 -0
  90. package/dist/utils/mcp-error-mapper.d.ts.map +1 -0
  91. package/dist/utils/mcp-error-mapper.js +79 -0
  92. package/dist/utils/mcp-error-mapper.js.map +1 -0
  93. package/dist/utils/mcp-response.d.ts +26 -0
  94. package/dist/utils/mcp-response.d.ts.map +1 -0
  95. package/dist/utils/mcp-response.js +34 -0
  96. package/dist/utils/mcp-response.js.map +1 -0
  97. package/package.json +60 -0
@@ -0,0 +1,845 @@
1
+ import { jsonResponse } from "../utils/mcp-response.js";
2
+ export class TestCaseTools {
3
+ apiClient;
4
+ projectContext;
5
+ constructor(apiClient, projectContext) {
6
+ this.apiClient = apiClient;
7
+ this.projectContext = projectContext;
8
+ }
9
+ getTools() {
10
+ return [
11
+ {
12
+ name: "list_test_cases",
13
+ description: "List test cases with filtering and sorting. Supports text-based queries in canonical format (e.g., \"status = 'New' AND priority = 'High'\"). IMPORTANT: Always call get_query_schema with entity='testcase' first to understand available fields, operators, and valid values. Use field names (not labels) in queries, and use IDs for association fields like generated_by_user, updated_by_user, and module.",
14
+ inputSchema: {
15
+ type: "object",
16
+ properties: {
17
+ query: {
18
+ type: "string",
19
+ description: "Optional text query in canonical format (e.g., \"status = 'New' AND priority = 'High'\"). Leave empty or omit to get all test cases. Max length: 5000 characters.",
20
+ maxLength: 5000,
21
+ },
22
+ page: {
23
+ type: "number",
24
+ description: "Page number for pagination (default: 1, minimum: 1)",
25
+ minimum: 1,
26
+ default: 1,
27
+ },
28
+ pageSize: {
29
+ type: "number",
30
+ description: "Number of results per page (default: 25, minimum: 1, maximum: 10000)",
31
+ minimum: 1,
32
+ maximum: 10000,
33
+ default: 25,
34
+ },
35
+ moduleId: {
36
+ type: ["string", "null"],
37
+ description: "Optional module ID to filter test cases by module, or 'all'/'default' for all modules (default: 'all')",
38
+ },
39
+ sortField: {
40
+ type: "string",
41
+ description: "Optional field to sort by. Allowed values: key, title, type, priority, status, generated_by_user, created_at, updated_at, updated_by_user, estimated_duration, labels, last_executed_date, last_executed_by, key_counter, automation_status",
42
+ },
43
+ sortOrder: {
44
+ type: "string",
45
+ enum: ["asc", "desc", "ASC", "DESC"],
46
+ description: "Sort order: 'asc'/'ASC' for ascending or 'desc'/'DESC' for descending",
47
+ },
48
+ timezone: {
49
+ type: ["string", "null"],
50
+ description: "Optional timezone for date parsing (e.g., 'UTC', 'Asia/Kolkata'). Max length: 100 characters. Default: 'UTC'",
51
+ maxLength: 100,
52
+ },
53
+ projectId: {
54
+ type: "string",
55
+ description: "Optional project ID (UUID). If not provided, uses the active project set via 'set_active_project'. " +
56
+ "If no active project is set, the tool will return an error with instructions on how to set one.",
57
+ },
58
+ },
59
+ },
60
+ },
61
+ {
62
+ name: "get_test_case",
63
+ description: "Get detailed information about a specific test case by ID",
64
+ inputSchema: {
65
+ type: "object",
66
+ properties: {
67
+ testCaseId: {
68
+ type: "string",
69
+ description: "ID of the test case to retrieve",
70
+ },
71
+ projectId: {
72
+ type: "string",
73
+ description: "Optional project ID (UUID). If not provided, uses the active project set via 'set_active_project'. " +
74
+ "If no active project is set, the tool will return an error with instructions on how to set one.",
75
+ },
76
+ },
77
+ required: ["testCaseId"],
78
+ },
79
+ },
80
+ {
81
+ name: "get_execution_history",
82
+ description: "Get execution history for a test case (runs, results, dates).",
83
+ inputSchema: {
84
+ type: "object",
85
+ properties: {
86
+ testCaseId: {
87
+ type: "string",
88
+ description: "ID (UUID) of the test case to get execution history for",
89
+ },
90
+ projectId: {
91
+ type: "string",
92
+ description: "Optional project ID (UUID). If not provided, uses the active project set via 'set_active_project'. " +
93
+ "If no active project is set, the tool will return an error with instructions on how to set one.",
94
+ },
95
+ page: {
96
+ type: "number",
97
+ description: "Page number for pagination (default: 1)",
98
+ },
99
+ pageSize: {
100
+ type: "number",
101
+ description: "Number of results per page (default: 25)",
102
+ },
103
+ },
104
+ required: ["testCaseId"],
105
+ },
106
+ },
107
+ {
108
+ name: "get_execution_plan",
109
+ description: "Get the exact details of the automated test steps (execution plan) for a test case. Returns the saved plan including steps, tool calls, assertions, and metadata. Use when you need the full automation script/plan for a test case (e.g. NLP-automated test).",
110
+ inputSchema: {
111
+ type: "object",
112
+ properties: {
113
+ testCaseId: {
114
+ type: "string",
115
+ description: "ID (UUID) of the test case to get the execution plan for",
116
+ },
117
+ projectId: {
118
+ type: "string",
119
+ description: "Optional project ID (UUID). If not provided, uses the active project set via 'set_active_project'. " +
120
+ "If no active project is set, the tool will return an error with instructions on how to set one.",
121
+ },
122
+ },
123
+ required: ["testCaseId"],
124
+ },
125
+ },
126
+ {
127
+ name: "create_test_case",
128
+ description: "Create a new test case in a specified module, component, or subcomponent",
129
+ inputSchema: {
130
+ type: "object",
131
+ properties: {
132
+ title: {
133
+ type: "string",
134
+ description: "Title/summary of the test case (required)",
135
+ },
136
+ description: {
137
+ type: "string",
138
+ description: "Detailed description of the test case",
139
+ },
140
+ moduleId: {
141
+ type: "string",
142
+ description: "ID of the module/component/subcomponent where the test case will be created (required)",
143
+ },
144
+ type: {
145
+ type: "string",
146
+ description: "Type of test case (e.g., 'Functional Test', 'Regression Test', 'Smoke Test')",
147
+ },
148
+ status: {
149
+ type: "string",
150
+ description: "Status of the test case (e.g., 'New', 'In Progress', 'Completed')",
151
+ },
152
+ priority: {
153
+ type: "string",
154
+ description: "Priority of the test case (e.g., 'Low', 'Normal', 'High', 'Critical')",
155
+ },
156
+ estimatedDuration: {
157
+ type: "number",
158
+ description: "Estimated duration in minutes",
159
+ },
160
+ testData: {
161
+ type: "string",
162
+ description: "Test data as JSON string",
163
+ },
164
+ labels: {
165
+ type: "array",
166
+ items: { type: "string" },
167
+ description: "Array of label names to attach to the test case",
168
+ },
169
+ projectId: {
170
+ type: "string",
171
+ description: "Optional project ID (UUID). If not provided, uses the active project set via 'set_active_project'. " +
172
+ "If no active project is set, the tool will return an error with instructions on how to set one.",
173
+ },
174
+ },
175
+ required: ["title", "moduleId"],
176
+ },
177
+ },
178
+ {
179
+ name: "update_test_case",
180
+ description: "Update an existing test case. You can update any field of the test case.",
181
+ inputSchema: {
182
+ type: "object",
183
+ properties: {
184
+ testCaseId: {
185
+ type: "string",
186
+ description: "ID of the test case to update (required)",
187
+ },
188
+ title: {
189
+ type: "string",
190
+ description: "New title for the test case",
191
+ },
192
+ description: {
193
+ type: "string",
194
+ description: "New description for the test case",
195
+ },
196
+ type: {
197
+ type: "string",
198
+ description: "New type for the test case",
199
+ },
200
+ status: {
201
+ type: "string",
202
+ description: "New status for the test case",
203
+ },
204
+ priority: {
205
+ type: "string",
206
+ description: "New priority for the test case",
207
+ },
208
+ estimatedDuration: {
209
+ type: "number",
210
+ description: "New estimated duration in minutes",
211
+ },
212
+ testData: {
213
+ type: "string",
214
+ description: "New test data as JSON string",
215
+ },
216
+ labels: {
217
+ type: "array",
218
+ items: { type: "string" },
219
+ description: "New array of label names",
220
+ },
221
+ projectId: {
222
+ type: "string",
223
+ description: "Optional project ID (UUID). If not provided, uses the active project set via 'set_active_project'. " +
224
+ "If no active project is set, the tool will return an error with instructions on how to set one.",
225
+ },
226
+ },
227
+ required: ["testCaseId"],
228
+ },
229
+ },
230
+ // {
231
+ // name: "delete_test_case",
232
+ // description:
233
+ // "Delete a test case by ID. This action cannot be undone.",
234
+ // inputSchema: {
235
+ // type: "object",
236
+ // properties: {
237
+ // testCaseId: {
238
+ // type: "string",
239
+ // description: "ID of the test case to delete (required)",
240
+ // },
241
+ // projectId: {
242
+ // type: "string",
243
+ // description:
244
+ // "Optional project ID. If not provided, uses the configured project ID.",
245
+ // },
246
+ // },
247
+ // required: ["testCaseId"],
248
+ // },
249
+ // },
250
+ // {
251
+ // name: "clone_test_case",
252
+ // description:
253
+ // "Clone one or more test cases to a target module. Can clone a single test case or multiple test cases at once.",
254
+ // inputSchema: {
255
+ // type: "object",
256
+ // properties: {
257
+ // testCaseId: {
258
+ // type: "string",
259
+ // description:
260
+ // "ID of the test case to clone (use this for single clone)",
261
+ // },
262
+ // testCaseIds: {
263
+ // type: "array",
264
+ // items: { type: "string" },
265
+ // description:
266
+ // "Array of test case IDs to clone (use this for bulk clone)",
267
+ // },
268
+ // targetModuleId: {
269
+ // type: "string",
270
+ // description:
271
+ // "ID of the target module/component/subcomponent where the cloned test case(s) will be created (required)",
272
+ // },
273
+ // projectId: {
274
+ // type: "string",
275
+ // description:
276
+ // "Optional project ID. If not provided, uses the configured project ID.",
277
+ // },
278
+ // },
279
+ // required: ["targetModuleId"],
280
+ // },
281
+ // },
282
+ // {
283
+ // name: "move_test_case",
284
+ // description:
285
+ // "Move a test case from one module to another module, component, or subcomponent",
286
+ // inputSchema: {
287
+ // type: "object",
288
+ // properties: {
289
+ // testCaseId: {
290
+ // type: "string",
291
+ // description: "ID of the test case to move (required)",
292
+ // },
293
+ // targetModuleId: {
294
+ // type: "string",
295
+ // description:
296
+ // "ID of the target module/component/subcomponent where the test case will be moved (required)",
297
+ // },
298
+ // projectId: {
299
+ // type: "string",
300
+ // description:
301
+ // "Optional project ID. If not provided, uses the configured project ID.",
302
+ // },
303
+ // },
304
+ // required: ["testCaseId", "targetModuleId"],
305
+ // },
306
+ // },
307
+ // {
308
+ // name: "bulk_create_test_cases",
309
+ // description:
310
+ // "Create multiple test cases at once. Useful for importing or creating many test cases efficiently.",
311
+ // inputSchema: {
312
+ // type: "object",
313
+ // properties: {
314
+ // testCases: {
315
+ // type: "array",
316
+ // items: {
317
+ // type: "object",
318
+ // properties: {
319
+ // title: { type: "string" },
320
+ // description: { type: "string" },
321
+ // moduleId: { type: "string" },
322
+ // type: { type: "string" },
323
+ // status: { type: "string" },
324
+ // priority: { type: "string" },
325
+ // estimatedDuration: { type: "number" },
326
+ // testData: { type: "string" },
327
+ // },
328
+ // required: ["title", "moduleId"],
329
+ // },
330
+ // description: "Array of test case objects to create",
331
+ // },
332
+ // projectId: {
333
+ // type: "string",
334
+ // description:
335
+ // "Optional project ID. If not provided, uses the configured project ID.",
336
+ // },
337
+ // },
338
+ // required: ["testCases"],
339
+ // },
340
+ // },
341
+ ];
342
+ }
343
+ handles(name) {
344
+ return [
345
+ "list_test_cases",
346
+ "get_test_case",
347
+ "get_execution_history",
348
+ "get_execution_plan",
349
+ "create_test_case",
350
+ "update_test_case",
351
+ // "delete_test_case",
352
+ // "clone_test_case",
353
+ // "move_test_case",
354
+ // "bulk_create_test_cases",
355
+ ].includes(name);
356
+ }
357
+ async handle(name, args) {
358
+ switch (name) {
359
+ case "list_test_cases":
360
+ return await this.listTestCases(args);
361
+ case "get_test_case":
362
+ return await this.getTestCase(args);
363
+ case "get_execution_history":
364
+ return await this.getExecutionHistory(args);
365
+ case "get_execution_plan":
366
+ return await this.getExecutionPlan(args);
367
+ case "create_test_case":
368
+ return await this.createTestCase(args);
369
+ case "update_test_case":
370
+ return await this.updateTestCase(args);
371
+ // case "delete_test_case":
372
+ // return await this.deleteTestCase(args);
373
+ // case "clone_test_case":
374
+ // return await this.cloneTestCase(args);
375
+ // case "move_test_case":
376
+ // return await this.moveTestCase(args);
377
+ // case "bulk_create_test_cases":
378
+ // return await this.bulkCreateTestCases(args);
379
+ default:
380
+ throw new Error(`Unknown test case tool: ${name}`);
381
+ }
382
+ }
383
+ async listTestCases(args) {
384
+ const { query, page = 1, pageSize = 25, moduleId = 'all', sortField, sortOrder, timezone, projectId: argProjectId, } = args;
385
+ const projectId = this.projectContext.resolveProjectId(args, "list_test_cases");
386
+ let normalizedQuery = null;
387
+ if (query !== undefined && query !== null && typeof query === 'string') {
388
+ const trimmed = query.trim();
389
+ normalizedQuery = trimmed.length > 0 ? trimmed : null;
390
+ }
391
+ const normalizedModuleId = moduleId === "default" ? "all" : (moduleId || "all");
392
+ const body = {
393
+ query: normalizedQuery,
394
+ page: Number(page) || 1,
395
+ pageSize: Number(pageSize) || 25,
396
+ moduleId: normalizedModuleId,
397
+ };
398
+ if (sortField !== undefined && sortField !== null) {
399
+ body.sortField = sortField;
400
+ }
401
+ if (sortOrder !== undefined && sortOrder !== null) {
402
+ body.sortOrder = sortOrder;
403
+ }
404
+ if (timezone !== undefined && timezone !== null) {
405
+ body.timezone = timezone;
406
+ }
407
+ try {
408
+ const response = await this.apiClient.post(`${projectId}/testcase/testcase-search`, body);
409
+ return jsonResponse({
410
+ success: response.success,
411
+ message: response.message,
412
+ data: response.data,
413
+ });
414
+ }
415
+ catch (error) {
416
+ // Enhanced error handling - the API client interceptor transforms errors
417
+ // but we can still access original axios error if available
418
+ const axiosError = error.response || error.request;
419
+ const errorData = axiosError?.data || {};
420
+ // Build detailed error message
421
+ let errorMessage = error.message || "Unknown error";
422
+ // If we have validation errors, include them
423
+ if (errorData.errors && Array.isArray(errorData.errors)) {
424
+ const validationErrors = errorData.errors
425
+ .map((err) => `${err.field || err.path?.join('.')}: ${err.message}`)
426
+ .join('\n');
427
+ errorMessage = `${errorData.message || errorMessage}\nValidation errors:\n${validationErrors}`;
428
+ }
429
+ else if (errorData.message) {
430
+ errorMessage = errorData.message;
431
+ }
432
+ // Include request body for debugging
433
+ errorMessage += `\n\nRequest body: ${JSON.stringify(body, null, 2)}`;
434
+ throw new Error(`Failed to list test cases: ${errorMessage}`);
435
+ }
436
+ }
437
+ async getTestCase(args) {
438
+ const { testCaseId } = args;
439
+ const projectId = this.projectContext.resolveProjectId(args, "get_test_case");
440
+ if (!testCaseId) {
441
+ throw new Error("Test case ID is required");
442
+ }
443
+ const response = await this.apiClient.get(`${projectId}/testcase/${testCaseId}`);
444
+ return jsonResponse({
445
+ success: response.success,
446
+ message: response.message,
447
+ data: response.data,
448
+ });
449
+ }
450
+ /**
451
+ * Resolve label names to IDs by fetching existing labels and creating missing ones.
452
+ * Returns an array of label objects with only the 'id' field, as expected by the backend.
453
+ */
454
+ async resolveLabels(projectId, labels) {
455
+ if (!labels || labels.length === 0) {
456
+ return [];
457
+ }
458
+ // Fetch all existing labels for the project
459
+ const existingLabelsResponse = await this.apiClient.get(`${projectId}/labels/getAll`);
460
+ if (!existingLabelsResponse.success || !existingLabelsResponse.data) {
461
+ throw new Error("Failed to fetch existing labels");
462
+ }
463
+ // Create a map of label name -> label ID (case-insensitive)
464
+ const labelMap = new Map();
465
+ existingLabelsResponse.data.forEach((label) => {
466
+ labelMap.set(label.name.toLowerCase().trim(), label.id);
467
+ });
468
+ const resolvedLabels = [];
469
+ for (const label of labels) {
470
+ let labelId;
471
+ if (typeof label === "string") {
472
+ // Label is a string (name)
473
+ const normalizedName = label.toLowerCase().trim();
474
+ labelId = labelMap.get(normalizedName);
475
+ // If label doesn't exist, create it
476
+ if (!labelId) {
477
+ try {
478
+ const createResponse = await this.apiClient.post(`${projectId}/labels/save`, {
479
+ labelName: label.trim(),
480
+ });
481
+ if (createResponse.success && createResponse.data) {
482
+ labelId = createResponse.data.id;
483
+ // Update the map for potential subsequent uses in the same batch
484
+ labelMap.set(normalizedName, labelId);
485
+ }
486
+ else {
487
+ throw new Error(`Failed to create label: ${label}`);
488
+ }
489
+ }
490
+ catch (error) {
491
+ throw new Error(`Failed to create label "${label}": ${error.message}`);
492
+ }
493
+ }
494
+ }
495
+ else if (label.id) {
496
+ // Label already has an ID - use it directly
497
+ labelId = label.id;
498
+ }
499
+ else if (label.name) {
500
+ // Label has a name but no ID
501
+ const normalizedName = label.name.toLowerCase().trim();
502
+ labelId = labelMap.get(normalizedName);
503
+ // If label doesn't exist, create it
504
+ if (!labelId) {
505
+ try {
506
+ const createResponse = await this.apiClient.post(`${projectId}/labels/save`, {
507
+ labelName: label.name.trim(),
508
+ });
509
+ if (createResponse.success && createResponse.data) {
510
+ labelId = createResponse.data.id;
511
+ labelMap.set(normalizedName, labelId);
512
+ }
513
+ else {
514
+ throw new Error(`Failed to create label: ${label.name}`);
515
+ }
516
+ }
517
+ catch (error) {
518
+ throw new Error(`Failed to create label "${label.name}": ${error.message}`);
519
+ }
520
+ }
521
+ }
522
+ if (labelId) {
523
+ resolvedLabels.push({ id: labelId });
524
+ }
525
+ }
526
+ return resolvedLabels;
527
+ }
528
+ async getExecutionHistory(args) {
529
+ const projectId = this.projectContext.resolveProjectId(args, "get_execution_history");
530
+ const { testCaseId, page = 1, pageSize = 25 } = args;
531
+ if (!testCaseId) {
532
+ throw new Error("Test case ID is required");
533
+ }
534
+ const response = await this.apiClient.get(`${projectId}/testcase/fetchExecutionHistory/${testCaseId}`, {
535
+ params: { page, pageSize },
536
+ });
537
+ return {
538
+ content: [
539
+ {
540
+ type: "text",
541
+ text: JSON.stringify({
542
+ success: response.success,
543
+ message: response.message,
544
+ data: response.data,
545
+ }, null, 2),
546
+ },
547
+ ],
548
+ };
549
+ }
550
+ async getExecutionPlan(args) {
551
+ const projectId = this.projectContext.resolveProjectId(args, "get_execution_plan");
552
+ const { testCaseId } = args;
553
+ if (!testCaseId) {
554
+ throw new Error("Test case ID is required");
555
+ }
556
+ try {
557
+ const response = await this.apiClient.get(`${projectId}/auto-execution/execution-records/${testCaseId}`);
558
+ const record = response?.data ?? null;
559
+ const plan = record?.plan ?? null;
560
+ const stepsCount = Array.isArray(plan?.steps) ? plan.steps.length : 0;
561
+ return {
562
+ content: [
563
+ {
564
+ type: "text",
565
+ text: JSON.stringify({
566
+ success: response?.success ?? true,
567
+ message: record
568
+ ? stepsCount > 0
569
+ ? "Execution plan fetched successfully."
570
+ : "Execution record exists but has no steps."
571
+ : "No automation plan saved for this test case.",
572
+ data: {
573
+ executionRecordId: record?.id,
574
+ testCaseId: record?.testcase_id ?? testCaseId,
575
+ plan: plan ?? null,
576
+ stepsCount,
577
+ created_at: record?.created_at,
578
+ updated_at: record?.updated_at,
579
+ created_by: record?.created_by,
580
+ updated_by: record?.updated_by,
581
+ },
582
+ }, null, 2),
583
+ },
584
+ ],
585
+ };
586
+ }
587
+ catch (err) {
588
+ const notFound = err?.message?.includes("not found") ||
589
+ err?.response?.status === 404;
590
+ return {
591
+ content: [
592
+ {
593
+ type: "text",
594
+ text: JSON.stringify({
595
+ success: false,
596
+ message: notFound
597
+ ? "No automation plan saved for this test case."
598
+ : err?.message ?? "Failed to fetch execution plan.",
599
+ data: {
600
+ testCaseId,
601
+ plan: null,
602
+ stepsCount: 0,
603
+ },
604
+ }, null, 2),
605
+ },
606
+ ],
607
+ };
608
+ }
609
+ }
610
+ async createTestCase(args) {
611
+ const { title, description, moduleId, type, status, priority, estimatedDuration, testData, labels, } = args;
612
+ const projectId = this.projectContext.resolveProjectId(args, "create_test_case");
613
+ if (!title) {
614
+ throw new Error("Test case title is required");
615
+ }
616
+ if (!moduleId) {
617
+ throw new Error("Module ID is required");
618
+ }
619
+ // Get user ID for generated_by and updated_by (required by backend)
620
+ const userId = this.apiClient.getUserId();
621
+ if (!userId) {
622
+ throw new Error("User ID not available. Please login first.");
623
+ }
624
+ const testCaseData = {
625
+ title: title.trim(),
626
+ module_id: moduleId,
627
+ description: description || "", // Default to empty string
628
+ type: type || "Functional Test", // Default type
629
+ status: status || "New", // Default status per OpenAPI spec
630
+ priority: priority || "Normal", // Default priority
631
+ generated_by: userId, // Required by database model
632
+ updated_by: userId, // Required by database model
633
+ };
634
+ if (estimatedDuration !== undefined) {
635
+ testCaseData.estimated_duration = estimatedDuration;
636
+ }
637
+ else {
638
+ testCaseData.estimated_duration = 15; // Default duration
639
+ }
640
+ if (testData) {
641
+ try {
642
+ testCaseData.testdata = typeof testData === "string" ? JSON.parse(testData) : testData;
643
+ }
644
+ catch {
645
+ testCaseData.testdata = testData;
646
+ }
647
+ }
648
+ if (labels && Array.isArray(labels)) {
649
+ // Resolve label names to IDs (fetch existing, create missing)
650
+ const resolvedLabels = await this.resolveLabels(projectId, labels);
651
+ testCaseData.labels = resolvedLabels;
652
+ }
653
+ try {
654
+ const response = await this.apiClient.post(`${projectId}/testcase/create`, testCaseData);
655
+ return jsonResponse({
656
+ success: response.success,
657
+ message: response.message,
658
+ data: response.data,
659
+ });
660
+ }
661
+ catch (error) {
662
+ // Provide more detailed error information
663
+ const errorMessage = error.message || "Error creating test case";
664
+ throw new Error(`Failed to create test case: ${errorMessage}`);
665
+ }
666
+ }
667
+ async updateTestCase(args) {
668
+ const { testCaseId, title, description, type, status, priority, estimatedDuration, testData, labels, } = args;
669
+ const projectId = this.projectContext.resolveProjectId(args, "update_test_case");
670
+ if (!testCaseId) {
671
+ throw new Error("Test case ID is required");
672
+ }
673
+ // Fetch current test case to preserve existing values for fields not being updated
674
+ // The backend assigns all fields directly, so we need to include existing values
675
+ let currentTestCase = null;
676
+ try {
677
+ const response = await this.apiClient.get(`${projectId}/testcase/${testCaseId}`);
678
+ currentTestCase = response.data;
679
+ }
680
+ catch (error) {
681
+ // If we can't fetch the current test case, continue without it
682
+ // The backend will handle missing fields
683
+ }
684
+ const updateData = {};
685
+ // Only include fields that are being updated
686
+ if (title !== undefined)
687
+ updateData.title = title;
688
+ if (description !== undefined)
689
+ updateData.description = description || "";
690
+ if (type !== undefined)
691
+ updateData.type = type;
692
+ if (status !== undefined)
693
+ updateData.status = status;
694
+ if (priority !== undefined)
695
+ updateData.priority = priority;
696
+ if (estimatedDuration !== undefined)
697
+ updateData.estimated_duration = estimatedDuration;
698
+ if (testData !== undefined) {
699
+ try {
700
+ updateData.testdata =
701
+ typeof testData === "string" ? JSON.parse(testData) : testData;
702
+ }
703
+ catch {
704
+ updateData.testdata = testData;
705
+ }
706
+ }
707
+ if (labels !== undefined) {
708
+ // Resolve label names to IDs (fetch existing, create missing)
709
+ const resolvedLabels = await this.resolveLabels(projectId, labels);
710
+ updateData.labels = resolvedLabels;
711
+ }
712
+ if (Object.keys(updateData).length === 0) {
713
+ throw new Error("At least one field must be provided to update");
714
+ }
715
+ // Add updated_by field from the logged-in user (required by backend)
716
+ const userId = this.apiClient.getUserId();
717
+ if (!userId) {
718
+ throw new Error("User ID not available. Please login first.");
719
+ }
720
+ updateData.updated_by = userId;
721
+ const response = await this.apiClient.put(`${projectId}/testcase/${testCaseId}`, updateData);
722
+ return jsonResponse({
723
+ success: response.success,
724
+ message: response.message,
725
+ data: response.data,
726
+ });
727
+ }
728
+ // private async deleteTestCase(args: any) {
729
+ // const { testCaseId, projectId: argProjectId } = args;
730
+ // const projectId = argProjectId || this.apiClient["projectId"];
731
+ // if (!testCaseId) {
732
+ // throw new Error("Test case ID is required");
733
+ // }
734
+ // if (!projectId) {
735
+ // throw new Error(
736
+ // "Project ID is required. Either call 'set_active_project' or provide 'projectId' in this call."
737
+ // );
738
+ // }
739
+ // // Backend expects ids as an array in the request body
740
+ // const response = await this.apiClient.delete<DeleteTestCaseResponse>(
741
+ // `${projectId}/testcase/delete`,
742
+ // {
743
+ // data: { ids: [testCaseId] },
744
+ // headers: {
745
+ // "Content-Type": "application/json",
746
+ // },
747
+ // }
748
+ // );
749
+ // return {
750
+ // content: [
751
+ // {
752
+ // type: "text",
753
+ // text: JSON.stringify(
754
+ // {
755
+ // success: response.success,
756
+ // message: response.message,
757
+ // data: response.data,
758
+ // },
759
+ // null,
760
+ // 2
761
+ // ),
762
+ // },
763
+ // ],
764
+ // };
765
+ // }
766
+ async cloneTestCase(args) {
767
+ const { testCaseId, testCaseIds, targetModuleId } = args;
768
+ const projectId = this.projectContext.resolveProjectId(args, "clone_test_case");
769
+ if (!targetModuleId) {
770
+ throw new Error("Target module ID is required");
771
+ }
772
+ if (!testCaseId && (!testCaseIds || testCaseIds.length === 0)) {
773
+ throw new Error("Either testCaseId or testCaseIds array is required");
774
+ }
775
+ if (!projectId) {
776
+ throw new Error("Project ID is required. Provide it as an argument or set QA_PATH_PROJECT_ID.");
777
+ }
778
+ const cloneData = {
779
+ targetModuleId,
780
+ };
781
+ if (testCaseId) {
782
+ cloneData.testCaseId = testCaseId;
783
+ }
784
+ if (testCaseIds) {
785
+ cloneData.testCaseIds = testCaseIds;
786
+ }
787
+ const response = await this.apiClient.post(`${projectId}/testcase/clone`, cloneData);
788
+ return jsonResponse({
789
+ success: response.success,
790
+ message: response.message,
791
+ data: response.data,
792
+ });
793
+ }
794
+ async moveTestCase(args) {
795
+ const { testCaseId, targetModuleId } = args;
796
+ const projectId = this.projectContext.resolveProjectId(args, "move_test_case");
797
+ if (!testCaseId) {
798
+ throw new Error("Test case ID is required");
799
+ }
800
+ if (!targetModuleId) {
801
+ throw new Error("Target module ID is required");
802
+ }
803
+ const response = await this.apiClient.post(`${projectId}/testcase/move`, {
804
+ testCaseId,
805
+ targetModuleId,
806
+ });
807
+ return jsonResponse({
808
+ success: response.success,
809
+ message: response.message,
810
+ data: response.data,
811
+ });
812
+ }
813
+ async bulkCreateTestCases(args) {
814
+ const { testCases } = args;
815
+ const projectId = this.projectContext.resolveProjectId(args, "bulk_create_test_cases");
816
+ if (!testCases || !Array.isArray(testCases) || testCases.length === 0) {
817
+ throw new Error("testCases array is required and must not be empty");
818
+ }
819
+ // Transform the test cases to match the API format
820
+ // Backend expects "Module" (capital M) for bulk create validation
821
+ const transformedTestCases = testCases.map((tc) => ({
822
+ title: tc.title,
823
+ description: tc.description || "",
824
+ Module: tc.moduleId, // Backend validation expects "Module" (capital M)
825
+ type: tc.type || "Functional Test",
826
+ status: tc.status || "New",
827
+ priority: tc.priority || "Normal",
828
+ estimated_duration: tc.estimatedDuration || 15,
829
+ testdata: tc.testData
830
+ ? typeof tc.testData === "string"
831
+ ? JSON.parse(tc.testData)
832
+ : tc.testData
833
+ : null,
834
+ }));
835
+ const response = await this.apiClient.post(`${projectId}/testcase/bulkcreate`, {
836
+ testCases: transformedTestCases,
837
+ });
838
+ return jsonResponse({
839
+ success: response.success,
840
+ message: response.message,
841
+ data: response.data,
842
+ });
843
+ }
844
+ }
845
+ //# sourceMappingURL=testcase-tools.js.map