@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,674 @@
1
+ import { ProjectTools } from "../../tools/project-tools.js";
2
+ import { mapApiErrorToMcpResponse, mapApiResponseToMcpError } from "../../utils/mcp-error-mapper.js";
3
+ // Mock the error mapper
4
+ jest.mock("../../utils/mcp-error-mapper.js", () => ({
5
+ mapApiErrorToMcpResponse: jest.fn(),
6
+ mapApiResponseToMcpError: jest.fn(),
7
+ }));
8
+ describe("ProjectTools", () => {
9
+ let projectTools;
10
+ let mockApiClient;
11
+ beforeEach(() => {
12
+ mockApiClient = {
13
+ get: jest.fn(),
14
+ post: jest.fn(),
15
+ put: jest.fn(),
16
+ };
17
+ projectTools = new ProjectTools(mockApiClient);
18
+ jest.clearAllMocks();
19
+ });
20
+ describe("listProjects", () => {
21
+ it("should return projects on success", async () => {
22
+ const mockResponse = {
23
+ success: true,
24
+ message: "Projects retrieved successfully",
25
+ data: [
26
+ { id: "1", name: "Project 1", prefix: "P1" },
27
+ { id: "2", name: "Project 2", prefix: "P2" },
28
+ ],
29
+ };
30
+ mockApiClient.post.mockResolvedValue(mockResponse);
31
+ const result = await projectTools.handle("list_projects", {
32
+ page: 1,
33
+ pageSize: 25,
34
+ });
35
+ expect(mockApiClient.post).toHaveBeenCalledWith("/project/getProjects", { page: 1, pageSize: 25 });
36
+ expect(result).toHaveProperty("content");
37
+ const parsed = JSON.parse(result.content[0].text);
38
+ expect(parsed.success).toBe(true);
39
+ expect(parsed.data).toHaveLength(2);
40
+ });
41
+ it("should validate required page parameter", async () => {
42
+ const result = await projectTools.handle("list_projects", {
43
+ pageSize: 25,
44
+ });
45
+ expect(mockApiClient.post).not.toHaveBeenCalled();
46
+ const parsed = JSON.parse(result.content[0].text);
47
+ expect(parsed.success).toBe(false);
48
+ expect(parsed.error_code).toBe("INVALID_PAGE");
49
+ });
50
+ it("should validate page is >= 1", async () => {
51
+ const result = await projectTools.handle("list_projects", {
52
+ page: 0,
53
+ pageSize: 25,
54
+ });
55
+ expect(mockApiClient.post).not.toHaveBeenCalled();
56
+ const parsed = JSON.parse(result.content[0].text);
57
+ expect(parsed.success).toBe(false);
58
+ expect(parsed.error_code).toBe("INVALID_PAGE");
59
+ });
60
+ it("should validate required pageSize parameter", async () => {
61
+ const result = await projectTools.handle("list_projects", {
62
+ page: 1,
63
+ });
64
+ expect(mockApiClient.post).not.toHaveBeenCalled();
65
+ const parsed = JSON.parse(result.content[0].text);
66
+ expect(parsed.success).toBe(false);
67
+ expect(parsed.error_code).toBe("INVALID_PAGE_SIZE");
68
+ });
69
+ it("should validate pageSize is between 1 and 10000", async () => {
70
+ const result = await projectTools.handle("list_projects", {
71
+ page: 1,
72
+ pageSize: 10001,
73
+ });
74
+ expect(mockApiClient.post).not.toHaveBeenCalled();
75
+ const parsed = JSON.parse(result.content[0].text);
76
+ expect(parsed.success).toBe(false);
77
+ expect(parsed.error_code).toBe("INVALID_PAGE_SIZE");
78
+ });
79
+ it("should validate sortField against allowed values", async () => {
80
+ const result = await projectTools.handle("list_projects", {
81
+ page: 1,
82
+ pageSize: 25,
83
+ sortField: "invalid_field",
84
+ });
85
+ expect(mockApiClient.post).not.toHaveBeenCalled();
86
+ const parsed = JSON.parse(result.content[0].text);
87
+ expect(parsed.success).toBe(false);
88
+ expect(parsed.error_code).toBe("INVALID_SORT_FIELD");
89
+ });
90
+ it("should accept valid sortField values", async () => {
91
+ const mockResponse = {
92
+ success: true,
93
+ message: "Success",
94
+ data: [],
95
+ };
96
+ mockApiClient.post.mockResolvedValue(mockResponse);
97
+ await projectTools.handle("list_projects", {
98
+ page: 1,
99
+ pageSize: 25,
100
+ sortField: "name",
101
+ sortOrder: "ASC",
102
+ });
103
+ expect(mockApiClient.post).toHaveBeenCalledWith("/project/getProjects", {
104
+ page: 1,
105
+ pageSize: 25,
106
+ sortField: "name",
107
+ sortOrder: "ASC",
108
+ });
109
+ });
110
+ it("should handle API failure response", async () => {
111
+ const mockResponse = {
112
+ success: false,
113
+ message: "Failed to retrieve",
114
+ };
115
+ mockApiClient.post.mockResolvedValue(mockResponse);
116
+ const result = await projectTools.handle("list_projects", {
117
+ page: 1,
118
+ pageSize: 25,
119
+ });
120
+ const parsed = JSON.parse(result.content[0].text);
121
+ expect(parsed.success).toBe(false);
122
+ expect(parsed.error_code).toBe("LIST_FAILED");
123
+ });
124
+ it("should throw on infrastructure errors", async () => {
125
+ mockApiClient.post.mockRejectedValue(new Error("Network error"));
126
+ await expect(projectTools.handle("list_projects", {
127
+ page: 1,
128
+ pageSize: 25,
129
+ })).rejects.toThrow("Network error");
130
+ });
131
+ it("should include query parameter if provided", async () => {
132
+ const mockResponse = {
133
+ success: true,
134
+ message: "Success",
135
+ data: [],
136
+ };
137
+ mockApiClient.post.mockResolvedValue(mockResponse);
138
+ await projectTools.handle("list_projects", {
139
+ page: 1,
140
+ pageSize: 25,
141
+ query: "name contains 'API'",
142
+ });
143
+ expect(mockApiClient.post).toHaveBeenCalledWith("/project/getProjects", {
144
+ page: 1,
145
+ pageSize: 25,
146
+ query: "name contains 'API'",
147
+ });
148
+ });
149
+ it("should trim query parameter before sending", async () => {
150
+ const mockResponse = {
151
+ success: true,
152
+ message: "Success",
153
+ data: [],
154
+ };
155
+ mockApiClient.post.mockResolvedValue(mockResponse);
156
+ await projectTools.handle("list_projects", {
157
+ page: 1,
158
+ pageSize: 25,
159
+ query: " name contains 'API' ",
160
+ });
161
+ expect(mockApiClient.post).toHaveBeenCalledWith("/project/getProjects", {
162
+ page: 1,
163
+ pageSize: 25,
164
+ query: "name contains 'API'",
165
+ });
166
+ });
167
+ it("should not include query if empty string", async () => {
168
+ const mockResponse = {
169
+ success: true,
170
+ message: "Success",
171
+ data: [],
172
+ };
173
+ mockApiClient.post.mockResolvedValue(mockResponse);
174
+ await projectTools.handle("list_projects", {
175
+ page: 1,
176
+ pageSize: 25,
177
+ query: " ",
178
+ });
179
+ expect(mockApiClient.post).toHaveBeenCalledWith("/project/getProjects", {
180
+ page: 1,
181
+ pageSize: 25,
182
+ });
183
+ });
184
+ it("should validate query length does not exceed 5000 characters", async () => {
185
+ const longQuery = "a".repeat(5001);
186
+ const result = await projectTools.handle("list_projects", {
187
+ page: 1,
188
+ pageSize: 25,
189
+ query: longQuery,
190
+ });
191
+ expect(mockApiClient.post).not.toHaveBeenCalled();
192
+ const parsed = JSON.parse(result.content[0].text);
193
+ expect(parsed.success).toBe(false);
194
+ expect(parsed.error_code).toBe("INVALID_QUERY");
195
+ });
196
+ });
197
+ describe("createProject", () => {
198
+ it("should validate required name field", async () => {
199
+ const result = await projectTools.handle("create_project", {
200
+ prefix: "PRJ",
201
+ });
202
+ expect(mockApiClient.post).not.toHaveBeenCalled();
203
+ const parsed = JSON.parse(result.content[0].text);
204
+ expect(parsed.success).toBe(false);
205
+ expect(parsed.error_code).toBe("INVALID_NAME");
206
+ });
207
+ it("should validate name is non-empty string", async () => {
208
+ const result = await projectTools.handle("create_project", {
209
+ name: "",
210
+ prefix: "PRJ",
211
+ });
212
+ expect(mockApiClient.post).not.toHaveBeenCalled();
213
+ const parsed = JSON.parse(result.content[0].text);
214
+ expect(parsed.success).toBe(false);
215
+ expect(parsed.error_code).toBe("INVALID_NAME");
216
+ });
217
+ it("should validate name is string type", async () => {
218
+ const result = await projectTools.handle("create_project", {
219
+ name: 123,
220
+ prefix: "PRJ",
221
+ });
222
+ expect(mockApiClient.post).not.toHaveBeenCalled();
223
+ const parsed = JSON.parse(result.content[0].text);
224
+ expect(parsed.success).toBe(false);
225
+ expect(parsed.error_code).toBe("INVALID_NAME");
226
+ });
227
+ it("should validate required prefix field", async () => {
228
+ const result = await projectTools.handle("create_project", {
229
+ name: "Test Project",
230
+ });
231
+ expect(mockApiClient.post).not.toHaveBeenCalled();
232
+ const parsed = JSON.parse(result.content[0].text);
233
+ expect(parsed.success).toBe(false);
234
+ expect(parsed.error_code).toBe("INVALID_PREFIX");
235
+ });
236
+ it("should validate prefix is non-empty string", async () => {
237
+ const result = await projectTools.handle("create_project", {
238
+ name: "Test Project",
239
+ prefix: "",
240
+ });
241
+ expect(mockApiClient.post).not.toHaveBeenCalled();
242
+ const parsed = JSON.parse(result.content[0].text);
243
+ expect(parsed.success).toBe(false);
244
+ expect(parsed.error_code).toBe("INVALID_PREFIX");
245
+ });
246
+ it("should validate description type if provided", async () => {
247
+ const result = await projectTools.handle("create_project", {
248
+ name: "Test Project",
249
+ prefix: "PRJ",
250
+ description: 123,
251
+ });
252
+ expect(mockApiClient.post).not.toHaveBeenCalled();
253
+ const parsed = JSON.parse(result.content[0].text);
254
+ expect(parsed.success).toBe(false);
255
+ expect(parsed.error_code).toBe("INVALID_DESCRIPTION_TYPE");
256
+ });
257
+ it("should validate context type if provided", async () => {
258
+ const result = await projectTools.handle("create_project", {
259
+ name: "Test Project",
260
+ prefix: "PRJ",
261
+ context: true,
262
+ });
263
+ expect(mockApiClient.post).not.toHaveBeenCalled();
264
+ const parsed = JSON.parse(result.content[0].text);
265
+ expect(parsed.success).toBe(false);
266
+ expect(parsed.error_code).toBe("INVALID_CONTEXT_TYPE");
267
+ });
268
+ it("should trim string fields before sending", async () => {
269
+ const mockResponse = {
270
+ success: true,
271
+ message: "Project created",
272
+ data: { id: "123", name: "Test Project", prefix: "PRJ" },
273
+ };
274
+ mockApiClient.post.mockResolvedValue(mockResponse);
275
+ mapApiErrorToMcpResponse.mockReturnValue(null);
276
+ mapApiResponseToMcpError.mockReturnValue(null);
277
+ await projectTools.handle("create_project", {
278
+ name: " Test Project ",
279
+ prefix: " PRJ ",
280
+ description: " Description ",
281
+ context: " Context ",
282
+ });
283
+ expect(mockApiClient.post).toHaveBeenCalledWith("/project/createProject", {
284
+ name: "Test Project",
285
+ prefix: "PRJ",
286
+ description: "Description",
287
+ context: "Context",
288
+ });
289
+ });
290
+ it("should create project successfully", async () => {
291
+ const mockResponse = {
292
+ success: true,
293
+ message: "Project created successfully",
294
+ data: {
295
+ id: "123",
296
+ name: "Test Project",
297
+ prefix: "PRJ",
298
+ },
299
+ };
300
+ mockApiClient.post.mockResolvedValue(mockResponse);
301
+ mapApiErrorToMcpResponse.mockReturnValue(null);
302
+ mapApiResponseToMcpError.mockReturnValue(null);
303
+ const result = await projectTools.handle("create_project", {
304
+ name: "Test Project",
305
+ prefix: "PRJ",
306
+ });
307
+ const parsed = JSON.parse(result.content[0].text);
308
+ expect(parsed.success).toBe(true);
309
+ expect(parsed.message).toBe("Project created successfully");
310
+ expect(parsed.data.id).toBe("123");
311
+ });
312
+ it("should handle API business failure", async () => {
313
+ const mockResponse = {
314
+ success: false,
315
+ message: "Project name already exists",
316
+ };
317
+ mockApiClient.post.mockResolvedValue(mockResponse);
318
+ mapApiErrorToMcpResponse.mockReturnValue(null);
319
+ mapApiResponseToMcpError.mockReturnValue({
320
+ content: [
321
+ {
322
+ type: "text",
323
+ text: JSON.stringify({
324
+ success: false,
325
+ error_code: "CREATE_FAILED",
326
+ message: "Project name already exists",
327
+ }),
328
+ },
329
+ ],
330
+ });
331
+ const result = await projectTools.handle("create_project", {
332
+ name: "Test Project",
333
+ prefix: "PRJ",
334
+ });
335
+ const parsed = JSON.parse(result.content[0].text);
336
+ expect(parsed.success).toBe(false);
337
+ expect(parsed.error_code).toBe("CREATE_FAILED");
338
+ });
339
+ it("should handle API validation error (4xx)", async () => {
340
+ const mockError = {
341
+ response: {
342
+ status: 400,
343
+ data: { message: "Invalid request data" },
344
+ },
345
+ };
346
+ mockApiClient.post.mockRejectedValue(mockError);
347
+ mapApiErrorToMcpResponse.mockReturnValue({
348
+ content: [
349
+ {
350
+ type: "text",
351
+ text: JSON.stringify({
352
+ success: false,
353
+ error_code: "BAD_REQUEST",
354
+ message: "Invalid request data",
355
+ }),
356
+ },
357
+ ],
358
+ });
359
+ const result = await projectTools.handle("create_project", {
360
+ name: "Test Project",
361
+ prefix: "PRJ",
362
+ });
363
+ const parsed = JSON.parse(result.content[0].text);
364
+ expect(parsed.success).toBe(false);
365
+ expect(parsed.error_code).toBe("BAD_REQUEST");
366
+ });
367
+ it("should throw on infrastructure errors (5xx)", async () => {
368
+ const mockError = new Error("Server error");
369
+ mockError.response = { status: 500, data: {} };
370
+ mockApiClient.post.mockRejectedValue(mockError);
371
+ mapApiErrorToMcpResponse.mockReturnValue(null);
372
+ await expect(projectTools.handle("create_project", {
373
+ name: "Test Project",
374
+ prefix: "PRJ",
375
+ })).rejects.toThrow("Server error");
376
+ });
377
+ it("should not include optional fields if not provided", async () => {
378
+ const mockResponse = {
379
+ success: true,
380
+ message: "Project created",
381
+ data: { id: "123", name: "Test", prefix: "PRJ" },
382
+ };
383
+ mockApiClient.post.mockResolvedValue(mockResponse);
384
+ mapApiErrorToMcpResponse.mockReturnValue(null);
385
+ mapApiResponseToMcpError.mockReturnValue(null);
386
+ await projectTools.handle("create_project", {
387
+ name: "Test Project",
388
+ prefix: "PRJ",
389
+ });
390
+ expect(mockApiClient.post).toHaveBeenCalledWith("/project/createProject", {
391
+ name: "Test Project",
392
+ prefix: "PRJ",
393
+ });
394
+ });
395
+ });
396
+ describe("updateProject", () => {
397
+ it("should validate required projectId field", async () => {
398
+ const result = await projectTools.handle("update_project", {
399
+ name: "Updated Name",
400
+ });
401
+ expect(mockApiClient.put).not.toHaveBeenCalled();
402
+ const parsed = JSON.parse(result.content[0].text);
403
+ expect(parsed.success).toBe(false);
404
+ expect(parsed.error_code).toBe("INVALID_PROJECT_ID");
405
+ });
406
+ it("should validate projectId is string", async () => {
407
+ const result = await projectTools.handle("update_project", {
408
+ projectId: 123,
409
+ name: "Updated Name",
410
+ });
411
+ expect(mockApiClient.put).not.toHaveBeenCalled();
412
+ const parsed = JSON.parse(result.content[0].text);
413
+ expect(parsed.success).toBe(false);
414
+ expect(parsed.error_code).toBe("INVALID_PROJECT_ID");
415
+ });
416
+ it("should validate required name field", async () => {
417
+ const result = await projectTools.handle("update_project", {
418
+ projectId: "123",
419
+ });
420
+ expect(mockApiClient.put).not.toHaveBeenCalled();
421
+ const parsed = JSON.parse(result.content[0].text);
422
+ expect(parsed.success).toBe(false);
423
+ expect(parsed.error_code).toBe("INVALID_NAME");
424
+ });
425
+ it("should validate name is non-empty string", async () => {
426
+ const result = await projectTools.handle("update_project", {
427
+ projectId: "123",
428
+ name: "",
429
+ });
430
+ expect(mockApiClient.put).not.toHaveBeenCalled();
431
+ const parsed = JSON.parse(result.content[0].text);
432
+ expect(parsed.success).toBe(false);
433
+ expect(parsed.error_code).toBe("INVALID_NAME");
434
+ });
435
+ it("should validate description type if provided", async () => {
436
+ const result = await projectTools.handle("update_project", {
437
+ projectId: "123",
438
+ name: "Updated Name",
439
+ description: 123,
440
+ });
441
+ expect(mockApiClient.put).not.toHaveBeenCalled();
442
+ const parsed = JSON.parse(result.content[0].text);
443
+ expect(parsed.success).toBe(false);
444
+ expect(parsed.error_code).toBe("INVALID_DESCRIPTION_TYPE");
445
+ });
446
+ it("should validate context type if provided", async () => {
447
+ const mockResponse = {
448
+ success: true,
449
+ message: "Project updated successfully",
450
+ data: {
451
+ id: "123",
452
+ name: "Updated Name",
453
+ prefix: "PRJ",
454
+ },
455
+ };
456
+ mockApiClient.put.mockResolvedValue(mockResponse);
457
+ mapApiErrorToMcpResponse.mockReturnValue(null);
458
+ mapApiResponseToMcpError.mockReturnValue(null);
459
+ const result = await projectTools.handle("update_project", {
460
+ projectId: "123",
461
+ name: "Updated Name",
462
+ context: null,
463
+ });
464
+ // null should be allowed (optional field) - should not be included in body
465
+ expect(mockApiClient.put).toHaveBeenCalledWith("/project/update", {
466
+ id: "123",
467
+ name: "Updated Name",
468
+ });
469
+ const parsed = JSON.parse(result.content[0].text);
470
+ expect(parsed.success).toBe(true);
471
+ });
472
+ it("should update project successfully", async () => {
473
+ const mockResponse = {
474
+ success: true,
475
+ message: "Project updated successfully",
476
+ data: {
477
+ id: "123",
478
+ name: "Updated Name",
479
+ prefix: "PRJ",
480
+ },
481
+ };
482
+ mockApiClient.put.mockResolvedValue(mockResponse);
483
+ mapApiErrorToMcpResponse.mockReturnValue(null);
484
+ mapApiResponseToMcpError.mockReturnValue(null);
485
+ const result = await projectTools.handle("update_project", {
486
+ projectId: "123",
487
+ name: "Updated Name",
488
+ });
489
+ expect(mockApiClient.put).toHaveBeenCalledWith("/project/update", {
490
+ id: "123",
491
+ name: "Updated Name",
492
+ });
493
+ const parsed = JSON.parse(result.content[0].text);
494
+ expect(parsed.success).toBe(true);
495
+ expect(parsed.message).toBe("Project updated successfully");
496
+ });
497
+ it("should handle API business failure", async () => {
498
+ const mockResponse = {
499
+ success: false,
500
+ message: "Project name already exists",
501
+ };
502
+ mockApiClient.put.mockResolvedValue(mockResponse);
503
+ mapApiErrorToMcpResponse.mockReturnValue(null);
504
+ mapApiResponseToMcpError.mockReturnValue({
505
+ content: [
506
+ {
507
+ type: "text",
508
+ text: JSON.stringify({
509
+ success: false,
510
+ error_code: "UPDATE_FAILED",
511
+ message: "Project name already exists",
512
+ }),
513
+ },
514
+ ],
515
+ });
516
+ const result = await projectTools.handle("update_project", {
517
+ projectId: "123",
518
+ name: "Updated Name",
519
+ });
520
+ const parsed = JSON.parse(result.content[0].text);
521
+ expect(parsed.success).toBe(false);
522
+ expect(parsed.error_code).toBe("UPDATE_FAILED");
523
+ });
524
+ it("should handle API validation error (4xx)", async () => {
525
+ const mockError = {
526
+ response: {
527
+ status: 404,
528
+ data: { message: "Project not found" },
529
+ },
530
+ };
531
+ mockApiClient.put.mockRejectedValue(mockError);
532
+ mapApiErrorToMcpResponse.mockReturnValue({
533
+ content: [
534
+ {
535
+ type: "text",
536
+ text: JSON.stringify({
537
+ success: false,
538
+ error_code: "NOT_FOUND",
539
+ message: "Project not found",
540
+ }),
541
+ },
542
+ ],
543
+ });
544
+ const result = await projectTools.handle("update_project", {
545
+ projectId: "123",
546
+ name: "Updated Name",
547
+ });
548
+ const parsed = JSON.parse(result.content[0].text);
549
+ expect(parsed.success).toBe(false);
550
+ expect(parsed.error_code).toBe("NOT_FOUND");
551
+ });
552
+ it("should throw on infrastructure errors (5xx)", async () => {
553
+ const mockError = new Error("Server error");
554
+ mockError.response = { status: 500, data: {} };
555
+ mockApiClient.put.mockRejectedValue(mockError);
556
+ mapApiErrorToMcpResponse.mockReturnValue(null);
557
+ await expect(projectTools.handle("update_project", {
558
+ projectId: "123",
559
+ name: "Updated Name",
560
+ })).rejects.toThrow("Server error");
561
+ });
562
+ });
563
+ describe("suggestProjectPrefix", () => {
564
+ it("should validate required projectName field", async () => {
565
+ const result = await projectTools.handle("suggest_project_prefix", {});
566
+ expect(mockApiClient.get).not.toHaveBeenCalled();
567
+ const parsed = JSON.parse(result.content[0].text);
568
+ expect(parsed.success).toBe(false);
569
+ expect(parsed.error_code).toBe("INVALID_PROJECT_NAME");
570
+ });
571
+ it("should validate projectName is non-empty string", async () => {
572
+ const result = await projectTools.handle("suggest_project_prefix", {
573
+ projectName: "",
574
+ });
575
+ expect(mockApiClient.get).not.toHaveBeenCalled();
576
+ const parsed = JSON.parse(result.content[0].text);
577
+ expect(parsed.success).toBe(false);
578
+ expect(parsed.error_code).toBe("INVALID_PROJECT_NAME");
579
+ });
580
+ it("should validate projectName is string type", async () => {
581
+ const result = await projectTools.handle("suggest_project_prefix", {
582
+ projectName: 123,
583
+ });
584
+ expect(mockApiClient.get).not.toHaveBeenCalled();
585
+ const parsed = JSON.parse(result.content[0].text);
586
+ expect(parsed.success).toBe(false);
587
+ expect(parsed.error_code).toBe("INVALID_PROJECT_NAME");
588
+ });
589
+ it("should trim projectName before sending", async () => {
590
+ const mockResponse = {
591
+ success: true,
592
+ message: "Key is available",
593
+ data: "TP",
594
+ };
595
+ mockApiClient.get.mockResolvedValue(mockResponse);
596
+ await projectTools.handle("suggest_project_prefix", {
597
+ projectName: " Test Project ",
598
+ });
599
+ expect(mockApiClient.get).toHaveBeenCalledWith("/project/generateUniquePrefixForProject", { params: { projectName: "Test Project" } });
600
+ });
601
+ it("should return prefix suggestion on success", async () => {
602
+ const mockResponse = {
603
+ success: true,
604
+ message: "Key is available",
605
+ data: "TP",
606
+ };
607
+ mockApiClient.get.mockResolvedValue(mockResponse);
608
+ const result = await projectTools.handle("suggest_project_prefix", {
609
+ projectName: "Test Project",
610
+ });
611
+ const parsed = JSON.parse(result.content[0].text);
612
+ expect(parsed.success).toBe(true);
613
+ expect(parsed.message).toBe("Key is available");
614
+ expect(parsed.data.prefix).toBe("TP");
615
+ expect(parsed.data.projectName).toBe("Test Project");
616
+ });
617
+ it("should handle API failure response", async () => {
618
+ const mockResponse = {
619
+ success: false,
620
+ message: "Failed to generate prefix",
621
+ };
622
+ mockApiClient.get.mockResolvedValue(mockResponse);
623
+ const result = await projectTools.handle("suggest_project_prefix", {
624
+ projectName: "Test Project",
625
+ });
626
+ const parsed = JSON.parse(result.content[0].text);
627
+ expect(parsed.success).toBe(false);
628
+ expect(parsed.error_code).toBe("PREFIX_GENERATION_FAILED");
629
+ expect(parsed.message).toBe("Failed to generate prefix");
630
+ });
631
+ it("should handle API validation error (4xx)", async () => {
632
+ const mockError = {
633
+ response: {
634
+ status: 400,
635
+ data: { message: "Project name is required" },
636
+ },
637
+ };
638
+ mockApiClient.get.mockRejectedValue(mockError);
639
+ mapApiErrorToMcpResponse.mockReturnValue({
640
+ content: [
641
+ {
642
+ type: "text",
643
+ text: JSON.stringify({
644
+ success: false,
645
+ error_code: "BAD_REQUEST",
646
+ message: "Project name is required",
647
+ }),
648
+ },
649
+ ],
650
+ });
651
+ const result = await projectTools.handle("suggest_project_prefix", {
652
+ projectName: "Test Project",
653
+ });
654
+ const parsed = JSON.parse(result.content[0].text);
655
+ expect(parsed.success).toBe(false);
656
+ expect(parsed.error_code).toBe("BAD_REQUEST");
657
+ });
658
+ it("should throw on infrastructure errors (5xx)", async () => {
659
+ const mockError = new Error("Server error");
660
+ mockError.response = { status: 500, data: {} };
661
+ mockApiClient.get.mockRejectedValue(mockError);
662
+ mapApiErrorToMcpResponse.mockReturnValue(null);
663
+ await expect(projectTools.handle("suggest_project_prefix", {
664
+ projectName: "Test Project",
665
+ })).rejects.toThrow("Server error");
666
+ });
667
+ });
668
+ describe("handle", () => {
669
+ it("should throw error for unknown tool", async () => {
670
+ await expect(projectTools.handle("unknown_tool", {})).rejects.toThrow("Unknown project tool: unknown_tool");
671
+ });
672
+ });
673
+ });
674
+ //# sourceMappingURL=project-tools.test.js.map