@claudetools/tools 0.9.0 → 0.9.2
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.js +9 -1
- package/dist/codedna/__tests__/examples/mongoose-example.d.ts +6 -0
- package/dist/codedna/__tests__/examples/mongoose-example.js +163 -0
- package/dist/codedna/__tests__/fixtures/typeorm-production-test.d.ts +1 -0
- package/dist/codedna/__tests__/fixtures/typeorm-production-test.js +231 -0
- package/dist/codedna/__tests__/fixtures/typeorm-test.d.ts +1 -0
- package/dist/codedna/__tests__/fixtures/typeorm-test.js +124 -0
- package/dist/codedna/__tests__/laravel-output-review.d.ts +1 -0
- package/dist/codedna/__tests__/laravel-output-review.js +249 -0
- package/dist/codedna/__tests__/mongoose-output-test.d.ts +1 -0
- package/dist/codedna/__tests__/mongoose-output-test.js +178 -0
- package/dist/codedna/examples/radix-example.d.ts +2 -0
- package/dist/codedna/examples/radix-example.js +259 -0
- package/dist/codedna/index.d.ts +5 -3
- package/dist/codedna/index.js +6 -3
- package/dist/codedna/kappa-ast.d.ts +143 -5
- package/dist/codedna/kappa-drizzle-generator.js +8 -5
- package/dist/codedna/kappa-gofiber-generator.d.ts +65 -0
- package/dist/codedna/kappa-gofiber-generator.js +587 -0
- package/dist/codedna/kappa-laravel-generator.d.ts +68 -0
- package/dist/codedna/kappa-laravel-generator.js +741 -0
- package/dist/codedna/kappa-lexer.d.ts +44 -0
- package/dist/codedna/kappa-lexer.js +124 -0
- package/dist/codedna/kappa-mantine-generator.d.ts +65 -0
- package/dist/codedna/kappa-mantine-generator.js +518 -0
- package/dist/codedna/kappa-mongoose-generator.d.ts +44 -0
- package/dist/codedna/kappa-mongoose-generator.js +442 -0
- package/dist/codedna/kappa-parser.d.ts +43 -1
- package/dist/codedna/kappa-parser.js +601 -0
- package/dist/codedna/kappa-radix-generator.d.ts +61 -0
- package/dist/codedna/kappa-radix-generator.js +566 -0
- package/dist/codedna/kappa-typeorm-generator.d.ts +59 -0
- package/dist/codedna/kappa-typeorm-generator.js +723 -0
- package/dist/codedna/kappa-vitest-generator.d.ts +85 -0
- package/dist/codedna/kappa-vitest-generator.js +739 -0
- package/dist/codedna/parser.js +26 -1
- package/dist/codegen/cloud-client.d.ts +160 -0
- package/dist/codegen/cloud-client.js +195 -0
- package/dist/codegen/codegen-tool.d.ts +35 -0
- package/dist/codegen/codegen-tool.js +312 -0
- package/dist/codegen/field-inference.d.ts +24 -0
- package/dist/codegen/field-inference.js +101 -0
- package/dist/codegen/form-parser.d.ts +13 -0
- package/dist/codegen/form-parser.js +186 -0
- package/dist/codegen/index.d.ts +2 -0
- package/dist/codegen/index.js +4 -0
- package/dist/codegen/natural-parser.d.ts +50 -0
- package/dist/codegen/natural-parser.js +769 -0
- package/dist/handlers/codedna-handlers.d.ts +1 -1
- package/dist/handlers/codegen-handlers.d.ts +20 -0
- package/dist/handlers/codegen-handlers.js +60 -0
- package/dist/handlers/kappa-handlers.d.ts +97 -0
- package/dist/handlers/kappa-handlers.js +408 -0
- package/dist/handlers/tool-handlers.js +124 -221
- package/dist/helpers/api-client.js +48 -3
- package/dist/helpers/compact-formatter.d.ts +9 -2
- package/dist/helpers/compact-formatter.js +26 -2
- package/dist/helpers/config.d.ts +7 -2
- package/dist/helpers/config.js +25 -10
- package/dist/helpers/session-validation.d.ts +1 -1
- package/dist/helpers/session-validation.js +2 -4
- package/dist/helpers/tasks.d.ts +21 -0
- package/dist/helpers/tasks.js +52 -0
- package/dist/helpers/workers.d.ts +1 -1
- package/dist/helpers/workers.js +19 -19
- package/dist/setup.d.ts +1 -0
- package/dist/setup.js +228 -3
- package/dist/templates/claude-md.d.ts +1 -1
- package/dist/templates/claude-md.js +37 -152
- package/dist/templates/orchestrator-prompt.d.ts +2 -2
- package/dist/templates/orchestrator-prompt.js +31 -38
- package/dist/templates/self-critique.d.ts +50 -0
- package/dist/templates/self-critique.js +209 -0
- package/dist/templates/worker-prompt.d.ts +3 -3
- package/dist/templates/worker-prompt.js +18 -18
- package/dist/tools.js +77 -413
- package/docs/codedna/generator-testing-summary.md +205 -0
- package/docs/codedna/radix-ui-generator.md +478 -0
- package/docs/kappa-gofiber-generator.md +274 -0
- package/docs/kappa-laravel-fixes.md +172 -0
- package/docs/kappa-mongoose-generator.md +322 -0
- package/docs/kappa-vitest-generator.md +337 -0
- package/package.json +1 -1
- package/dist/context/deduplication.test.d.ts +0 -6
- package/dist/context/deduplication.test.js +0 -84
|
@@ -0,0 +1,739 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Kappa v2.5 Vitest Test Generator
|
|
3
|
+
// =============================================================================
|
|
4
|
+
//
|
|
5
|
+
// Generates Vitest unit and integration tests from Kappa entity and API blocks.
|
|
6
|
+
// Includes test factories, mocks, and proper describe/it structure.
|
|
7
|
+
//
|
|
8
|
+
// =============================================================================
|
|
9
|
+
// Type Mappings
|
|
10
|
+
// =============================================================================
|
|
11
|
+
const PRIMITIVE_DEFAULT_VALUES = {
|
|
12
|
+
string: "'test-string-' + Math.random().toString(36).substring(7)",
|
|
13
|
+
int: 'Math.floor(Math.random() * 100)',
|
|
14
|
+
float: 'Math.random() * 100',
|
|
15
|
+
bool: 'true',
|
|
16
|
+
email: "'test-' + Math.random().toString(36).substring(7) + '@example.com'",
|
|
17
|
+
url: "'https://example.com/' + Math.random().toString(36).substring(7)",
|
|
18
|
+
uuid: "crypto.randomUUID ? crypto.randomUUID() : '123e4567-e89b-12d3-a456-426614174000'",
|
|
19
|
+
phone: "'+61400' + Math.floor(Math.random() * 1000000).toString().padStart(6, '0')",
|
|
20
|
+
slug: "'test-slug-' + Math.random().toString(36).substring(7)",
|
|
21
|
+
markdown: "'# Test Content\\n\\nGenerated at ' + new Date().toISOString()",
|
|
22
|
+
json: '{}',
|
|
23
|
+
timestamp: 'new Date()',
|
|
24
|
+
date: 'new Date()',
|
|
25
|
+
time: "new Date().toTimeString().split(' ')[0]",
|
|
26
|
+
duration: "'1h'",
|
|
27
|
+
};
|
|
28
|
+
const ASSERTION_MAPPING = {
|
|
29
|
+
equals: 'toBe',
|
|
30
|
+
notEquals: 'not.toBe',
|
|
31
|
+
contains: 'toContain',
|
|
32
|
+
matches: 'toMatch',
|
|
33
|
+
throws: 'toThrow',
|
|
34
|
+
resolves: 'resolves.toBe',
|
|
35
|
+
rejects: 'rejects.toThrow',
|
|
36
|
+
truthy: 'toBeTruthy',
|
|
37
|
+
falsy: 'toBeFalsy',
|
|
38
|
+
defined: 'toBeDefined',
|
|
39
|
+
undefined: 'toBeUndefined',
|
|
40
|
+
null: 'toBeNull',
|
|
41
|
+
instanceOf: 'toBeInstanceOf',
|
|
42
|
+
lengthOf: 'toHaveLength',
|
|
43
|
+
deepEquals: 'toEqual',
|
|
44
|
+
};
|
|
45
|
+
// =============================================================================
|
|
46
|
+
// Generator Class
|
|
47
|
+
// =============================================================================
|
|
48
|
+
export class KappaVitestGenerator {
|
|
49
|
+
provenance;
|
|
50
|
+
generateFactories;
|
|
51
|
+
generateMocks;
|
|
52
|
+
testType;
|
|
53
|
+
integration;
|
|
54
|
+
constructor(options = {}) {
|
|
55
|
+
this.provenance = options.provenance ?? true;
|
|
56
|
+
this.generateFactories = options.factories ?? true;
|
|
57
|
+
this.generateMocks = options.mocks ?? true;
|
|
58
|
+
this.testType = options.testType ?? 'unit';
|
|
59
|
+
this.integration = options.integration ?? false;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Generate tests from entity block
|
|
63
|
+
*/
|
|
64
|
+
generateEntityTests(entity) {
|
|
65
|
+
const tests = this.generateEntityTestFile(entity);
|
|
66
|
+
const result = { tests };
|
|
67
|
+
if (this.generateFactories) {
|
|
68
|
+
result.factories = this.generateFactoryFile(entity);
|
|
69
|
+
}
|
|
70
|
+
if (this.generateMocks) {
|
|
71
|
+
result.mocks = this.generateMockFile(entity);
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Generate tests from API block
|
|
77
|
+
*/
|
|
78
|
+
generateAPITests(api, entities) {
|
|
79
|
+
const tests = this.generateAPITestFile(api, entities);
|
|
80
|
+
const result = { tests };
|
|
81
|
+
if (this.generateMocks) {
|
|
82
|
+
result.mocks = this.generateAPIMockFile(api);
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Generate tests from TestBlock
|
|
88
|
+
*/
|
|
89
|
+
generateFromTestBlock(testBlock, entities) {
|
|
90
|
+
const tests = this.generateTestBlockFile(testBlock, entities);
|
|
91
|
+
return { tests };
|
|
92
|
+
}
|
|
93
|
+
// ===========================================================================
|
|
94
|
+
// Entity Test File Generation
|
|
95
|
+
// ===========================================================================
|
|
96
|
+
generateEntityTestFile(entity) {
|
|
97
|
+
const lines = [];
|
|
98
|
+
// Header comment
|
|
99
|
+
if (this.provenance) {
|
|
100
|
+
lines.push('// Generated by Kappa v2.5 CodeDNA');
|
|
101
|
+
lines.push(`// Entity: ${entity.name}`);
|
|
102
|
+
lines.push(`// Generated at: ${new Date().toISOString()}`);
|
|
103
|
+
lines.push('');
|
|
104
|
+
}
|
|
105
|
+
// Imports
|
|
106
|
+
lines.push(this.generateEntityTestImports(entity));
|
|
107
|
+
lines.push('');
|
|
108
|
+
// Test suites
|
|
109
|
+
lines.push(this.generateEntityValidationTests(entity));
|
|
110
|
+
lines.push('');
|
|
111
|
+
lines.push(this.generateEntityCRUDTests(entity));
|
|
112
|
+
lines.push('');
|
|
113
|
+
if (entity.relationships.length > 0) {
|
|
114
|
+
lines.push(this.generateEntityRelationshipTests(entity));
|
|
115
|
+
lines.push('');
|
|
116
|
+
}
|
|
117
|
+
if (this.integration) {
|
|
118
|
+
lines.push(this.generateEntityIntegrationTests(entity));
|
|
119
|
+
lines.push('');
|
|
120
|
+
}
|
|
121
|
+
return lines.join('\n');
|
|
122
|
+
}
|
|
123
|
+
generateEntityTestImports(entity) {
|
|
124
|
+
const lines = [];
|
|
125
|
+
const entityName = this.toSnakeCase(entity.name);
|
|
126
|
+
lines.push("import { describe, it, expect, beforeEach, afterEach } from 'vitest';");
|
|
127
|
+
if (this.generateFactories) {
|
|
128
|
+
lines.push(`import { create${entity.name}Factory } from './${entityName}.factory';`);
|
|
129
|
+
}
|
|
130
|
+
if (this.generateMocks) {
|
|
131
|
+
lines.push(`import { mock${entity.name}Repository } from './${entityName}.mock';`);
|
|
132
|
+
}
|
|
133
|
+
return lines.join('\n');
|
|
134
|
+
}
|
|
135
|
+
generateEntityValidationTests(entity) {
|
|
136
|
+
const lines = [];
|
|
137
|
+
lines.push(`describe('${entity.name} Validation', () => {`);
|
|
138
|
+
// Generate validation tests for each field
|
|
139
|
+
for (const field of entity.fields) {
|
|
140
|
+
// Skip auto-generated and optional fields
|
|
141
|
+
if (field.modifiers.includes('optional') || field.modifiers.includes('auto'))
|
|
142
|
+
continue;
|
|
143
|
+
lines.push(` it('should require ${field.name}', () => {`);
|
|
144
|
+
lines.push(` const valid = create${entity.name}Factory();`);
|
|
145
|
+
lines.push(` const invalid = { ...valid, ${field.name}: undefined };`);
|
|
146
|
+
lines.push(` expect(() => validate${entity.name}(invalid)).toThrow();`);
|
|
147
|
+
lines.push(` });`);
|
|
148
|
+
lines.push('');
|
|
149
|
+
// Type-specific validation
|
|
150
|
+
if (field.type.kind === 'primitive') {
|
|
151
|
+
if (field.type.type === 'email') {
|
|
152
|
+
lines.push(` it('should validate ${field.name} email format', () => {`);
|
|
153
|
+
lines.push(` const valid = create${entity.name}Factory();`);
|
|
154
|
+
lines.push(` const invalid = { ...valid, ${field.name}: 'not-an-email' };`);
|
|
155
|
+
lines.push(` expect(() => validate${entity.name}(invalid)).toThrow('Invalid email');`);
|
|
156
|
+
lines.push(` });`);
|
|
157
|
+
lines.push('');
|
|
158
|
+
}
|
|
159
|
+
if (field.type.type === 'url') {
|
|
160
|
+
lines.push(` it('should validate ${field.name} URL format', () => {`);
|
|
161
|
+
lines.push(` const valid = create${entity.name}Factory();`);
|
|
162
|
+
lines.push(` const invalid = { ...valid, ${field.name}: 'not-a-url' };`);
|
|
163
|
+
lines.push(` expect(() => validate${entity.name}(invalid)).toThrow('Invalid URL');`);
|
|
164
|
+
lines.push(` });`);
|
|
165
|
+
lines.push('');
|
|
166
|
+
}
|
|
167
|
+
if (field.type.range) {
|
|
168
|
+
if (field.type.range.min !== undefined) {
|
|
169
|
+
// String length vs numeric value
|
|
170
|
+
const isStringType = field.type.type === 'string';
|
|
171
|
+
const invalidValue = isStringType
|
|
172
|
+
? `'${'x'.repeat(field.type.range.min - 1)}'`
|
|
173
|
+
: `${field.type.range.min - 1}`;
|
|
174
|
+
lines.push(` it('should enforce minimum ${field.type.range.min} for ${field.name}', () => {`);
|
|
175
|
+
lines.push(` const valid = create${entity.name}Factory();`);
|
|
176
|
+
lines.push(` const invalid = { ...valid, ${field.name}: ${invalidValue} };`);
|
|
177
|
+
lines.push(` expect(() => validate${entity.name}(invalid)).toThrow();`);
|
|
178
|
+
lines.push(` });`);
|
|
179
|
+
lines.push('');
|
|
180
|
+
}
|
|
181
|
+
if (field.type.range.max !== undefined) {
|
|
182
|
+
// String length vs numeric value
|
|
183
|
+
const isStringType = field.type.type === 'string';
|
|
184
|
+
const invalidValue = isStringType
|
|
185
|
+
? `'${'x'.repeat(field.type.range.max + 1)}'`
|
|
186
|
+
: `${field.type.range.max + 1}`;
|
|
187
|
+
lines.push(` it('should enforce maximum ${field.type.range.max} for ${field.name}', () => {`);
|
|
188
|
+
lines.push(` const valid = create${entity.name}Factory();`);
|
|
189
|
+
lines.push(` const invalid = { ...valid, ${field.name}: ${invalidValue} };`);
|
|
190
|
+
lines.push(` expect(() => validate${entity.name}(invalid)).toThrow();`);
|
|
191
|
+
lines.push(` });`);
|
|
192
|
+
lines.push('');
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (field.type.kind === 'enum') {
|
|
197
|
+
lines.push(` it('should validate ${field.name} enum values', () => {`);
|
|
198
|
+
lines.push(` const valid = create${entity.name}Factory();`);
|
|
199
|
+
lines.push(` const invalid = { ...valid, ${field.name}: 'INVALID_VALUE' };`);
|
|
200
|
+
lines.push(` expect(() => validate${entity.name}(invalid)).toThrow();`);
|
|
201
|
+
lines.push(` });`);
|
|
202
|
+
lines.push('');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
lines.push('});');
|
|
206
|
+
return lines.join('\n');
|
|
207
|
+
}
|
|
208
|
+
generateEntityCRUDTests(entity) {
|
|
209
|
+
const lines = [];
|
|
210
|
+
const entityName = entity.name;
|
|
211
|
+
const varName = this.toLowerCamelCase(entityName);
|
|
212
|
+
lines.push(`describe('${entityName} CRUD Operations', () => {`);
|
|
213
|
+
lines.push(` let repository: ReturnType<typeof mock${entityName}Repository>;`);
|
|
214
|
+
lines.push('');
|
|
215
|
+
lines.push(' beforeEach(() => {');
|
|
216
|
+
lines.push(` repository = mock${entityName}Repository();`);
|
|
217
|
+
lines.push(' });');
|
|
218
|
+
lines.push('');
|
|
219
|
+
// Create test
|
|
220
|
+
lines.push(` it('should create a new ${entityName}', async () => {`);
|
|
221
|
+
lines.push(` const new${entityName} = create${entityName}Factory();`);
|
|
222
|
+
lines.push(` const created = await repository.create(new${entityName});`);
|
|
223
|
+
lines.push('');
|
|
224
|
+
lines.push(` expect(created).toBeDefined();`);
|
|
225
|
+
lines.push(` expect(created.id).toBeDefined();`);
|
|
226
|
+
// Check a few key fields (skip auto-generated fields)
|
|
227
|
+
const keyFields = entity.fields
|
|
228
|
+
.filter(f => !f.modifiers.includes('auto'))
|
|
229
|
+
.slice(0, 2);
|
|
230
|
+
for (const field of keyFields) {
|
|
231
|
+
lines.push(` expect(created.${field.name}).toBe(new${entityName}.${field.name});`);
|
|
232
|
+
}
|
|
233
|
+
lines.push(` });`);
|
|
234
|
+
lines.push('');
|
|
235
|
+
// Read test
|
|
236
|
+
lines.push(` it('should read an existing ${entityName}', async () => {`);
|
|
237
|
+
lines.push(` const ${varName} = create${entityName}Factory();`);
|
|
238
|
+
lines.push(` const created = await repository.create(${varName});`);
|
|
239
|
+
lines.push(` const found = await repository.findById(created.id);`);
|
|
240
|
+
lines.push('');
|
|
241
|
+
lines.push(` expect(found).toBeDefined();`);
|
|
242
|
+
lines.push(` expect(found?.id).toBe(created.id);`);
|
|
243
|
+
lines.push(` });`);
|
|
244
|
+
lines.push('');
|
|
245
|
+
// Update test
|
|
246
|
+
lines.push(` it('should update an existing ${entityName}', async () => {`);
|
|
247
|
+
lines.push(` const ${varName} = create${entityName}Factory();`);
|
|
248
|
+
lines.push(` const created = await repository.create(${varName});`);
|
|
249
|
+
lines.push('');
|
|
250
|
+
// Find a mutable field to update (skip primary, auto, immutable)
|
|
251
|
+
const mutableField = entity.fields.find(f => !f.modifiers.includes('immutable') &&
|
|
252
|
+
!f.modifiers.includes('primary') &&
|
|
253
|
+
!f.modifiers.includes('auto'));
|
|
254
|
+
if (mutableField) {
|
|
255
|
+
const updateValue = this.generateTestValue(mutableField.type, true);
|
|
256
|
+
lines.push(` const updated = await repository.update(created.id, {`);
|
|
257
|
+
lines.push(` ${mutableField.name}: ${updateValue},`);
|
|
258
|
+
lines.push(` });`);
|
|
259
|
+
lines.push('');
|
|
260
|
+
lines.push(` expect(updated.${mutableField.name}).toBe(${updateValue});`);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
// If no mutable fields, just verify update doesn't throw
|
|
264
|
+
lines.push(` await expect(repository.update(created.id, {})).resolves.toBeDefined();`);
|
|
265
|
+
}
|
|
266
|
+
lines.push(` });`);
|
|
267
|
+
lines.push('');
|
|
268
|
+
// Delete test
|
|
269
|
+
lines.push(` it('should delete an existing ${entityName}', async () => {`);
|
|
270
|
+
lines.push(` const ${varName} = create${entityName}Factory();`);
|
|
271
|
+
lines.push(` const created = await repository.create(${varName});`);
|
|
272
|
+
lines.push(` await repository.delete(created.id);`);
|
|
273
|
+
lines.push('');
|
|
274
|
+
lines.push(` const found = await repository.findById(created.id);`);
|
|
275
|
+
lines.push(` expect(found).toBeUndefined();`);
|
|
276
|
+
lines.push(` });`);
|
|
277
|
+
lines.push('');
|
|
278
|
+
// List test
|
|
279
|
+
lines.push(` it('should list all ${entityName} records', async () => {`);
|
|
280
|
+
lines.push(` const ${varName}1 = create${entityName}Factory();`);
|
|
281
|
+
lines.push(` const ${varName}2 = create${entityName}Factory();`);
|
|
282
|
+
lines.push(` await repository.create(${varName}1);`);
|
|
283
|
+
lines.push(` await repository.create(${varName}2);`);
|
|
284
|
+
lines.push('');
|
|
285
|
+
lines.push(` const list = await repository.findAll();`);
|
|
286
|
+
lines.push(` expect(list.length).toBeGreaterThanOrEqual(2);`);
|
|
287
|
+
lines.push(` });`);
|
|
288
|
+
lines.push('');
|
|
289
|
+
lines.push('});');
|
|
290
|
+
return lines.join('\n');
|
|
291
|
+
}
|
|
292
|
+
generateEntityRelationshipTests(entity) {
|
|
293
|
+
const lines = [];
|
|
294
|
+
lines.push(`describe('${entity.name} Relationships', () => {`);
|
|
295
|
+
for (const rel of entity.relationships) {
|
|
296
|
+
const relName = rel.as || rel.entity;
|
|
297
|
+
lines.push(` it('should load ${relName} relationship', async () => {`);
|
|
298
|
+
lines.push(` const ${this.toLowerCamelCase(entity.name)} = create${entity.name}Factory();`);
|
|
299
|
+
lines.push(` const created = await repository.create(${this.toLowerCamelCase(entity.name)});`);
|
|
300
|
+
lines.push('');
|
|
301
|
+
lines.push(` const with${relName} = await repository.findById(created.id, {`);
|
|
302
|
+
lines.push(` include: ['${relName}']`);
|
|
303
|
+
lines.push(` });`);
|
|
304
|
+
lines.push('');
|
|
305
|
+
lines.push(` expect(with${relName}.${relName}).toBeDefined();`);
|
|
306
|
+
lines.push(` });`);
|
|
307
|
+
lines.push('');
|
|
308
|
+
}
|
|
309
|
+
lines.push('});');
|
|
310
|
+
return lines.join('\n');
|
|
311
|
+
}
|
|
312
|
+
generateEntityIntegrationTests(entity) {
|
|
313
|
+
const lines = [];
|
|
314
|
+
lines.push(`describe('${entity.name} Integration Tests', () => {`);
|
|
315
|
+
lines.push(` it('should persist to database', async () => {`);
|
|
316
|
+
lines.push(` const ${this.toLowerCamelCase(entity.name)} = create${entity.name}Factory();`);
|
|
317
|
+
lines.push(` const created = await db.${this.toSnakeCase(entity.name)}.create(${this.toLowerCamelCase(entity.name)});`);
|
|
318
|
+
lines.push('');
|
|
319
|
+
lines.push(` const found = await db.${this.toSnakeCase(entity.name)}.findUnique({`);
|
|
320
|
+
lines.push(` where: { id: created.id }`);
|
|
321
|
+
lines.push(` });`);
|
|
322
|
+
lines.push('');
|
|
323
|
+
lines.push(` expect(found).toBeDefined();`);
|
|
324
|
+
lines.push(` expect(found?.id).toBe(created.id);`);
|
|
325
|
+
lines.push(` });`);
|
|
326
|
+
lines.push('});');
|
|
327
|
+
return lines.join('\n');
|
|
328
|
+
}
|
|
329
|
+
// ===========================================================================
|
|
330
|
+
// API Test File Generation
|
|
331
|
+
// ===========================================================================
|
|
332
|
+
generateAPITestFile(api, entities) {
|
|
333
|
+
const lines = [];
|
|
334
|
+
// Header comment
|
|
335
|
+
if (this.provenance) {
|
|
336
|
+
lines.push('// Generated by Kappa v2.5 CodeDNA');
|
|
337
|
+
lines.push(`// API: ${api.name}`);
|
|
338
|
+
lines.push(`// Generated at: ${new Date().toISOString()}`);
|
|
339
|
+
lines.push('');
|
|
340
|
+
}
|
|
341
|
+
// Imports
|
|
342
|
+
lines.push(this.generateAPITestImports(api));
|
|
343
|
+
lines.push('');
|
|
344
|
+
// Test suites for each operation
|
|
345
|
+
for (const operation of api.operations) {
|
|
346
|
+
lines.push(this.generateAPIOperationTests(operation, api.name));
|
|
347
|
+
lines.push('');
|
|
348
|
+
}
|
|
349
|
+
return lines.join('\n');
|
|
350
|
+
}
|
|
351
|
+
generateAPITestImports(api) {
|
|
352
|
+
const lines = [];
|
|
353
|
+
lines.push("import { describe, it, expect, beforeEach, vi } from 'vitest';");
|
|
354
|
+
if (this.generateMocks) {
|
|
355
|
+
lines.push(`import { mockAPIClient } from './api.mock';`);
|
|
356
|
+
}
|
|
357
|
+
return lines.join('\n');
|
|
358
|
+
}
|
|
359
|
+
generateAPIOperationTests(operation, apiName) {
|
|
360
|
+
const lines = [];
|
|
361
|
+
lines.push(`describe('${apiName}.${operation.name}', () => {`);
|
|
362
|
+
lines.push(' let apiClient: ReturnType<typeof mockAPIClient>;');
|
|
363
|
+
lines.push('');
|
|
364
|
+
lines.push(' beforeEach(() => {');
|
|
365
|
+
lines.push(' apiClient = mockAPIClient();');
|
|
366
|
+
lines.push(' });');
|
|
367
|
+
lines.push('');
|
|
368
|
+
// Success test
|
|
369
|
+
lines.push(' it("should successfully execute", async () => {');
|
|
370
|
+
// Generate test parameters
|
|
371
|
+
const params = [];
|
|
372
|
+
for (const param of operation.parameters) {
|
|
373
|
+
const value = this.generateTestValue(param.type);
|
|
374
|
+
params.push(`${param.name}: ${value}`);
|
|
375
|
+
}
|
|
376
|
+
if (params.length > 0) {
|
|
377
|
+
lines.push(` const params = { ${params.join(', ')} };`);
|
|
378
|
+
lines.push(` const result = await apiClient.${operation.name}(params);`);
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
lines.push(` const result = await apiClient.${operation.name}();`);
|
|
382
|
+
}
|
|
383
|
+
lines.push('');
|
|
384
|
+
lines.push(' expect(result).toBeDefined();');
|
|
385
|
+
if (operation.returnType.isArray) {
|
|
386
|
+
lines.push(' expect(Array.isArray(result)).toBe(true);');
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
lines.push(` expect(result).toHaveProperty('id');`);
|
|
390
|
+
}
|
|
391
|
+
lines.push(' });');
|
|
392
|
+
lines.push('');
|
|
393
|
+
// Error tests
|
|
394
|
+
if (operation.throws && operation.throws.length > 0) {
|
|
395
|
+
for (const errorType of operation.throws) {
|
|
396
|
+
lines.push(` it('should handle ${errorType}', async () => {`);
|
|
397
|
+
lines.push(` apiClient.${operation.name}.mockRejectedValueOnce(new Error('${errorType}'));`);
|
|
398
|
+
lines.push('');
|
|
399
|
+
lines.push(` await expect(apiClient.${operation.name}()).rejects.toThrow('${errorType}');`);
|
|
400
|
+
lines.push(' });');
|
|
401
|
+
lines.push('');
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
// Rate limit test
|
|
405
|
+
if (operation.rateLimit) {
|
|
406
|
+
lines.push(' it("should respect rate limits", async () => {');
|
|
407
|
+
lines.push(` // Rate limit: ${operation.rateLimit}`);
|
|
408
|
+
lines.push(' // Test implementation depends on rate limiting strategy');
|
|
409
|
+
lines.push(' });');
|
|
410
|
+
lines.push('');
|
|
411
|
+
}
|
|
412
|
+
lines.push('});');
|
|
413
|
+
return lines.join('\n');
|
|
414
|
+
}
|
|
415
|
+
// ===========================================================================
|
|
416
|
+
// TestBlock Generation
|
|
417
|
+
// ===========================================================================
|
|
418
|
+
generateTestBlockFile(testBlock, entities) {
|
|
419
|
+
const lines = [];
|
|
420
|
+
// Header comment
|
|
421
|
+
if (this.provenance) {
|
|
422
|
+
lines.push('// Generated by Kappa v2.5 CodeDNA');
|
|
423
|
+
lines.push(`// Test: ${testBlock.name}`);
|
|
424
|
+
lines.push(`// Target: ${testBlock.target}`);
|
|
425
|
+
lines.push(`// Type: ${testBlock.type}`);
|
|
426
|
+
lines.push(`// Generated at: ${new Date().toISOString()}`);
|
|
427
|
+
lines.push('');
|
|
428
|
+
}
|
|
429
|
+
// Imports
|
|
430
|
+
lines.push("import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach } from 'vitest';");
|
|
431
|
+
lines.push('');
|
|
432
|
+
// Generate test suite
|
|
433
|
+
lines.push(this.generateTestSuite(testBlock.suite, 0));
|
|
434
|
+
return lines.join('\n');
|
|
435
|
+
}
|
|
436
|
+
generateTestSuite(suite, depth) {
|
|
437
|
+
const lines = [];
|
|
438
|
+
const indent = ' '.repeat(depth);
|
|
439
|
+
lines.push(`${indent}describe('${suite.name}', () => {`);
|
|
440
|
+
// Setup hooks
|
|
441
|
+
if (suite.beforeAll && suite.beforeAll.length > 0) {
|
|
442
|
+
lines.push(`${indent} beforeAll(() => {`);
|
|
443
|
+
for (const stmt of suite.beforeAll) {
|
|
444
|
+
lines.push(`${indent} ${stmt};`);
|
|
445
|
+
}
|
|
446
|
+
lines.push(`${indent} });`);
|
|
447
|
+
lines.push('');
|
|
448
|
+
}
|
|
449
|
+
if (suite.afterAll && suite.afterAll.length > 0) {
|
|
450
|
+
lines.push(`${indent} afterAll(() => {`);
|
|
451
|
+
for (const stmt of suite.afterAll) {
|
|
452
|
+
lines.push(`${indent} ${stmt};`);
|
|
453
|
+
}
|
|
454
|
+
lines.push(`${indent} });`);
|
|
455
|
+
lines.push('');
|
|
456
|
+
}
|
|
457
|
+
if (suite.beforeEach && suite.beforeEach.length > 0) {
|
|
458
|
+
lines.push(`${indent} beforeEach(() => {`);
|
|
459
|
+
for (const stmt of suite.beforeEach) {
|
|
460
|
+
lines.push(`${indent} ${stmt};`);
|
|
461
|
+
}
|
|
462
|
+
lines.push(`${indent} });`);
|
|
463
|
+
lines.push('');
|
|
464
|
+
}
|
|
465
|
+
if (suite.afterEach && suite.afterEach.length > 0) {
|
|
466
|
+
lines.push(`${indent} afterEach(() => {`);
|
|
467
|
+
for (const stmt of suite.afterEach) {
|
|
468
|
+
lines.push(`${indent} ${stmt};`);
|
|
469
|
+
}
|
|
470
|
+
lines.push(`${indent} });`);
|
|
471
|
+
lines.push('');
|
|
472
|
+
}
|
|
473
|
+
// Mocks
|
|
474
|
+
for (const mock of suite.mocks) {
|
|
475
|
+
lines.push(this.generateMock(mock, depth + 1));
|
|
476
|
+
lines.push('');
|
|
477
|
+
}
|
|
478
|
+
// Test cases
|
|
479
|
+
for (const testCase of suite.tests) {
|
|
480
|
+
lines.push(this.generateTestCase(testCase, depth + 1));
|
|
481
|
+
lines.push('');
|
|
482
|
+
}
|
|
483
|
+
// Nested suites
|
|
484
|
+
for (const nestedSuite of suite.suites) {
|
|
485
|
+
lines.push(this.generateTestSuite(nestedSuite, depth + 1));
|
|
486
|
+
lines.push('');
|
|
487
|
+
}
|
|
488
|
+
lines.push(`${indent}});`);
|
|
489
|
+
return lines.join('\n');
|
|
490
|
+
}
|
|
491
|
+
generateTestCase(testCase, depth) {
|
|
492
|
+
const lines = [];
|
|
493
|
+
const indent = ' '.repeat(depth);
|
|
494
|
+
let testFn = 'it';
|
|
495
|
+
if (testCase.skip)
|
|
496
|
+
testFn = 'it.skip';
|
|
497
|
+
if (testCase.only)
|
|
498
|
+
testFn = 'it.only';
|
|
499
|
+
if (testCase.todo)
|
|
500
|
+
testFn = 'it.todo';
|
|
501
|
+
const timeout = testCase.timeout ? `, ${testCase.timeout}` : '';
|
|
502
|
+
lines.push(`${indent}${testFn}('${testCase.name}', async () => {${timeout}`);
|
|
503
|
+
// Setup
|
|
504
|
+
if (testCase.setup && testCase.setup.length > 0) {
|
|
505
|
+
for (const stmt of testCase.setup) {
|
|
506
|
+
lines.push(`${indent} ${stmt};`);
|
|
507
|
+
}
|
|
508
|
+
lines.push('');
|
|
509
|
+
}
|
|
510
|
+
// Action
|
|
511
|
+
lines.push(`${indent} ${testCase.action};`);
|
|
512
|
+
lines.push('');
|
|
513
|
+
// Assertions
|
|
514
|
+
for (const assertion of testCase.assertions) {
|
|
515
|
+
lines.push(this.generateAssertion(assertion, depth + 1));
|
|
516
|
+
}
|
|
517
|
+
// Teardown
|
|
518
|
+
if (testCase.teardown && testCase.teardown.length > 0) {
|
|
519
|
+
lines.push('');
|
|
520
|
+
for (const stmt of testCase.teardown) {
|
|
521
|
+
lines.push(`${indent} ${stmt};`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
lines.push(`${indent}});`);
|
|
525
|
+
return lines.join('\n');
|
|
526
|
+
}
|
|
527
|
+
generateAssertion(assertion, depth) {
|
|
528
|
+
const indent = ' '.repeat(depth);
|
|
529
|
+
const matcher = ASSERTION_MAPPING[assertion.type];
|
|
530
|
+
if (assertion.expected !== undefined) {
|
|
531
|
+
return `${indent}expect(${assertion.actual}).${matcher}(${assertion.expected});`;
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
return `${indent}expect(${assertion.actual}).${matcher}();`;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
generateMock(mock, depth) {
|
|
538
|
+
const indent = ' '.repeat(depth);
|
|
539
|
+
if (mock.implementation) {
|
|
540
|
+
return `${indent}vi.mock('${mock.target}', () => (${mock.implementation}));`;
|
|
541
|
+
}
|
|
542
|
+
else if (mock.returns) {
|
|
543
|
+
return `${indent}vi.mock('${mock.target}', () => ({ default: vi.fn(() => ${mock.returns}) }));`;
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
return `${indent}vi.mock('${mock.target}');`;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
// ===========================================================================
|
|
550
|
+
// Factory File Generation
|
|
551
|
+
// ===========================================================================
|
|
552
|
+
generateFactoryFile(entity) {
|
|
553
|
+
const lines = [];
|
|
554
|
+
if (this.provenance) {
|
|
555
|
+
lines.push('// Generated by Kappa v2.5 CodeDNA');
|
|
556
|
+
lines.push(`// Test Factory: ${entity.name}`);
|
|
557
|
+
lines.push(`// Generated at: ${new Date().toISOString()}`);
|
|
558
|
+
lines.push('');
|
|
559
|
+
}
|
|
560
|
+
lines.push(`export function create${entity.name}Factory(overrides = {}) {`);
|
|
561
|
+
lines.push(' return {');
|
|
562
|
+
for (const field of entity.fields) {
|
|
563
|
+
if (field.modifiers.includes('auto'))
|
|
564
|
+
continue; // Skip auto-generated fields
|
|
565
|
+
const value = this.generateTestValue(field.type);
|
|
566
|
+
lines.push(` ${field.name}: ${value},`);
|
|
567
|
+
}
|
|
568
|
+
lines.push(' ...overrides,');
|
|
569
|
+
lines.push(' };');
|
|
570
|
+
lines.push('}');
|
|
571
|
+
return lines.join('\n');
|
|
572
|
+
}
|
|
573
|
+
// ===========================================================================
|
|
574
|
+
// Mock File Generation
|
|
575
|
+
// ===========================================================================
|
|
576
|
+
generateMockFile(entity) {
|
|
577
|
+
const lines = [];
|
|
578
|
+
if (this.provenance) {
|
|
579
|
+
lines.push('// Generated by Kappa v2.5 CodeDNA');
|
|
580
|
+
lines.push(`// Mock Repository: ${entity.name}`);
|
|
581
|
+
lines.push(`// Generated at: ${new Date().toISOString()}`);
|
|
582
|
+
lines.push('');
|
|
583
|
+
}
|
|
584
|
+
lines.push("import { vi } from 'vitest';");
|
|
585
|
+
lines.push(`import { create${entity.name}Factory } from './${this.toSnakeCase(entity.name)}.factory';`);
|
|
586
|
+
lines.push('');
|
|
587
|
+
lines.push(`export function mock${entity.name}Repository() {`);
|
|
588
|
+
lines.push(` const store = new Map();`);
|
|
589
|
+
lines.push('');
|
|
590
|
+
lines.push(' return {');
|
|
591
|
+
lines.push(' create: vi.fn(async (data) => {');
|
|
592
|
+
lines.push(" const id = Math.random().toString(36).substring(7);");
|
|
593
|
+
lines.push(' const record = { ...data, id, createdAt: new Date(), updatedAt: new Date() };');
|
|
594
|
+
lines.push(' store.set(id, record);');
|
|
595
|
+
lines.push(' return record;');
|
|
596
|
+
lines.push(' }),');
|
|
597
|
+
lines.push('');
|
|
598
|
+
lines.push(' findById: vi.fn(async (id) => {');
|
|
599
|
+
lines.push(' return store.get(id);');
|
|
600
|
+
lines.push(' }),');
|
|
601
|
+
lines.push('');
|
|
602
|
+
lines.push(' findAll: vi.fn(async () => {');
|
|
603
|
+
lines.push(' return Array.from(store.values());');
|
|
604
|
+
lines.push(' }),');
|
|
605
|
+
lines.push('');
|
|
606
|
+
lines.push(' update: vi.fn(async (id, data) => {');
|
|
607
|
+
lines.push(' const existing = store.get(id);');
|
|
608
|
+
lines.push(' if (!existing) throw new Error("Not found");');
|
|
609
|
+
lines.push(' const updated = { ...existing, ...data, updatedAt: new Date() };');
|
|
610
|
+
lines.push(' store.set(id, updated);');
|
|
611
|
+
lines.push(' return updated;');
|
|
612
|
+
lines.push(' }),');
|
|
613
|
+
lines.push('');
|
|
614
|
+
lines.push(' delete: vi.fn(async (id) => {');
|
|
615
|
+
lines.push(' store.delete(id);');
|
|
616
|
+
lines.push(' }),');
|
|
617
|
+
lines.push(' };');
|
|
618
|
+
lines.push('}');
|
|
619
|
+
return lines.join('\n');
|
|
620
|
+
}
|
|
621
|
+
generateAPIMockFile(api) {
|
|
622
|
+
const lines = [];
|
|
623
|
+
if (this.provenance) {
|
|
624
|
+
lines.push('// Generated by Kappa v2.5 CodeDNA');
|
|
625
|
+
lines.push(`// Mock API Client: ${api.name}`);
|
|
626
|
+
lines.push(`// Generated at: ${new Date().toISOString()}`);
|
|
627
|
+
lines.push('');
|
|
628
|
+
}
|
|
629
|
+
lines.push("import { vi } from 'vitest';");
|
|
630
|
+
lines.push('');
|
|
631
|
+
lines.push('export function mockAPIClient() {');
|
|
632
|
+
lines.push(' return {');
|
|
633
|
+
for (const operation of api.operations) {
|
|
634
|
+
lines.push(` ${operation.name}: vi.fn(),`);
|
|
635
|
+
}
|
|
636
|
+
lines.push(' };');
|
|
637
|
+
lines.push('}');
|
|
638
|
+
return lines.join('\n');
|
|
639
|
+
}
|
|
640
|
+
// ===========================================================================
|
|
641
|
+
// Utilities
|
|
642
|
+
// ===========================================================================
|
|
643
|
+
generateTestValue(type, alternate = false) {
|
|
644
|
+
switch (type.kind) {
|
|
645
|
+
case 'primitive':
|
|
646
|
+
if (alternate && type.type === 'string')
|
|
647
|
+
return "'updated-value'";
|
|
648
|
+
if (alternate && type.type === 'int')
|
|
649
|
+
return '99';
|
|
650
|
+
return PRIMITIVE_DEFAULT_VALUES[type.type];
|
|
651
|
+
case 'enum':
|
|
652
|
+
return `'${type.values[alternate ? 1 : 0]}'`;
|
|
653
|
+
case 'reference':
|
|
654
|
+
return "'ref-id-123'";
|
|
655
|
+
case 'array':
|
|
656
|
+
return '[]';
|
|
657
|
+
default:
|
|
658
|
+
return 'null';
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
toSnakeCase(str) {
|
|
662
|
+
return str
|
|
663
|
+
.replace(/([A-Z])/g, '_$1')
|
|
664
|
+
.toLowerCase()
|
|
665
|
+
.replace(/^_/, '');
|
|
666
|
+
}
|
|
667
|
+
toLowerCamelCase(str) {
|
|
668
|
+
return str.charAt(0).toLowerCase() + str.slice(1);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
// =============================================================================
|
|
672
|
+
// Convenience Functions
|
|
673
|
+
// =============================================================================
|
|
674
|
+
/**
|
|
675
|
+
* Generate Vitest tests from entity
|
|
676
|
+
*/
|
|
677
|
+
export function generateEntityTests(entity, options = {}) {
|
|
678
|
+
const generator = new KappaVitestGenerator(options);
|
|
679
|
+
return generator.generateEntityTests(entity);
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Generate Vitest tests from API
|
|
683
|
+
*/
|
|
684
|
+
export function generateAPITests(api, entities, options = {}) {
|
|
685
|
+
const generator = new KappaVitestGenerator(options);
|
|
686
|
+
return generator.generateAPITests(api, entities);
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Generate Vitest tests from TestBlock
|
|
690
|
+
*/
|
|
691
|
+
export function generateTestsFromBlock(testBlock, entities, options = {}) {
|
|
692
|
+
const generator = new KappaVitestGenerator(options);
|
|
693
|
+
return generator.generateFromTestBlock(testBlock, entities);
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Generate tests for all entities and APIs (orchestrator function)
|
|
697
|
+
*/
|
|
698
|
+
export function generateTests(entities, apis, options = {}) {
|
|
699
|
+
const generator = new KappaVitestGenerator(options);
|
|
700
|
+
const testFiles = [];
|
|
701
|
+
// Generate entity tests
|
|
702
|
+
for (const entity of entities) {
|
|
703
|
+
const result = generator.generateEntityTests(entity);
|
|
704
|
+
const entityName = entity.name.toLowerCase();
|
|
705
|
+
testFiles.push({
|
|
706
|
+
path: `__tests__/${entityName}.test.ts`,
|
|
707
|
+
content: result.tests,
|
|
708
|
+
});
|
|
709
|
+
// Collect factories and mocks (combine all entities into single files)
|
|
710
|
+
if (result.factories) {
|
|
711
|
+
testFiles.push({
|
|
712
|
+
path: `__tests__/${entityName}.factory.ts`,
|
|
713
|
+
content: result.factories,
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
if (result.mocks) {
|
|
717
|
+
testFiles.push({
|
|
718
|
+
path: `__tests__/${entityName}.mock.ts`,
|
|
719
|
+
content: result.mocks,
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
// Generate API tests
|
|
724
|
+
for (const api of apis) {
|
|
725
|
+
const result = generator.generateAPITests(api, entities);
|
|
726
|
+
const apiName = api.name.toLowerCase();
|
|
727
|
+
testFiles.push({
|
|
728
|
+
path: `__tests__/${apiName}.test.ts`,
|
|
729
|
+
content: result.tests,
|
|
730
|
+
});
|
|
731
|
+
if (result.mocks) {
|
|
732
|
+
testFiles.push({
|
|
733
|
+
path: `__tests__/${apiName}.mock.ts`,
|
|
734
|
+
content: result.mocks,
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
return { testFiles };
|
|
739
|
+
}
|