Files
proffesor-for-testing__agen…/tests/integration/coverage-analysis-qlearning-integration.test.ts
Profa 2253d05618 feat: flatten project structure — archive v2, promote v3 to root
Remove the nested v3/ subdirectory by moving all active source code,
tests, configs, and assets directly to the project root. Archive v2/
(dead code, 847MB) to v2-archive.tar.gz.

Key changes:
- Move v3/src/ → src/, v3/tests/ → tests/, v3/packages/ → packages/
- Move v3/tsconfig.json, v3/vitest.config.ts to root
- Merge v3/package.json into root package.json (ESM, scripts, deps, exports)
- Merge v3/scripts/ into scripts/, v3/implementation/ into docs/implementation/
- Update all CI workflows to remove "cd v3 &&" and v3/ path prefixes
- Fix all init installer path resolution (3-level-up → 2-level-up)
- Fix hooks to use "node ./dist/cli/bundle.js" instead of bare "aqe"
- Update settings.json ADR/DDD directories to docs/implementation/
- Fix helper scripts (statusline, adr-compliance, ddd-tracker, guidance-hooks)
- Update 58 agent definitions: aqe/v3/ → aqe/ memory namespaces
- Update 15 guidance shards: v3/src/ → src/ path references
- Fix sync interfaces: remove v3/ and v2/ path references
- Fix test path calculations (process.cwd() + '..' no longer needed)
- Fix flaky perf test threshold (500ms → 1000ms for CI environments)
- Fix complexity-analyzer test to match ADR-051 keyword-based eligibility

Build passes, 17,901 tests pass, CLI and MCP bundles verified.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 11:35:41 +00:00

390 lines
14 KiB
TypeScript

