@vfarcic/dot-ai 0.5.1 → 0.7.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/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/{src/cli.ts → dist/cli.js} +19 -26
- package/dist/core/claude.d.ts +42 -0
- package/dist/core/claude.d.ts.map +1 -0
- package/dist/core/claude.js +229 -0
- package/dist/core/deploy-operation.d.ts +38 -0
- package/dist/core/deploy-operation.d.ts.map +1 -0
- package/dist/core/deploy-operation.js +101 -0
- package/dist/core/discovery.d.ts +162 -0
- package/dist/core/discovery.d.ts.map +1 -0
- package/dist/core/discovery.js +758 -0
- package/dist/core/error-handling.d.ts +167 -0
- package/dist/core/error-handling.d.ts.map +1 -0
- package/dist/core/error-handling.js +399 -0
- package/dist/core/index.d.ts +42 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +123 -0
- package/dist/core/kubernetes-utils.d.ts +38 -0
- package/dist/core/kubernetes-utils.d.ts.map +1 -0
- package/dist/core/kubernetes-utils.js +177 -0
- package/dist/core/memory.d.ts +45 -0
- package/dist/core/memory.d.ts.map +1 -0
- package/dist/core/memory.js +113 -0
- package/dist/core/schema.d.ts +187 -0
- package/dist/core/schema.d.ts.map +1 -0
- package/dist/core/schema.js +655 -0
- package/dist/core/session-utils.d.ts +29 -0
- package/dist/core/session-utils.d.ts.map +1 -0
- package/dist/core/session-utils.js +121 -0
- package/dist/core/workflow.d.ts +70 -0
- package/dist/core/workflow.d.ts.map +1 -0
- package/dist/core/workflow.js +161 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/interfaces/cli.d.ts +74 -0
- package/dist/interfaces/cli.d.ts.map +1 -0
- package/dist/interfaces/cli.js +769 -0
- package/dist/interfaces/mcp.d.ts +30 -0
- package/dist/interfaces/mcp.d.ts.map +1 -0
- package/dist/interfaces/mcp.js +105 -0
- package/dist/mcp/server.d.ts +9 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +151 -0
- package/dist/tools/answer-question.d.ts +27 -0
- package/dist/tools/answer-question.d.ts.map +1 -0
- package/dist/tools/answer-question.js +696 -0
- package/dist/tools/choose-solution.d.ts +23 -0
- package/dist/tools/choose-solution.d.ts.map +1 -0
- package/dist/tools/choose-solution.js +171 -0
- package/dist/tools/deploy-manifests.d.ts +25 -0
- package/dist/tools/deploy-manifests.d.ts.map +1 -0
- package/dist/tools/deploy-manifests.js +74 -0
- package/dist/tools/generate-manifests.d.ts +23 -0
- package/dist/tools/generate-manifests.d.ts.map +1 -0
- package/dist/tools/generate-manifests.js +424 -0
- package/dist/tools/index.d.ts +11 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +34 -0
- package/dist/tools/recommend.d.ts +23 -0
- package/dist/tools/recommend.d.ts.map +1 -0
- package/dist/tools/recommend.js +332 -0
- package/package.json +124 -2
- package/.claude/commands/context-load.md +0 -11
- package/.claude/commands/context-save.md +0 -16
- package/.claude/commands/prd-done.md +0 -115
- package/.claude/commands/prd-get.md +0 -25
- package/.claude/commands/prd-start.md +0 -87
- package/.claude/commands/task-done.md +0 -77
- package/.claude/commands/tests-reminder.md +0 -32
- package/.claude/settings.local.json +0 -20
- package/.eslintrc.json +0 -25
- package/.github/workflows/ci.yml +0 -170
- package/.prettierrc.json +0 -10
- package/.teller.yml +0 -8
- package/CLAUDE.md +0 -162
- package/assets/images/logo.png +0 -0
- package/bin/dot-ai.ts +0 -47
- package/bin.js +0 -19
- package/destroy.sh +0 -45
- package/devbox.json +0 -13
- package/devbox.lock +0 -225
- package/docs/API.md +0 -449
- package/docs/CONTEXT.md +0 -49
- package/docs/DEVELOPMENT.md +0 -203
- package/docs/NEXT_STEPS.md +0 -97
- package/docs/STAGE_BASED_API.md +0 -97
- package/docs/cli-guide.md +0 -798
- package/docs/design.md +0 -750
- package/docs/discovery-engine.md +0 -515
- package/docs/error-handling.md +0 -429
- package/docs/function-registration.md +0 -157
- package/docs/mcp-guide.md +0 -416
- package/renovate.json +0 -51
- package/setup.sh +0 -111
- package/src/core/claude.ts +0 -280
- package/src/core/deploy-operation.ts +0 -127
- package/src/core/discovery.ts +0 -900
- package/src/core/error-handling.ts +0 -562
- package/src/core/index.ts +0 -143
- package/src/core/kubernetes-utils.ts +0 -218
- package/src/core/memory.ts +0 -148
- package/src/core/schema.ts +0 -830
- package/src/core/session-utils.ts +0 -97
- package/src/core/workflow.ts +0 -234
- package/src/index.ts +0 -18
- package/src/interfaces/cli.ts +0 -872
- package/src/interfaces/mcp.ts +0 -183
- package/src/mcp/server.ts +0 -131
- package/src/tools/answer-question.ts +0 -807
- package/src/tools/choose-solution.ts +0 -169
- package/src/tools/deploy-manifests.ts +0 -94
- package/src/tools/generate-manifests.ts +0 -502
- package/src/tools/index.ts +0 -41
- package/src/tools/recommend.ts +0 -370
- package/tests/__mocks__/@kubernetes/client-node.ts +0 -106
- package/tests/build-system.test.ts +0 -345
- package/tests/configuration.test.ts +0 -226
- package/tests/core/deploy-operation.test.ts +0 -38
- package/tests/core/discovery.test.ts +0 -1648
- package/tests/core/error-handling.test.ts +0 -632
- package/tests/core/schema.test.ts +0 -1658
- package/tests/core/session-utils.test.ts +0 -245
- package/tests/core.test.ts +0 -439
- package/tests/fixtures/configmap-no-labels.yaml +0 -8
- package/tests/fixtures/crossplane-app-configuration.yaml +0 -6
- package/tests/fixtures/crossplane-providers.yaml +0 -45
- package/tests/fixtures/crossplane-rbac.yaml +0 -48
- package/tests/fixtures/invalid-configmap.yaml +0 -8
- package/tests/fixtures/invalid-deployment.yaml +0 -17
- package/tests/fixtures/test-deployment.yaml +0 -28
- package/tests/fixtures/valid-configmap.yaml +0 -15
- package/tests/infrastructure.test.ts +0 -426
- package/tests/interfaces/cli.test.ts +0 -1036
- package/tests/interfaces/mcp.test.ts +0 -139
- package/tests/kubernetes-utils.test.ts +0 -200
- package/tests/mcp/server.test.ts +0 -126
- package/tests/setup.ts +0 -31
- package/tests/tools/answer-question.test.ts +0 -367
- package/tests/tools/choose-solution.test.ts +0 -481
- package/tests/tools/deploy-manifests.test.ts +0 -185
- package/tests/tools/generate-manifests.test.ts +0 -441
- package/tests/tools/index.test.ts +0 -111
- package/tests/tools/recommend.test.ts +0 -180
- package/tsconfig.json +0 -34
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Session Utils Tests
|
|
3
|
-
* Tests for shared session directory utilities including relative path support
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { getSessionDirectory, validateSessionDirectory, getAndValidateSessionDirectory } from '../../src/core/session-utils';
|
|
7
|
-
import * as fs from 'fs';
|
|
8
|
-
import * as path from 'path';
|
|
9
|
-
|
|
10
|
-
describe('Session Utils', () => {
|
|
11
|
-
let testDir: string;
|
|
12
|
-
let originalCwd: string;
|
|
13
|
-
let originalEnv: string | undefined;
|
|
14
|
-
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
// Create test directory
|
|
17
|
-
testDir = path.join(process.cwd(), 'tmp', 'session-utils-test', Date.now().toString());
|
|
18
|
-
fs.mkdirSync(testDir, { recursive: true });
|
|
19
|
-
|
|
20
|
-
// Save original state
|
|
21
|
-
originalCwd = process.cwd();
|
|
22
|
-
originalEnv = process.env.DOT_AI_SESSION_DIR;
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
afterEach(() => {
|
|
26
|
-
// Restore original state
|
|
27
|
-
process.chdir(originalCwd);
|
|
28
|
-
if (originalEnv) {
|
|
29
|
-
process.env.DOT_AI_SESSION_DIR = originalEnv;
|
|
30
|
-
} else {
|
|
31
|
-
delete process.env.DOT_AI_SESSION_DIR;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Clean up test directory
|
|
35
|
-
try {
|
|
36
|
-
fs.rmSync(testDir, { recursive: true, force: true });
|
|
37
|
-
} catch (error) {
|
|
38
|
-
console.warn('Could not clean up test directory:', error);
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
describe('getSessionDirectory', () => {
|
|
43
|
-
it('should prefer CLI args over environment variable', () => {
|
|
44
|
-
process.env.DOT_AI_SESSION_DIR = '/env/path';
|
|
45
|
-
const args = { sessionDir: '/cli/path' };
|
|
46
|
-
|
|
47
|
-
const result = getSessionDirectory(args);
|
|
48
|
-
|
|
49
|
-
expect(result).toBe('/cli/path');
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('should use environment variable when CLI args not provided', () => {
|
|
53
|
-
process.env.DOT_AI_SESSION_DIR = '/env/path';
|
|
54
|
-
const args = {};
|
|
55
|
-
|
|
56
|
-
const result = getSessionDirectory(args);
|
|
57
|
-
|
|
58
|
-
expect(result).toBe('/env/path');
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('should throw error when neither CLI args nor environment variable provided', () => {
|
|
62
|
-
delete process.env.DOT_AI_SESSION_DIR;
|
|
63
|
-
const args = {};
|
|
64
|
-
|
|
65
|
-
expect(() => getSessionDirectory(args)).toThrow(
|
|
66
|
-
'Session directory must be specified via --session-dir parameter or DOT_AI_SESSION_DIR environment variable'
|
|
67
|
-
);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('should work with relative paths from environment variable', () => {
|
|
71
|
-
process.env.DOT_AI_SESSION_DIR = './relative/path';
|
|
72
|
-
const args = {};
|
|
73
|
-
|
|
74
|
-
const result = getSessionDirectory(args);
|
|
75
|
-
|
|
76
|
-
expect(result).toBe('./relative/path');
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
describe('validateSessionDirectory', () => {
|
|
81
|
-
it('should validate existing absolute directory', () => {
|
|
82
|
-
expect(() => validateSessionDirectory(testDir)).not.toThrow();
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('should validate existing relative directory', () => {
|
|
86
|
-
// Create relative directory structure
|
|
87
|
-
const relativeTestDir = path.join(testDir, 'relative-test');
|
|
88
|
-
const relativeSessionDir = path.join(relativeTestDir, 'sessions');
|
|
89
|
-
fs.mkdirSync(relativeSessionDir, { recursive: true });
|
|
90
|
-
|
|
91
|
-
// Change to test directory and validate relative path
|
|
92
|
-
process.chdir(relativeTestDir);
|
|
93
|
-
|
|
94
|
-
expect(() => validateSessionDirectory('./sessions')).not.toThrow();
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('should throw error for non-existent directory', () => {
|
|
98
|
-
const nonExistentDir = path.join(testDir, 'does-not-exist');
|
|
99
|
-
|
|
100
|
-
expect(() => validateSessionDirectory(nonExistentDir)).toThrow(
|
|
101
|
-
`Session directory does not exist: ${nonExistentDir}`
|
|
102
|
-
);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('should throw error when path is not a directory', () => {
|
|
106
|
-
const filePath = path.join(testDir, 'test-file.txt');
|
|
107
|
-
fs.writeFileSync(filePath, 'test');
|
|
108
|
-
|
|
109
|
-
expect(() => validateSessionDirectory(filePath)).toThrow(
|
|
110
|
-
`Session directory path is not a directory: ${filePath}`
|
|
111
|
-
);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it('should test write permissions when required', () => {
|
|
115
|
-
expect(() => validateSessionDirectory(testDir, true)).not.toThrow();
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it('should throw error when directory is not writable', () => {
|
|
119
|
-
// Create read-only directory (only works on Unix systems)
|
|
120
|
-
const readOnlyDir = path.join(testDir, 'readonly');
|
|
121
|
-
fs.mkdirSync(readOnlyDir);
|
|
122
|
-
|
|
123
|
-
// Make directory read-only
|
|
124
|
-
try {
|
|
125
|
-
fs.chmodSync(readOnlyDir, 0o444);
|
|
126
|
-
|
|
127
|
-
expect(() => validateSessionDirectory(readOnlyDir, true)).toThrow();
|
|
128
|
-
|
|
129
|
-
// Restore permissions for cleanup
|
|
130
|
-
fs.chmodSync(readOnlyDir, 0o755);
|
|
131
|
-
} catch (error) {
|
|
132
|
-
// Skip this test on systems where chmod doesn't work (e.g., Windows)
|
|
133
|
-
console.warn('Skipping write permission test - chmod not supported');
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
describe('getAndValidateSessionDirectory', () => {
|
|
139
|
-
it('should get and validate session directory from environment variable', () => {
|
|
140
|
-
process.env.DOT_AI_SESSION_DIR = testDir;
|
|
141
|
-
const args = {};
|
|
142
|
-
|
|
143
|
-
const result = getAndValidateSessionDirectory(args, false);
|
|
144
|
-
|
|
145
|
-
expect(result).toBe(testDir);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it('should get and validate session directory with write permissions', () => {
|
|
149
|
-
process.env.DOT_AI_SESSION_DIR = testDir;
|
|
150
|
-
const args = {};
|
|
151
|
-
|
|
152
|
-
const result = getAndValidateSessionDirectory(args, true);
|
|
153
|
-
|
|
154
|
-
expect(result).toBe(testDir);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it('should work with relative paths when cwd is set', () => {
|
|
158
|
-
// Create test directory structure
|
|
159
|
-
const testCwd = path.join(testDir, 'cwd-test');
|
|
160
|
-
const relativeSessionDir = './sessions';
|
|
161
|
-
const absoluteSessionDir = path.join(testCwd, 'sessions');
|
|
162
|
-
|
|
163
|
-
fs.mkdirSync(testCwd, { recursive: true });
|
|
164
|
-
fs.mkdirSync(absoluteSessionDir, { recursive: true });
|
|
165
|
-
|
|
166
|
-
// Change to test directory and set relative path
|
|
167
|
-
process.chdir(testCwd);
|
|
168
|
-
process.env.DOT_AI_SESSION_DIR = relativeSessionDir;
|
|
169
|
-
|
|
170
|
-
const args = {};
|
|
171
|
-
const result = getAndValidateSessionDirectory(args, true);
|
|
172
|
-
|
|
173
|
-
expect(result).toBe('./sessions');
|
|
174
|
-
|
|
175
|
-
// Verify the relative path actually resolves to the correct absolute path
|
|
176
|
-
expect(path.resolve(result)).toBe(absoluteSessionDir);
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
it('should throw error when session directory not configured', () => {
|
|
180
|
-
delete process.env.DOT_AI_SESSION_DIR;
|
|
181
|
-
const args = {};
|
|
182
|
-
|
|
183
|
-
expect(() => getAndValidateSessionDirectory(args)).toThrow(
|
|
184
|
-
'Session directory must be specified via --session-dir parameter or DOT_AI_SESSION_DIR environment variable'
|
|
185
|
-
);
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
it('should throw error when session directory does not exist', () => {
|
|
189
|
-
const nonExistentDir = path.join(testDir, 'does-not-exist');
|
|
190
|
-
process.env.DOT_AI_SESSION_DIR = nonExistentDir;
|
|
191
|
-
const args = {};
|
|
192
|
-
|
|
193
|
-
expect(() => getAndValidateSessionDirectory(args)).toThrow(
|
|
194
|
-
`Session directory does not exist: ${nonExistentDir}`
|
|
195
|
-
);
|
|
196
|
-
});
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
describe('MCP cwd simulation', () => {
|
|
200
|
-
it('should work exactly like MCP server with cwd and relative DOT_AI_SESSION_DIR', () => {
|
|
201
|
-
// Simulate MCP configuration:
|
|
202
|
-
// {
|
|
203
|
-
// "mcpServers": {
|
|
204
|
-
// "dot-ai": {
|
|
205
|
-
// "command": "npm",
|
|
206
|
-
// "args": ["run", "mcp:start"],
|
|
207
|
-
// "cwd": "/path/to/dot-ai",
|
|
208
|
-
// "env": {
|
|
209
|
-
// "DOT_AI_SESSION_DIR": "./tmp/sessions"
|
|
210
|
-
// }
|
|
211
|
-
// }
|
|
212
|
-
// }
|
|
213
|
-
// }
|
|
214
|
-
|
|
215
|
-
const mcpCwd = path.join(testDir, 'mcp-simulation');
|
|
216
|
-
const relativeSessionDir = './tmp/sessions';
|
|
217
|
-
const absoluteSessionDir = path.join(mcpCwd, 'tmp', 'sessions');
|
|
218
|
-
|
|
219
|
-
// Create directories
|
|
220
|
-
fs.mkdirSync(mcpCwd, { recursive: true });
|
|
221
|
-
fs.mkdirSync(absoluteSessionDir, { recursive: true });
|
|
222
|
-
|
|
223
|
-
// Change to MCP cwd and set relative environment variable
|
|
224
|
-
process.chdir(mcpCwd);
|
|
225
|
-
process.env.DOT_AI_SESSION_DIR = relativeSessionDir;
|
|
226
|
-
|
|
227
|
-
// This simulates what happens when MCP tools are called
|
|
228
|
-
const args = {}; // MCP tools don't get sessionDir as args
|
|
229
|
-
const context = { requestId: 'mcp-test-123' };
|
|
230
|
-
|
|
231
|
-
const result = getAndValidateSessionDirectory(args, true);
|
|
232
|
-
|
|
233
|
-
// Should return the relative path as configured
|
|
234
|
-
expect(result).toBe('./tmp/sessions');
|
|
235
|
-
|
|
236
|
-
// But should resolve to the correct absolute path
|
|
237
|
-
expect(path.resolve(result)).toBe(absoluteSessionDir);
|
|
238
|
-
|
|
239
|
-
// Should be able to write to it
|
|
240
|
-
const testFile = path.join(result, 'test-solution.json');
|
|
241
|
-
fs.writeFileSync(testFile, JSON.stringify({ test: 'data' }));
|
|
242
|
-
expect(fs.existsSync(testFile)).toBe(true);
|
|
243
|
-
});
|
|
244
|
-
});
|
|
245
|
-
});
|
package/tests/core.test.ts
DELETED
|
@@ -1,439 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Core Module Tests
|
|
3
|
-
*
|
|
4
|
-
* These tests define the API contracts and behavior for our core intelligence modules
|
|
5
|
-
* Following TDD approach - these tests define what we SHOULD implement
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
DotAI,
|
|
10
|
-
KubernetesDiscovery,
|
|
11
|
-
MemorySystem,
|
|
12
|
-
WorkflowEngine,
|
|
13
|
-
ClaudeIntegration
|
|
14
|
-
} from '../src/core';
|
|
15
|
-
import { ErrorClassifier, buildKubectlCommand, executeKubectl } from '../src/core/kubernetes-utils';
|
|
16
|
-
import * as path from 'path';
|
|
17
|
-
|
|
18
|
-
describe('Core Module Structure', () => {
|
|
19
|
-
describe('DotAI Class', () => {
|
|
20
|
-
test('should be constructible with configuration options', () => {
|
|
21
|
-
const agent = new DotAI({
|
|
22
|
-
kubernetesConfig: '/path/to/kubeconfig',
|
|
23
|
-
anthropicApiKey: 'test-key'
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
expect(agent).toBeInstanceOf(DotAI);
|
|
27
|
-
expect(agent.getVersion()).toBe('0.1.0');
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test('should provide access to all core modules', async () => {
|
|
31
|
-
// Use project's working kubeconfig.yaml for integration tests
|
|
32
|
-
const projectKubeconfig = path.join(process.cwd(), 'kubeconfig.yaml');
|
|
33
|
-
const agent = new DotAI({ kubernetesConfig: projectKubeconfig });
|
|
34
|
-
await agent.initialize();
|
|
35
|
-
|
|
36
|
-
expect(agent.discovery).toBeInstanceOf(KubernetesDiscovery);
|
|
37
|
-
expect(agent.memory).toBeInstanceOf(MemorySystem);
|
|
38
|
-
expect(agent.workflow).toBeInstanceOf(WorkflowEngine);
|
|
39
|
-
expect(agent.claude).toBeInstanceOf(ClaudeIntegration);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test('should handle initialization errors gracefully', async () => {
|
|
43
|
-
// Use project's working kubeconfig.yaml for integration tests
|
|
44
|
-
const projectKubeconfig = path.join(process.cwd(), 'kubeconfig.yaml');
|
|
45
|
-
const agent = new DotAI({ kubernetesConfig: projectKubeconfig });
|
|
46
|
-
|
|
47
|
-
// Mock the discovery connect method to fail
|
|
48
|
-
jest.spyOn(agent.discovery, 'connect').mockRejectedValue(new Error('Connection failed'));
|
|
49
|
-
|
|
50
|
-
await expect(agent.initialize()).rejects.toThrow();
|
|
51
|
-
expect(agent.isInitialized()).toBe(false);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test('should provide configuration validation', () => {
|
|
55
|
-
expect(() => {
|
|
56
|
-
new DotAI({ anthropicApiKey: '' });
|
|
57
|
-
}).toThrow('Invalid configuration');
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
describe('Module Integration', () => {
|
|
62
|
-
test('should allow modules to communicate with each other', async () => {
|
|
63
|
-
// Use project's working kubeconfig.yaml for integration tests
|
|
64
|
-
const projectKubeconfig = path.join(process.cwd(), 'kubeconfig.yaml');
|
|
65
|
-
const agent = new DotAI({ kubernetesConfig: projectKubeconfig });
|
|
66
|
-
await agent.initialize();
|
|
67
|
-
|
|
68
|
-
// Memory should be able to store discovery results
|
|
69
|
-
const discoveryData = { resources: ['deployment', 'service'] };
|
|
70
|
-
await agent.memory.store('cluster-capabilities', discoveryData);
|
|
71
|
-
|
|
72
|
-
// Workflow should be able to access memory
|
|
73
|
-
const stored = await agent.memory.retrieve('cluster-capabilities');
|
|
74
|
-
expect(stored).toEqual(discoveryData);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test('should handle module dependency failures', async () => {
|
|
78
|
-
// Use project's working kubeconfig.yaml for integration tests
|
|
79
|
-
const projectKubeconfig = path.join(process.cwd(), 'kubeconfig.yaml');
|
|
80
|
-
const agent = new DotAI({ kubernetesConfig: projectKubeconfig });
|
|
81
|
-
|
|
82
|
-
// Mock discovery connect to fail, but other modules should still initialize
|
|
83
|
-
jest.spyOn(agent.discovery, 'connect').mockRejectedValue(new Error('Discovery failed'));
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
await agent.initialize();
|
|
87
|
-
} catch (error) {
|
|
88
|
-
// Initialization should fail, but modules should still be accessible
|
|
89
|
-
expect(agent.memory).toBeDefined();
|
|
90
|
-
expect(agent.workflow).toBeDefined();
|
|
91
|
-
expect(agent.discovery).toBeDefined();
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
describe('Memory System Module', () => {
|
|
98
|
-
let memory: MemorySystem;
|
|
99
|
-
|
|
100
|
-
beforeEach(() => {
|
|
101
|
-
memory = new MemorySystem();
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
describe('Basic Storage Operations', () => {
|
|
105
|
-
test('should store and retrieve data', async () => {
|
|
106
|
-
const testData = { key: 'value', number: 42 };
|
|
107
|
-
|
|
108
|
-
await memory.store('test-key', testData);
|
|
109
|
-
const retrieved = await memory.retrieve('test-key');
|
|
110
|
-
|
|
111
|
-
expect(retrieved).toEqual(testData);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
test('should handle non-existent keys gracefully', async () => {
|
|
115
|
-
const result = await memory.retrieve('non-existent');
|
|
116
|
-
expect(result).toBeNull();
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test('should support different data types', async () => {
|
|
120
|
-
await memory.store('string', 'hello');
|
|
121
|
-
await memory.store('number', 123);
|
|
122
|
-
await memory.store('boolean', true);
|
|
123
|
-
await memory.store('array', [1, 2, 3]);
|
|
124
|
-
await memory.store('object', { nested: { value: 'test' } });
|
|
125
|
-
|
|
126
|
-
expect(await memory.retrieve('string')).toBe('hello');
|
|
127
|
-
expect(await memory.retrieve('number')).toBe(123);
|
|
128
|
-
expect(await memory.retrieve('boolean')).toBe(true);
|
|
129
|
-
expect(await memory.retrieve('array')).toEqual([1, 2, 3]);
|
|
130
|
-
expect(await memory.retrieve('object')).toEqual({ nested: { value: 'test' } });
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
describe('Learning and Context', () => {
|
|
135
|
-
test('should learn from successful deployments', async () => {
|
|
136
|
-
const deployment = {
|
|
137
|
-
app: 'nginx',
|
|
138
|
-
namespace: 'production',
|
|
139
|
-
replicas: 3,
|
|
140
|
-
resources: { cpu: '500m', memory: '1Gi' }
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
await memory.learnSuccess('deployment', deployment);
|
|
144
|
-
|
|
145
|
-
const patterns = await memory.getSuccessPatterns('deployment');
|
|
146
|
-
expect(patterns).toContainEqual(expect.objectContaining({
|
|
147
|
-
config: deployment,
|
|
148
|
-
type: 'deployment'
|
|
149
|
-
}));
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
test('should track failure patterns to avoid repetition', async () => {
|
|
153
|
-
const failedConfig = {
|
|
154
|
-
app: 'broken-app',
|
|
155
|
-
issue: 'insufficient resources'
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
await memory.learnFailure('deployment', failedConfig, 'Pod failed to schedule');
|
|
159
|
-
|
|
160
|
-
const failures = await memory.getFailurePatterns('deployment');
|
|
161
|
-
expect(failures).toContainEqual(expect.objectContaining({
|
|
162
|
-
config: failedConfig,
|
|
163
|
-
error: 'Pod failed to schedule'
|
|
164
|
-
}));
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
test('should provide recommendations based on learning', async () => {
|
|
168
|
-
// Store successful pattern
|
|
169
|
-
await memory.learnSuccess('service', {
|
|
170
|
-
type: 'LoadBalancer',
|
|
171
|
-
ports: [{ port: 80, targetPort: 8080 }]
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
const recommendations = await memory.getRecommendations('service', {
|
|
175
|
-
type: 'LoadBalancer'
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
expect(recommendations).toContainEqual(expect.objectContaining({
|
|
179
|
-
suggestion: expect.any(String),
|
|
180
|
-
confidence: expect.any(Number)
|
|
181
|
-
}));
|
|
182
|
-
});
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
describe('Context Management', () => {
|
|
186
|
-
test('should maintain session context', async () => {
|
|
187
|
-
await memory.setContext('current-namespace', 'my-app');
|
|
188
|
-
await memory.setContext('deployment-strategy', 'rolling');
|
|
189
|
-
|
|
190
|
-
const context = await memory.getContext();
|
|
191
|
-
expect(context).toHaveProperty('current-namespace', 'my-app');
|
|
192
|
-
expect(context).toHaveProperty('deployment-strategy', 'rolling');
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
test('should clear context when needed', async () => {
|
|
196
|
-
await memory.setContext('temp-setting', 'value');
|
|
197
|
-
await memory.clearContext('temp-setting');
|
|
198
|
-
|
|
199
|
-
const context = await memory.getContext();
|
|
200
|
-
expect(context).not.toHaveProperty('temp-setting');
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
describe('Workflow Engine Module', () => {
|
|
206
|
-
let workflow: WorkflowEngine;
|
|
207
|
-
|
|
208
|
-
beforeEach(() => {
|
|
209
|
-
workflow = new WorkflowEngine();
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
describe('Workflow Creation', () => {
|
|
213
|
-
test('should create deployment workflows', async () => {
|
|
214
|
-
const spec = {
|
|
215
|
-
app: 'my-app',
|
|
216
|
-
image: 'nginx:latest',
|
|
217
|
-
replicas: 2
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
const workflowId = await workflow.createDeploymentWorkflow(spec);
|
|
221
|
-
expect(typeof workflowId).toBe('string');
|
|
222
|
-
expect(workflowId.length).toBeGreaterThan(0);
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
test('should validate workflow specifications', async () => {
|
|
226
|
-
const invalidSpec = {
|
|
227
|
-
// Missing required fields
|
|
228
|
-
replicas: 'invalid'
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
await expect(workflow.createDeploymentWorkflow(invalidSpec)).rejects.toThrow('Invalid workflow specification');
|
|
232
|
-
});
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
describe('Workflow Execution', () => {
|
|
236
|
-
test('should execute workflows step by step', async () => {
|
|
237
|
-
const spec = { app: 'test-app', image: 'nginx:latest' };
|
|
238
|
-
const workflowId = await workflow.createDeploymentWorkflow(spec);
|
|
239
|
-
|
|
240
|
-
const execution = await workflow.execute(workflowId);
|
|
241
|
-
expect(execution).toHaveProperty('id');
|
|
242
|
-
expect(execution).toHaveProperty('status');
|
|
243
|
-
expect(execution).toHaveProperty('steps');
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
test('should handle step failures gracefully', async () => {
|
|
247
|
-
const spec = { app: 'failing-app', image: 'invalid:image' };
|
|
248
|
-
const workflowId = await workflow.createDeploymentWorkflow(spec);
|
|
249
|
-
|
|
250
|
-
const execution = await workflow.execute(workflowId);
|
|
251
|
-
expect(execution.status).toBe('failed');
|
|
252
|
-
expect(execution).toHaveProperty('error');
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
test('should support workflow rollback', async () => {
|
|
256
|
-
const spec = { app: 'rollback-test', image: 'nginx:latest' };
|
|
257
|
-
const workflowId = await workflow.createDeploymentWorkflow(spec);
|
|
258
|
-
|
|
259
|
-
const execution = await workflow.execute(workflowId);
|
|
260
|
-
const rollbackResult = await workflow.rollback(execution.id);
|
|
261
|
-
|
|
262
|
-
expect(rollbackResult).toHaveProperty('success', true);
|
|
263
|
-
});
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
describe('Workflow Templates', () => {
|
|
267
|
-
test('should support predefined workflow templates', async () => {
|
|
268
|
-
const templates = await workflow.getAvailableTemplates();
|
|
269
|
-
|
|
270
|
-
expect(Array.isArray(templates)).toBe(true);
|
|
271
|
-
expect(templates).toContainEqual(expect.objectContaining({
|
|
272
|
-
name: expect.any(String),
|
|
273
|
-
description: expect.any(String),
|
|
274
|
-
parameters: expect.any(Array)
|
|
275
|
-
}));
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
test('should create workflows from templates', async () => {
|
|
279
|
-
const templateParams = {
|
|
280
|
-
template: 'web-app',
|
|
281
|
-
parameters: {
|
|
282
|
-
appName: 'my-web-app',
|
|
283
|
-
image: 'nginx:latest',
|
|
284
|
-
domain: 'my-app.example.com'
|
|
285
|
-
}
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
const workflowId = await workflow.createFromTemplate(templateParams);
|
|
289
|
-
expect(typeof workflowId).toBe('string');
|
|
290
|
-
});
|
|
291
|
-
});
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
describe('Claude Integration Module', () => {
|
|
295
|
-
let claude: ClaudeIntegration;
|
|
296
|
-
|
|
297
|
-
beforeEach(() => {
|
|
298
|
-
claude = new ClaudeIntegration('test-api-key');
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
describe('AI Communication', () => {
|
|
302
|
-
test('should send messages to Claude and receive responses', async () => {
|
|
303
|
-
const response = await claude.sendMessage('Hello, can you help me deploy a web application?');
|
|
304
|
-
|
|
305
|
-
expect(response).toHaveProperty('content');
|
|
306
|
-
expect(response).toHaveProperty('usage');
|
|
307
|
-
expect(typeof response.content).toBe('string');
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
test('should handle conversation context', async () => {
|
|
311
|
-
await claude.sendMessage('I want to deploy nginx');
|
|
312
|
-
const response = await claude.sendMessage('What are the recommended resources?');
|
|
313
|
-
|
|
314
|
-
// Response should reference the previous nginx context
|
|
315
|
-
expect(response.content.toLowerCase()).toContain('nginx');
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
test('should format Kubernetes YAML properly', async () => {
|
|
319
|
-
const response = await claude.generateYAML('deployment', {
|
|
320
|
-
app: 'nginx',
|
|
321
|
-
replicas: 2,
|
|
322
|
-
image: 'nginx:latest'
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
expect(response).toHaveProperty('yaml');
|
|
326
|
-
expect(response).toHaveProperty('explanation');
|
|
327
|
-
expect(response.yaml).toContain('apiVersion');
|
|
328
|
-
expect(response.yaml).toContain('kind: Deployment');
|
|
329
|
-
});
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
describe('Error Handling', () => {
|
|
333
|
-
test('should handle API errors gracefully', async () => {
|
|
334
|
-
const invalidClaude = new ClaudeIntegration('invalid-key');
|
|
335
|
-
|
|
336
|
-
await expect(invalidClaude.sendMessage('test')).rejects.toThrow();
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
test('should provide meaningful error messages', async () => {
|
|
340
|
-
try {
|
|
341
|
-
const invalidClaude = new ClaudeIntegration('');
|
|
342
|
-
await invalidClaude.sendMessage('test');
|
|
343
|
-
} catch (error) {
|
|
344
|
-
expect((error as Error).message).toContain('API key');
|
|
345
|
-
}
|
|
346
|
-
});
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
describe('Learning Integration', () => {
|
|
350
|
-
test('should learn from successful interactions', async () => {
|
|
351
|
-
const interaction = {
|
|
352
|
-
input: 'Deploy nginx with 2 replicas',
|
|
353
|
-
output: 'Successfully created deployment',
|
|
354
|
-
success: true
|
|
355
|
-
};
|
|
356
|
-
|
|
357
|
-
await claude.recordInteraction(interaction);
|
|
358
|
-
|
|
359
|
-
const patterns = await claude.getSuccessfulPatterns();
|
|
360
|
-
expect(patterns).toContainEqual(expect.objectContaining(interaction));
|
|
361
|
-
});
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
describe('Centralized Configuration Management', () => {
|
|
365
|
-
const originalKubeconfig = process.env.KUBECONFIG;
|
|
366
|
-
const originalAnthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
367
|
-
|
|
368
|
-
afterEach(() => {
|
|
369
|
-
// Restore original environment variables
|
|
370
|
-
if (originalKubeconfig) {
|
|
371
|
-
process.env.KUBECONFIG = originalKubeconfig;
|
|
372
|
-
} else {
|
|
373
|
-
delete process.env.KUBECONFIG;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
if (originalAnthropicKey) {
|
|
377
|
-
process.env.ANTHROPIC_API_KEY = originalAnthropicKey;
|
|
378
|
-
} else {
|
|
379
|
-
delete process.env.ANTHROPIC_API_KEY;
|
|
380
|
-
}
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
test('should read ANTHROPIC_API_KEY from environment when not provided in config', () => {
|
|
384
|
-
process.env.ANTHROPIC_API_KEY = 'test-api-key-from-env';
|
|
385
|
-
delete process.env.KUBECONFIG;
|
|
386
|
-
|
|
387
|
-
const dotAI = new DotAI();
|
|
388
|
-
|
|
389
|
-
// Use the public getter to verify API key was read from environment
|
|
390
|
-
expect(dotAI.getAnthropicApiKey()).toBe('test-api-key-from-env');
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
test('should use provided config values over environment variables', () => {
|
|
394
|
-
process.env.KUBECONFIG = '/env/kubeconfig.yaml';
|
|
395
|
-
process.env.ANTHROPIC_API_KEY = 'env-api-key';
|
|
396
|
-
|
|
397
|
-
const dotAI = new DotAI({
|
|
398
|
-
kubernetesConfig: '/override/kubeconfig.yaml',
|
|
399
|
-
anthropicApiKey: 'override-api-key'
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
// Should use the provided config values, not environment
|
|
403
|
-
expect(dotAI.getAnthropicApiKey()).toBe('override-api-key');
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
test('should handle missing environment variables gracefully', () => {
|
|
407
|
-
delete process.env.KUBECONFIG;
|
|
408
|
-
delete process.env.ANTHROPIC_API_KEY;
|
|
409
|
-
|
|
410
|
-
const dotAI = new DotAI();
|
|
411
|
-
|
|
412
|
-
expect(dotAI.getAnthropicApiKey()).toBeUndefined();
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
test('should provide public getter for API key', () => {
|
|
416
|
-
const dotAI = new DotAI({
|
|
417
|
-
anthropicApiKey: 'test-getter-key'
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
expect(dotAI.getAnthropicApiKey()).toBe('test-getter-key');
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
test('should return undefined when no API key is available', () => {
|
|
424
|
-
delete process.env.ANTHROPIC_API_KEY;
|
|
425
|
-
|
|
426
|
-
const dotAI = new DotAI();
|
|
427
|
-
|
|
428
|
-
expect(dotAI.getAnthropicApiKey()).toBeUndefined();
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
test('should validate configuration and reject empty API key', () => {
|
|
432
|
-
expect(() => {
|
|
433
|
-
new DotAI({
|
|
434
|
-
anthropicApiKey: ''
|
|
435
|
-
});
|
|
436
|
-
}).toThrow('Invalid configuration: Empty API key provided');
|
|
437
|
-
});
|
|
438
|
-
});
|
|
439
|
-
});
|