@vfarcic/dot-ai 0.4.9 → 0.5.1

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 (145) hide show
  1. package/.claude/commands/context-load.md +11 -0
  2. package/.claude/commands/context-save.md +16 -0
  3. package/.claude/commands/prd-done.md +115 -0
  4. package/.claude/commands/prd-get.md +25 -0
  5. package/.claude/commands/prd-start.md +87 -0
  6. package/.claude/commands/task-done.md +77 -0
  7. package/.claude/commands/tests-reminder.md +32 -0
  8. package/.claude/settings.local.json +20 -0
  9. package/.eslintrc.json +25 -0
  10. package/.github/workflows/ci.yml +170 -0
  11. package/.prettierrc.json +10 -0
  12. package/.teller.yml +8 -0
  13. package/CLAUDE.md +162 -0
  14. package/assets/images/logo.png +0 -0
  15. package/bin/dot-ai.ts +47 -0
  16. package/destroy.sh +45 -0
  17. package/devbox.json +13 -0
  18. package/devbox.lock +225 -0
  19. package/docs/API.md +449 -0
  20. package/docs/CONTEXT.md +49 -0
  21. package/docs/DEVELOPMENT.md +203 -0
  22. package/docs/NEXT_STEPS.md +97 -0
  23. package/docs/STAGE_BASED_API.md +97 -0
  24. package/docs/cli-guide.md +798 -0
  25. package/docs/design.md +750 -0
  26. package/docs/discovery-engine.md +515 -0
  27. package/docs/error-handling.md +429 -0
  28. package/docs/function-registration.md +157 -0
  29. package/docs/mcp-guide.md +416 -0
  30. package/package.json +2 -123
  31. package/renovate.json +51 -0
  32. package/setup.sh +111 -0
  33. package/{dist/cli.js → src/cli.ts} +26 -19
  34. package/src/core/claude.ts +280 -0
  35. package/src/core/deploy-operation.ts +127 -0
  36. package/src/core/discovery.ts +900 -0
  37. package/src/core/error-handling.ts +562 -0
  38. package/src/core/index.ts +143 -0
  39. package/src/core/kubernetes-utils.ts +218 -0
  40. package/src/core/memory.ts +148 -0
  41. package/src/core/schema.ts +830 -0
  42. package/src/core/session-utils.ts +97 -0
  43. package/src/core/workflow.ts +234 -0
  44. package/src/index.ts +18 -0
  45. package/src/interfaces/cli.ts +872 -0
  46. package/src/interfaces/mcp.ts +183 -0
  47. package/src/mcp/server.ts +131 -0
  48. package/src/tools/answer-question.ts +807 -0
  49. package/src/tools/choose-solution.ts +169 -0
  50. package/src/tools/deploy-manifests.ts +94 -0
  51. package/src/tools/generate-manifests.ts +502 -0
  52. package/src/tools/index.ts +41 -0
  53. package/src/tools/recommend.ts +370 -0
  54. package/tests/__mocks__/@kubernetes/client-node.ts +106 -0
  55. package/tests/build-system.test.ts +345 -0
  56. package/tests/configuration.test.ts +226 -0
  57. package/tests/core/deploy-operation.test.ts +38 -0
  58. package/tests/core/discovery.test.ts +1648 -0
  59. package/tests/core/error-handling.test.ts +632 -0
  60. package/tests/core/schema.test.ts +1658 -0
  61. package/tests/core/session-utils.test.ts +245 -0
  62. package/tests/core.test.ts +439 -0
  63. package/tests/fixtures/configmap-no-labels.yaml +8 -0
  64. package/tests/fixtures/crossplane-app-configuration.yaml +6 -0
  65. package/tests/fixtures/crossplane-providers.yaml +45 -0
  66. package/tests/fixtures/crossplane-rbac.yaml +48 -0
  67. package/tests/fixtures/invalid-configmap.yaml +8 -0
  68. package/tests/fixtures/invalid-deployment.yaml +17 -0
  69. package/tests/fixtures/test-deployment.yaml +28 -0
  70. package/tests/fixtures/valid-configmap.yaml +15 -0
  71. package/tests/infrastructure.test.ts +426 -0
  72. package/tests/interfaces/cli.test.ts +1036 -0
  73. package/tests/interfaces/mcp.test.ts +139 -0
  74. package/tests/kubernetes-utils.test.ts +200 -0
  75. package/tests/mcp/server.test.ts +126 -0
  76. package/tests/setup.ts +31 -0
  77. package/tests/tools/answer-question.test.ts +367 -0
  78. package/tests/tools/choose-solution.test.ts +481 -0
  79. package/tests/tools/deploy-manifests.test.ts +185 -0
  80. package/tests/tools/generate-manifests.test.ts +441 -0
  81. package/tests/tools/index.test.ts +111 -0
  82. package/tests/tools/recommend.test.ts +180 -0
  83. package/tsconfig.json +34 -0
  84. package/dist/cli.d.ts +0 -3
  85. package/dist/cli.d.ts.map +0 -1
  86. package/dist/core/claude.d.ts +0 -42
  87. package/dist/core/claude.d.ts.map +0 -1
  88. package/dist/core/claude.js +0 -229
  89. package/dist/core/deploy-operation.d.ts +0 -38
  90. package/dist/core/deploy-operation.d.ts.map +0 -1
  91. package/dist/core/deploy-operation.js +0 -101
  92. package/dist/core/discovery.d.ts +0 -162
  93. package/dist/core/discovery.d.ts.map +0 -1
  94. package/dist/core/discovery.js +0 -758
  95. package/dist/core/error-handling.d.ts +0 -167
  96. package/dist/core/error-handling.d.ts.map +0 -1
  97. package/dist/core/error-handling.js +0 -399
  98. package/dist/core/index.d.ts +0 -42
  99. package/dist/core/index.d.ts.map +0 -1
  100. package/dist/core/index.js +0 -123
  101. package/dist/core/kubernetes-utils.d.ts +0 -38
  102. package/dist/core/kubernetes-utils.d.ts.map +0 -1
  103. package/dist/core/kubernetes-utils.js +0 -177
  104. package/dist/core/memory.d.ts +0 -45
  105. package/dist/core/memory.d.ts.map +0 -1
  106. package/dist/core/memory.js +0 -113
  107. package/dist/core/schema.d.ts +0 -187
  108. package/dist/core/schema.d.ts.map +0 -1
  109. package/dist/core/schema.js +0 -655
  110. package/dist/core/session-utils.d.ts +0 -29
  111. package/dist/core/session-utils.d.ts.map +0 -1
  112. package/dist/core/session-utils.js +0 -121
  113. package/dist/core/workflow.d.ts +0 -70
  114. package/dist/core/workflow.d.ts.map +0 -1
  115. package/dist/core/workflow.js +0 -161
  116. package/dist/index.d.ts +0 -15
  117. package/dist/index.d.ts.map +0 -1
  118. package/dist/index.js +0 -32
  119. package/dist/interfaces/cli.d.ts +0 -74
  120. package/dist/interfaces/cli.d.ts.map +0 -1
  121. package/dist/interfaces/cli.js +0 -769
  122. package/dist/interfaces/mcp.d.ts +0 -30
  123. package/dist/interfaces/mcp.d.ts.map +0 -1
  124. package/dist/interfaces/mcp.js +0 -105
  125. package/dist/mcp/server.d.ts +0 -9
  126. package/dist/mcp/server.d.ts.map +0 -1
  127. package/dist/mcp/server.js +0 -151
  128. package/dist/tools/answer-question.d.ts +0 -27
  129. package/dist/tools/answer-question.d.ts.map +0 -1
  130. package/dist/tools/answer-question.js +0 -696
  131. package/dist/tools/choose-solution.d.ts +0 -23
  132. package/dist/tools/choose-solution.d.ts.map +0 -1
  133. package/dist/tools/choose-solution.js +0 -171
  134. package/dist/tools/deploy-manifests.d.ts +0 -25
  135. package/dist/tools/deploy-manifests.d.ts.map +0 -1
  136. package/dist/tools/deploy-manifests.js +0 -74
  137. package/dist/tools/generate-manifests.d.ts +0 -23
  138. package/dist/tools/generate-manifests.d.ts.map +0 -1
  139. package/dist/tools/generate-manifests.js +0 -424
  140. package/dist/tools/index.d.ts +0 -11
  141. package/dist/tools/index.d.ts.map +0 -1
  142. package/dist/tools/index.js +0 -34
  143. package/dist/tools/recommend.d.ts +0 -23
  144. package/dist/tools/recommend.d.ts.map +0 -1
  145. package/dist/tools/recommend.js +0 -332