/**
* Agentic QE v3 - Q-Learning Integration Tests for Coverage Analysis
*
* Tests the integration between Q-Learning RL algorithm and coverage-analysis domain
* for intelligent test prioritization and path optimization.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { CoverageAnalysisCoordinator } from '../../src/domains/coverage-analysis/coordinator';
import type {
CoverageGap,
CoverageQLPrediction,
QLPrioritizedTests,
} from '../../src/domains/coverage-analysis/interfaces';
import type { RLExperience } from '../../src/integrations/rl-suite/interfaces';
import { InMemoryEventBus } from '../../src/kernel/event-bus';
import { InMemoryBackend } from '../../src/kernel/memory-backend';
import { checkRuvectorPackagesAvailable } from '../../src/integrations/ruvector/wrappers';
// Check if @ruvector/gnn native operations work (required for full RL stack)
const canTest = checkRuvectorPackagesAvailable();
describe.runIf(canTest.gnn)('Coverage Analysis Q-Learning Integration', () => {
let coordinator: CoverageAnalysisCoordinator;
let eventBus: InMemoryEventBus;
let memory: InMemoryBackend;
beforeEach(async () => {
eventBus = new InMemoryEventBus();
memory = new InMemoryBackend();
coordinator = new CoverageAnalysisCoordinator(eventBus, memory);
await coordinator.initialize();
});
afterEach(async () => {
await coordinator.dispose();
});
describe('Q-Learning State Representation', () => {
it('should convert coverage gap to Q-Learning state with correct features', async () => {
const gap: CoverageGap = {
id: 'gap-1',
file: 'src/services/auth.service.ts',
lines: [10, 15, 20, 25, 30],
branches: [5, 10],
riskScore: 8.5,
severity: 'high',
recommendation: 'Add unit tests for authentication logic',
};
const prediction = await coordinator.predictQL(gap);
expect(prediction).toBeDefined();
expect(prediction.action).toBeDefined();
expect(prediction.action.type).toMatch(/^(generate-unit|generate-integration|prioritize|skip)$/);
expect(prediction.confidence).toBeGreaterThanOrEqual(0);
expect(prediction.confidence).toBeLessThanOrEqual(1);
// When Q-learning is not trained, predictions may be 0 - this is acceptable
expect(prediction.estimatedCoverageGain).toBeGreaterThanOrEqual(0);
expect(prediction.estimatedTestCount).toBeGreaterThanOrEqual(0);
});
it('should generate different predictions for different gap types', async () => {
const highRiskGap: CoverageGap = {
id: 'gap-high',
file: 'src/services/payment.service.ts',
lines: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
branches: [1, 2, 3],
riskScore: 9.5,
severity: 'critical',
recommendation: 'Critical payment logic needs tests',
};
const lowRiskGap: CoverageGap = {
id: 'gap-low',
file: 'src/utils/helpers.ts',
lines: [100],
branches: [],
riskScore: 2.0,
severity: 'low',
recommendation: 'Add test for utility function',
};
const highRiskPrediction = await coordinator.predictQL(highRiskGap);
const lowRiskPrediction = await coordinator.predictQL(lowRiskGap);
// Both predictions should be valid
expect(highRiskPrediction.action).toBeDefined();
expect(lowRiskPrediction.action).toBeDefined();
// High-risk gaps should have higher or equal estimated coverage gain
// (due to more uncovered lines and branches)
// Note: When Q-learning is not trained, values may be non-deterministic
// and very small - skip relative comparison in that case
const UNTRAINED_THRESHOLD = 0.001;
const bothUntrainedOrZero =
highRiskPrediction.estimatedCoverageGain < UNTRAINED_THRESHOLD &&
lowRiskPrediction.estimatedCoverageGain < UNTRAINED_THRESHOLD;
if (!bothUntrainedOrZero) {
// Only compare when the model appears to have learned something meaningful
expect(highRiskPrediction.estimatedCoverageGain).toBeGreaterThanOrEqual(
lowRiskPrediction.estimatedCoverageGain
);
}
// When both are effectively zero/untrained, the test passes trivially
});
});
describe('Test Prioritization with Q-Learning', () => {
it('should prioritize tests based on Q-Learning predictions', async () => {
const gaps: CoverageGap[] = [
{
id: 'gap-1',
file: 'src/services/auth.service.ts',
lines: [10, 15, 20],
branches: [5, 10],
riskScore: 8.5,
severity: 'high',
recommendation: 'Test authentication logic',
},
{
id: 'gap-2',
file: 'src/controllers/user.controller.ts',
lines: [50, 55, 60],
branches: [20],
riskScore: 6.0,
severity: 'medium',
recommendation: 'Test user endpoints',
},
{
id: 'gap-3',
file: 'src/utils/date-helper.ts',
lines: [100],
branches: [],
riskScore: 2.0,
severity: 'low',
recommendation: 'Test date formatting',
},
];
const result = await coordinator.getQLRecommendations(gaps, 3);
expect(result.success).toBe(true);
if (result.success) {
const prioritized = result.value;
expect(prioritized.tests).toHaveLength(3);
// Q-learning may choose skip actions, resulting in 0 total gain - this is valid
expect(prioritized.totalEstimatedCoverageGain).toBeGreaterThanOrEqual(0);
expect(prioritized.totalEstimatedDuration).toBeGreaterThanOrEqual(0);
expect(prioritized.reasoning).toContain('Q-Learning');
// Tests should be sorted by priority (Q-value) descending
const priorities = prioritized.tests.map((t) => t.priority);
for (let i = 1; i < priorities.length; i++) {
expect(priorities[i - 1]).toBeGreaterThanOrEqual(priorities[i]);
}
// High-risk gap should be prioritized higher
const authTest = prioritized.tests.find((t) => t.filePath.includes('auth.service'));
expect(authTest).toBeDefined();
if (authTest) {
expect(authTest.testType).toBeDefined();
// Coverage gain may be 0 if skip action was chosen for this gap
expect(authTest.estimatedCoverageGain).toBeGreaterThanOrEqual(0);
expect(authTest.confidence).toBeGreaterThanOrEqual(0);
}
}
});
it('should respect limit parameter for recommendations', async () => {
const gaps: CoverageGap[] = Array.from({ length: 20 }, (_, i) => ({
id: `gap-${i}`,
file: `src/file-${i}.ts`,
lines: [i * 10],
branches: [],
riskScore: 5.0,
severity: 'medium' as const,
recommendation: 'Test this file',
}));
const result = await coordinator.getQLRecommendations(gaps, 5);
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.tests).toHaveLength(5);
}
});
});
describe('Q-Learning Training Integration', () => {
it('should train Q-Learning with execution results', async () => {
const gap: CoverageGap = {
id: 'gap-train-1',
file: 'src/services/training.service.ts',
lines: [10, 20, 30],
branches: [5, 15],
riskScore: 7.0,
severity: 'high',
recommendation: 'Test training logic',
};
// Get initial prediction
const initialPrediction = await coordinator.predictQL(gap);
const initialValue = initialPrediction.value;
// Simulate training experience
const experience: RLExperience = {
state: {
id: gap.id,
features: [
gap.riskScore / 10,
gap.lines.length / 50,
gap.branches.length / 10,
0.75, // severity
0.7, // complexity
0.6, // change frequency
0.8, // business criticality
3, // line to branch ratio
0.5, // coverage potential
0.6, // file complexity
0.4, // test complexity
0.5, // execution cost
],
metadata: { gap },
},
action: initialPrediction.action,
reward: 0.8, // Positive reward for good prediction
nextState: {
id: `${gap.id}-next`,
features: [
0.5, // reduced risk after testing
0.1, // fewer uncovered lines
0.2, // fewer uncovered branches
0.5,
0.7,
0.6,
0.8,
3,
0.5,
0.6,
0.4,
0.5,
],
},
done: true,
timestamp: new Date(),
};
// Train the model
await coordinator.trainQL(experience);
// Get updated prediction
const updatedPrediction = await coordinator.predictQL(gap);
// Model should have learned (values may change due to exploration)
expect(updatedPrediction).toBeDefined();
expect(updatedPrediction.action).toBeDefined();
});
it('should persist training state to memory', async () => {
const experience: RLExperience = {
state: {
id: 'test-state',
features: [0.5, 0.5, 0.5, 0.5],
},
action: { type: 'generate-unit', value: 'test' },
reward: 1.0,
nextState: {
id: 'test-next-state',
features: [0.6, 0.6, 0.6, 0.6],
},
done: true,
};
// Train multiple times to trigger persistence (every 10 episodes)
for (let i = 0; i < 10; i++) {
await coordinator.trainQL(experience);
}
// Verify persistence (check if memory has the model state)
const modelState = await memory.get('coverage:ql:model:latest');
expect(modelState).toBeDefined();
expect(modelState).toHaveProperty('stats');
});
});
describe('Coverage Gain Estimation', () => {
it('should estimate different coverage gains for different actions', async () => {
const gap: CoverageGap = {
id: 'gap-estimate',
file: 'src/services/estimate.service.ts',
lines: [10, 20, 30, 40, 50],
branches: [5, 15, 25],
riskScore: 6.0,
severity: 'medium',
recommendation: 'Test estimation logic',
};
const prediction = await coordinator.predictQL(gap);
// Different action types should have different coverage gains
// Skip actions can have 0 gain, all others should be > 0
const actionType = prediction.action.type;
if (actionType === 'skip') {
expect(prediction.estimatedCoverageGain).toBeGreaterThanOrEqual(0);
} else {
expect(prediction.estimatedCoverageGain).toBeGreaterThan(0);
}
// Always expect a valid number
expect(typeof prediction.estimatedCoverageGain).toBe('number');
expect(prediction.estimatedCoverageGain).toBeGreaterThanOrEqual(0);
});
});
describe('Test Duration Estimation', () => {
it('should estimate longer duration for integration tests', async () => {
const gap: CoverageGap = {
id: 'gap-duration',
file: 'src/services/duration.service.ts',
lines: [10, 20, 30, 40, 50],
branches: [5, 15],
riskScore: 5.0,
severity: 'medium',
recommendation: 'Test duration logic',
};
const result = await coordinator.getQLRecommendations([gap], 1);
expect(result.success).toBe(true);
if (result.success) {
const test = result.value.tests[0];
// Integration tests should take longer than unit tests
if (test.testType === 'integration') {
expect(test.estimatedDuration).toBeGreaterThan(gap.lines.length * 0.5);
}
}
});
});
describe('Action Type Mapping', () => {
it('should map actions to appropriate test types', async () => {
const gap: CoverageGap = {
id: 'gap-action-map',
file: 'src/services/actionmap.service.ts',
lines: [10, 20],
branches: [5],
riskScore: 5.0,
severity: 'medium',
recommendation: 'Test action mapping',
};
const prediction = await coordinator.predictQL(gap);
// Verify action type is valid
expect(['generate-unit', 'generate-integration', 'prioritize', 'skip']).toContain(
prediction.action.type
);
// Verify test count is reasonable
expect(prediction.estimatedTestCount).toBeGreaterThanOrEqual(0);
expect(prediction.estimatedTestCount).toBeLessThanOrEqual(5);
});
});
describe('Confidence and Reasoning', () => {
it('should provide reasoning for predictions', async () => {
const gap: CoverageGap = {
id: 'gap-reasoning',
file: 'src/services/reasoning.service.ts',
lines: [10, 20, 30],
branches: [5, 10],
riskScore: 7.5,
severity: 'high',
recommendation: 'Test reasoning logic',
};
const prediction = await coordinator.predictQL(gap);
expect(prediction.reasoning).toBeDefined();
expect(prediction.reasoning.length).toBeGreaterThan(0);
expect(prediction.confidence).toBeGreaterThanOrEqual(0);
expect(prediction.confidence).toBeLessThanOrEqual(1);
});
});
});