@vfarcic/dot-ai 0.5.1 → 0.6.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,632 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ErrorHandler,
|
|
3
|
-
ErrorCategory,
|
|
4
|
-
ErrorSeverity,
|
|
5
|
-
ConsoleLogger,
|
|
6
|
-
LogLevel,
|
|
7
|
-
AppError,
|
|
8
|
-
ErrorContext
|
|
9
|
-
} from '../../src/core/error-handling';
|
|
10
|
-
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
|
|
11
|
-
|
|
12
|
-
describe('Error Handling System', () => {
|
|
13
|
-
describe('ErrorHandler', () => {
|
|
14
|
-
beforeEach(() => {
|
|
15
|
-
// Reset logger to default for each test
|
|
16
|
-
ErrorHandler.setLogger(new ConsoleLogger('Test', LogLevel.DEBUG));
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
describe('Error Creation', () => {
|
|
20
|
-
test('should create comprehensive AppError', () => {
|
|
21
|
-
const context: Partial<ErrorContext> = {
|
|
22
|
-
operation: 'test_operation',
|
|
23
|
-
component: 'TestComponent',
|
|
24
|
-
userId: 'user123',
|
|
25
|
-
sessionId: 'session456',
|
|
26
|
-
input: { test: 'data' }
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const error = ErrorHandler.createError(
|
|
30
|
-
ErrorCategory.VALIDATION,
|
|
31
|
-
ErrorSeverity.MEDIUM,
|
|
32
|
-
'Test error message',
|
|
33
|
-
context
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
expect(error).toMatchObject({
|
|
37
|
-
category: ErrorCategory.VALIDATION,
|
|
38
|
-
severity: ErrorSeverity.MEDIUM,
|
|
39
|
-
message: 'Test error message',
|
|
40
|
-
isRetryable: false
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
expect(error.id).toMatch(/^err_\d+_[a-z0-9]+$/);
|
|
44
|
-
expect(error.code).toMatch(/^VALIDATION_M_\d+_[a-z0-9]+$/);
|
|
45
|
-
expect(error.timestamp).toBeInstanceOf(Date);
|
|
46
|
-
expect(error.context.operation).toBe('test_operation');
|
|
47
|
-
expect(error.context.component).toBe('TestComponent');
|
|
48
|
-
expect(error.context.userId).toBe('user123');
|
|
49
|
-
expect(error.suggestedActions).toBeInstanceOf(Array);
|
|
50
|
-
expect(error.suggestedActions.length).toBeGreaterThan(0);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test('should wrap original error with context', () => {
|
|
54
|
-
const originalError = new Error('Original error message');
|
|
55
|
-
const context: Partial<ErrorContext> = {
|
|
56
|
-
operation: 'test_wrap',
|
|
57
|
-
component: 'TestWrapper'
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const appError = ErrorHandler.createError(
|
|
61
|
-
ErrorCategory.INTERNAL,
|
|
62
|
-
ErrorSeverity.HIGH,
|
|
63
|
-
'Wrapped error',
|
|
64
|
-
context,
|
|
65
|
-
originalError
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
expect(appError.context.originalError).toBe(originalError);
|
|
69
|
-
expect(appError.context.stackTrace).toBe(originalError.stack);
|
|
70
|
-
expect(appError.technicalDetails).toBe('Original error message');
|
|
71
|
-
// The cause field is removed to prevent circular references
|
|
72
|
-
expect(appError.cause).toBeUndefined();
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test('should generate unique error IDs and codes', () => {
|
|
76
|
-
const error1 = ErrorHandler.createError(
|
|
77
|
-
ErrorCategory.VALIDATION,
|
|
78
|
-
ErrorSeverity.LOW,
|
|
79
|
-
'Error 1',
|
|
80
|
-
{ operation: 'test1', component: 'Test' }
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
const error2 = ErrorHandler.createError(
|
|
84
|
-
ErrorCategory.VALIDATION,
|
|
85
|
-
ErrorSeverity.LOW,
|
|
86
|
-
'Error 2',
|
|
87
|
-
{ operation: 'test2', component: 'Test' }
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
expect(error1.id).not.toBe(error2.id);
|
|
91
|
-
expect(error1.code).not.toBe(error2.code);
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
describe('MCP Error Conversion', () => {
|
|
96
|
-
test('should convert AppError to McpError with correct error codes', () => {
|
|
97
|
-
const validationError = ErrorHandler.createError(
|
|
98
|
-
ErrorCategory.VALIDATION,
|
|
99
|
-
ErrorSeverity.MEDIUM,
|
|
100
|
-
'Validation failed',
|
|
101
|
-
{ operation: 'test', component: 'Test' }
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
const mcpError = ErrorHandler.toMcpError(validationError);
|
|
105
|
-
|
|
106
|
-
expect(mcpError).toBeInstanceOf(McpError);
|
|
107
|
-
expect(mcpError.code).toBe(ErrorCode.InvalidParams);
|
|
108
|
-
expect(mcpError.message).toContain('Validation failed');
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test('should map different error categories to appropriate MCP codes', () => {
|
|
112
|
-
const testCases = [
|
|
113
|
-
{ category: ErrorCategory.VALIDATION, expected: ErrorCode.InvalidParams },
|
|
114
|
-
{ category: ErrorCategory.AUTHENTICATION, expected: ErrorCode.InvalidParams },
|
|
115
|
-
{ category: ErrorCategory.MCP_PROTOCOL, expected: ErrorCode.MethodNotFound },
|
|
116
|
-
{ category: ErrorCategory.OPERATION, expected: ErrorCode.InvalidRequest },
|
|
117
|
-
{ category: ErrorCategory.INTERNAL, expected: ErrorCode.InternalError },
|
|
118
|
-
{ category: ErrorCategory.UNKNOWN, expected: ErrorCode.InternalError }
|
|
119
|
-
];
|
|
120
|
-
|
|
121
|
-
testCases.forEach(({ category, expected }) => {
|
|
122
|
-
const appError = ErrorHandler.createError(
|
|
123
|
-
category,
|
|
124
|
-
ErrorSeverity.MEDIUM,
|
|
125
|
-
'Test error',
|
|
126
|
-
{ operation: 'test', component: 'Test' }
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
const mcpError = ErrorHandler.toMcpError(appError);
|
|
130
|
-
expect(mcpError.code).toBe(expected);
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
describe('Error Handling with Context', () => {
|
|
136
|
-
test('should handle native Error and enhance with context', () => {
|
|
137
|
-
const nativeError = new Error('Native error message');
|
|
138
|
-
const context: Partial<ErrorContext> = {
|
|
139
|
-
operation: 'test_operation',
|
|
140
|
-
component: 'TestComponent',
|
|
141
|
-
requestId: 'req123'
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
const result = ErrorHandler.handleError(nativeError, context, {
|
|
145
|
-
rethrow: false
|
|
146
|
-
}) as AppError;
|
|
147
|
-
|
|
148
|
-
expect(result.message).toBe('Native error message');
|
|
149
|
-
expect(result.context.operation).toBe('test_operation');
|
|
150
|
-
expect(result.context.component).toBe('TestComponent');
|
|
151
|
-
expect(result.context.requestId).toBe('req123');
|
|
152
|
-
expect(result.context.originalError).toBe(nativeError);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
test('should pass through AppError unchanged', () => {
|
|
156
|
-
const appError = ErrorHandler.createError(
|
|
157
|
-
ErrorCategory.VALIDATION,
|
|
158
|
-
ErrorSeverity.MEDIUM,
|
|
159
|
-
'Original app error',
|
|
160
|
-
{ operation: 'original', component: 'Original' }
|
|
161
|
-
);
|
|
162
|
-
|
|
163
|
-
const context: Partial<ErrorContext> = {
|
|
164
|
-
operation: 'handler_operation',
|
|
165
|
-
component: 'HandlerComponent'
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
const result = ErrorHandler.handleError(appError, context, {
|
|
169
|
-
rethrow: false
|
|
170
|
-
}) as AppError;
|
|
171
|
-
|
|
172
|
-
expect(result).toBe(appError);
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
test('should convert to MCP error when requested', () => {
|
|
176
|
-
const nativeError = new Error('Test error');
|
|
177
|
-
const context: Partial<ErrorContext> = {
|
|
178
|
-
operation: 'test',
|
|
179
|
-
component: 'Test'
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
const result = ErrorHandler.handleError(nativeError, context, {
|
|
183
|
-
convertToMcp: true,
|
|
184
|
-
rethrow: false
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
expect(result).toBeInstanceOf(McpError);
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
test('should rethrow error when requested', () => {
|
|
191
|
-
const nativeError = new Error('Test error');
|
|
192
|
-
const context: Partial<ErrorContext> = {
|
|
193
|
-
operation: 'test',
|
|
194
|
-
component: 'Test'
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
expect(() => {
|
|
198
|
-
ErrorHandler.handleError(nativeError, context, {
|
|
199
|
-
rethrow: true
|
|
200
|
-
});
|
|
201
|
-
}).toThrow();
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
describe('Error Wrapping with Retry Logic', () => {
|
|
206
|
-
test('should execute operation successfully', async () => {
|
|
207
|
-
const mockOperation = jest.fn().mockResolvedValue('success');
|
|
208
|
-
const context: Partial<ErrorContext> = {
|
|
209
|
-
operation: 'test_success',
|
|
210
|
-
component: 'Test'
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
const result = await ErrorHandler.withErrorHandling(
|
|
214
|
-
mockOperation,
|
|
215
|
-
context
|
|
216
|
-
);
|
|
217
|
-
|
|
218
|
-
expect(result).toBe('success');
|
|
219
|
-
expect(mockOperation).toHaveBeenCalledTimes(1);
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
test('should retry operation on failure', async () => {
|
|
223
|
-
const mockOperation = jest.fn()
|
|
224
|
-
.mockRejectedValueOnce(new Error('First failure'))
|
|
225
|
-
.mockResolvedValueOnce('success');
|
|
226
|
-
|
|
227
|
-
const context: Partial<ErrorContext> = {
|
|
228
|
-
operation: 'test_retry',
|
|
229
|
-
component: 'Test'
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
const result = await ErrorHandler.withErrorHandling(
|
|
233
|
-
mockOperation,
|
|
234
|
-
context,
|
|
235
|
-
{ retryCount: 1 }
|
|
236
|
-
);
|
|
237
|
-
|
|
238
|
-
expect(result).toBe('success');
|
|
239
|
-
expect(mockOperation).toHaveBeenCalledTimes(2);
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
test('should fail after max retries', async () => {
|
|
243
|
-
const mockOperation = jest.fn()
|
|
244
|
-
.mockRejectedValue(new Error('Persistent failure'));
|
|
245
|
-
|
|
246
|
-
const context: Partial<ErrorContext> = {
|
|
247
|
-
operation: 'test_failure',
|
|
248
|
-
component: 'Test'
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
try {
|
|
252
|
-
await ErrorHandler.withErrorHandling(
|
|
253
|
-
mockOperation,
|
|
254
|
-
context,
|
|
255
|
-
{ retryCount: 2 }
|
|
256
|
-
);
|
|
257
|
-
// If we get here, the function didn't throw
|
|
258
|
-
fail('Expected withErrorHandling to throw but it resolved');
|
|
259
|
-
} catch (error) {
|
|
260
|
-
// This is expected behavior
|
|
261
|
-
expect(error).toBeDefined();
|
|
262
|
-
expect(mockOperation).toHaveBeenCalledTimes(3); // Original + 2 retries
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
test('should convert to MCP error on final failure', async () => {
|
|
267
|
-
const mockOperation = jest.fn()
|
|
268
|
-
.mockRejectedValue(new Error('Test failure'));
|
|
269
|
-
|
|
270
|
-
const context: Partial<ErrorContext> = {
|
|
271
|
-
operation: 'test_mcp_failure',
|
|
272
|
-
component: 'Test'
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
await expect(ErrorHandler.withErrorHandling(
|
|
276
|
-
mockOperation,
|
|
277
|
-
context,
|
|
278
|
-
{ convertToMcp: true }
|
|
279
|
-
)).rejects.toBeInstanceOf(McpError);
|
|
280
|
-
});
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
describe('Error Categorization', () => {
|
|
284
|
-
test('should correctly categorize Kubernetes errors', () => {
|
|
285
|
-
const kubeError = new Error('kubeconfig file not found');
|
|
286
|
-
const context: Partial<ErrorContext> = {
|
|
287
|
-
operation: 'test',
|
|
288
|
-
component: 'Test'
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
const appError = ErrorHandler.handleError(kubeError, context, {
|
|
292
|
-
rethrow: false
|
|
293
|
-
}) as AppError;
|
|
294
|
-
|
|
295
|
-
expect(appError.category).toBe(ErrorCategory.KUBERNETES);
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
test('should correctly categorize network errors', () => {
|
|
299
|
-
const networkError = new Error('connection refused');
|
|
300
|
-
const context: Partial<ErrorContext> = {
|
|
301
|
-
operation: 'test',
|
|
302
|
-
component: 'Test'
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
const appError = ErrorHandler.handleError(networkError, context, {
|
|
306
|
-
rethrow: false
|
|
307
|
-
}) as AppError;
|
|
308
|
-
|
|
309
|
-
expect(appError.category).toBe(ErrorCategory.NETWORK);
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
test('should correctly categorize authentication errors', () => {
|
|
313
|
-
const authError = new Error('unauthorized access');
|
|
314
|
-
const context: Partial<ErrorContext> = {
|
|
315
|
-
operation: 'test',
|
|
316
|
-
component: 'Test'
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
const appError = ErrorHandler.handleError(authError, context, {
|
|
320
|
-
rethrow: false
|
|
321
|
-
}) as AppError;
|
|
322
|
-
|
|
323
|
-
expect(appError.category).toBe(ErrorCategory.AUTHENTICATION);
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
test('should correctly categorize validation errors', () => {
|
|
327
|
-
const validationError = new Error('invalid parameter format');
|
|
328
|
-
const context: Partial<ErrorContext> = {
|
|
329
|
-
operation: 'test',
|
|
330
|
-
component: 'Test'
|
|
331
|
-
};
|
|
332
|
-
|
|
333
|
-
const appError = ErrorHandler.handleError(validationError, context, {
|
|
334
|
-
rethrow: false
|
|
335
|
-
}) as AppError;
|
|
336
|
-
|
|
337
|
-
expect(appError.category).toBe(ErrorCategory.VALIDATION);
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
test('should correctly categorize AI service errors', () => {
|
|
341
|
-
const aiError = new Error('anthropic api key invalid');
|
|
342
|
-
const context: Partial<ErrorContext> = {
|
|
343
|
-
operation: 'test',
|
|
344
|
-
component: 'Test'
|
|
345
|
-
};
|
|
346
|
-
|
|
347
|
-
const appError = ErrorHandler.handleError(aiError, context, {
|
|
348
|
-
rethrow: false
|
|
349
|
-
}) as AppError;
|
|
350
|
-
|
|
351
|
-
expect(appError.category).toBe(ErrorCategory.AI_SERVICE);
|
|
352
|
-
});
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
describe('Error Severity Assessment', () => {
|
|
356
|
-
test('should assign critical severity for critical errors', () => {
|
|
357
|
-
const criticalError = new Error('critical system failure');
|
|
358
|
-
const context: Partial<ErrorContext> = {
|
|
359
|
-
operation: 'test',
|
|
360
|
-
component: 'Test'
|
|
361
|
-
};
|
|
362
|
-
|
|
363
|
-
const appError = ErrorHandler.handleError(criticalError, context, {
|
|
364
|
-
rethrow: false
|
|
365
|
-
}) as AppError;
|
|
366
|
-
|
|
367
|
-
expect(appError.severity).toBe(ErrorSeverity.CRITICAL);
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
test('should assign high severity for authentication errors', () => {
|
|
371
|
-
const authError = new Error('authentication failed');
|
|
372
|
-
const context: Partial<ErrorContext> = {
|
|
373
|
-
operation: 'test',
|
|
374
|
-
component: 'Test'
|
|
375
|
-
};
|
|
376
|
-
|
|
377
|
-
const appError = ErrorHandler.handleError(authError, context, {
|
|
378
|
-
rethrow: false
|
|
379
|
-
}) as AppError;
|
|
380
|
-
|
|
381
|
-
expect(appError.severity).toBe(ErrorSeverity.HIGH);
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
test('should assign medium severity for validation errors', () => {
|
|
385
|
-
const validationError = new Error('validation error');
|
|
386
|
-
const context: Partial<ErrorContext> = {
|
|
387
|
-
operation: 'test',
|
|
388
|
-
component: 'Test'
|
|
389
|
-
};
|
|
390
|
-
|
|
391
|
-
const appError = ErrorHandler.handleError(validationError, context, {
|
|
392
|
-
rethrow: false
|
|
393
|
-
}) as AppError;
|
|
394
|
-
|
|
395
|
-
expect(appError.severity).toBe(ErrorSeverity.MEDIUM);
|
|
396
|
-
});
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
describe('Suggested Actions', () => {
|
|
400
|
-
test('should provide Kubernetes-specific suggestions', () => {
|
|
401
|
-
const appError = ErrorHandler.createError(
|
|
402
|
-
ErrorCategory.KUBERNETES,
|
|
403
|
-
ErrorSeverity.HIGH,
|
|
404
|
-
'Kubernetes error',
|
|
405
|
-
{ operation: 'test', component: 'Test' }
|
|
406
|
-
);
|
|
407
|
-
|
|
408
|
-
expect(appError.suggestedActions).toContain('Verify kubeconfig file exists and is valid');
|
|
409
|
-
expect(appError.suggestedActions).toContain('Check cluster connectivity with kubectl cluster-info');
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
test('should provide validation-specific suggestions', () => {
|
|
413
|
-
const appError = ErrorHandler.createError(
|
|
414
|
-
ErrorCategory.VALIDATION,
|
|
415
|
-
ErrorSeverity.MEDIUM,
|
|
416
|
-
'Validation error',
|
|
417
|
-
{ operation: 'test', component: 'Test' }
|
|
418
|
-
);
|
|
419
|
-
|
|
420
|
-
expect(appError.suggestedActions).toContain('Review input parameters for correct format');
|
|
421
|
-
expect(appError.suggestedActions).toContain('Check required fields are provided');
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
test('should provide AI service-specific suggestions', () => {
|
|
425
|
-
const appError = ErrorHandler.createError(
|
|
426
|
-
ErrorCategory.AI_SERVICE,
|
|
427
|
-
ErrorSeverity.HIGH,
|
|
428
|
-
'AI service error',
|
|
429
|
-
{ operation: 'test', component: 'Test' }
|
|
430
|
-
);
|
|
431
|
-
|
|
432
|
-
expect(appError.suggestedActions).toContain('Check ANTHROPIC_API_KEY environment variable');
|
|
433
|
-
expect(appError.suggestedActions).toContain('Verify API key is valid and has sufficient credits');
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
test('should use custom suggested actions when provided', () => {
|
|
437
|
-
const customActions = ['Custom action 1', 'Custom action 2'];
|
|
438
|
-
const context: Partial<ErrorContext> = {
|
|
439
|
-
operation: 'test',
|
|
440
|
-
component: 'Test',
|
|
441
|
-
suggestedActions: customActions
|
|
442
|
-
};
|
|
443
|
-
|
|
444
|
-
const appError = ErrorHandler.createError(
|
|
445
|
-
ErrorCategory.INTERNAL,
|
|
446
|
-
ErrorSeverity.LOW,
|
|
447
|
-
'Test error',
|
|
448
|
-
context
|
|
449
|
-
);
|
|
450
|
-
|
|
451
|
-
expect(appError.suggestedActions).toEqual(customActions);
|
|
452
|
-
});
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
describe('Request ID Generation', () => {
|
|
456
|
-
test('should generate unique request IDs', () => {
|
|
457
|
-
const id1 = ErrorHandler.generateRequestId();
|
|
458
|
-
const id2 = ErrorHandler.generateRequestId();
|
|
459
|
-
|
|
460
|
-
expect(id1).not.toBe(id2);
|
|
461
|
-
expect(id1).toMatch(/^req_\d+_\d+$/);
|
|
462
|
-
expect(id2).toMatch(/^req_\d+_\d+$/);
|
|
463
|
-
});
|
|
464
|
-
});
|
|
465
|
-
});
|
|
466
|
-
|
|
467
|
-
describe('ConsoleLogger', () => {
|
|
468
|
-
let consoleSpy: {
|
|
469
|
-
debug: jest.SpyInstance;
|
|
470
|
-
info: jest.SpyInstance;
|
|
471
|
-
warn: jest.SpyInstance;
|
|
472
|
-
error: jest.SpyInstance;
|
|
473
|
-
};
|
|
474
|
-
|
|
475
|
-
beforeEach(() => {
|
|
476
|
-
consoleSpy = {
|
|
477
|
-
debug: jest.spyOn(console, 'debug').mockImplementation(),
|
|
478
|
-
info: jest.spyOn(console, 'info').mockImplementation(),
|
|
479
|
-
warn: jest.spyOn(console, 'warn').mockImplementation(),
|
|
480
|
-
error: jest.spyOn(console, 'error').mockImplementation()
|
|
481
|
-
};
|
|
482
|
-
});
|
|
483
|
-
|
|
484
|
-
afterEach(() => {
|
|
485
|
-
Object.values(consoleSpy).forEach(spy => spy.mockRestore());
|
|
486
|
-
});
|
|
487
|
-
|
|
488
|
-
test('should log at appropriate levels', () => {
|
|
489
|
-
const logger = new ConsoleLogger('TestComponent', LogLevel.DEBUG);
|
|
490
|
-
|
|
491
|
-
logger.debug('Debug message');
|
|
492
|
-
logger.info('Info message');
|
|
493
|
-
logger.warn('Warn message');
|
|
494
|
-
logger.error('Error message');
|
|
495
|
-
|
|
496
|
-
expect(consoleSpy.debug).toHaveBeenCalledWith(
|
|
497
|
-
expect.stringContaining('[TestComponent] Debug message')
|
|
498
|
-
);
|
|
499
|
-
expect(consoleSpy.info).toHaveBeenCalledWith(
|
|
500
|
-
expect.stringContaining('[TestComponent] Info message')
|
|
501
|
-
);
|
|
502
|
-
expect(consoleSpy.warn).toHaveBeenCalledWith(
|
|
503
|
-
expect.stringContaining('[TestComponent] Warn message')
|
|
504
|
-
);
|
|
505
|
-
expect(consoleSpy.error).toHaveBeenCalledWith(
|
|
506
|
-
expect.stringContaining('[TestComponent] Error message')
|
|
507
|
-
);
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
test('should respect minimum log level', () => {
|
|
511
|
-
const logger = new ConsoleLogger('TestComponent', LogLevel.WARN);
|
|
512
|
-
|
|
513
|
-
logger.debug('Debug message');
|
|
514
|
-
logger.info('Info message');
|
|
515
|
-
logger.warn('Warn message');
|
|
516
|
-
logger.error('Error message');
|
|
517
|
-
|
|
518
|
-
expect(consoleSpy.debug).not.toHaveBeenCalled();
|
|
519
|
-
expect(consoleSpy.info).not.toHaveBeenCalled();
|
|
520
|
-
expect(consoleSpy.warn).toHaveBeenCalled();
|
|
521
|
-
expect(consoleSpy.error).toHaveBeenCalled();
|
|
522
|
-
});
|
|
523
|
-
|
|
524
|
-
test('should format messages with timestamps and component', () => {
|
|
525
|
-
const logger = new ConsoleLogger('TestComponent', LogLevel.INFO);
|
|
526
|
-
|
|
527
|
-
logger.info('Test message');
|
|
528
|
-
|
|
529
|
-
expect(consoleSpy.info).toHaveBeenCalledWith(
|
|
530
|
-
expect.stringMatching(/^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\] INFO \[TestComponent\] Test message$/)
|
|
531
|
-
);
|
|
532
|
-
});
|
|
533
|
-
|
|
534
|
-
test('should include data in log messages', () => {
|
|
535
|
-
const logger = new ConsoleLogger('TestComponent', LogLevel.INFO);
|
|
536
|
-
const testData = { key: 'value', number: 42 };
|
|
537
|
-
|
|
538
|
-
logger.info('Test message', testData);
|
|
539
|
-
|
|
540
|
-
expect(consoleSpy.info).toHaveBeenCalledWith(
|
|
541
|
-
expect.stringContaining(JSON.stringify(testData, null, 2))
|
|
542
|
-
);
|
|
543
|
-
});
|
|
544
|
-
|
|
545
|
-
test('should serialize errors properly', () => {
|
|
546
|
-
const logger = new ConsoleLogger('TestComponent', LogLevel.ERROR);
|
|
547
|
-
const testError = new Error('Test error');
|
|
548
|
-
const appError = ErrorHandler.createError(
|
|
549
|
-
ErrorCategory.VALIDATION,
|
|
550
|
-
ErrorSeverity.MEDIUM,
|
|
551
|
-
'App error',
|
|
552
|
-
{ operation: 'test', component: 'Test' }
|
|
553
|
-
);
|
|
554
|
-
|
|
555
|
-
logger.error('Native error', testError);
|
|
556
|
-
logger.error('App error', appError);
|
|
557
|
-
|
|
558
|
-
expect(consoleSpy.error).toHaveBeenCalledWith(
|
|
559
|
-
expect.stringContaining('"name": "Error"')
|
|
560
|
-
);
|
|
561
|
-
expect(consoleSpy.error).toHaveBeenCalledWith(
|
|
562
|
-
expect.stringContaining('"category": "validation"')
|
|
563
|
-
);
|
|
564
|
-
});
|
|
565
|
-
});
|
|
566
|
-
|
|
567
|
-
describe('Error Context Tracking', () => {
|
|
568
|
-
test('should preserve context through error chain', () => {
|
|
569
|
-
const originalContext: Partial<ErrorContext> = {
|
|
570
|
-
operation: 'original_operation',
|
|
571
|
-
component: 'OriginalComponent',
|
|
572
|
-
userId: 'user123',
|
|
573
|
-
sessionId: 'session456',
|
|
574
|
-
requestId: 'req789'
|
|
575
|
-
};
|
|
576
|
-
|
|
577
|
-
const originalError = ErrorHandler.createError(
|
|
578
|
-
ErrorCategory.VALIDATION,
|
|
579
|
-
ErrorSeverity.MEDIUM,
|
|
580
|
-
'Original error',
|
|
581
|
-
originalContext
|
|
582
|
-
);
|
|
583
|
-
|
|
584
|
-
const wrappedContext: Partial<ErrorContext> = {
|
|
585
|
-
operation: 'wrapped_operation',
|
|
586
|
-
component: 'WrapperComponent'
|
|
587
|
-
};
|
|
588
|
-
|
|
589
|
-
const wrappedError = ErrorHandler.handleError(originalError, wrappedContext, {
|
|
590
|
-
rethrow: false
|
|
591
|
-
}) as AppError;
|
|
592
|
-
|
|
593
|
-
// Original AppError should be passed through unchanged
|
|
594
|
-
expect(wrappedError).toBe(originalError);
|
|
595
|
-
expect(wrappedError.context.operation).toBe('original_operation');
|
|
596
|
-
expect(wrappedError.context.userId).toBe('user123');
|
|
597
|
-
});
|
|
598
|
-
|
|
599
|
-
test('should include version and timestamp in context', () => {
|
|
600
|
-
const appError = ErrorHandler.createError(
|
|
601
|
-
ErrorCategory.INTERNAL,
|
|
602
|
-
ErrorSeverity.LOW,
|
|
603
|
-
'Test error',
|
|
604
|
-
{ operation: 'test', component: 'Test' }
|
|
605
|
-
);
|
|
606
|
-
|
|
607
|
-
expect(appError.context.version).toBeDefined();
|
|
608
|
-
expect(appError.context.timestamp).toBeInstanceOf(Date);
|
|
609
|
-
expect(appError.timestamp).toBeInstanceOf(Date);
|
|
610
|
-
});
|
|
611
|
-
|
|
612
|
-
test('should handle retry count in context', () => {
|
|
613
|
-
const context: Partial<ErrorContext> = {
|
|
614
|
-
operation: 'test_retry',
|
|
615
|
-
component: 'Test',
|
|
616
|
-
retryCount: 2,
|
|
617
|
-
isRetryable: true
|
|
618
|
-
};
|
|
619
|
-
|
|
620
|
-
const appError = ErrorHandler.createError(
|
|
621
|
-
ErrorCategory.NETWORK,
|
|
622
|
-
ErrorSeverity.MEDIUM,
|
|
623
|
-
'Network error',
|
|
624
|
-
context
|
|
625
|
-
);
|
|
626
|
-
|
|
627
|
-
expect(appError.context.retryCount).toBe(2);
|
|
628
|
-
expect(appError.context.isRetryable).toBe(true);
|
|
629
|
-
expect(appError.isRetryable).toBe(true);
|
|
630
|
-
});
|
|
631
|
-
});
|
|
632
|
-
});
|