aios-core 4.2.13 → 4.2.14
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/.aios-core/core/code-intel/helpers/dev-helper.js +206 -0
- package/.aios-core/core/registry/registry-schema.json +166 -166
- package/.aios-core/core/synapse/diagnostics/collectors/hook-collector.js +3 -3
- package/.aios-core/data/entity-registry.yaml +27 -0
- package/.aios-core/development/scripts/approval-workflow.js +642 -642
- package/.aios-core/development/scripts/backup-manager.js +606 -606
- package/.aios-core/development/scripts/branch-manager.js +389 -389
- package/.aios-core/development/scripts/code-quality-improver.js +1311 -1311
- package/.aios-core/development/scripts/commit-message-generator.js +849 -849
- package/.aios-core/development/scripts/conflict-resolver.js +674 -674
- package/.aios-core/development/scripts/dependency-analyzer.js +637 -637
- package/.aios-core/development/scripts/diff-generator.js +351 -351
- package/.aios-core/development/scripts/elicitation-engine.js +384 -384
- package/.aios-core/development/scripts/elicitation-session-manager.js +299 -299
- package/.aios-core/development/scripts/git-wrapper.js +461 -461
- package/.aios-core/development/scripts/manifest-preview.js +244 -244
- package/.aios-core/development/scripts/metrics-tracker.js +775 -775
- package/.aios-core/development/scripts/modification-validator.js +554 -554
- package/.aios-core/development/scripts/pattern-learner.js +1224 -1224
- package/.aios-core/development/scripts/performance-analyzer.js +757 -757
- package/.aios-core/development/scripts/refactoring-suggester.js +1138 -1138
- package/.aios-core/development/scripts/rollback-handler.js +530 -530
- package/.aios-core/development/scripts/security-checker.js +358 -358
- package/.aios-core/development/scripts/template-engine.js +239 -239
- package/.aios-core/development/scripts/template-validator.js +278 -278
- package/.aios-core/development/scripts/test-generator.js +843 -843
- package/.aios-core/development/scripts/transaction-manager.js +589 -589
- package/.aios-core/development/scripts/usage-tracker.js +673 -673
- package/.aios-core/development/scripts/validate-filenames.js +226 -226
- package/.aios-core/development/scripts/version-tracker.js +526 -526
- package/.aios-core/development/scripts/yaml-validator.js +396 -396
- package/.aios-core/development/tasks/build-autonomous.md +10 -4
- package/.aios-core/development/tasks/create-service.md +23 -0
- package/.aios-core/development/tasks/dev-develop-story.md +12 -6
- package/.aios-core/development/tasks/dev-suggest-refactoring.md +7 -1
- package/.aios-core/development/tasks/publish-npm.md +3 -3
- package/.aios-core/hooks/unified/README.md +1 -1
- package/.aios-core/install-manifest.yaml +65 -61
- package/.aios-core/manifests/schema/manifest-schema.json +190 -190
- package/.aios-core/product/templates/component-react-tmpl.tsx +98 -98
- package/.aios-core/product/templates/engine/schemas/adr.schema.json +102 -102
- package/.aios-core/product/templates/engine/schemas/dbdr.schema.json +205 -205
- package/.aios-core/product/templates/engine/schemas/epic.schema.json +175 -175
- package/.aios-core/product/templates/engine/schemas/pmdr.schema.json +175 -175
- package/.aios-core/product/templates/engine/schemas/prd-v2.schema.json +300 -300
- package/.aios-core/product/templates/engine/schemas/prd.schema.json +152 -152
- package/.aios-core/product/templates/engine/schemas/story.schema.json +222 -222
- package/.aios-core/product/templates/engine/schemas/task.schema.json +154 -154
- package/.aios-core/product/templates/eslintrc-security.json +32 -32
- package/.aios-core/product/templates/github-actions-cd.yml +212 -212
- package/.aios-core/product/templates/github-actions-ci.yml +172 -172
- package/.aios-core/product/templates/shock-report-tmpl.html +502 -502
- package/.aios-core/product/templates/token-exports-css-tmpl.css +240 -240
- package/.aios-core/quality/schemas/quality-metrics.schema.json +233 -233
- package/.aios-core/scripts/migrate-framework-docs.sh +300 -300
- package/README.en.md +747 -0
- package/README.md +4 -2
- package/bin/aios.js +7 -4
- package/package.json +1 -1
- package/packages/aios-pro-cli/src/recover.js +1 -1
- package/packages/installer/src/wizard/ide-config-generator.js +6 -6
- package/packages/installer/src/wizard/pro-setup.js +3 -3
- package/scripts/package-synapse.js +5 -5
- package/scripts/validate-package-completeness.js +3 -3
- package/.aios-core/.session/current-session.json +0 -14
- package/.aios-core/data/registry-update-log.jsonl +0 -191
- package/.aios-core/docs/SHARD-TRANSLATION-GUIDE.md +0 -335
- package/.aios-core/docs/component-creation-guide.md +0 -458
- package/.aios-core/docs/session-update-pattern.md +0 -307
- package/.aios-core/docs/standards/AIOS-FRAMEWORK-MASTER.md +0 -1963
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-SUMMARY.md +0 -1190
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1.md +0 -439
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO.md +0 -5398
- package/.aios-core/docs/standards/V3-ARCHITECTURAL-DECISIONS.md +0 -523
- package/.aios-core/docs/template-syntax.md +0 -267
- package/.aios-core/docs/troubleshooting-guide.md +0 -625
- package/.aios-core/infrastructure/tests/utilities-audit-results.json +0 -501
- package/.aios-core/manifests/agents.csv +0 -29
- package/.aios-core/manifests/tasks.csv +0 -198
- package/.aios-core/manifests/workers.csv +0 -204
- package/.claude/rules/agent-authority.md +0 -105
- package/.claude/rules/coderabbit-integration.md +0 -93
- package/.claude/rules/ids-principles.md +0 -112
- package/.claude/rules/story-lifecycle.md +0 -139
- package/.claude/rules/workflow-execution.md +0 -150
- package/pro/README.md +0 -66
- package/pro/license/degradation.js +0 -220
- package/pro/license/errors.js +0 -450
- package/pro/license/feature-gate.js +0 -354
- package/pro/license/index.js +0 -181
- package/pro/license/license-api.js +0 -651
- package/pro/license/license-cache.js +0 -523
- package/pro/license/license-crypto.js +0 -303
- package/scripts/glue/README.md +0 -355
- package/scripts/glue/compose-agent-prompt.cjs +0 -362
- /package/.claude/hooks/{precompact-session-digest.js → precompact-session-digest.cjs} +0 -0
- /package/.claude/hooks/{synapse-engine.js → synapse-engine.cjs} +0 -0
|
@@ -1,844 +1,844 @@
|
|
|
1
|
-
const fs = require('fs').promises;
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const chalk = require('chalk');
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Test generator for AIOS-FULLSTACK automated test generation
|
|
7
|
-
* Orchestrates test file creation using the template system
|
|
8
|
-
*/
|
|
9
|
-
class TestGenerator {
|
|
10
|
-
constructor(options = {}) {
|
|
11
|
-
this.rootPath = options.rootPath || process.cwd();
|
|
12
|
-
this.templateSystem = options.templateSystem;
|
|
13
|
-
this.generationCache = new Map();
|
|
14
|
-
this.generationStats = {
|
|
15
|
-
total_generated: 0,
|
|
16
|
-
successful: 0,
|
|
17
|
-
failed: 0,
|
|
18
|
-
generation_time: 0
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Initialize test generator
|
|
24
|
-
*/
|
|
25
|
-
async initialize() {
|
|
26
|
-
try {
|
|
27
|
-
if (!this.templateSystem) {
|
|
28
|
-
throw new Error('Template system not provided');
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Ensure template system is initialized
|
|
32
|
-
if (typeof this.templateSystem.initialize === 'function') {
|
|
33
|
-
await this.templateSystem.initialize();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
console.log(chalk.green('✅ Test generator initialized'));
|
|
37
|
-
return true;
|
|
38
|
-
|
|
39
|
-
} catch (_error) {
|
|
40
|
-
console.error(chalk.red(`Failed to initialize test generator: ${error.message}`));
|
|
41
|
-
throw error;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Generate test file for a component
|
|
47
|
-
*/
|
|
48
|
-
async generateTestFile(component, testFile, config) {
|
|
49
|
-
const startTime = Date.now();
|
|
50
|
-
|
|
51
|
-
try {
|
|
52
|
-
console.log(chalk.blue(`🧪 Generating ${testFile.test_type} test for ${component.name}`));
|
|
53
|
-
|
|
54
|
-
// Validate inputs
|
|
55
|
-
this.validateGenerationInputs(component, testFile, config);
|
|
56
|
-
|
|
57
|
-
// Generate test content using template system
|
|
58
|
-
const testContent = await this.generateTestContent(component, testFile, config);
|
|
59
|
-
|
|
60
|
-
// Apply post-processing
|
|
61
|
-
const processedContent = await this.postProcessTestContent(testContent, component, config);
|
|
62
|
-
|
|
63
|
-
// Update generation stats
|
|
64
|
-
this.updateGenerationStats(true, Date.now() - startTime);
|
|
65
|
-
|
|
66
|
-
console.log(chalk.green(`✅ Generated ${testFile.test_type} test for ${component.name}`));
|
|
67
|
-
|
|
68
|
-
return processedContent;
|
|
69
|
-
|
|
70
|
-
} catch (_error) {
|
|
71
|
-
this.updateGenerationStats(false, Date.now() - startTime);
|
|
72
|
-
console.error(chalk.red(`Failed to generate test for ${component.name}: ${error.message}`));
|
|
73
|
-
throw error;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Generate multiple test files for a component
|
|
79
|
-
*/
|
|
80
|
-
async generateTestSuite(component, testSuite, config) {
|
|
81
|
-
const generatedFiles = [];
|
|
82
|
-
const errors = [];
|
|
83
|
-
|
|
84
|
-
console.log(chalk.blue(`📋 Generating test suite for ${component.name}`));
|
|
85
|
-
console.log(chalk.gray(` Test files: ${testSuite.test_files.length}`));
|
|
86
|
-
|
|
87
|
-
for (const testFile of testSuite.test_files) {
|
|
88
|
-
try {
|
|
89
|
-
const testContent = await this.generateTestFile(component, testFile, config);
|
|
90
|
-
|
|
91
|
-
generatedFiles.push({
|
|
92
|
-
file_path: testFile.file_path,
|
|
93
|
-
test_type: testFile.test_type,
|
|
94
|
-
content: testContent,
|
|
95
|
-
test_count: testFile.test_count
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
} catch (_error) {
|
|
99
|
-
errors.push({
|
|
100
|
-
file_path: testFile.file_path,
|
|
101
|
-
test_type: testFile.test_type,
|
|
102
|
-
error: error.message
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const result = {
|
|
108
|
-
component_id: component.id,
|
|
109
|
-
generated_files: generatedFiles,
|
|
110
|
-
errors: errors,
|
|
111
|
-
success_rate: generatedFiles.length / testSuite.test_files.length
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
console.log(chalk.green(`✅ Test suite generated for ${component.name}`));
|
|
115
|
-
console.log(chalk.gray(` Generated: ${generatedFiles.length}/${testSuite.test_files.length} files`));
|
|
116
|
-
console.log(chalk.gray(` Success rate: ${Math.round(result.success_rate * 100)}%`));
|
|
117
|
-
|
|
118
|
-
return result;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Generate test content using template system
|
|
123
|
-
*/
|
|
124
|
-
async generateTestContent(component, testFile, config) {
|
|
125
|
-
// Use template system to generate base content
|
|
126
|
-
const baseContent = await this.templateSystem.generateTestContent(component, testFile, config);
|
|
127
|
-
|
|
128
|
-
// Enhance with component-specific analysis
|
|
129
|
-
const enhancedContent = await this.enhanceTestContent(baseContent, component, testFile, config);
|
|
130
|
-
|
|
131
|
-
// Apply framework-specific optimizations
|
|
132
|
-
const optimizedContent = await this.optimizeForFramework(enhancedContent, config.framework);
|
|
133
|
-
|
|
134
|
-
return optimizedContent;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Enhance test content with component-specific logic
|
|
139
|
-
*/
|
|
140
|
-
async enhanceTestContent(baseContent, component, testFile, config) {
|
|
141
|
-
let enhancedContent = baseContent;
|
|
142
|
-
|
|
143
|
-
try {
|
|
144
|
-
// Analyze component to identify testable elements
|
|
145
|
-
const componentAnalysis = await this.analyzeComponent(component);
|
|
146
|
-
|
|
147
|
-
// Add component-specific test cases
|
|
148
|
-
const additionalTestCases = await this.generateAdditionalTestCases(
|
|
149
|
-
componentAnalysis,
|
|
150
|
-
testFile.test_type,
|
|
151
|
-
config
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
if (additionalTestCases.length > 0) {
|
|
155
|
-
enhancedContent = this.injectAdditionalTestCases(enhancedContent, additionalTestCases);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Add dynamic imports if needed
|
|
159
|
-
const dynamicImports = await this.generateDynamicImports(componentAnalysis, config);
|
|
160
|
-
if (dynamicImports) {
|
|
161
|
-
enhancedContent = this.injectDynamicImports(enhancedContent, dynamicImports);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Add component-specific setup/teardown
|
|
165
|
-
const setupTeardown = await this.generateSetupTeardown(componentAnalysis, testFile.test_type);
|
|
166
|
-
if (setupTeardown) {
|
|
167
|
-
enhancedContent = this.injectSetupTeardown(enhancedContent, setupTeardown);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
} catch (_error) {
|
|
171
|
-
console.warn(chalk.yellow(`Failed to enhance test content: ${error.message}`));
|
|
172
|
-
// Return base content if enhancement fails
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return enhancedContent;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Analyze component to identify testable elements
|
|
180
|
-
*/
|
|
181
|
-
async analyzeComponent(component) {
|
|
182
|
-
const analysis = {
|
|
183
|
-
component_id: component.id,
|
|
184
|
-
type: component.type,
|
|
185
|
-
name: component.name,
|
|
186
|
-
file_path: component.filePath,
|
|
187
|
-
exports: [],
|
|
188
|
-
functions: [],
|
|
189
|
-
classes: [],
|
|
190
|
-
dependencies: [],
|
|
191
|
-
async_operations: false,
|
|
192
|
-
error_handling: false,
|
|
193
|
-
configuration: null
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
try {
|
|
197
|
-
if (!component.filePath) {
|
|
198
|
-
return analysis;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Read component file
|
|
202
|
-
const content = await fs.readFile(component.filePath, 'utf-8');
|
|
203
|
-
|
|
204
|
-
if (component.type === 'util') {
|
|
205
|
-
// Analyze JavaScript utility
|
|
206
|
-
analysis.exports = this.extractExports(content);
|
|
207
|
-
analysis.functions = this.extractFunctions(content);
|
|
208
|
-
analysis.classes = this.extractClasses(content);
|
|
209
|
-
analysis.dependencies = this.extractDependencies(content);
|
|
210
|
-
analysis.async_operations = content.includes('async') || content.includes('await');
|
|
211
|
-
analysis.error_handling = content.includes('try') || content.includes('catch');
|
|
212
|
-
} else if (component.type === 'agent') {
|
|
213
|
-
// Analyze agent markdown
|
|
214
|
-
analysis.configuration = this.extractAgentConfig(content);
|
|
215
|
-
} else if (component.type === 'workflow') {
|
|
216
|
-
// Analyze workflow YAML
|
|
217
|
-
analysis.configuration = this.extractWorkflowConfig(content);
|
|
218
|
-
} else if (component.type === 'task') {
|
|
219
|
-
// Analyze task markdown with embedded JavaScript
|
|
220
|
-
analysis.functions = this.extractFunctions(content);
|
|
221
|
-
analysis.configuration = this.extractTaskConfig(content);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
} catch (_error) {
|
|
225
|
-
console.warn(chalk.yellow(`Failed to analyze component ${component.id}: ${error.message}`));
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
return analysis;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Generate additional test cases based on component analysis
|
|
233
|
-
*/
|
|
234
|
-
async generateAdditionalTestCases(componentAnalysis, _testType, config) {
|
|
235
|
-
const additionalCases = [];
|
|
236
|
-
|
|
237
|
-
// Generate test cases for exported functions
|
|
238
|
-
for (const func of componentAnalysis.functions) {
|
|
239
|
-
if (func.visibility === 'public') {
|
|
240
|
-
additionalCases.push(...this.generateFunctionTestCases(func, _testType, config));
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Generate test cases for classes
|
|
245
|
-
for (const cls of componentAnalysis.classes) {
|
|
246
|
-
additionalCases.push(...this.generateClassTestCases(cls, _testType, config));
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Generate async/error handling test cases
|
|
250
|
-
if (componentAnalysis.async_operations) {
|
|
251
|
-
additionalCases.push(...this.generateAsyncTestCases(_testType, config));
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (componentAnalysis.error_handling) {
|
|
255
|
-
additionalCases.push(...this.generateErrorHandlingTestCases(_testType, config));
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return additionalCases;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Generate test cases for a function
|
|
263
|
-
*/
|
|
264
|
-
generateFunctionTestCases(func, _testType, config) {
|
|
265
|
-
const testCases = [];
|
|
266
|
-
|
|
267
|
-
// Basic functionality test
|
|
268
|
-
testCases.push({
|
|
269
|
-
name: `should execute ${func.name} successfully`,
|
|
270
|
-
type: 'functionality',
|
|
271
|
-
setup: func.async ? 'const result = await ' : 'const result = ',
|
|
272
|
-
assertions: [
|
|
273
|
-
`expect(result).toBeDefined();`,
|
|
274
|
-
func.async ? `expect(typeof result).toBe('object');` : `expect(result).toBeTruthy();`
|
|
275
|
-
]
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
// Parameter validation test
|
|
279
|
-
if (func.parameters && func.parameters.length > 0) {
|
|
280
|
-
testCases.push({
|
|
281
|
-
name: `should handle invalid parameters for ${func.name}`,
|
|
282
|
-
type: 'validation',
|
|
283
|
-
setup: `const invalidCall = () => ${func.name}();`,
|
|
284
|
-
assertions: [
|
|
285
|
-
`expect(invalidCall).toThrow();`
|
|
286
|
-
]
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// Edge case tests for complex functions
|
|
291
|
-
if (config.qualityLevel === 'comprehensive') {
|
|
292
|
-
testCases.push({
|
|
293
|
-
name: `should handle edge cases for ${func.name}`,
|
|
294
|
-
type: 'edge_case',
|
|
295
|
-
setup: `// Edge case testing for ${func.name}`,
|
|
296
|
-
assertions: [
|
|
297
|
-
`// Add edge case assertions here`
|
|
298
|
-
]
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
return testCases;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Generate test cases for a class
|
|
307
|
-
*/
|
|
308
|
-
generateClassTestCases(cls, _testType, config) {
|
|
309
|
-
const testCases = [];
|
|
310
|
-
|
|
311
|
-
// Constructor test
|
|
312
|
-
testCases.push({
|
|
313
|
-
name: `should instantiate ${cls.name} correctly`,
|
|
314
|
-
type: 'instantiation',
|
|
315
|
-
setup: `const instance = new ${cls.name}();`,
|
|
316
|
-
assertions: [
|
|
317
|
-
`expect(instance).toBeInstanceOf(${cls.name});`,
|
|
318
|
-
`expect(instance).toBeDefined();`
|
|
319
|
-
]
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
// Method tests
|
|
323
|
-
for (const method of cls.methods || []) {
|
|
324
|
-
testCases.push({
|
|
325
|
-
name: `should execute ${cls.name}.${method.name} correctly`,
|
|
326
|
-
type: 'method',
|
|
327
|
-
setup: `const instance = new ${cls.name}();\nconst result = ${method.async ? 'await ' : ''}instance.${method.name}();`,
|
|
328
|
-
assertions: [
|
|
329
|
-
`expect(result).toBeDefined();`
|
|
330
|
-
]
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
return testCases;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Generate async operation test cases
|
|
339
|
-
*/
|
|
340
|
-
generateAsyncTestCases(_testType, config) {
|
|
341
|
-
return [
|
|
342
|
-
{
|
|
343
|
-
name: 'should handle async operations correctly',
|
|
344
|
-
type: 'async',
|
|
345
|
-
setup: '// Async operation test setup',
|
|
346
|
-
assertions: [
|
|
347
|
-
'// Add async-specific assertions',
|
|
348
|
-
'expect(result).resolves.toBeDefined();'
|
|
349
|
-
]
|
|
350
|
-
},
|
|
351
|
-
{
|
|
352
|
-
name: 'should handle async operation timeouts',
|
|
353
|
-
type: 'timeout',
|
|
354
|
-
setup: '// Timeout test setup',
|
|
355
|
-
assertions: [
|
|
356
|
-
'expect(longRunningOperation).rejects.toThrow("timeout");'
|
|
357
|
-
]
|
|
358
|
-
}
|
|
359
|
-
];
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
/**
|
|
363
|
-
* Generate error handling test cases
|
|
364
|
-
*/
|
|
365
|
-
generateErrorHandlingTestCases(_testType, config) {
|
|
366
|
-
return [
|
|
367
|
-
{
|
|
368
|
-
name: 'should handle errors gracefully',
|
|
369
|
-
type: 'error_handling',
|
|
370
|
-
setup: '// Error simulation setup',
|
|
371
|
-
assertions: [
|
|
372
|
-
'expect(errorHandlerFunction).not.toThrow();',
|
|
373
|
-
'expect(result.error).toBeDefined();'
|
|
374
|
-
]
|
|
375
|
-
}
|
|
376
|
-
];
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
* Optimize content for specific test framework
|
|
381
|
-
*/
|
|
382
|
-
async optimizeForFramework(content, framework) {
|
|
383
|
-
switch (framework) {
|
|
384
|
-
case 'jest':
|
|
385
|
-
return this.optimizeForJest(content);
|
|
386
|
-
case 'mocha':
|
|
387
|
-
return this.optimizeForMocha(content);
|
|
388
|
-
case 'vitest':
|
|
389
|
-
return this.optimizeForVitest(content);
|
|
390
|
-
default:
|
|
391
|
-
return content;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
/**
|
|
396
|
-
* Optimize for Jest framework
|
|
397
|
-
*/
|
|
398
|
-
optimizeForJest(content) {
|
|
399
|
-
// Add Jest-specific optimizations
|
|
400
|
-
let optimized = content;
|
|
401
|
-
|
|
402
|
-
// Add jest-specific expect extensions if needed
|
|
403
|
-
if (content.includes('toBeInstanceOf') && !content.includes('expect.extend')) {
|
|
404
|
-
optimized = `const { expect } = require('@jest/globals');\n\n${optimized}`;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// Add performance timing for slow tests
|
|
408
|
-
if (content.includes('async') && content.length > 5000) {
|
|
409
|
-
optimized = optimized.replace(
|
|
410
|
-
/describe\('([^']+)', \(\) => \{/,
|
|
411
|
-
"describe('$1', () => {\n jest.setTimeout(10000);\n"
|
|
412
|
-
);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
return optimized;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
/**
|
|
419
|
-
* Optimize for Mocha framework
|
|
420
|
-
*/
|
|
421
|
-
optimizeForMocha(content) {
|
|
422
|
-
// Add Mocha-specific optimizations
|
|
423
|
-
let optimized = content;
|
|
424
|
-
|
|
425
|
-
// Set timeout for async tests
|
|
426
|
-
if (content.includes('async')) {
|
|
427
|
-
optimized = optimized.replace(
|
|
428
|
-
/describe\('([^']+)', function\(\) \{/,
|
|
429
|
-
"describe('$1', function() {\n this.timeout(5000);\n"
|
|
430
|
-
);
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
return optimized;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
/**
|
|
437
|
-
* Optimize for Vitest framework
|
|
438
|
-
*/
|
|
439
|
-
optimizeForVitest(content) {
|
|
440
|
-
// Add Vitest-specific optimizations
|
|
441
|
-
let optimized = content;
|
|
442
|
-
|
|
443
|
-
// Use vi mock utilities
|
|
444
|
-
optimized = optimized.replace(/jest\.mock/g, 'vi.mock');
|
|
445
|
-
optimized = optimized.replace(/jest\.spyOn/g, 'vi.spyOn');
|
|
446
|
-
|
|
447
|
-
return optimized;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
/**
|
|
451
|
-
* Post-process test content
|
|
452
|
-
*/
|
|
453
|
-
async postProcessTestContent(content, component, config) {
|
|
454
|
-
let processed = content;
|
|
455
|
-
|
|
456
|
-
// Replace template variables
|
|
457
|
-
processed = this.replaceTemplateVariables(processed, component, config);
|
|
458
|
-
|
|
459
|
-
// Format code
|
|
460
|
-
processed = this.formatTestCode(processed);
|
|
461
|
-
|
|
462
|
-
// Add generation metadata
|
|
463
|
-
processed = this.addGenerationMetadata(processed, component, config);
|
|
464
|
-
|
|
465
|
-
// Validate syntax
|
|
466
|
-
await this.validateTestSyntax(processed, config.framework);
|
|
467
|
-
|
|
468
|
-
return processed;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
/**
|
|
472
|
-
* Replace template variables with actual values
|
|
473
|
-
*/
|
|
474
|
-
replaceTemplateVariables(content, component, config) {
|
|
475
|
-
let result = content;
|
|
476
|
-
|
|
477
|
-
// Replace component variables
|
|
478
|
-
result = result.replace(/\${data\.component\.name}/g, component.name);
|
|
479
|
-
result = result.replace(/\${data\.component\.type}/g, component.type);
|
|
480
|
-
result = result.replace(/\${this\.toClassName\(data\.component\.name\)}/g, this.toClassName(component.name));
|
|
481
|
-
|
|
482
|
-
// Replace config variables
|
|
483
|
-
result = result.replace(/\${data\.config\.framework}/g, config.framework);
|
|
484
|
-
result = result.replace(/\${data\.metadata\.generatedAt}/g, new Date().toISOString());
|
|
485
|
-
|
|
486
|
-
return result;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
/**
|
|
490
|
-
* Format test code
|
|
491
|
-
*/
|
|
492
|
-
formatTestCode(content) {
|
|
493
|
-
// Basic code formatting
|
|
494
|
-
let formatted = content;
|
|
495
|
-
|
|
496
|
-
// Fix indentation
|
|
497
|
-
formatted = formatted.replace(/\n {2,}/g, match => '\n' + ' '.repeat(Math.floor(match.length / 2)));
|
|
498
|
-
|
|
499
|
-
// Remove excessive blank lines
|
|
500
|
-
formatted = formatted.replace(/\n{3,}/g, '\n\n');
|
|
501
|
-
|
|
502
|
-
// Ensure proper spacing around blocks
|
|
503
|
-
formatted = formatted.replace(/}\n{/g, '}\n\n{');
|
|
504
|
-
|
|
505
|
-
return formatted.trim();
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
/**
|
|
509
|
-
* Add generation metadata as comments
|
|
510
|
-
*/
|
|
511
|
-
addGenerationMetadata(content, component, config) {
|
|
512
|
-
const metadata = `
|
|
513
|
-
// Generated by AIOS Test Generator
|
|
514
|
-
// Generated at: ${new Date().toISOString()}
|
|
515
|
-
// Component: ${component.type}/${component.name}
|
|
516
|
-
// Framework: ${config.framework}
|
|
517
|
-
// Quality Level: ${config.qualityLevel}
|
|
518
|
-
|
|
519
|
-
${content}`;
|
|
520
|
-
|
|
521
|
-
return metadata;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
/**
|
|
525
|
-
* Validate test syntax
|
|
526
|
-
*/
|
|
527
|
-
async validateTestSyntax(content, framework) {
|
|
528
|
-
try {
|
|
529
|
-
// Basic syntax validation
|
|
530
|
-
if (framework === 'jest' || framework === 'vitest') {
|
|
531
|
-
// Check for required Jest/Vitest patterns
|
|
532
|
-
if (!content.includes('describe(') && !content.includes('test(') && !content.includes('it(')) {
|
|
533
|
-
throw new Error('No test cases found');
|
|
534
|
-
}
|
|
535
|
-
} else if (framework === 'mocha') {
|
|
536
|
-
// Check for required Mocha patterns
|
|
537
|
-
if (!content.includes('describe(') && !content.includes('it(')) {
|
|
538
|
-
throw new Error('No test cases found');
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
// Check for balanced brackets
|
|
543
|
-
const openBrackets = (content.match(/\{/g) || []).length;
|
|
544
|
-
const closeBrackets = (content.match(/\}/g) || []).length;
|
|
545
|
-
|
|
546
|
-
if (openBrackets !== closeBrackets) {
|
|
547
|
-
throw new Error('Unbalanced brackets in generated test');
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
} catch (_error) {
|
|
551
|
-
console.warn(chalk.yellow(`Test syntax validation warning: ${error.message}`));
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
// Helper methods for content injection and analysis
|
|
556
|
-
|
|
557
|
-
injectAdditionalTestCases(content, testCases) {
|
|
558
|
-
if (testCases.length === 0) return content;
|
|
559
|
-
|
|
560
|
-
const additionalTests = testCases.map(testCase => {
|
|
561
|
-
let caseContent = ` it('${testCase.name}', async () => {\n`;
|
|
562
|
-
|
|
563
|
-
if (testCase.setup) {
|
|
564
|
-
caseContent += ` ${testCase.setup}\n\n`;
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
caseContent += testCase.assertions.map(assertion => ` ${assertion}`).join('\n');
|
|
568
|
-
caseContent += '\n });';
|
|
569
|
-
|
|
570
|
-
return caseContent;
|
|
571
|
-
}).join('\n\n');
|
|
572
|
-
|
|
573
|
-
// Find insertion point (before closing of describe block)
|
|
574
|
-
const insertionPoint = content.lastIndexOf('});');
|
|
575
|
-
if (insertionPoint !== -1) {
|
|
576
|
-
return content.slice(0, insertionPoint) + '\n\n' + additionalTests + '\n\n' + content.slice(insertionPoint);
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
return content + '\n\n' + additionalTests;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
injectDynamicImports(content, imports) {
|
|
583
|
-
if (!imports) return content;
|
|
584
|
-
|
|
585
|
-
const importSection = imports.join('\n');
|
|
586
|
-
const existingImports = content.match(/^(const|import|require).*$/gm);
|
|
587
|
-
|
|
588
|
-
if (existingImports && existingImports.length > 0) {
|
|
589
|
-
// Add after existing imports
|
|
590
|
-
const lastImportIndex = content.lastIndexOf(existingImports[existingImports.length - 1]);
|
|
591
|
-
const insertionPoint = lastImportIndex + existingImports[existingImports.length - 1].length;
|
|
592
|
-
return content.slice(0, insertionPoint) + '\n' + importSection + content.slice(insertionPoint);
|
|
593
|
-
} else {
|
|
594
|
-
// Add at the beginning
|
|
595
|
-
return importSection + '\n\n' + content;
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
injectSetupTeardown(content, setupTeardown) {
|
|
600
|
-
if (!setupTeardown) return content;
|
|
601
|
-
|
|
602
|
-
let injected = content;
|
|
603
|
-
|
|
604
|
-
// Find describe block and inject setup/teardown
|
|
605
|
-
const describeMatch = content.match(/describe\([^{]+\{/);
|
|
606
|
-
if (describeMatch) {
|
|
607
|
-
const insertionPoint = describeMatch.index + describeMatch[0].length;
|
|
608
|
-
injected = content.slice(0, insertionPoint) + '\n' + setupTeardown + content.slice(insertionPoint);
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
return injected;
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
// Component analysis helper methods
|
|
615
|
-
|
|
616
|
-
extractExports(content) {
|
|
617
|
-
const exports = [];
|
|
618
|
-
|
|
619
|
-
// Extract module.exports
|
|
620
|
-
const moduleExports = content.match(/module\.exports\s*=\s*([^;]+)/);
|
|
621
|
-
if (moduleExports) {
|
|
622
|
-
exports.push({ type: 'module.exports', value: moduleExports[1] });
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
// Extract named exports
|
|
626
|
-
const namedExports = content.match(/exports\.(\w+)/g);
|
|
627
|
-
if (namedExports) {
|
|
628
|
-
namedExports.forEach(exp => {
|
|
629
|
-
const name = exp.replace('exports.', '');
|
|
630
|
-
exports.push({ type: 'named', name: name });
|
|
631
|
-
});
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
return exports;
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
extractFunctions(content) {
|
|
638
|
-
const functions = [];
|
|
639
|
-
|
|
640
|
-
// Extract function declarations
|
|
641
|
-
const functionDeclarations = content.match(/(?:async\s+)?function\s+(\w+)\s*\([^)]*\)/g);
|
|
642
|
-
if (functionDeclarations) {
|
|
643
|
-
functionDeclarations.forEach(func => {
|
|
644
|
-
const match = func.match(/(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/);
|
|
645
|
-
if (match) {
|
|
646
|
-
functions.push({
|
|
647
|
-
name: match[1],
|
|
648
|
-
parameters: match[2] ? match[2].split(',').map(p => p.trim()) : [],
|
|
649
|
-
async: func.includes('async'),
|
|
650
|
-
visibility: 'public'
|
|
651
|
-
});
|
|
652
|
-
}
|
|
653
|
-
});
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
// Extract arrow functions
|
|
657
|
-
const arrowFunctions = content.match(/(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/g);
|
|
658
|
-
if (arrowFunctions) {
|
|
659
|
-
arrowFunctions.forEach(func => {
|
|
660
|
-
const match = func.match(/(\w+)\s*=\s*(async\s+)?\(([^)]*)\)\s*=>/);
|
|
661
|
-
if (match) {
|
|
662
|
-
functions.push({
|
|
663
|
-
name: match[1],
|
|
664
|
-
parameters: match[3] ? match[3].split(',').map(p => p.trim()) : [],
|
|
665
|
-
async: !!match[2],
|
|
666
|
-
visibility: 'public'
|
|
667
|
-
});
|
|
668
|
-
}
|
|
669
|
-
});
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
return functions;
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
extractClasses(content) {
|
|
676
|
-
const classes = [];
|
|
677
|
-
|
|
678
|
-
const classDeclarations = content.match(/class\s+(\w+)(?:\s+extends\s+\w+)?\s*\{[^}]*\}/g);
|
|
679
|
-
if (classDeclarations) {
|
|
680
|
-
classDeclarations.forEach(cls => {
|
|
681
|
-
const nameMatch = cls.match(/class\s+(\w+)/);
|
|
682
|
-
if (nameMatch) {
|
|
683
|
-
const methods = cls.match(/(\w+)\s*\([^)]*\)\s*\{/g) || [];
|
|
684
|
-
classes.push({
|
|
685
|
-
name: nameMatch[1],
|
|
686
|
-
methods: methods.map(method => {
|
|
687
|
-
const methodMatch = method.match(/(\w+)\s*\(/);
|
|
688
|
-
return {
|
|
689
|
-
name: methodMatch ? methodMatch[1] : 'unknown',
|
|
690
|
-
async: method.includes('async')
|
|
691
|
-
};
|
|
692
|
-
}).filter(m => m.name !== 'constructor')
|
|
693
|
-
});
|
|
694
|
-
}
|
|
695
|
-
});
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
return classes;
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
extractDependencies(content) {
|
|
702
|
-
const dependencies = [];
|
|
703
|
-
|
|
704
|
-
const requires = content.match(/require\(['"]([^'"]+)['"]\)/g);
|
|
705
|
-
if (requires) {
|
|
706
|
-
requires.forEach(req => {
|
|
707
|
-
const match = req.match(/require\(['"]([^'"]+)['"]\)/);
|
|
708
|
-
if (match) {
|
|
709
|
-
dependencies.push({
|
|
710
|
-
name: match[1],
|
|
711
|
-
type: match[1].startsWith('./') || match[1].startsWith('../') ? 'local' : 'external'
|
|
712
|
-
});
|
|
713
|
-
}
|
|
714
|
-
});
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
return dependencies;
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
extractAgentConfig(content) {
|
|
721
|
-
const yamlMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
722
|
-
if (yamlMatch) {
|
|
723
|
-
try {
|
|
724
|
-
const yaml = require('js-yaml');
|
|
725
|
-
return yaml.load(yamlMatch[1]);
|
|
726
|
-
} catch (_error) {
|
|
727
|
-
return null;
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
return null;
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
extractWorkflowConfig(content) {
|
|
734
|
-
try {
|
|
735
|
-
const yaml = require('js-yaml');
|
|
736
|
-
return yaml.load(content);
|
|
737
|
-
} catch (_error) {
|
|
738
|
-
return null;
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
extractTaskConfig(content) {
|
|
743
|
-
// Extract YAML frontmatter from task markdown
|
|
744
|
-
return this.extractAgentConfig(content);
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
generateDynamicImports(componentAnalysis, config) {
|
|
748
|
-
const imports = [];
|
|
749
|
-
|
|
750
|
-
// Add imports for dependencies
|
|
751
|
-
for (const dep of componentAnalysis.dependencies) {
|
|
752
|
-
if (dep.type === 'local') {
|
|
753
|
-
imports.push(`const ${this.toVariableName(dep.name)} = require('${dep.name}');`);
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
// Add framework-specific test utilities
|
|
758
|
-
if (config.framework === 'jest') {
|
|
759
|
-
if (componentAnalysis.async_operations) {
|
|
760
|
-
imports.push(`const { jest } = require('@jest/globals');`);
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
return imports.length > 0 ? imports : null;
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
generateSetupTeardown(componentAnalysis, testType) {
|
|
768
|
-
const setup = [];
|
|
769
|
-
|
|
770
|
-
if (componentAnalysis.type === 'util' && componentAnalysis.classes.length > 0) {
|
|
771
|
-
setup.push(` let instance;\n`);
|
|
772
|
-
setup.push(` beforeEach(() => {\n instance = new ${componentAnalysis.classes[0].name}();\n });\n`);
|
|
773
|
-
setup.push(` afterEach(() => {\n if (instance && instance.cleanup) instance.cleanup();\n });\n`);
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
return setup.length > 0 ? setup.join('\n') : null;
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
// Utility methods
|
|
780
|
-
|
|
781
|
-
validateGenerationInputs(component, testFile, config) {
|
|
782
|
-
if (!component || !component.name) {
|
|
783
|
-
throw new Error('Invalid component: missing name');
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
if (!testFile || !testFile.test_type) {
|
|
787
|
-
throw new Error('Invalid test file: missing test_type');
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
if (!config || !config.framework) {
|
|
791
|
-
throw new Error('Invalid config: missing framework');
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
const validFrameworks = ['jest', 'mocha', 'vitest'];
|
|
795
|
-
if (!validFrameworks.includes(config.framework)) {
|
|
796
|
-
throw new Error(`Unsupported framework: ${config.framework}`);
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
updateGenerationStats(success, duration) {
|
|
801
|
-
this.generationStats.total_generated++;
|
|
802
|
-
if (success) {
|
|
803
|
-
this.generationStats.successful++;
|
|
804
|
-
} else {
|
|
805
|
-
this.generationStats.failed++;
|
|
806
|
-
}
|
|
807
|
-
this.generationStats.generation_time += duration;
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
toClassName(name) {
|
|
811
|
-
return name.split('-')
|
|
812
|
-
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
813
|
-
.join('');
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
toVariableName(_path) {
|
|
817
|
-
return path.split('/').pop().replace(/[-\.]/g, '_');
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
/**
|
|
821
|
-
* Get generation statistics
|
|
822
|
-
*/
|
|
823
|
-
getGenerationStats() {
|
|
824
|
-
return {
|
|
825
|
-
...this.generationStats,
|
|
826
|
-
success_rate: this.generationStats.total_generated > 0
|
|
827
|
-
? this.generationStats.successful / this.generationStats.total_generated
|
|
828
|
-
: 0,
|
|
829
|
-
average_generation_time: this.generationStats.total_generated > 0
|
|
830
|
-
? this.generationStats.generation_time / this.generationStats.total_generated
|
|
831
|
-
: 0
|
|
832
|
-
};
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
/**
|
|
836
|
-
* Clear generation cache
|
|
837
|
-
*/
|
|
838
|
-
clearCache() {
|
|
839
|
-
this.generationCache.clear();
|
|
840
|
-
console.log(chalk.gray('Test generation cache cleared'));
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
|
|
1
|
+
const fs = require('fs').promises;
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Test generator for AIOS-FULLSTACK automated test generation
|
|
7
|
+
* Orchestrates test file creation using the template system
|
|
8
|
+
*/
|
|
9
|
+
class TestGenerator {
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
this.rootPath = options.rootPath || process.cwd();
|
|
12
|
+
this.templateSystem = options.templateSystem;
|
|
13
|
+
this.generationCache = new Map();
|
|
14
|
+
this.generationStats = {
|
|
15
|
+
total_generated: 0,
|
|
16
|
+
successful: 0,
|
|
17
|
+
failed: 0,
|
|
18
|
+
generation_time: 0
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Initialize test generator
|
|
24
|
+
*/
|
|
25
|
+
async initialize() {
|
|
26
|
+
try {
|
|
27
|
+
if (!this.templateSystem) {
|
|
28
|
+
throw new Error('Template system not provided');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Ensure template system is initialized
|
|
32
|
+
if (typeof this.templateSystem.initialize === 'function') {
|
|
33
|
+
await this.templateSystem.initialize();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
console.log(chalk.green('✅ Test generator initialized'));
|
|
37
|
+
return true;
|
|
38
|
+
|
|
39
|
+
} catch (_error) {
|
|
40
|
+
console.error(chalk.red(`Failed to initialize test generator: ${error.message}`));
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Generate test file for a component
|
|
47
|
+
*/
|
|
48
|
+
async generateTestFile(component, testFile, config) {
|
|
49
|
+
const startTime = Date.now();
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
console.log(chalk.blue(`🧪 Generating ${testFile.test_type} test for ${component.name}`));
|
|
53
|
+
|
|
54
|
+
// Validate inputs
|
|
55
|
+
this.validateGenerationInputs(component, testFile, config);
|
|
56
|
+
|
|
57
|
+
// Generate test content using template system
|
|
58
|
+
const testContent = await this.generateTestContent(component, testFile, config);
|
|
59
|
+
|
|
60
|
+
// Apply post-processing
|
|
61
|
+
const processedContent = await this.postProcessTestContent(testContent, component, config);
|
|
62
|
+
|
|
63
|
+
// Update generation stats
|
|
64
|
+
this.updateGenerationStats(true, Date.now() - startTime);
|
|
65
|
+
|
|
66
|
+
console.log(chalk.green(`✅ Generated ${testFile.test_type} test for ${component.name}`));
|
|
67
|
+
|
|
68
|
+
return processedContent;
|
|
69
|
+
|
|
70
|
+
} catch (_error) {
|
|
71
|
+
this.updateGenerationStats(false, Date.now() - startTime);
|
|
72
|
+
console.error(chalk.red(`Failed to generate test for ${component.name}: ${error.message}`));
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Generate multiple test files for a component
|
|
79
|
+
*/
|
|
80
|
+
async generateTestSuite(component, testSuite, config) {
|
|
81
|
+
const generatedFiles = [];
|
|
82
|
+
const errors = [];
|
|
83
|
+
|
|
84
|
+
console.log(chalk.blue(`📋 Generating test suite for ${component.name}`));
|
|
85
|
+
console.log(chalk.gray(` Test files: ${testSuite.test_files.length}`));
|
|
86
|
+
|
|
87
|
+
for (const testFile of testSuite.test_files) {
|
|
88
|
+
try {
|
|
89
|
+
const testContent = await this.generateTestFile(component, testFile, config);
|
|
90
|
+
|
|
91
|
+
generatedFiles.push({
|
|
92
|
+
file_path: testFile.file_path,
|
|
93
|
+
test_type: testFile.test_type,
|
|
94
|
+
content: testContent,
|
|
95
|
+
test_count: testFile.test_count
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
} catch (_error) {
|
|
99
|
+
errors.push({
|
|
100
|
+
file_path: testFile.file_path,
|
|
101
|
+
test_type: testFile.test_type,
|
|
102
|
+
error: error.message
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const result = {
|
|
108
|
+
component_id: component.id,
|
|
109
|
+
generated_files: generatedFiles,
|
|
110
|
+
errors: errors,
|
|
111
|
+
success_rate: generatedFiles.length / testSuite.test_files.length
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
console.log(chalk.green(`✅ Test suite generated for ${component.name}`));
|
|
115
|
+
console.log(chalk.gray(` Generated: ${generatedFiles.length}/${testSuite.test_files.length} files`));
|
|
116
|
+
console.log(chalk.gray(` Success rate: ${Math.round(result.success_rate * 100)}%`));
|
|
117
|
+
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Generate test content using template system
|
|
123
|
+
*/
|
|
124
|
+
async generateTestContent(component, testFile, config) {
|
|
125
|
+
// Use template system to generate base content
|
|
126
|
+
const baseContent = await this.templateSystem.generateTestContent(component, testFile, config);
|
|
127
|
+
|
|
128
|
+
// Enhance with component-specific analysis
|
|
129
|
+
const enhancedContent = await this.enhanceTestContent(baseContent, component, testFile, config);
|
|
130
|
+
|
|
131
|
+
// Apply framework-specific optimizations
|
|
132
|
+
const optimizedContent = await this.optimizeForFramework(enhancedContent, config.framework);
|
|
133
|
+
|
|
134
|
+
return optimizedContent;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Enhance test content with component-specific logic
|
|
139
|
+
*/
|
|
140
|
+
async enhanceTestContent(baseContent, component, testFile, config) {
|
|
141
|
+
let enhancedContent = baseContent;
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
// Analyze component to identify testable elements
|
|
145
|
+
const componentAnalysis = await this.analyzeComponent(component);
|
|
146
|
+
|
|
147
|
+
// Add component-specific test cases
|
|
148
|
+
const additionalTestCases = await this.generateAdditionalTestCases(
|
|
149
|
+
componentAnalysis,
|
|
150
|
+
testFile.test_type,
|
|
151
|
+
config
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
if (additionalTestCases.length > 0) {
|
|
155
|
+
enhancedContent = this.injectAdditionalTestCases(enhancedContent, additionalTestCases);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Add dynamic imports if needed
|
|
159
|
+
const dynamicImports = await this.generateDynamicImports(componentAnalysis, config);
|
|
160
|
+
if (dynamicImports) {
|
|
161
|
+
enhancedContent = this.injectDynamicImports(enhancedContent, dynamicImports);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Add component-specific setup/teardown
|
|
165
|
+
const setupTeardown = await this.generateSetupTeardown(componentAnalysis, testFile.test_type);
|
|
166
|
+
if (setupTeardown) {
|
|
167
|
+
enhancedContent = this.injectSetupTeardown(enhancedContent, setupTeardown);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
} catch (_error) {
|
|
171
|
+
console.warn(chalk.yellow(`Failed to enhance test content: ${error.message}`));
|
|
172
|
+
// Return base content if enhancement fails
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return enhancedContent;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Analyze component to identify testable elements
|
|
180
|
+
*/
|
|
181
|
+
async analyzeComponent(component) {
|
|
182
|
+
const analysis = {
|
|
183
|
+
component_id: component.id,
|
|
184
|
+
type: component.type,
|
|
185
|
+
name: component.name,
|
|
186
|
+
file_path: component.filePath,
|
|
187
|
+
exports: [],
|
|
188
|
+
functions: [],
|
|
189
|
+
classes: [],
|
|
190
|
+
dependencies: [],
|
|
191
|
+
async_operations: false,
|
|
192
|
+
error_handling: false,
|
|
193
|
+
configuration: null
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
if (!component.filePath) {
|
|
198
|
+
return analysis;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Read component file
|
|
202
|
+
const content = await fs.readFile(component.filePath, 'utf-8');
|
|
203
|
+
|
|
204
|
+
if (component.type === 'util') {
|
|
205
|
+
// Analyze JavaScript utility
|
|
206
|
+
analysis.exports = this.extractExports(content);
|
|
207
|
+
analysis.functions = this.extractFunctions(content);
|
|
208
|
+
analysis.classes = this.extractClasses(content);
|
|
209
|
+
analysis.dependencies = this.extractDependencies(content);
|
|
210
|
+
analysis.async_operations = content.includes('async') || content.includes('await');
|
|
211
|
+
analysis.error_handling = content.includes('try') || content.includes('catch');
|
|
212
|
+
} else if (component.type === 'agent') {
|
|
213
|
+
// Analyze agent markdown
|
|
214
|
+
analysis.configuration = this.extractAgentConfig(content);
|
|
215
|
+
} else if (component.type === 'workflow') {
|
|
216
|
+
// Analyze workflow YAML
|
|
217
|
+
analysis.configuration = this.extractWorkflowConfig(content);
|
|
218
|
+
} else if (component.type === 'task') {
|
|
219
|
+
// Analyze task markdown with embedded JavaScript
|
|
220
|
+
analysis.functions = this.extractFunctions(content);
|
|
221
|
+
analysis.configuration = this.extractTaskConfig(content);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
} catch (_error) {
|
|
225
|
+
console.warn(chalk.yellow(`Failed to analyze component ${component.id}: ${error.message}`));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return analysis;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Generate additional test cases based on component analysis
|
|
233
|
+
*/
|
|
234
|
+
async generateAdditionalTestCases(componentAnalysis, _testType, config) {
|
|
235
|
+
const additionalCases = [];
|
|
236
|
+
|
|
237
|
+
// Generate test cases for exported functions
|
|
238
|
+
for (const func of componentAnalysis.functions) {
|
|
239
|
+
if (func.visibility === 'public') {
|
|
240
|
+
additionalCases.push(...this.generateFunctionTestCases(func, _testType, config));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Generate test cases for classes
|
|
245
|
+
for (const cls of componentAnalysis.classes) {
|
|
246
|
+
additionalCases.push(...this.generateClassTestCases(cls, _testType, config));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Generate async/error handling test cases
|
|
250
|
+
if (componentAnalysis.async_operations) {
|
|
251
|
+
additionalCases.push(...this.generateAsyncTestCases(_testType, config));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (componentAnalysis.error_handling) {
|
|
255
|
+
additionalCases.push(...this.generateErrorHandlingTestCases(_testType, config));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return additionalCases;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Generate test cases for a function
|
|
263
|
+
*/
|
|
264
|
+
generateFunctionTestCases(func, _testType, config) {
|
|
265
|
+
const testCases = [];
|
|
266
|
+
|
|
267
|
+
// Basic functionality test
|
|
268
|
+
testCases.push({
|
|
269
|
+
name: `should execute ${func.name} successfully`,
|
|
270
|
+
type: 'functionality',
|
|
271
|
+
setup: func.async ? 'const result = await ' : 'const result = ',
|
|
272
|
+
assertions: [
|
|
273
|
+
`expect(result).toBeDefined();`,
|
|
274
|
+
func.async ? `expect(typeof result).toBe('object');` : `expect(result).toBeTruthy();`
|
|
275
|
+
]
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// Parameter validation test
|
|
279
|
+
if (func.parameters && func.parameters.length > 0) {
|
|
280
|
+
testCases.push({
|
|
281
|
+
name: `should handle invalid parameters for ${func.name}`,
|
|
282
|
+
type: 'validation',
|
|
283
|
+
setup: `const invalidCall = () => ${func.name}();`,
|
|
284
|
+
assertions: [
|
|
285
|
+
`expect(invalidCall).toThrow();`
|
|
286
|
+
]
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Edge case tests for complex functions
|
|
291
|
+
if (config.qualityLevel === 'comprehensive') {
|
|
292
|
+
testCases.push({
|
|
293
|
+
name: `should handle edge cases for ${func.name}`,
|
|
294
|
+
type: 'edge_case',
|
|
295
|
+
setup: `// Edge case testing for ${func.name}`,
|
|
296
|
+
assertions: [
|
|
297
|
+
`// Add edge case assertions here`
|
|
298
|
+
]
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return testCases;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Generate test cases for a class
|
|
307
|
+
*/
|
|
308
|
+
generateClassTestCases(cls, _testType, config) {
|
|
309
|
+
const testCases = [];
|
|
310
|
+
|
|
311
|
+
// Constructor test
|
|
312
|
+
testCases.push({
|
|
313
|
+
name: `should instantiate ${cls.name} correctly`,
|
|
314
|
+
type: 'instantiation',
|
|
315
|
+
setup: `const instance = new ${cls.name}();`,
|
|
316
|
+
assertions: [
|
|
317
|
+
`expect(instance).toBeInstanceOf(${cls.name});`,
|
|
318
|
+
`expect(instance).toBeDefined();`
|
|
319
|
+
]
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// Method tests
|
|
323
|
+
for (const method of cls.methods || []) {
|
|
324
|
+
testCases.push({
|
|
325
|
+
name: `should execute ${cls.name}.${method.name} correctly`,
|
|
326
|
+
type: 'method',
|
|
327
|
+
setup: `const instance = new ${cls.name}();\nconst result = ${method.async ? 'await ' : ''}instance.${method.name}();`,
|
|
328
|
+
assertions: [
|
|
329
|
+
`expect(result).toBeDefined();`
|
|
330
|
+
]
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return testCases;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Generate async operation test cases
|
|
339
|
+
*/
|
|
340
|
+
generateAsyncTestCases(_testType, config) {
|
|
341
|
+
return [
|
|
342
|
+
{
|
|
343
|
+
name: 'should handle async operations correctly',
|
|
344
|
+
type: 'async',
|
|
345
|
+
setup: '// Async operation test setup',
|
|
346
|
+
assertions: [
|
|
347
|
+
'// Add async-specific assertions',
|
|
348
|
+
'expect(result).resolves.toBeDefined();'
|
|
349
|
+
]
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
name: 'should handle async operation timeouts',
|
|
353
|
+
type: 'timeout',
|
|
354
|
+
setup: '// Timeout test setup',
|
|
355
|
+
assertions: [
|
|
356
|
+
'expect(longRunningOperation).rejects.toThrow("timeout");'
|
|
357
|
+
]
|
|
358
|
+
}
|
|
359
|
+
];
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Generate error handling test cases
|
|
364
|
+
*/
|
|
365
|
+
generateErrorHandlingTestCases(_testType, config) {
|
|
366
|
+
return [
|
|
367
|
+
{
|
|
368
|
+
name: 'should handle errors gracefully',
|
|
369
|
+
type: 'error_handling',
|
|
370
|
+
setup: '// Error simulation setup',
|
|
371
|
+
assertions: [
|
|
372
|
+
'expect(errorHandlerFunction).not.toThrow();',
|
|
373
|
+
'expect(result.error).toBeDefined();'
|
|
374
|
+
]
|
|
375
|
+
}
|
|
376
|
+
];
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Optimize content for specific test framework
|
|
381
|
+
*/
|
|
382
|
+
async optimizeForFramework(content, framework) {
|
|
383
|
+
switch (framework) {
|
|
384
|
+
case 'jest':
|
|
385
|
+
return this.optimizeForJest(content);
|
|
386
|
+
case 'mocha':
|
|
387
|
+
return this.optimizeForMocha(content);
|
|
388
|
+
case 'vitest':
|
|
389
|
+
return this.optimizeForVitest(content);
|
|
390
|
+
default:
|
|
391
|
+
return content;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Optimize for Jest framework
|
|
397
|
+
*/
|
|
398
|
+
optimizeForJest(content) {
|
|
399
|
+
// Add Jest-specific optimizations
|
|
400
|
+
let optimized = content;
|
|
401
|
+
|
|
402
|
+
// Add jest-specific expect extensions if needed
|
|
403
|
+
if (content.includes('toBeInstanceOf') && !content.includes('expect.extend')) {
|
|
404
|
+
optimized = `const { expect } = require('@jest/globals');\n\n${optimized}`;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Add performance timing for slow tests
|
|
408
|
+
if (content.includes('async') && content.length > 5000) {
|
|
409
|
+
optimized = optimized.replace(
|
|
410
|
+
/describe\('([^']+)', \(\) => \{/,
|
|
411
|
+
"describe('$1', () => {\n jest.setTimeout(10000);\n"
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return optimized;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Optimize for Mocha framework
|
|
420
|
+
*/
|
|
421
|
+
optimizeForMocha(content) {
|
|
422
|
+
// Add Mocha-specific optimizations
|
|
423
|
+
let optimized = content;
|
|
424
|
+
|
|
425
|
+
// Set timeout for async tests
|
|
426
|
+
if (content.includes('async')) {
|
|
427
|
+
optimized = optimized.replace(
|
|
428
|
+
/describe\('([^']+)', function\(\) \{/,
|
|
429
|
+
"describe('$1', function() {\n this.timeout(5000);\n"
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return optimized;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Optimize for Vitest framework
|
|
438
|
+
*/
|
|
439
|
+
optimizeForVitest(content) {
|
|
440
|
+
// Add Vitest-specific optimizations
|
|
441
|
+
let optimized = content;
|
|
442
|
+
|
|
443
|
+
// Use vi mock utilities
|
|
444
|
+
optimized = optimized.replace(/jest\.mock/g, 'vi.mock');
|
|
445
|
+
optimized = optimized.replace(/jest\.spyOn/g, 'vi.spyOn');
|
|
446
|
+
|
|
447
|
+
return optimized;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Post-process test content
|
|
452
|
+
*/
|
|
453
|
+
async postProcessTestContent(content, component, config) {
|
|
454
|
+
let processed = content;
|
|
455
|
+
|
|
456
|
+
// Replace template variables
|
|
457
|
+
processed = this.replaceTemplateVariables(processed, component, config);
|
|
458
|
+
|
|
459
|
+
// Format code
|
|
460
|
+
processed = this.formatTestCode(processed);
|
|
461
|
+
|
|
462
|
+
// Add generation metadata
|
|
463
|
+
processed = this.addGenerationMetadata(processed, component, config);
|
|
464
|
+
|
|
465
|
+
// Validate syntax
|
|
466
|
+
await this.validateTestSyntax(processed, config.framework);
|
|
467
|
+
|
|
468
|
+
return processed;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Replace template variables with actual values
|
|
473
|
+
*/
|
|
474
|
+
replaceTemplateVariables(content, component, config) {
|
|
475
|
+
let result = content;
|
|
476
|
+
|
|
477
|
+
// Replace component variables
|
|
478
|
+
result = result.replace(/\${data\.component\.name}/g, component.name);
|
|
479
|
+
result = result.replace(/\${data\.component\.type}/g, component.type);
|
|
480
|
+
result = result.replace(/\${this\.toClassName\(data\.component\.name\)}/g, this.toClassName(component.name));
|
|
481
|
+
|
|
482
|
+
// Replace config variables
|
|
483
|
+
result = result.replace(/\${data\.config\.framework}/g, config.framework);
|
|
484
|
+
result = result.replace(/\${data\.metadata\.generatedAt}/g, new Date().toISOString());
|
|
485
|
+
|
|
486
|
+
return result;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Format test code
|
|
491
|
+
*/
|
|
492
|
+
formatTestCode(content) {
|
|
493
|
+
// Basic code formatting
|
|
494
|
+
let formatted = content;
|
|
495
|
+
|
|
496
|
+
// Fix indentation
|
|
497
|
+
formatted = formatted.replace(/\n {2,}/g, match => '\n' + ' '.repeat(Math.floor(match.length / 2)));
|
|
498
|
+
|
|
499
|
+
// Remove excessive blank lines
|
|
500
|
+
formatted = formatted.replace(/\n{3,}/g, '\n\n');
|
|
501
|
+
|
|
502
|
+
// Ensure proper spacing around blocks
|
|
503
|
+
formatted = formatted.replace(/}\n{/g, '}\n\n{');
|
|
504
|
+
|
|
505
|
+
return formatted.trim();
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Add generation metadata as comments
|
|
510
|
+
*/
|
|
511
|
+
addGenerationMetadata(content, component, config) {
|
|
512
|
+
const metadata = `
|
|
513
|
+
// Generated by AIOS Test Generator
|
|
514
|
+
// Generated at: ${new Date().toISOString()}
|
|
515
|
+
// Component: ${component.type}/${component.name}
|
|
516
|
+
// Framework: ${config.framework}
|
|
517
|
+
// Quality Level: ${config.qualityLevel}
|
|
518
|
+
|
|
519
|
+
${content}`;
|
|
520
|
+
|
|
521
|
+
return metadata;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Validate test syntax
|
|
526
|
+
*/
|
|
527
|
+
async validateTestSyntax(content, framework) {
|
|
528
|
+
try {
|
|
529
|
+
// Basic syntax validation
|
|
530
|
+
if (framework === 'jest' || framework === 'vitest') {
|
|
531
|
+
// Check for required Jest/Vitest patterns
|
|
532
|
+
if (!content.includes('describe(') && !content.includes('test(') && !content.includes('it(')) {
|
|
533
|
+
throw new Error('No test cases found');
|
|
534
|
+
}
|
|
535
|
+
} else if (framework === 'mocha') {
|
|
536
|
+
// Check for required Mocha patterns
|
|
537
|
+
if (!content.includes('describe(') && !content.includes('it(')) {
|
|
538
|
+
throw new Error('No test cases found');
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Check for balanced brackets
|
|
543
|
+
const openBrackets = (content.match(/\{/g) || []).length;
|
|
544
|
+
const closeBrackets = (content.match(/\}/g) || []).length;
|
|
545
|
+
|
|
546
|
+
if (openBrackets !== closeBrackets) {
|
|
547
|
+
throw new Error('Unbalanced brackets in generated test');
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
} catch (_error) {
|
|
551
|
+
console.warn(chalk.yellow(`Test syntax validation warning: ${error.message}`));
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Helper methods for content injection and analysis
|
|
556
|
+
|
|
557
|
+
injectAdditionalTestCases(content, testCases) {
|
|
558
|
+
if (testCases.length === 0) return content;
|
|
559
|
+
|
|
560
|
+
const additionalTests = testCases.map(testCase => {
|
|
561
|
+
let caseContent = ` it('${testCase.name}', async () => {\n`;
|
|
562
|
+
|
|
563
|
+
if (testCase.setup) {
|
|
564
|
+
caseContent += ` ${testCase.setup}\n\n`;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
caseContent += testCase.assertions.map(assertion => ` ${assertion}`).join('\n');
|
|
568
|
+
caseContent += '\n });';
|
|
569
|
+
|
|
570
|
+
return caseContent;
|
|
571
|
+
}).join('\n\n');
|
|
572
|
+
|
|
573
|
+
// Find insertion point (before closing of describe block)
|
|
574
|
+
const insertionPoint = content.lastIndexOf('});');
|
|
575
|
+
if (insertionPoint !== -1) {
|
|
576
|
+
return content.slice(0, insertionPoint) + '\n\n' + additionalTests + '\n\n' + content.slice(insertionPoint);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
return content + '\n\n' + additionalTests;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
injectDynamicImports(content, imports) {
|
|
583
|
+
if (!imports) return content;
|
|
584
|
+
|
|
585
|
+
const importSection = imports.join('\n');
|
|
586
|
+
const existingImports = content.match(/^(const|import|require).*$/gm);
|
|
587
|
+
|
|
588
|
+
if (existingImports && existingImports.length > 0) {
|
|
589
|
+
// Add after existing imports
|
|
590
|
+
const lastImportIndex = content.lastIndexOf(existingImports[existingImports.length - 1]);
|
|
591
|
+
const insertionPoint = lastImportIndex + existingImports[existingImports.length - 1].length;
|
|
592
|
+
return content.slice(0, insertionPoint) + '\n' + importSection + content.slice(insertionPoint);
|
|
593
|
+
} else {
|
|
594
|
+
// Add at the beginning
|
|
595
|
+
return importSection + '\n\n' + content;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
injectSetupTeardown(content, setupTeardown) {
|
|
600
|
+
if (!setupTeardown) return content;
|
|
601
|
+
|
|
602
|
+
let injected = content;
|
|
603
|
+
|
|
604
|
+
// Find describe block and inject setup/teardown
|
|
605
|
+
const describeMatch = content.match(/describe\([^{]+\{/);
|
|
606
|
+
if (describeMatch) {
|
|
607
|
+
const insertionPoint = describeMatch.index + describeMatch[0].length;
|
|
608
|
+
injected = content.slice(0, insertionPoint) + '\n' + setupTeardown + content.slice(insertionPoint);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
return injected;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// Component analysis helper methods
|
|
615
|
+
|
|
616
|
+
extractExports(content) {
|
|
617
|
+
const exports = [];
|
|
618
|
+
|
|
619
|
+
// Extract module.exports
|
|
620
|
+
const moduleExports = content.match(/module\.exports\s*=\s*([^;]+)/);
|
|
621
|
+
if (moduleExports) {
|
|
622
|
+
exports.push({ type: 'module.exports', value: moduleExports[1] });
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// Extract named exports
|
|
626
|
+
const namedExports = content.match(/exports\.(\w+)/g);
|
|
627
|
+
if (namedExports) {
|
|
628
|
+
namedExports.forEach(exp => {
|
|
629
|
+
const name = exp.replace('exports.', '');
|
|
630
|
+
exports.push({ type: 'named', name: name });
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
return exports;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
extractFunctions(content) {
|
|
638
|
+
const functions = [];
|
|
639
|
+
|
|
640
|
+
// Extract function declarations
|
|
641
|
+
const functionDeclarations = content.match(/(?:async\s+)?function\s+(\w+)\s*\([^)]*\)/g);
|
|
642
|
+
if (functionDeclarations) {
|
|
643
|
+
functionDeclarations.forEach(func => {
|
|
644
|
+
const match = func.match(/(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/);
|
|
645
|
+
if (match) {
|
|
646
|
+
functions.push({
|
|
647
|
+
name: match[1],
|
|
648
|
+
parameters: match[2] ? match[2].split(',').map(p => p.trim()) : [],
|
|
649
|
+
async: func.includes('async'),
|
|
650
|
+
visibility: 'public'
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Extract arrow functions
|
|
657
|
+
const arrowFunctions = content.match(/(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/g);
|
|
658
|
+
if (arrowFunctions) {
|
|
659
|
+
arrowFunctions.forEach(func => {
|
|
660
|
+
const match = func.match(/(\w+)\s*=\s*(async\s+)?\(([^)]*)\)\s*=>/);
|
|
661
|
+
if (match) {
|
|
662
|
+
functions.push({
|
|
663
|
+
name: match[1],
|
|
664
|
+
parameters: match[3] ? match[3].split(',').map(p => p.trim()) : [],
|
|
665
|
+
async: !!match[2],
|
|
666
|
+
visibility: 'public'
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
return functions;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
extractClasses(content) {
|
|
676
|
+
const classes = [];
|
|
677
|
+
|
|
678
|
+
const classDeclarations = content.match(/class\s+(\w+)(?:\s+extends\s+\w+)?\s*\{[^}]*\}/g);
|
|
679
|
+
if (classDeclarations) {
|
|
680
|
+
classDeclarations.forEach(cls => {
|
|
681
|
+
const nameMatch = cls.match(/class\s+(\w+)/);
|
|
682
|
+
if (nameMatch) {
|
|
683
|
+
const methods = cls.match(/(\w+)\s*\([^)]*\)\s*\{/g) || [];
|
|
684
|
+
classes.push({
|
|
685
|
+
name: nameMatch[1],
|
|
686
|
+
methods: methods.map(method => {
|
|
687
|
+
const methodMatch = method.match(/(\w+)\s*\(/);
|
|
688
|
+
return {
|
|
689
|
+
name: methodMatch ? methodMatch[1] : 'unknown',
|
|
690
|
+
async: method.includes('async')
|
|
691
|
+
};
|
|
692
|
+
}).filter(m => m.name !== 'constructor')
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
return classes;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
extractDependencies(content) {
|
|
702
|
+
const dependencies = [];
|
|
703
|
+
|
|
704
|
+
const requires = content.match(/require\(['"]([^'"]+)['"]\)/g);
|
|
705
|
+
if (requires) {
|
|
706
|
+
requires.forEach(req => {
|
|
707
|
+
const match = req.match(/require\(['"]([^'"]+)['"]\)/);
|
|
708
|
+
if (match) {
|
|
709
|
+
dependencies.push({
|
|
710
|
+
name: match[1],
|
|
711
|
+
type: match[1].startsWith('./') || match[1].startsWith('../') ? 'local' : 'external'
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
return dependencies;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
extractAgentConfig(content) {
|
|
721
|
+
const yamlMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
722
|
+
if (yamlMatch) {
|
|
723
|
+
try {
|
|
724
|
+
const yaml = require('js-yaml');
|
|
725
|
+
return yaml.load(yamlMatch[1]);
|
|
726
|
+
} catch (_error) {
|
|
727
|
+
return null;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return null;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
extractWorkflowConfig(content) {
|
|
734
|
+
try {
|
|
735
|
+
const yaml = require('js-yaml');
|
|
736
|
+
return yaml.load(content);
|
|
737
|
+
} catch (_error) {
|
|
738
|
+
return null;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
extractTaskConfig(content) {
|
|
743
|
+
// Extract YAML frontmatter from task markdown
|
|
744
|
+
return this.extractAgentConfig(content);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
generateDynamicImports(componentAnalysis, config) {
|
|
748
|
+
const imports = [];
|
|
749
|
+
|
|
750
|
+
// Add imports for dependencies
|
|
751
|
+
for (const dep of componentAnalysis.dependencies) {
|
|
752
|
+
if (dep.type === 'local') {
|
|
753
|
+
imports.push(`const ${this.toVariableName(dep.name)} = require('${dep.name}');`);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// Add framework-specific test utilities
|
|
758
|
+
if (config.framework === 'jest') {
|
|
759
|
+
if (componentAnalysis.async_operations) {
|
|
760
|
+
imports.push(`const { jest } = require('@jest/globals');`);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
return imports.length > 0 ? imports : null;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
generateSetupTeardown(componentAnalysis, testType) {
|
|
768
|
+
const setup = [];
|
|
769
|
+
|
|
770
|
+
if (componentAnalysis.type === 'util' && componentAnalysis.classes.length > 0) {
|
|
771
|
+
setup.push(` let instance;\n`);
|
|
772
|
+
setup.push(` beforeEach(() => {\n instance = new ${componentAnalysis.classes[0].name}();\n });\n`);
|
|
773
|
+
setup.push(` afterEach(() => {\n if (instance && instance.cleanup) instance.cleanup();\n });\n`);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
return setup.length > 0 ? setup.join('\n') : null;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// Utility methods
|
|
780
|
+
|
|
781
|
+
validateGenerationInputs(component, testFile, config) {
|
|
782
|
+
if (!component || !component.name) {
|
|
783
|
+
throw new Error('Invalid component: missing name');
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
if (!testFile || !testFile.test_type) {
|
|
787
|
+
throw new Error('Invalid test file: missing test_type');
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
if (!config || !config.framework) {
|
|
791
|
+
throw new Error('Invalid config: missing framework');
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
const validFrameworks = ['jest', 'mocha', 'vitest'];
|
|
795
|
+
if (!validFrameworks.includes(config.framework)) {
|
|
796
|
+
throw new Error(`Unsupported framework: ${config.framework}`);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
updateGenerationStats(success, duration) {
|
|
801
|
+
this.generationStats.total_generated++;
|
|
802
|
+
if (success) {
|
|
803
|
+
this.generationStats.successful++;
|
|
804
|
+
} else {
|
|
805
|
+
this.generationStats.failed++;
|
|
806
|
+
}
|
|
807
|
+
this.generationStats.generation_time += duration;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
toClassName(name) {
|
|
811
|
+
return name.split('-')
|
|
812
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
813
|
+
.join('');
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
toVariableName(_path) {
|
|
817
|
+
return path.split('/').pop().replace(/[-\.]/g, '_');
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* Get generation statistics
|
|
822
|
+
*/
|
|
823
|
+
getGenerationStats() {
|
|
824
|
+
return {
|
|
825
|
+
...this.generationStats,
|
|
826
|
+
success_rate: this.generationStats.total_generated > 0
|
|
827
|
+
? this.generationStats.successful / this.generationStats.total_generated
|
|
828
|
+
: 0,
|
|
829
|
+
average_generation_time: this.generationStats.total_generated > 0
|
|
830
|
+
? this.generationStats.generation_time / this.generationStats.total_generated
|
|
831
|
+
: 0
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/**
|
|
836
|
+
* Clear generation cache
|
|
837
|
+
*/
|
|
838
|
+
clearCache() {
|
|
839
|
+
this.generationCache.clear();
|
|
840
|
+
console.log(chalk.gray('Test generation cache cleared'));
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
844
|
module.exports = TestGenerator;
|