@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.
Files changed (85) hide show
  1. package/dist/cli.js +9 -1
  2. package/dist/codedna/__tests__/examples/mongoose-example.d.ts +6 -0
  3. package/dist/codedna/__tests__/examples/mongoose-example.js +163 -0
  4. package/dist/codedna/__tests__/fixtures/typeorm-production-test.d.ts +1 -0
  5. package/dist/codedna/__tests__/fixtures/typeorm-production-test.js +231 -0
  6. package/dist/codedna/__tests__/fixtures/typeorm-test.d.ts +1 -0
  7. package/dist/codedna/__tests__/fixtures/typeorm-test.js +124 -0
  8. package/dist/codedna/__tests__/laravel-output-review.d.ts +1 -0
  9. package/dist/codedna/__tests__/laravel-output-review.js +249 -0
  10. package/dist/codedna/__tests__/mongoose-output-test.d.ts +1 -0
  11. package/dist/codedna/__tests__/mongoose-output-test.js +178 -0
  12. package/dist/codedna/examples/radix-example.d.ts +2 -0
  13. package/dist/codedna/examples/radix-example.js +259 -0
  14. package/dist/codedna/index.d.ts +5 -3
  15. package/dist/codedna/index.js +6 -3
  16. package/dist/codedna/kappa-ast.d.ts +143 -5
  17. package/dist/codedna/kappa-drizzle-generator.js +8 -5
  18. package/dist/codedna/kappa-gofiber-generator.d.ts +65 -0
  19. package/dist/codedna/kappa-gofiber-generator.js +587 -0
  20. package/dist/codedna/kappa-laravel-generator.d.ts +68 -0
  21. package/dist/codedna/kappa-laravel-generator.js +741 -0
  22. package/dist/codedna/kappa-lexer.d.ts +44 -0
  23. package/dist/codedna/kappa-lexer.js +124 -0
  24. package/dist/codedna/kappa-mantine-generator.d.ts +65 -0
  25. package/dist/codedna/kappa-mantine-generator.js +518 -0
  26. package/dist/codedna/kappa-mongoose-generator.d.ts +44 -0
  27. package/dist/codedna/kappa-mongoose-generator.js +442 -0
  28. package/dist/codedna/kappa-parser.d.ts +43 -1
  29. package/dist/codedna/kappa-parser.js +601 -0
  30. package/dist/codedna/kappa-radix-generator.d.ts +61 -0
  31. package/dist/codedna/kappa-radix-generator.js +566 -0
  32. package/dist/codedna/kappa-typeorm-generator.d.ts +59 -0
  33. package/dist/codedna/kappa-typeorm-generator.js +723 -0
  34. package/dist/codedna/kappa-vitest-generator.d.ts +85 -0
  35. package/dist/codedna/kappa-vitest-generator.js +739 -0
  36. package/dist/codedna/parser.js +26 -1
  37. package/dist/codegen/cloud-client.d.ts +160 -0
  38. package/dist/codegen/cloud-client.js +195 -0
  39. package/dist/codegen/codegen-tool.d.ts +35 -0
  40. package/dist/codegen/codegen-tool.js +312 -0
  41. package/dist/codegen/field-inference.d.ts +24 -0
  42. package/dist/codegen/field-inference.js +101 -0
  43. package/dist/codegen/form-parser.d.ts +13 -0
  44. package/dist/codegen/form-parser.js +186 -0
  45. package/dist/codegen/index.d.ts +2 -0
  46. package/dist/codegen/index.js +4 -0
  47. package/dist/codegen/natural-parser.d.ts +50 -0
  48. package/dist/codegen/natural-parser.js +769 -0
  49. package/dist/handlers/codedna-handlers.d.ts +1 -1
  50. package/dist/handlers/codegen-handlers.d.ts +20 -0
  51. package/dist/handlers/codegen-handlers.js +60 -0
  52. package/dist/handlers/kappa-handlers.d.ts +97 -0
  53. package/dist/handlers/kappa-handlers.js +408 -0
  54. package/dist/handlers/tool-handlers.js +124 -221
  55. package/dist/helpers/api-client.js +48 -3
  56. package/dist/helpers/compact-formatter.d.ts +9 -2
  57. package/dist/helpers/compact-formatter.js +26 -2
  58. package/dist/helpers/config.d.ts +7 -2
  59. package/dist/helpers/config.js +25 -10
  60. package/dist/helpers/session-validation.d.ts +1 -1
  61. package/dist/helpers/session-validation.js +2 -4
  62. package/dist/helpers/tasks.d.ts +21 -0
  63. package/dist/helpers/tasks.js +52 -0
  64. package/dist/helpers/workers.d.ts +1 -1
  65. package/dist/helpers/workers.js +19 -19
  66. package/dist/setup.d.ts +1 -0
  67. package/dist/setup.js +228 -3
  68. package/dist/templates/claude-md.d.ts +1 -1
  69. package/dist/templates/claude-md.js +37 -152
  70. package/dist/templates/orchestrator-prompt.d.ts +2 -2
  71. package/dist/templates/orchestrator-prompt.js +31 -38
  72. package/dist/templates/self-critique.d.ts +50 -0
  73. package/dist/templates/self-critique.js +209 -0
  74. package/dist/templates/worker-prompt.d.ts +3 -3
  75. package/dist/templates/worker-prompt.js +18 -18
  76. package/dist/tools.js +77 -413
  77. package/docs/codedna/generator-testing-summary.md +205 -0
  78. package/docs/codedna/radix-ui-generator.md +478 -0
  79. package/docs/kappa-gofiber-generator.md +274 -0
  80. package/docs/kappa-laravel-fixes.md +172 -0
  81. package/docs/kappa-mongoose-generator.md +322 -0
  82. package/docs/kappa-vitest-generator.md +337 -0
  83. package/package.json +1 -1
  84. package/dist/context/deduplication.test.d.ts +0 -6
  85. 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
+ }