@@ -0,0 +1,441 @@
1
+ /**
2
+ * Tests for Generate Manifests Tool
3
+ */
4
+
5
+ import {
6
+ GENERATEMANIFESTS_TOOL_NAME,
7
+ GENERATEMANIFESTS_TOOL_DESCRIPTION,
8
+ GENERATEMANIFESTS_TOOL_INPUT_SCHEMA,
9
+ handleGenerateManifestsTool
10
+ } from '../../src/tools/generate-manifests';
11
+ import * as fs from 'fs';
12
+
13
+ // Mock fs module
14
+ jest.mock('fs');
15
+ const mockFs = fs as jest.Mocked<typeof fs>;
16
+
17
+ // Mock DotAI for schema retrieval tests
18
+ const mockDotAI = {
19
+ initialize: jest.fn(),
20
+ discovery: {
21
+ explainResource: jest.fn()
22
+ }
23
+ };
24
+
25
+ jest.mock('../../src/core/index', () => ({
26
+ DotAI: jest.fn(() => mockDotAI)
27
+ }));
28
+
29
+ // Mock Claude integration
30
+ const mockClaudeIntegration = {
31
+ sendMessage: jest.fn()
32
+ };
33
+
34
+ jest.mock('../../src/core/claude', () => ({
35
+ ClaudeIntegration: jest.fn(() => mockClaudeIntegration)
36
+ }));
37
+
38
+ // Mock child process for kubectl commands
39
+ jest.mock('child_process', () => ({
40
+ spawn: jest.fn()
41
+ }));
42
+
43
+ // Mock yaml library
44
+ jest.mock('js-yaml', () => ({
45
+ loadAll: jest.fn(),
46
+ dump: jest.fn()
47
+ }));
48
+
49
+ const mockLogger = {
50
+ debug: jest.fn(),
51
+ info: jest.fn(),
52
+ warn: jest.fn(),
53
+ error: jest.fn(),
54
+ fatal: jest.fn()
55
+ };
56
+
57
+ const mockContext: any = {
58
+ requestId: 'test-request',
59
+ logger: mockLogger,
60
+ dotAI: mockDotAI
61
+ };
62
+
63
+ describe('Generate Manifests Tool', () => {
64
+ beforeEach(() => {
65
+ jest.clearAllMocks();
66
+ // Reset environment
67
+ delete process.env.DOT_AI_SESSION_DIR;
68
+ });
69
+
70
+ describe('Input Validation', () => {
71
+ it('should validate solution ID format', async () => {
72
+ process.env.DOT_AI_SESSION_DIR = '/test/session';
73
+
74
+ const args = {
75
+ solutionId: 'invalid-format'
76
+ };
77
+
78
+ await expect(handleGenerateManifestsTool(args, mockContext.dotAI, mockContext.logger, mockContext.requestId))
79
+ .rejects.toMatchObject({
80
+ message: 'Session directory does not exist: /test/session'
81
+ });
82
+ });
83
+
84
+ it('should require session directory from environment or args', async () => {
85
+ const args = {
86
+ solutionId: 'sol_2025-01-01T120000_abc123def456'
87
+ };
88
+
89
+ await expect(handleGenerateManifestsTool(args, mockContext.dotAI, mockContext.logger, mockContext.requestId))
90
+ .rejects.toMatchObject({
91
+ message: 'Session directory must be specified via --session-dir parameter or DOT_AI_SESSION_DIR environment variable'
92
+ });
93
+ });
94
+
95
+ it('should accept session directory from args', async () => {
96
+ const args = {
97
+ solutionId: 'sol_2025-01-01T120000_abc123def456',
98
+ sessionDir: '/nonexistent/path'
99
+ };
100
+
101
+ // Should fail on directory validation, not on session dir config
102
+ await expect(handleGenerateManifestsTool(args, mockContext.dotAI, mockContext.logger, mockContext.requestId))
103
+ .rejects.toMatchObject({
104
+ message: 'Session directory does not exist: /nonexistent/path'
105
+ });
106
+ });
107
+
108
+ it('should accept session directory from environment', async () => {
109
+ process.env.DOT_AI_SESSION_DIR = '/nonexistent/path';
110
+
111
+ const args = {
112
+ solutionId: 'sol_2025-01-01T120000_abc123def456'
113
+ };
114
+
115
+ // Should fail on directory validation, not on session dir config
116
+ await expect(handleGenerateManifestsTool(args, mockContext.dotAI, mockContext.logger, mockContext.requestId))
117
+ .rejects.toMatchObject({
118
+ message: 'Session directory does not exist: /nonexistent/path'
119
+ });
120
+ });
121
+ });
122
+
123
+ describe('Tool Metadata', () => {
124
+ it('should have valid MCP tool metadata', () => {
125
+ expect(GENERATEMANIFESTS_TOOL_NAME).toBe('generateManifests');
126
+ expect(GENERATEMANIFESTS_TOOL_DESCRIPTION).toContain('Generate final Kubernetes manifests');
127
+ expect(GENERATEMANIFESTS_TOOL_INPUT_SCHEMA.solutionId).toBeDefined();
128
+ });
129
+ });
130
+
131
+ describe('Logging and Error Reporting', () => {
132
+ it('should log meaningful error messages', async () => {
133
+ const args = {
134
+ solutionId: 'invalid-format'
135
+ };
136
+
137
+ try {
138
+ await handleGenerateManifestsTool(args, mockContext.dotAI, mockContext.logger, mockContext.requestId);
139
+ } catch (error) {
140
+ // Should throw validation error before logging execution error
141
+ expect(error).toBeDefined();
142
+ }
143
+ });
144
+
145
+ it('should handle tool execution context properly', async () => {
146
+ const args = {
147
+ solutionId: 'sol_2025-01-01T120000_abc123def456'
148
+ };
149
+
150
+ try {
151
+ await handleGenerateManifestsTool(args, mockContext.dotAI, mockContext.logger, mockContext.requestId);
152
+ } catch (error) {
153
+ // Should fail with session directory error, not context error
154
+ expect((error as Error).message).toContain('Session directory must be specified via --session-dir parameter or DOT_AI_SESSION_DIR environment variable');
155
+ }
156
+ });
157
+ });
158
+
159
+ describe('Schema Retrieval Functionality', () => {
160
+ beforeEach(() => {
161
+ jest.clearAllMocks();
162
+
163
+ // Setup basic file system mocks
164
+ mockFs.existsSync.mockImplementation((path: any) => {
165
+ if (typeof path === 'string') {
166
+ if (path.includes('/test/session')) return true;
167
+ if (path.includes('sol_2025-01-01T120000_abc123def456.json')) return true;
168
+ if (path.includes('.yaml')) return true; // Allow yaml file writes
169
+ }
170
+ return false;
171
+ });
172
+
173
+ mockFs.statSync.mockReturnValue({ isDirectory: () => true } as any);
174
+ mockFs.readdirSync.mockReturnValue([]);
175
+ mockFs.writeFileSync.mockImplementation(() => {});
176
+ mockFs.renameSync.mockImplementation(() => {});
177
+ mockFs.unlinkSync.mockImplementation(() => {});
178
+
179
+ // Mock Claude AI to return valid YAML
180
+ mockClaudeIntegration.sendMessage.mockResolvedValue({
181
+ content: `apiVersion: devopstoolkit.live/v1alpha1
182
+ kind: AppClaim
183
+ metadata:
184
+ name: test-webapp
185
+ namespace: default
186
+ spec:
187
+ namespace: default
188
+ image: nginx:latest
189
+ tag: latest
190
+ port: 80
191
+ host: test-webapp.local`
192
+ });
193
+
194
+ // Mock yaml parsing to succeed
195
+ const yaml = require('js-yaml');
196
+ yaml.loadAll.mockImplementation(() => {}); // No errors = valid YAML
197
+
198
+ // Mock kubectl dry-run to succeed
199
+ const { spawn } = require('child_process');
200
+ const mockSpawn = {
201
+ stdout: { on: jest.fn((event, cb) => event === 'data' ? cb('') : null) },
202
+ stderr: { on: jest.fn((event, cb) => event === 'data' ? cb('') : null) },
203
+ on: jest.fn((event, cb) => event === 'close' ? cb(0) : null) // Exit code 0 = success
204
+ };
205
+ spawn.mockReturnValue(mockSpawn);
206
+ });
207
+
208
+ it('should retrieve schemas for all resources in solution', async () => {
209
+ // Mock solution with AppClaim resource
210
+ const mockSolution = {
211
+ solutionId: 'sol_2025-01-01T120000_abc123def456',
212
+ resources: [
213
+ {
214
+ kind: 'AppClaim',
215
+ apiVersion: 'devopstoolkit.live/v1alpha1',
216
+ group: 'devopstoolkit.live'
217
+ }
218
+ ],
219
+ questions: { required: [], basic: [], advanced: [], open: {} }
220
+ };
221
+
222
+ mockFs.readFileSync.mockReturnValue(JSON.stringify(mockSolution));
223
+
224
+ // Mock kubectl explain output as raw string
225
+ const mockExplanation = `GROUP: devopstoolkit.live
226
+ KIND: AppClaim
227
+ VERSION: v1alpha1
228
+
229
+ DESCRIPTION:
230
+ AppClaim resource for application deployment
231
+
232
+ FIELDS:
233
+ apiVersion <string>
234
+ APIVersion defines the versioned schema
235
+
236
+ kind <string>
237
+ Kind is a string value representing the REST resource
238
+
239
+ metadata <Object>
240
+ Standard object metadata
241
+
242
+ spec <Object> -required-
243
+
244
+ id <string> -required-
245
+ ID of this application
246
+
247
+ parameters <Object> -required-
248
+
249
+ image <string> -required-
250
+ The container image
251
+
252
+ port <integer>
253
+ The application port
254
+
255
+ host <string>
256
+ The host address of the application`;
257
+
258
+ mockDotAI.discovery.explainResource.mockResolvedValue(mockExplanation);
259
+
260
+ process.env.DOT_AI_SESSION_DIR = '/test/session';
261
+
262
+ const args = {
263
+ solutionId: 'sol_2025-01-01T120000_abc123def456'
264
+ };
265
+
266
+ // This should succeed now that we have schema retrieval
267
+ const result = await handleGenerateManifestsTool(args, mockContext.dotAI, mockContext.logger, mockContext.requestId);
268
+
269
+ // Verify schema retrieval was attempted
270
+ expect(mockDotAI.discovery.explainResource).toHaveBeenCalledWith('AppClaim');
271
+
272
+ // Verify the result contains manifest data
273
+ expect(result.content).toBeDefined();
274
+ expect(result.content[0].type).toBe('text');
275
+
276
+ const response = JSON.parse(result.content[0].text);
277
+ expect(response.success).toBe(true);
278
+ expect(response.solutionId).toBe('sol_2025-01-01T120000_abc123def456');
279
+ });
280
+
281
+ it('should handle multiple resources in solution', async () => {
282
+ const mockSolution = {
283
+ solutionId: 'sol_2025-01-01T120000_abc123def456',
284
+ resources: [
285
+ {
286
+ kind: 'Deployment',
287
+ apiVersion: 'apps/v1',
288
+ group: 'apps'
289
+ },
290
+ {
291
+ kind: 'Service',
292
+ apiVersion: 'v1',
293
+ group: ''
294
+ }
295
+ ],
296
+ questions: { required: [], basic: [], advanced: [], open: {} }
297
+ };
298
+
299
+ mockFs.readFileSync.mockReturnValue(JSON.stringify(mockSolution));
300
+
301
+ // Mock different explanations for each resource
302
+ const deploymentExplanation = `GROUP: apps
303
+ KIND: Deployment
304
+ VERSION: v1
305
+
306
+ FIELDS:
307
+ spec <Object>
308
+ replicas <integer>
309
+ Number of desired pods`;
310
+
311
+ const serviceExplanation = `GROUP:
312
+ KIND: Service
313
+ VERSION: v1
314
+
315
+ FIELDS:
316
+ spec <Object>
317
+ ports <[]Object>
318
+ List of ports that are exposed`;
319
+
320
+ mockDotAI.discovery.explainResource
321
+ .mockResolvedValueOnce(deploymentExplanation)
322
+ .mockResolvedValueOnce(serviceExplanation);
323
+
324
+ process.env.DOT_AI_SESSION_DIR = '/test/session';
325
+
326
+ const args = {
327
+ solutionId: 'sol_2025-01-01T120000_abc123def456'
328
+ };
329
+
330
+ await handleGenerateManifestsTool(args, mockContext.dotAI, mockContext.logger, mockContext.requestId);
331
+
332
+ // Should call explainResource for each resource type
333
+ expect(mockDotAI.discovery.explainResource).toHaveBeenCalledTimes(2);
334
+ expect(mockDotAI.discovery.explainResource).toHaveBeenCalledWith('Deployment');
335
+ expect(mockDotAI.discovery.explainResource).toHaveBeenCalledWith('Service');
336
+ });
337
+
338
+ it('should handle schema retrieval errors gracefully', async () => {
339
+ const mockSolution = {
340
+ solutionId: 'sol_2025-01-01T120000_abc123def456',
341
+ resources: [
342
+ {
343
+ kind: 'UnknownResource',
344
+ apiVersion: 'example.com/v1',
345
+ group: 'example.com'
346
+ }
347
+ ],
348
+ questions: { required: [], basic: [], advanced: [], open: {} }
349
+ };
350
+
351
+ mockFs.readFileSync.mockReturnValue(JSON.stringify(mockSolution));
352
+
353
+ // Mock schema retrieval failure
354
+ mockDotAI.discovery.explainResource.mockRejectedValue(new Error('Resource not found in cluster'));
355
+
356
+ process.env.DOT_AI_SESSION_DIR = '/test/session';
357
+
358
+ const args = {
359
+ solutionId: 'sol_2025-01-01T120000_abc123def456'
360
+ };
361
+
362
+ await expect(handleGenerateManifestsTool(args, mockContext.dotAI, mockContext.logger, mockContext.requestId))
363
+ .rejects.toMatchObject({
364
+ message: expect.stringContaining('Failed to retrieve schema for UnknownResource')
365
+ });
366
+
367
+ // Should have attempted schema retrieval
368
+ expect(mockDotAI.discovery.explainResource).toHaveBeenCalledWith('UnknownResource');
369
+
370
+ // Should have logged the error
371
+ expect(mockLogger.error).toHaveBeenCalledWith(
372
+ 'Failed to retrieve schema for resource',
373
+ expect.any(Error),
374
+ expect.objectContaining({
375
+ resource: expect.objectContaining({ kind: 'UnknownResource' })
376
+ })
377
+ );
378
+ });
379
+
380
+ it('should handle solutions with no resources', async () => {
381
+ const mockSolution = {
382
+ solutionId: 'sol_2025-01-01T120000_abc123def456',
383
+ resources: [], // No resources
384
+ questions: { required: [], basic: [], advanced: [], open: {} }
385
+ };
386
+
387
+ mockFs.readFileSync.mockReturnValue(JSON.stringify(mockSolution));
388
+
389
+ process.env.DOT_AI_SESSION_DIR = '/test/session';
390
+
391
+ const args = {
392
+ solutionId: 'sol_2025-01-01T120000_abc123def456'
393
+ };
394
+
395
+ const result = await handleGenerateManifestsTool(args, mockContext.dotAI, mockContext.logger, mockContext.requestId);
396
+
397
+ // Should not attempt schema retrieval
398
+ expect(mockDotAI.discovery.explainResource).not.toHaveBeenCalled();
399
+
400
+ // Should log warning about no resources
401
+ expect(mockLogger.warn).toHaveBeenCalledWith('No resources found in solution for schema retrieval');
402
+
403
+ // Should still complete successfully
404
+ const response = JSON.parse(result.content[0].text);
405
+ expect(response.success).toBe(true);
406
+ });
407
+
408
+ it('should fail fast when schema retrieval fails', async () => {
409
+ const mockSolution = {
410
+ solutionId: 'sol_2025-01-01T120000_abc123def456',
411
+ resources: [
412
+ {
413
+ kind: 'AppClaim',
414
+ apiVersion: 'devopstoolkit.live/v1alpha1',
415
+ group: 'devopstoolkit.live'
416
+ }
417
+ ],
418
+ questions: { required: [], basic: [], advanced: [], open: {} }
419
+ };
420
+
421
+ mockFs.readFileSync.mockReturnValue(JSON.stringify(mockSolution));
422
+
423
+ // Mock schema retrieval failure
424
+ mockDotAI.discovery.explainResource.mockRejectedValue(new Error('Cluster connection failed'));
425
+
426
+ process.env.DOT_AI_SESSION_DIR = '/test/session';
427
+
428
+ const args = {
429
+ solutionId: 'sol_2025-01-01T120000_abc123def456'
430
+ };
431
+
432
+ await expect(handleGenerateManifestsTool(args, mockContext.dotAI, mockContext.logger, mockContext.requestId))
433
+ .rejects.toMatchObject({
434
+ message: expect.stringContaining('Failed to retrieve resource schemas')
435
+ });
436
+
437
+ expect(mockDotAI.discovery.explainResource).toHaveBeenCalledWith('AppClaim');
438
+ expect(mockLogger.error).toHaveBeenCalledWith('Schema retrieval failed', expect.any(Error));
439
+ });
440
+ });
441
+ });
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Tests for Tool Metadata and Integration
3
+ *
4
+ * Tests the tool metadata exports and their availability
5
+ */
6
+
7
+ import {
8
+ RECOMMEND_TOOL_NAME,
9
+ RECOMMEND_TOOL_DESCRIPTION,
10
+ RECOMMEND_TOOL_INPUT_SCHEMA,
11
+ handleRecommendTool
12
+ } from '../../src/tools/recommend';
13
+
14
+ import {
15
+ CHOOSESOLUTION_TOOL_NAME,
16
+ CHOOSESOLUTION_TOOL_DESCRIPTION,
17
+ CHOOSESOLUTION_TOOL_INPUT_SCHEMA,
18
+ handleChooseSolutionTool
19
+ } from '../../src/tools/choose-solution';
20
+
21
+ import {
22
+ ANSWERQUESTION_TOOL_NAME,
23
+ ANSWERQUESTION_TOOL_DESCRIPTION,
24
+ ANSWERQUESTION_TOOL_INPUT_SCHEMA,
25
+ handleAnswerQuestionTool
26
+ } from '../../src/tools/answer-question';
27
+
28
+ import {
29
+ GENERATEMANIFESTS_TOOL_NAME,
30
+ GENERATEMANIFESTS_TOOL_DESCRIPTION,
31
+ GENERATEMANIFESTS_TOOL_INPUT_SCHEMA,
32
+ handleGenerateManifestsTool
33
+ } from '../../src/tools/generate-manifests';
34
+
35
+ import {
36
+ DEPLOYMANIFESTS_TOOL_NAME,
37
+ DEPLOYMANIFESTS_TOOL_DESCRIPTION,
38
+ DEPLOYMANIFESTS_TOOL_INPUT_SCHEMA,
39
+ handleDeployManifestsTool
40
+ } from '../../src/tools/deploy-manifests';
41
+
42
+ describe('Tool Integration', () => {
43
+
44
+ describe('Tool Metadata Availability', () => {
45
+ test('should have all required tool metadata available', () => {
46
+ const toolNames = [
47
+ RECOMMEND_TOOL_NAME,
48
+ CHOOSESOLUTION_TOOL_NAME,
49
+ ANSWERQUESTION_TOOL_NAME,
50
+ GENERATEMANIFESTS_TOOL_NAME,
51
+ DEPLOYMANIFESTS_TOOL_NAME
52
+ ];
53
+
54
+ expect(toolNames).toHaveLength(5);
55
+ expect(toolNames).toContain('recommend');
56
+ expect(toolNames).toContain('chooseSolution');
57
+ expect(toolNames).toContain('answerQuestion');
58
+ expect(toolNames).toContain('generateManifests');
59
+ expect(toolNames).toContain('deployManifests');
60
+ });
61
+
62
+ test('should have all tool handlers available', () => {
63
+ const handlers = [
64
+ handleRecommendTool,
65
+ handleChooseSolutionTool,
66
+ handleAnswerQuestionTool,
67
+ handleGenerateManifestsTool,
68
+ handleDeployManifestsTool
69
+ ];
70
+
71
+ handlers.forEach(handler => {
72
+ expect(typeof handler).toBe('function');
73
+ });
74
+ });
75
+ });
76
+
77
+ describe('Tool Metadata Structure', () => {
78
+ test('recommend tool should have valid metadata', () => {
79
+ expect(RECOMMEND_TOOL_NAME).toBe('recommend');
80
+ expect(RECOMMEND_TOOL_DESCRIPTION).toContain('Deploy, create, run, or setup applications');
81
+ expect(RECOMMEND_TOOL_INPUT_SCHEMA.intent).toBeDefined();
82
+ });
83
+
84
+ test('chooseSolution tool should have valid metadata', () => {
85
+ expect(CHOOSESOLUTION_TOOL_NAME).toBe('chooseSolution');
86
+ expect(CHOOSESOLUTION_TOOL_DESCRIPTION).toContain('Select a solution');
87
+ expect(CHOOSESOLUTION_TOOL_INPUT_SCHEMA.solutionId).toBeDefined();
88
+ });
89
+
90
+ test('answerQuestion tool should have valid metadata', () => {
91
+ expect(ANSWERQUESTION_TOOL_NAME).toBe('answerQuestion');
92
+ expect(ANSWERQUESTION_TOOL_DESCRIPTION).toContain('Process user answers');
93
+ expect(ANSWERQUESTION_TOOL_INPUT_SCHEMA.solutionId).toBeDefined();
94
+ expect(ANSWERQUESTION_TOOL_INPUT_SCHEMA.stage).toBeDefined();
95
+ expect(ANSWERQUESTION_TOOL_INPUT_SCHEMA.answers).toBeDefined();
96
+ });
97
+
98
+ test('generateManifests tool should have valid metadata', () => {
99
+ expect(GENERATEMANIFESTS_TOOL_NAME).toBe('generateManifests');
100
+ expect(GENERATEMANIFESTS_TOOL_DESCRIPTION).toContain('Generate final Kubernetes manifests');
101
+ expect(GENERATEMANIFESTS_TOOL_INPUT_SCHEMA.solutionId).toBeDefined();
102
+ });
103
+
104
+ test('deployManifests tool should have valid metadata', () => {
105
+ expect(DEPLOYMANIFESTS_TOOL_NAME).toBe('deployManifests');
106
+ expect(DEPLOYMANIFESTS_TOOL_DESCRIPTION).toContain('Deploy Kubernetes manifests');
107
+ expect(DEPLOYMANIFESTS_TOOL_INPUT_SCHEMA.solutionId).toBeDefined();
108
+ expect(DEPLOYMANIFESTS_TOOL_INPUT_SCHEMA.timeout).toBeDefined();
109
+ });
110
+ });
111
+ });