mirror of
https://github.com/proffesor-for-testing/agentic-qe.git
synced 2026-09-19 08:45:47 +08:00
108676e602
Phase 2 Implementation (95% complete): - All 12 planned deliverables implemented - 5/5 validation criteria passing (VC1-VC5) - 0 TypeScript errors, 0 critical bugs - Comprehensive documentation (14 files) Core Infrastructure: - Agent lifecycle instrumentation (agent.ts, task.ts) - Memory operation instrumentation (memory.ts) with SwarmMemoryManager integration - Token tracking and cost calculation (CostTracker) - 4 clause evaluators (AST, metric, pattern, semantic) - Multi-agent voting protocol with consensus algorithms - Distributed trace propagation with OTEL CLI Commands (Phase 3/4 completed early): - Telemetry: status, metrics, trace, export-prometheus - Constitution: validate, list, show, evaluate Validation: - VC1: Agent trace retrieval ✅ - VC2: Token tracking ✅ - VC3: Clause evaluation ✅ - VC4: Multi-agent voting ✅ - VC5: Memory operation instrumentation ✅ Tests: - 5 validation scripts (790+ LOC) - 13+ unit tests - Integration tests for SwarmMemoryManager - All tests passing Documentation: - MEMORY-TS-ANALYSIS.md (10+ pages deep analysis) - PHASE2-FINAL-STATUS.md (comprehensive status) - BRUTAL-HONESTY-REVIEW-PHASE2.md (technical review) - SESSION-SUMMARY.md (achievements summary) - CLI documentation (telemetry + constitution) - Memory instrumentation guides (integration + examples) Files Created (60+): - Instrumentation: 4 files (agent, task, memory, index) - Evaluators: 6 files (AST, metric, pattern, semantic, base, index) - Voting: 6 files (protocol, panel, consensus, orchestrator, types, index) - Validation: 5 scripts - Documentation: 14+ files - Tests: 10+ test files - CLI: 2 command files What's Left (5%): - CLI integration testing against real OTEL collector - Real-world validation with production agent workflows Ready for Phase 3: ✅ Dashboards & Visualization 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
684 lines
20 KiB
TypeScript
684 lines
20 KiB
TypeScript
/**
|
|
* Clause Evaluation Integration Tests
|
|
*
|
|
* Tests constitution clause evaluators including:
|
|
* - AST evaluator on real TypeScript files
|
|
* - Metric evaluator with cyclomatic complexity
|
|
* - Pattern evaluator with regex patterns
|
|
* - Semantic evaluator (mocked LLM)
|
|
* - Evaluator framework coordination
|
|
*
|
|
* @version 1.0.0
|
|
* @module tests/phase2/evaluation
|
|
*/
|
|
|
|
import { describe, it, expect, beforeAll, afterAll, beforeEach, jest } from '@jest/globals';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import {
|
|
ASTEvaluator,
|
|
MetricEvaluator,
|
|
PatternEvaluator,
|
|
SemanticEvaluator,
|
|
EvaluatorFactory,
|
|
ClauseEvaluation,
|
|
ConstitutionClause,
|
|
} from '@/constitution/evaluators';
|
|
|
|
describe('Clause Evaluation Integration Tests', () => {
|
|
const testFilesDir = path.join(__dirname, '../fixtures/test-code');
|
|
const sampleTsFile = path.join(testFilesDir, 'sample.ts');
|
|
const complexTsFile = path.join(testFilesDir, 'complex.ts');
|
|
|
|
beforeAll(() => {
|
|
// Create test directory and sample files
|
|
if (!fs.existsSync(testFilesDir)) {
|
|
fs.mkdirSync(testFilesDir, { recursive: true });
|
|
}
|
|
|
|
// Create simple TypeScript file
|
|
fs.writeFileSync(
|
|
sampleTsFile,
|
|
`export class Calculator {
|
|
add(a: number, b: number): number {
|
|
return a + b;
|
|
}
|
|
|
|
subtract(a: number, b: number): number {
|
|
return a - b;
|
|
}
|
|
}
|
|
`
|
|
);
|
|
|
|
// Create complex file with high cyclomatic complexity
|
|
fs.writeFileSync(
|
|
complexTsFile,
|
|
`export class ComplexLogic {
|
|
process(input: any): string {
|
|
if (input === null) {
|
|
return 'null';
|
|
} else if (typeof input === 'string') {
|
|
if (input.length === 0) {
|
|
return 'empty';
|
|
} else if (input.length < 10) {
|
|
return 'short';
|
|
} else if (input.length < 100) {
|
|
return 'medium';
|
|
} else {
|
|
return 'long';
|
|
}
|
|
} else if (typeof input === 'number') {
|
|
if (input === 0) {
|
|
return 'zero';
|
|
} else if (input < 0) {
|
|
return 'negative';
|
|
} else if (input < 10) {
|
|
return 'small';
|
|
} else if (input < 100) {
|
|
return 'medium';
|
|
} else {
|
|
return 'large';
|
|
}
|
|
} else if (Array.isArray(input)) {
|
|
return 'array';
|
|
} else {
|
|
return 'object';
|
|
}
|
|
}
|
|
}
|
|
`
|
|
);
|
|
});
|
|
|
|
afterAll(() => {
|
|
// Cleanup test files
|
|
if (fs.existsSync(testFilesDir)) {
|
|
fs.rmSync(testFilesDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
describe('AST Evaluator on Real TypeScript Files', () => {
|
|
let astEvaluator: ASTEvaluator;
|
|
|
|
beforeEach(() => {
|
|
astEvaluator = new ASTEvaluator();
|
|
});
|
|
|
|
it('should parse TypeScript file and extract AST', async () => {
|
|
const sourceCode = fs.readFileSync(sampleTsFile, 'utf-8');
|
|
const clause: ConstitutionClause = {
|
|
id: 'ast-test-1',
|
|
type: 'ast',
|
|
condition: 'ClassDeclaration[name="Calculator"]',
|
|
action: 'allow',
|
|
severity: 'info',
|
|
message: 'Calculator class found',
|
|
metadata: {},
|
|
};
|
|
|
|
const result = await astEvaluator.evaluate(clause, {
|
|
sourceCode,
|
|
filePath: sampleTsFile,
|
|
});
|
|
|
|
expect(result.passed).toBe(true);
|
|
expect(result.evaluatorType).toBe('ast');
|
|
});
|
|
|
|
it('should detect missing required methods', async () => {
|
|
const sourceCode = fs.readFileSync(sampleTsFile, 'utf-8');
|
|
const clause: ConstitutionClause = {
|
|
id: 'ast-test-2',
|
|
type: 'ast',
|
|
condition: 'MethodDefinition[name="multiply"]',
|
|
action: 'deny',
|
|
severity: 'warning',
|
|
message: 'Missing multiply method',
|
|
metadata: {},
|
|
};
|
|
|
|
const result = await astEvaluator.evaluate(clause, {
|
|
sourceCode,
|
|
filePath: sampleTsFile,
|
|
});
|
|
|
|
expect(result.passed).toBe(false);
|
|
expect(result.message).toContain('multiply');
|
|
});
|
|
|
|
it('should validate class structure', async () => {
|
|
const sourceCode = fs.readFileSync(sampleTsFile, 'utf-8');
|
|
const clause: ConstitutionClause = {
|
|
id: 'ast-test-3',
|
|
type: 'ast',
|
|
condition: 'ClassDeclaration > MethodDefinition',
|
|
action: 'allow',
|
|
severity: 'info',
|
|
message: 'Class has methods',
|
|
metadata: { minMethods: 2 },
|
|
};
|
|
|
|
const result = await astEvaluator.evaluate(clause, {
|
|
sourceCode,
|
|
filePath: sampleTsFile,
|
|
});
|
|
|
|
expect(result.passed).toBe(true);
|
|
expect(result.metadata?.methodCount).toBeGreaterThanOrEqual(2);
|
|
});
|
|
|
|
it('should handle parse errors gracefully', async () => {
|
|
const invalidCode = 'class Invalid { method() { } } }'; // Extra closing brace
|
|
const clause: ConstitutionClause = {
|
|
id: 'ast-test-4',
|
|
type: 'ast',
|
|
condition: 'ClassDeclaration',
|
|
action: 'allow',
|
|
severity: 'error',
|
|
message: 'Parse error test',
|
|
metadata: {},
|
|
};
|
|
|
|
const result = await astEvaluator.evaluate(clause, {
|
|
sourceCode: invalidCode,
|
|
filePath: 'invalid.ts',
|
|
});
|
|
|
|
expect(result.passed).toBe(false);
|
|
expect(result.message).toContain('parse');
|
|
});
|
|
});
|
|
|
|
describe('Metric Evaluator with Cyclomatic Complexity', () => {
|
|
let metricEvaluator: MetricEvaluator;
|
|
|
|
beforeEach(() => {
|
|
metricEvaluator = new MetricEvaluator();
|
|
});
|
|
|
|
it('should calculate cyclomatic complexity', async () => {
|
|
const sourceCode = fs.readFileSync(complexTsFile, 'utf-8');
|
|
const clause: ConstitutionClause = {
|
|
id: 'metric-test-1',
|
|
type: 'metric',
|
|
condition: 'cyclomatic_complexity <= 10',
|
|
action: 'warn',
|
|
severity: 'warning',
|
|
message: 'High complexity detected',
|
|
metadata: { threshold: 10 },
|
|
};
|
|
|
|
const result = await metricEvaluator.evaluate(clause, {
|
|
sourceCode,
|
|
filePath: complexTsFile,
|
|
});
|
|
|
|
expect(result.evaluatorType).toBe('metric');
|
|
expect(result.metadata?.complexity).toBeDefined();
|
|
expect(typeof result.metadata?.complexity).toBe('number');
|
|
});
|
|
|
|
it('should enforce complexity threshold', async () => {
|
|
const sourceCode = fs.readFileSync(complexTsFile, 'utf-8');
|
|
const clause: ConstitutionClause = {
|
|
id: 'metric-test-2',
|
|
type: 'metric',
|
|
condition: 'cyclomatic_complexity <= 5',
|
|
action: 'deny',
|
|
severity: 'error',
|
|
message: 'Complexity exceeds threshold',
|
|
metadata: { threshold: 5 },
|
|
};
|
|
|
|
const result = await metricEvaluator.evaluate(clause, {
|
|
sourceCode,
|
|
filePath: complexTsFile,
|
|
});
|
|
|
|
// Complex file should fail low threshold
|
|
expect(result.passed).toBe(false);
|
|
expect(result.message).toContain('complexity');
|
|
});
|
|
|
|
it('should calculate lines of code metrics', async () => {
|
|
const sourceCode = fs.readFileSync(sampleTsFile, 'utf-8');
|
|
const clause: ConstitutionClause = {
|
|
id: 'metric-test-3',
|
|
type: 'metric',
|
|
condition: 'lines_of_code <= 100',
|
|
action: 'allow',
|
|
severity: 'info',
|
|
message: 'File size acceptable',
|
|
metadata: { maxLines: 100 },
|
|
};
|
|
|
|
const result = await metricEvaluator.evaluate(clause, {
|
|
sourceCode,
|
|
filePath: sampleTsFile,
|
|
});
|
|
|
|
expect(result.passed).toBe(true);
|
|
expect(result.metadata?.loc).toBeDefined();
|
|
expect(result.metadata?.loc).toBeLessThanOrEqual(100);
|
|
});
|
|
|
|
it('should measure code duplication', async () => {
|
|
const duplicateCode = `
|
|
function process1() {
|
|
const x = 1;
|
|
const y = 2;
|
|
return x + y;
|
|
}
|
|
|
|
function process2() {
|
|
const x = 1;
|
|
const y = 2;
|
|
return x + y;
|
|
}
|
|
`;
|
|
const clause: ConstitutionClause = {
|
|
id: 'metric-test-4',
|
|
type: 'metric',
|
|
condition: 'duplication_ratio < 0.3',
|
|
action: 'warn',
|
|
severity: 'warning',
|
|
message: 'Code duplication detected',
|
|
metadata: { threshold: 0.3 },
|
|
};
|
|
|
|
const result = await metricEvaluator.evaluate(clause, {
|
|
sourceCode: duplicateCode,
|
|
filePath: 'duplicate.ts',
|
|
});
|
|
|
|
expect(result.evaluatorType).toBe('metric');
|
|
expect(result.metadata?.duplicationRatio).toBeDefined();
|
|
});
|
|
|
|
it('should complete evaluation within performance threshold', async () => {
|
|
const sourceCode = fs.readFileSync(complexTsFile, 'utf-8');
|
|
const clause: ConstitutionClause = {
|
|
id: 'metric-perf-1',
|
|
type: 'metric',
|
|
condition: 'cyclomatic_complexity <= 20',
|
|
action: 'allow',
|
|
severity: 'info',
|
|
message: 'Performance test',
|
|
metadata: {},
|
|
};
|
|
|
|
const startTime = Date.now();
|
|
await metricEvaluator.evaluate(clause, {
|
|
sourceCode,
|
|
filePath: complexTsFile,
|
|
});
|
|
const duration = Date.now() - startTime;
|
|
|
|
expect(duration).toBeLessThan(5000); // <5s threshold
|
|
});
|
|
});
|
|
|
|
describe('Pattern Evaluator with Regex Patterns', () => {
|
|
let patternEvaluator: PatternEvaluator;
|
|
|
|
beforeEach(() => {
|
|
patternEvaluator = new PatternEvaluator();
|
|
});
|
|
|
|
it('should match simple regex patterns', async () => {
|
|
const clause: ConstitutionClause = {
|
|
id: 'pattern-test-1',
|
|
type: 'pattern',
|
|
condition: '/export\\s+class/',
|
|
action: 'allow',
|
|
severity: 'info',
|
|
message: 'Class export detected',
|
|
metadata: { pattern: '/export\\s+class/' },
|
|
};
|
|
|
|
const sourceCode = fs.readFileSync(sampleTsFile, 'utf-8');
|
|
const result = await patternEvaluator.evaluate(clause, {
|
|
sourceCode,
|
|
filePath: sampleTsFile,
|
|
});
|
|
|
|
expect(result.passed).toBe(true);
|
|
expect(result.evaluatorType).toBe('pattern');
|
|
});
|
|
|
|
it('should detect prohibited patterns', async () => {
|
|
const dangerousCode = `
|
|
console.log('test');
|
|
eval('dangerous code');
|
|
`;
|
|
const clause: ConstitutionClause = {
|
|
id: 'pattern-test-2',
|
|
type: 'pattern',
|
|
condition: '/\\beval\\s*\\(/',
|
|
action: 'deny',
|
|
severity: 'critical',
|
|
message: 'Eval usage prohibited',
|
|
metadata: { pattern: '/\\beval\\s*\\(/' },
|
|
};
|
|
|
|
const result = await patternEvaluator.evaluate(clause, {
|
|
sourceCode: dangerousCode,
|
|
filePath: 'dangerous.ts',
|
|
});
|
|
|
|
expect(result.passed).toBe(false);
|
|
expect(result.severity).toBe('critical');
|
|
expect(result.message).toContain('eval');
|
|
});
|
|
|
|
it('should match multiple pattern occurrences', async () => {
|
|
const multiPattern = `
|
|
function test1() {}
|
|
function test2() {}
|
|
function test3() {}
|
|
`;
|
|
const clause: ConstitutionClause = {
|
|
id: 'pattern-test-3',
|
|
type: 'pattern',
|
|
condition: '/function\\s+\\w+/',
|
|
action: 'allow',
|
|
severity: 'info',
|
|
message: 'Functions found',
|
|
metadata: { pattern: '/function\\s+\\w+/', minMatches: 3 },
|
|
};
|
|
|
|
const result = await patternEvaluator.evaluate(clause, {
|
|
sourceCode: multiPattern,
|
|
filePath: 'multi.ts',
|
|
});
|
|
|
|
expect(result.passed).toBe(true);
|
|
expect(result.metadata?.matchCount).toBeGreaterThanOrEqual(3);
|
|
});
|
|
|
|
it('should support complex regex with capture groups', async () => {
|
|
const clause: ConstitutionClause = {
|
|
id: 'pattern-test-4',
|
|
type: 'pattern',
|
|
condition: '/class\\s+(\\w+)\\s*\\{/',
|
|
action: 'allow',
|
|
severity: 'info',
|
|
message: 'Extract class names',
|
|
metadata: { pattern: '/class\\s+(\\w+)\\s*\\{/' },
|
|
};
|
|
|
|
const sourceCode = fs.readFileSync(sampleTsFile, 'utf-8');
|
|
const result = await patternEvaluator.evaluate(clause, {
|
|
sourceCode,
|
|
filePath: sampleTsFile,
|
|
});
|
|
|
|
expect(result.passed).toBe(true);
|
|
expect(result.metadata?.matches).toBeDefined();
|
|
expect(result.metadata?.matches).toContain('Calculator');
|
|
});
|
|
});
|
|
|
|
describe('Semantic Evaluator (Mocked LLM)', () => {
|
|
let semanticEvaluator: SemanticEvaluator;
|
|
|
|
beforeEach(() => {
|
|
semanticEvaluator = new SemanticEvaluator();
|
|
|
|
// Mock LLM API calls
|
|
jest.spyOn(semanticEvaluator as any, 'callLLM').mockImplementation(async (prompt: string) => {
|
|
if (prompt.includes('Calculator')) {
|
|
return {
|
|
passed: true,
|
|
reasoning: 'Code follows single responsibility principle',
|
|
confidence: 0.9,
|
|
};
|
|
} else if (prompt.includes('ComplexLogic')) {
|
|
return {
|
|
passed: false,
|
|
reasoning: 'High cyclomatic complexity violates maintainability',
|
|
confidence: 0.85,
|
|
};
|
|
}
|
|
return { passed: true, reasoning: 'Generic analysis', confidence: 0.5 };
|
|
});
|
|
});
|
|
|
|
it('should perform semantic analysis with mocked LLM', async () => {
|
|
const clause: ConstitutionClause = {
|
|
id: 'semantic-test-1',
|
|
type: 'semantic',
|
|
condition: 'code follows SOLID principles',
|
|
action: 'allow',
|
|
severity: 'warning',
|
|
message: 'SOLID principles check',
|
|
metadata: { principle: 'single_responsibility' },
|
|
};
|
|
|
|
const sourceCode = fs.readFileSync(sampleTsFile, 'utf-8');
|
|
const result = await semanticEvaluator.evaluate(clause, {
|
|
sourceCode,
|
|
filePath: sampleTsFile,
|
|
});
|
|
|
|
expect(result.evaluatorType).toBe('semantic');
|
|
expect(result.passed).toBe(true);
|
|
expect(result.metadata?.reasoning).toBeDefined();
|
|
expect(result.metadata?.confidence).toBeGreaterThan(0.8);
|
|
});
|
|
|
|
it('should detect maintainability issues', async () => {
|
|
const clause: ConstitutionClause = {
|
|
id: 'semantic-test-2',
|
|
type: 'semantic',
|
|
condition: 'code is maintainable',
|
|
action: 'warn',
|
|
severity: 'warning',
|
|
message: 'Maintainability check',
|
|
metadata: { aspect: 'complexity' },
|
|
};
|
|
|
|
const sourceCode = fs.readFileSync(complexTsFile, 'utf-8');
|
|
const result = await semanticEvaluator.evaluate(clause, {
|
|
sourceCode,
|
|
filePath: complexTsFile,
|
|
});
|
|
|
|
expect(result.passed).toBe(false);
|
|
expect(result.message).toContain('complexity');
|
|
});
|
|
|
|
it('should provide confidence scores', async () => {
|
|
const clause: ConstitutionClause = {
|
|
id: 'semantic-test-3',
|
|
type: 'semantic',
|
|
condition: 'code quality assessment',
|
|
action: 'allow',
|
|
severity: 'info',
|
|
message: 'Quality check',
|
|
metadata: {},
|
|
};
|
|
|
|
const sourceCode = fs.readFileSync(sampleTsFile, 'utf-8');
|
|
const result = await semanticEvaluator.evaluate(clause, {
|
|
sourceCode,
|
|
filePath: sampleTsFile,
|
|
});
|
|
|
|
expect(result.metadata?.confidence).toBeDefined();
|
|
expect(result.metadata?.confidence).toBeGreaterThanOrEqual(0);
|
|
expect(result.metadata?.confidence).toBeLessThanOrEqual(1);
|
|
});
|
|
});
|
|
|
|
describe('Evaluator Framework Coordination', () => {
|
|
beforeEach(() => {
|
|
// Register all evaluators
|
|
EvaluatorFactory.register('ast', () => new ASTEvaluator());
|
|
EvaluatorFactory.register('metric', () => new MetricEvaluator());
|
|
EvaluatorFactory.register('pattern', () => new PatternEvaluator());
|
|
EvaluatorFactory.register('semantic', () => new SemanticEvaluator());
|
|
});
|
|
|
|
it('should coordinate multiple evaluators', async () => {
|
|
const clauses: ConstitutionClause[] = [
|
|
{
|
|
id: 'coord-1',
|
|
type: 'ast',
|
|
condition: 'ClassDeclaration',
|
|
action: 'allow',
|
|
severity: 'info',
|
|
message: 'AST check',
|
|
metadata: {},
|
|
},
|
|
{
|
|
id: 'coord-2',
|
|
type: 'metric',
|
|
condition: 'cyclomatic_complexity <= 10',
|
|
action: 'allow',
|
|
severity: 'warning',
|
|
message: 'Metric check',
|
|
metadata: {},
|
|
},
|
|
{
|
|
id: 'coord-3',
|
|
type: 'pattern',
|
|
condition: '/export/',
|
|
action: 'allow',
|
|
severity: 'info',
|
|
message: 'Pattern check',
|
|
metadata: {},
|
|
},
|
|
];
|
|
|
|
const sourceCode = fs.readFileSync(sampleTsFile, 'utf-8');
|
|
const results: ClauseEvaluation[] = [];
|
|
|
|
for (const clause of clauses) {
|
|
const evaluator = EvaluatorFactory.create(clause.type);
|
|
const result = await evaluator.evaluate(clause, {
|
|
sourceCode,
|
|
filePath: sampleTsFile,
|
|
});
|
|
results.push(result);
|
|
}
|
|
|
|
expect(results.length).toBe(3);
|
|
expect(results.every(r => r.clauseId)).toBe(true);
|
|
expect(results.map(r => r.evaluatorType)).toEqual(['ast', 'metric', 'pattern']);
|
|
});
|
|
|
|
it('should handle evaluator failures gracefully', async () => {
|
|
const clause: ConstitutionClause = {
|
|
id: 'error-test-1',
|
|
type: 'ast',
|
|
condition: 'invalid syntax',
|
|
action: 'deny',
|
|
severity: 'error',
|
|
message: 'Error handling test',
|
|
metadata: {},
|
|
};
|
|
|
|
const evaluator = EvaluatorFactory.create('ast');
|
|
const result = await evaluator.evaluate(clause, {
|
|
sourceCode: 'invalid code {{{',
|
|
filePath: 'error.ts',
|
|
});
|
|
|
|
expect(result.passed).toBe(false);
|
|
expect(result.severity).toBe('error');
|
|
});
|
|
|
|
it('should aggregate evaluation results', async () => {
|
|
const clauses: ConstitutionClause[] = [
|
|
{
|
|
id: 'agg-1',
|
|
type: 'metric',
|
|
condition: 'lines_of_code <= 50',
|
|
action: 'allow',
|
|
severity: 'info',
|
|
message: 'Size check',
|
|
metadata: {},
|
|
},
|
|
{
|
|
id: 'agg-2',
|
|
type: 'pattern',
|
|
condition: '/TODO|FIXME/',
|
|
action: 'warn',
|
|
severity: 'warning',
|
|
message: 'Tech debt check',
|
|
metadata: {},
|
|
},
|
|
];
|
|
|
|
const sourceCode = fs.readFileSync(sampleTsFile, 'utf-8');
|
|
const results: ClauseEvaluation[] = [];
|
|
|
|
for (const clause of clauses) {
|
|
const evaluator = EvaluatorFactory.create(clause.type);
|
|
const result = await evaluator.evaluate(clause, {
|
|
sourceCode,
|
|
filePath: sampleTsFile,
|
|
});
|
|
results.push(result);
|
|
}
|
|
|
|
const allPassed = results.every(r => r.passed);
|
|
const hasWarnings = results.some(r => r.severity === 'warning');
|
|
const hasErrors = results.some(r => r.severity === 'error');
|
|
|
|
expect(results.length).toBe(2);
|
|
expect(typeof allPassed).toBe('boolean');
|
|
expect(typeof hasWarnings).toBe('boolean');
|
|
expect(typeof hasErrors).toBe('boolean');
|
|
});
|
|
|
|
it('should complete multi-evaluator workflow within threshold', async () => {
|
|
const clauses: ConstitutionClause[] = [
|
|
{
|
|
id: 'perf-1',
|
|
type: 'ast',
|
|
condition: 'ClassDeclaration',
|
|
action: 'allow',
|
|
severity: 'info',
|
|
message: 'AST',
|
|
metadata: {},
|
|
},
|
|
{
|
|
id: 'perf-2',
|
|
type: 'metric',
|
|
condition: 'cyclomatic_complexity <= 15',
|
|
action: 'allow',
|
|
severity: 'warning',
|
|
message: 'Metric',
|
|
metadata: {},
|
|
},
|
|
{
|
|
id: 'perf-3',
|
|
type: 'pattern',
|
|
condition: '/class/',
|
|
action: 'allow',
|
|
severity: 'info',
|
|
message: 'Pattern',
|
|
metadata: {},
|
|
},
|
|
];
|
|
|
|
const sourceCode = fs.readFileSync(sampleTsFile, 'utf-8');
|
|
const startTime = Date.now();
|
|
|
|
for (const clause of clauses) {
|
|
const evaluator = EvaluatorFactory.create(clause.type);
|
|
await evaluator.evaluate(clause, {
|
|
sourceCode,
|
|
filePath: sampleTsFile,
|
|
});
|
|
}
|
|
|
|
const duration = Date.now() - startTime;
|
|
expect(duration).toBeLessThan(5000); // <5s for all evaluators
|
|
});
|
|
});
|
|
});
|