/**
* Tests for expanded error detection capabilities
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { promises as fs } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import { BuildToolDetector } from '@/detectors/build-tool-detector.js';
import { ProcessMonitorDetector } from '@/detectors/process-monitor-detector.js';
import { MultiLanguageDetector } from '@/detectors/multi-language-detector.js';
import type { ErrorDetectorOptions } from '@/detectors/base-detector.js';
import { ErrorCategory, ErrorSeverity } from '@/types/errors.js';
describe('Expanded Error Detection', () => {
let tempDir: string;
let detectorOptions: ErrorDetectorOptions;
beforeEach(async () => {
tempDir = await fs.mkdtemp(join(tmpdir(), 'expanded-error-test-'));
detectorOptions = {
enabled: true,
includeWarnings: true,
filters: {
categories: [],
severities: [],
excludeFiles: [],
excludePatterns: []
},
bufferSize: 100,
realTime: true,
polling: {
interval: 1000,
maxRetries: 3
}
};
});
afterEach(async () => {
try {
await fs.rm(tempDir, { recursive: true, force: true });
} catch {
// Ignore cleanup errors
}
});
describe('BuildToolDetector', () => {
let detector: BuildToolDetector;
beforeEach(() => {
detector = new BuildToolDetector(detectorOptions, {
workspaceRoot: tempDir,
timeoutMs: 5000,
parallelBuilds: false
});
});
afterEach(async () => {
if (detector.isRunning) {
await detector.stop();
}
});
it('should initialize with correct capabilities', () => {
const capabilities = detector.getCapabilities();
expect(capabilities.supportsRealTime).toBe(true);
expect(capabilities.supportsPolling).toBe(true);
expect(capabilities.supportsFileWatching).toBe(true);
expect(capabilities.supportedLanguages).toContain('typescript');
expect(capabilities.supportedLanguages).toContain('rust');
expect(capabilities.supportedLanguages).toContain('go');
expect(capabilities.supportedFrameworks).toContain('webpack');
expect(capabilities.supportedFrameworks).toContain('cargo');
});
it('should detect TypeScript compilation errors', async () => {
// Create a TypeScript file with errors
const tsFile = join(tempDir, 'error.ts');
await fs.writeFile(tsFile, `
interface User {
name: string;
age: number;
}
const user: User = {
name: "John",
// Missing age property - should cause error
};
// Type error - string assigned to number
const count: number = "invalid";
`);
// Create tsconfig.json
await fs.writeFile(join(tempDir, 'tsconfig.json'), JSON.stringify({
compilerOptions: {
target: 'es2020',
strict: true
}
}));
const errors = await detector.detectErrors();
// Should detect compilation errors if TypeScript is available
if (errors.length > 0) {
expect(errors.some(e => e.type === 'BuildFailure' || e.type === 'BuildPatternMatch')).toBe(true);
expect(errors.some(e => e.category === ErrorCategory.SYNTAX || e.category === ErrorCategory.LOGICAL)).toBe(true);
}
});
it('should handle build tool configuration changes', async () => {
const configChanged = vi.fn();
detector.on('config-updated', configChanged);
await detector.start();
// Create and modify a config file
const configFile = join(tempDir, 'tsconfig.json');
await fs.writeFile(configFile, '{"compilerOptions": {}}');
// Wait for file watching to detect change
await new Promise(resolve => setTimeout(resolve, 200));
// Config change handling is internal, but we can verify the detector is running
expect(detector.isRunning).toBe(true);
});
it('should provide build process information', async () => {
await detector.start();
const buildTools = detector.getEnabledBuildTools();
expect(Array.isArray(buildTools)).toBe(true);
const buildProcesses = detector.getBuildProcesses();
expect(Array.isArray(buildProcesses)).toBe(true);
});
it('should handle build tool updates', () => {
const configUpdated = vi.fn();
detector.on('config-updated', configUpdated);
detector.updateBuildToolConfig('TypeScript', { enabled: false });
expect(configUpdated).toHaveBeenCalledWith({
tool: 'TypeScript',
updates: { enabled: false }
});
});
});
describe('ProcessMonitorDetector', () => {
let detector: ProcessMonitorDetector;
beforeEach(() => {
detector = new ProcessMonitorDetector(detectorOptions, {
workspaceRoot: tempDir,
monitorInterval: 1000,
hangingProcessThreshold: 5, // 5 seconds for testing
memoryThreshold: 100, // 100MB
cpuThreshold: 80
});
});
afterEach(async () => {
if (detector.isRunning) {
await detector.stop();
}
});
it('should initialize with correct capabilities', () => {
const capabilities = detector.getCapabilities();
expect(capabilities.supportsRealTime).toBe(true);
expect(capabilities.supportsPolling).toBe(true);
expect(capabilities.supportsFileWatching).toBe(false);
expect(capabilities.supportedLanguages).toContain('*');
expect(capabilities.supportedFrameworks).toContain('*');
});
it('should start and stop monitoring correctly', async () => {
expect(detector.isRunning).toBe(false);
await detector.start();
expect(detector.isRunning).toBe(true);
await detector.stop();
expect(detector.isRunning).toBe(false);
});
it('should track processes', async () => {
await detector.start();
// Let it run for a short time to collect process data
await new Promise(resolve => setTimeout(resolve, 1500));
const trackedProcesses = detector.getTrackedProcesses();
expect(Array.isArray(trackedProcesses)).toBe(true);
const processAlerts = detector.getProcessAlerts();
expect(processAlerts instanceof Map).toBe(true);
});
it('should detect process issues', async () => {
const errors = await detector.detectErrors();
// Should return array even if no errors found
expect(Array.isArray(errors)).toBe(true);
// If errors are found, they should have correct structure
for (const error of errors) {
expect(error).toHaveProperty('id');
expect(error).toHaveProperty('message');
expect(error).toHaveProperty('type');
expect(error).toHaveProperty('category');
expect(error).toHaveProperty('severity');
expect(['HangingProcess', 'ResourceIssue', 'ZombieProcess'].includes(error.type)).toBe(true);
}
});
it('should handle configuration updates', () => {
const configUpdated = vi.fn();
detector.on('config-updated', configUpdated);
detector.updateConfig({ memoryThreshold: 200 });
expect(configUpdated).toHaveBeenCalledWith(
expect.objectContaining({ memoryThreshold: 200 })
);
});
});
describe('MultiLanguageDetector', () => {
let detector: MultiLanguageDetector;
beforeEach(() => {
detector = new MultiLanguageDetector(detectorOptions, {
workspaceRoot: tempDir,
parallelCompilation: false,
timeoutMs: 10000,
includeWarnings: true,
crossLanguageAnalysis: true
});
});
afterEach(async () => {
if (detector.isRunning) {
await detector.stop();
}
});
it('should initialize with correct capabilities', () => {
const capabilities = detector.getCapabilities();
expect(capabilities.supportsRealTime).toBe(true);
expect(capabilities.supportsPolling).toBe(true);
expect(capabilities.supportsFileWatching).toBe(true);
expect(capabilities.supportedLanguages).toContain('typescript');
expect(capabilities.supportedLanguages).toContain('rust');
expect(capabilities.supportedLanguages).toContain('go');
expect(capabilities.supportedLanguages).toContain('python');
expect(capabilities.supportedFrameworks).toContain('cargo');
expect(capabilities.supportedFrameworks).toContain('babel');
});
it('should detect available compilers', async () => {
await detector.start();
const enabledLanguages = detector.getEnabledLanguages();
expect(Array.isArray(enabledLanguages)).toBe(true);
// Should have at least some languages enabled (depending on system)
// TypeScript is most likely to be available in a Node.js environment
const hasTypeScript = enabledLanguages.some(lang => lang.language === 'typescript');
if (hasTypeScript) {
expect(enabledLanguages.find(lang => lang.language === 'typescript')?.enabled).toBe(true);
}
});
it('should handle TypeScript compilation', async () => {
// Create TypeScript files
await fs.writeFile(join(tempDir, 'valid.ts'), `
interface Config {
name: string;
version: number;
}
const config: Config = {
name: "test",
version: 1
};
export { config };
`);
await fs.writeFile(join(tempDir, 'invalid.ts'), `
interface User {
id: number;
name: string;
}
const user: User = {
id: "invalid", // Type error
name: 123 // Type error
};
`);
await fs.writeFile(join(tempDir, 'tsconfig.json'), JSON.stringify({
compilerOptions: {
target: 'es2020',
strict: true,
noEmit: true
}
}));
const errors = await detector.detectErrors();
// Should detect TypeScript errors if compiler is available
const tsErrors = errors.filter(e =>
e.context.metadata?.language === 'typescript' ||
e.type.includes('typescript') ||
e.type.includes('TypeScript')
);
if (tsErrors.length > 0) {
expect(tsErrors.some(e => e.category === ErrorCategory.SYNTAX || e.category === ErrorCategory.LOGICAL)).toBe(true);
expect(tsErrors.some(e => e.severity === ErrorSeverity.HIGH || e.severity === ErrorSeverity.MEDIUM)).toBe(true);
}
});
it('should handle file watching', async () => {
const errorDetected = vi.fn();
detector.on('error-detected', errorDetected);
await detector.start();
// Create a file with errors
const errorFile = join(tempDir, 'watch-test.ts');
await fs.writeFile(errorFile, `
const invalidCode: number = "string";
`);
// Wait for file watching to process
await new Promise(resolve => setTimeout(resolve, 1000));
// File watching might trigger error detection
// This depends on TypeScript being available
});
it('should provide compilation queue information', async () => {
await detector.start();
const queue = detector.getCompilationQueue();
expect(Array.isArray(queue)).toBe(true);
});
it('should handle forced compilation', async () => {
await detector.start();
const results = await detector.forceCompilation();
expect(Array.isArray(results)).toBe(true);
for (const result of results) {
expect(result).toHaveProperty('language');
expect(result).toHaveProperty('success');
expect(result).toHaveProperty('errors');
expect(result).toHaveProperty('warnings');
expect(result).toHaveProperty('duration');
expect(typeof result.success).toBe('boolean');
expect(Array.isArray(result.errors)).toBe(true);
expect(Array.isArray(result.warnings)).toBe(true);
expect(typeof result.duration).toBe('number');
}
});
it('should handle language configuration updates', () => {
const configUpdated = vi.fn();
detector.on('config-updated', configUpdated);
detector.updateLanguageConfig('typescript', { enabled: false });
expect(configUpdated).toHaveBeenCalledWith({
language: 'typescript',
updates: { enabled: false }
});
});
it('should perform cross-language analysis', async () => {
// Create files in different languages that might have related issues
await fs.writeFile(join(tempDir, 'interface.ts'), `
export interface SharedData {
id: number;
name: string;
}
`);
await fs.writeFile(join(tempDir, 'implementation.py'), `
# Python implementation that might have type mismatches
class SharedData:
def __init__(self, id: str, name: int): # Intentional type mismatch
self.id = id
self.name = name
`);
const errors = await detector.detectErrors();
// Cross-language analysis might detect interface mismatches
const crossLanguageErrors = errors.filter(e => e.type === 'CrossLanguageError');
// This is optional since it depends on having multiple language compilers
if (crossLanguageErrors.length > 0) {
expect(crossLanguageErrors[0].category).toBe(ErrorCategory.CONFIGURATION);
expect(crossLanguageErrors[0].severity).toBe(ErrorSeverity.HIGH);
}
});
});
describe('Integration Tests', () => {
it('should work together for comprehensive error detection', async () => {
const buildToolDetector = new BuildToolDetector(detectorOptions, {
workspaceRoot: tempDir,
parallelBuilds: false
});
const processMonitorDetector = new ProcessMonitorDetector(detectorOptions, {
workspaceRoot: tempDir,
monitorInterval: 2000
});
const multiLanguageDetector = new MultiLanguageDetector(detectorOptions, {
workspaceRoot: tempDir,
parallelCompilation: false
});
try {
// Start all detectors
await Promise.all([
buildToolDetector.start(),
processMonitorDetector.start(),
multiLanguageDetector.start()
]);
// Create a complex project with multiple issues
await fs.writeFile(join(tempDir, 'package.json'), JSON.stringify({
name: 'test-project',
scripts: {
build: 'tsc'
}
}));
await fs.writeFile(join(tempDir, 'tsconfig.json'), JSON.stringify({
compilerOptions: {
target: 'es2020',
strict: true
}
}));
await fs.writeFile(join(tempDir, 'main.ts'), `
interface Config {
port: number;
host: string;
}
const config: Config = {
port: "3000", // Type error
host: 3000 // Type error
};
console.log(config);
`);
// Run detection on all detectors
const [buildErrors, processErrors, languageErrors] = await Promise.all([
buildToolDetector.detectErrors(),
processMonitorDetector.detectErrors(),
multiLanguageDetector.detectErrors()
]);
// Verify each detector returns results
expect(Array.isArray(buildErrors)).toBe(true);
expect(Array.isArray(processErrors)).toBe(true);
expect(Array.isArray(languageErrors)).toBe(true);
// Combined error detection should provide comprehensive coverage
const allErrors = [...buildErrors, ...processErrors, ...languageErrors];
// Should have various error types and categories
const errorTypes = new Set(allErrors.map(e => e.type));
const errorCategories = new Set(allErrors.map(e => e.category));
expect(errorTypes.size).toBeGreaterThanOrEqual(0); // At least some variety
expect(errorCategories.size).toBeGreaterThanOrEqual(0); // At least some variety
} finally {
// Clean up
await Promise.all([
buildToolDetector.stop(),
processMonitorDetector.stop(),
multiLanguageDetector.stop()
]);
}
});
});
});