Files
proffesor-for-testing__agen…/tests/unit/causal-discovery/causal-root-cause-analyzer.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

352 lines
10 KiB
TypeScript

/**
* Agentic QE v3 - Causal Root Cause Analyzer Service Tests
* ADR-035: STDP-based spike timing correlation for root cause analysis
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import {
CausalRootCauseAnalyzerService,
createCausalRootCauseAnalyzer,
createTestEvent,
testResultToEvents,
} from '../../../src/domains/defect-intelligence/services/causal-root-cause-analyzer';
import { MemoryBackend } from '../../../src/kernel/interfaces';
// Mock MemoryBackend
const createMockMemory = (): MemoryBackend => ({
get: vi.fn().mockResolvedValue(null),
set: vi.fn().mockResolvedValue(undefined),
delete: vi.fn().mockResolvedValue(true),
search: vi.fn().mockResolvedValue([]),
list: vi.fn().mockResolvedValue([]),
stats: vi.fn().mockResolvedValue({
totalKeys: 0,
totalSize: 0,
namespaces: [],
}),
initialize: vi.fn().mockResolvedValue(undefined),
dispose: vi.fn().mockResolvedValue(undefined),
});
describe('CausalRootCauseAnalyzerService', () => {
let service: CausalRootCauseAnalyzerService;
let mockMemory: MemoryBackend;
beforeEach(() => {
mockMemory = createMockMemory();
service = new CausalRootCauseAnalyzerService(mockMemory);
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('initialization', () => {
it('should create service with default config', () => {
expect(service).toBeDefined();
expect(service.getObservationCount()).toBe(0);
});
it('should accept custom configuration', () => {
const customService = new CausalRootCauseAnalyzerService(mockMemory, {
minConfidence: 0.5,
autoPersist: false,
});
expect(customService).toBeDefined();
});
});
describe('event observation', () => {
it('should observe single events', () => {
service.observeEvent({
type: 'test_started',
timestamp: Date.now(),
testId: 'test-1',
});
expect(service.getObservationCount()).toBe(1);
});
it('should observe batch of events', () => {
const baseTime = Date.now();
service.observeEvents([
{ type: 'code_changed', timestamp: baseTime },
{ type: 'build_started', timestamp: baseTime + 10 },
{ type: 'test_failed', timestamp: baseTime + 20 },
]);
expect(service.getObservationCount()).toBe(3);
});
});
describe('root cause analysis', () => {
beforeEach(() => {
const baseTime = Date.now();
// Create clear causal patterns
for (let i = 0; i < 15; i++) {
const t = baseTime + i * 1000;
service.observeEvent({ type: 'code_changed', timestamp: t });
service.observeEvent({ type: 'build_started', timestamp: t + 10 });
service.observeEvent({ type: 'test_failed', timestamp: t + 25 });
}
});
it('should analyze root cause successfully', async () => {
const result = await service.analyzeRootCause({
targetEvent: 'test_failed',
});
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.targetEvent).toBe('test_failed');
expect(result.value.observationCount).toBeGreaterThan(0);
}
});
it('should include direct causes in response', async () => {
const result = await service.analyzeRootCause({
targetEvent: 'test_failed',
includeIndirect: false,
});
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.directCauses).toBeDefined();
if (result.value.directCauses.length > 0) {
expect(result.value.directCauses[0].event).toBeDefined();
expect(result.value.directCauses[0].strength).toBeGreaterThan(0);
}
}
});
it('should include indirect causes when requested', async () => {
const result = await service.analyzeRootCause({
targetEvent: 'test_failed',
includeIndirect: true,
});
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.indirectCauses).toBeDefined();
}
});
it('should provide intervention recommendations', async () => {
const result = await service.analyzeRootCause({
targetEvent: 'test_failed',
maxInterventions: 3,
});
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.interventions).toBeDefined();
expect(result.value.interventions.length).toBeLessThanOrEqual(3);
}
});
it('should generate human-readable summary', async () => {
const result = await service.analyzeRootCause({
targetEvent: 'test_failed',
});
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.summary).toBeTruthy();
expect(typeof result.value.summary).toBe('string');
}
});
it('should handle unobserved events gracefully', async () => {
const result = await service.analyzeRootCause({
targetEvent: 'rollback_triggered',
});
expect(result.success).toBe(true);
if (result.success) {
// rollback_triggered was not observed, so no causes
expect(result.value.directCauses).toHaveLength(0);
// Summary should indicate no significant patterns found
expect(result.value.summary).toBeTruthy();
}
});
});
describe('predictions', () => {
beforeEach(() => {
const baseTime = Date.now();
// Create strong pattern with many observations
for (let i = 0; i < 100; i++) {
const t = baseTime + i * 1000;
service.observeEvent({ type: 'timeout', timestamp: t });
service.observeEvent({ type: 'test_failed', timestamp: t + 15 });
}
});
it('should predict causes', () => {
// The service uses default high threshold, so we check internal state
const summary = service.getSummary();
expect(summary.eventsObserved).toBe(200);
// Causes prediction depends on threshold
});
it('should predict effects', () => {
// The service uses default high threshold
const summary = service.getSummary();
expect(summary.uniqueEventTypes).toBe(2);
});
});
describe('summary', () => {
it('should provide summary statistics', () => {
const baseTime = Date.now();
service.observeEvent({ type: 'test_started', timestamp: baseTime });
service.observeEvent({ type: 'test_passed', timestamp: baseTime + 20 });
const summary = service.getSummary();
expect(summary.eventsObserved).toBe(2);
expect(summary.uniqueEventTypes).toBe(2);
});
});
describe('persistence', () => {
it('should persist state to memory', async () => {
service.observeEvent({ type: 'test_failed', timestamp: Date.now() });
const result = await service.persist();
expect(result.success).toBe(true);
expect(mockMemory.set).toHaveBeenCalled();
});
it('should restore state from memory', async () => {
const result = await service.restore();
expect(result.success).toBe(true);
expect(mockMemory.get).toHaveBeenCalled();
});
it('should handle restore with no saved state', async () => {
(mockMemory.get as ReturnType<typeof vi.fn>).mockResolvedValue(null);
const result = await service.restore();
expect(result.success).toBe(true);
});
});
describe('reset', () => {
it('should reset all learned patterns', () => {
service.observeEvent({ type: 'test_failed', timestamp: Date.now() });
expect(service.getObservationCount()).toBe(1);
service.reset();
expect(service.getObservationCount()).toBe(0);
});
});
describe('decay', () => {
it('should apply decay without errors', () => {
service.observeEvent({ type: 'test_failed', timestamp: Date.now() });
expect(() => service.decay()).not.toThrow();
});
});
});
describe('createCausalRootCauseAnalyzer', () => {
it('should create analyzer with factory function', () => {
const mockMemory = createMockMemory();
const analyzer = createCausalRootCauseAnalyzer(mockMemory);
expect(analyzer).toBeInstanceOf(CausalRootCauseAnalyzerService);
});
it('should accept custom config in factory', () => {
const mockMemory = createMockMemory();
const analyzer = createCausalRootCauseAnalyzer(mockMemory, {
minConfidence: 0.7,
});
expect(analyzer).toBeDefined();
});
});
describe('createTestEvent', () => {
it('should create event with required fields', () => {
const event = createTestEvent('test_failed');
expect(event.type).toBe('test_failed');
expect(event.timestamp).toBeDefined();
expect(event.id).toBeDefined();
});
it('should include optional fields', () => {
const event = createTestEvent('test_failed', {
testId: 'test-123',
file: 'test.spec.ts',
data: { error: 'assertion failed' },
});
expect(event.testId).toBe('test-123');
expect(event.file).toBe('test.spec.ts');
expect(event.data?.error).toBe('assertion failed');
});
});
describe('testResultToEvents', () => {
it('should convert passing test to events', () => {
const events = testResultToEvents({
testId: 'test-1',
file: 'auth.test.ts',
passed: true,
duration: 100,
});
expect(events).toHaveLength(2);
expect(events[0].type).toBe('test_started');
expect(events[1].type).toBe('test_passed');
});
it('should convert failing test to events', () => {
const events = testResultToEvents({
testId: 'test-2',
file: 'api.test.ts',
passed: false,
duration: 500,
error: 'assertion failed: expected 200 got 500',
});
expect(events).toHaveLength(2);
expect(events[0].type).toBe('test_started');
expect(events[1].type).toBe('assertion_failed');
});
it('should detect timeout failures', () => {
const events = testResultToEvents({
testId: 'test-3',
file: 'slow.test.ts',
passed: false,
duration: 30000,
error: 'timeout: test took too long',
});
expect(events[1].type).toBe('timeout');
});
it('should detect exception failures', () => {
const events = testResultToEvents({
testId: 'test-4',
file: 'crash.test.ts',
passed: false,
duration: 50,
error: 'Error: unexpected null pointer',
});
expect(events[1].type).toBe('exception');
});
});