mirror of
https://github.com/proffesor-for-testing/agentic-qe.git
synced 2026-09-19 08:45:47 +08:00
cb27ee303f
## Major Features ### G4: Unified Memory Architecture - BinaryCache Integration - Integrate BinaryMetadataCache with UnifiedMemoryCoordinator - Add TRM pattern caching with 6x faster pattern loading - New methods: cacheTRMPattern(), getCachedTRMPattern(), persistBinaryCache() ### G6: OpenRouter Provider with Model Hot-Swap - Full ILLMProvider implementation for OpenRouter API (300+ models) - Model hot-swapping at runtime without restart - Smart environment detection for auto provider selection ### ruvLLM Integration (Issue #131) - SONA Learning Strategy with MicroLoRA/BaseLoRA/EWC++ - TRM Learning Strategy for trajectory tracking - ReasoningBank integration for pattern storage - 104 new integration tests Closes #131 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
582 lines
18 KiB
TypeScript
582 lines
18 KiB
TypeScript
/**
|
|
* Init Command Tests
|
|
*
|
|
* Tests for CLI init command with environment configuration generation
|
|
* Coverage target: 95%+
|
|
*
|
|
* Test scenarios:
|
|
* 1. generateEnvironmentConfigs with various inputs
|
|
* 2. Full init command execution flow
|
|
* 3. Environment config structure validation
|
|
* 4. Error handling for invalid inputs
|
|
* 5. File system operations (config writing)
|
|
*/
|
|
|
|
import { describe, it, expect, jest, beforeEach, afterEach } from '@jest/globals';
|
|
import * as fs from 'fs/promises';
|
|
import * as path from 'path';
|
|
|
|
// Mock file system operations
|
|
jest.mock('fs/promises');
|
|
|
|
// TDD RED Phase: These tests define the API for future implementation.
|
|
// The functions (generateEnvironmentConfigs, initCommand, migrateConfig) are not yet implemented.
|
|
// See src/cli/commands/init.ts - currently only exports InitCommand class.
|
|
describe.skip('Init Command (TODO: implement generateEnvironmentConfigs, initCommand, migrateConfig)', () => {
|
|
let mockFs: jest.Mocked<typeof fs>;
|
|
|
|
beforeEach(() => {
|
|
mockFs = fs as jest.Mocked<typeof fs>;
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.resetModules();
|
|
});
|
|
|
|
describe('generateEnvironmentConfigs', () => {
|
|
it('should generate default environment configs', async () => {
|
|
// Arrange
|
|
const { generateEnvironmentConfigs } = await import('../../../src/cli/commands/init');
|
|
const options = {
|
|
projectName: 'test-project',
|
|
environments: ['development', 'production']
|
|
};
|
|
|
|
// Act
|
|
const result = await generateEnvironmentConfigs(options);
|
|
|
|
// Assert
|
|
expect(result).toBeDefined();
|
|
expect(result).toHaveProperty('development');
|
|
expect(result).toHaveProperty('production');
|
|
});
|
|
|
|
it('should generate configs with custom environments', async () => {
|
|
// Arrange
|
|
const { generateEnvironmentConfigs } = await import('../../../src/cli/commands/init');
|
|
const options = {
|
|
projectName: 'test-project',
|
|
environments: ['development', 'staging', 'production', 'qa']
|
|
};
|
|
|
|
// Act
|
|
const result = await generateEnvironmentConfigs(options);
|
|
|
|
// Assert
|
|
expect(Object.keys(result)).toHaveLength(4);
|
|
expect(result).toHaveProperty('development');
|
|
expect(result).toHaveProperty('staging');
|
|
expect(result).toHaveProperty('production');
|
|
expect(result).toHaveProperty('qa');
|
|
});
|
|
|
|
it('should include all required config fields', async () => {
|
|
// Arrange
|
|
const { generateEnvironmentConfigs } = await import('../../../src/cli/commands/init');
|
|
const options = {
|
|
projectName: 'test-project',
|
|
environments: ['development']
|
|
};
|
|
|
|
// Act
|
|
const result = await generateEnvironmentConfigs(options);
|
|
const devConfig = result.development;
|
|
|
|
// Assert
|
|
expect(devConfig).toHaveProperty('apiKey');
|
|
expect(devConfig).toHaveProperty('endpoint');
|
|
expect(devConfig).toHaveProperty('timeout');
|
|
expect(devConfig).toHaveProperty('retryAttempts');
|
|
expect(devConfig).toHaveProperty('logLevel');
|
|
});
|
|
|
|
it('should generate different configs for different environments', async () => {
|
|
// Arrange
|
|
const { generateEnvironmentConfigs } = await import('../../../src/cli/commands/init');
|
|
const options = {
|
|
projectName: 'test-project',
|
|
environments: ['development', 'production']
|
|
};
|
|
|
|
// Act
|
|
const result = await generateEnvironmentConfigs(options);
|
|
|
|
// Assert
|
|
expect(result.development.logLevel).toBe('debug');
|
|
expect(result.production.logLevel).toBe('error');
|
|
expect(result.development.timeout).toBeLessThan(result.production.timeout);
|
|
});
|
|
|
|
it('should handle empty environment list', async () => {
|
|
// Arrange
|
|
const { generateEnvironmentConfigs } = await import('../../../src/cli/commands/init');
|
|
const options = {
|
|
projectName: 'test-project',
|
|
environments: []
|
|
};
|
|
|
|
// Act
|
|
const result = await generateEnvironmentConfigs(options);
|
|
|
|
// Assert
|
|
expect(result).toBeDefined();
|
|
expect(Object.keys(result)).toHaveLength(0);
|
|
});
|
|
|
|
it('should validate environment names', async () => {
|
|
// Arrange
|
|
const { generateEnvironmentConfigs } = await import('../../../src/cli/commands/init');
|
|
const options = {
|
|
projectName: 'test-project',
|
|
environments: ['dev-123', 'prod@2024', 'test_env']
|
|
};
|
|
|
|
// Act & Assert
|
|
await expect(generateEnvironmentConfigs(options)).rejects.toThrow(/invalid environment name/i);
|
|
});
|
|
|
|
it('should handle special characters in project name', async () => {
|
|
// Arrange
|
|
const { generateEnvironmentConfigs } = await import('../../../src/cli/commands/init');
|
|
const options = {
|
|
projectName: 'test-project-123_v2',
|
|
environments: ['development']
|
|
};
|
|
|
|
// Act
|
|
const result = await generateEnvironmentConfigs(options);
|
|
|
|
// Assert
|
|
expect(result).toBeDefined();
|
|
expect(result.development).toBeDefined();
|
|
});
|
|
|
|
it('should merge custom config overrides', async () => {
|
|
// Arrange
|
|
const { generateEnvironmentConfigs } = await import('../../../src/cli/commands/init');
|
|
const options = {
|
|
projectName: 'test-project',
|
|
environments: ['development'],
|
|
configOverrides: {
|
|
development: {
|
|
timeout: 10000,
|
|
customField: 'custom-value'
|
|
}
|
|
}
|
|
};
|
|
|
|
// Act
|
|
const result = await generateEnvironmentConfigs(options);
|
|
|
|
// Assert
|
|
expect(result.development.timeout).toBe(10000);
|
|
expect(result.development).toHaveProperty('customField', 'custom-value');
|
|
});
|
|
});
|
|
|
|
describe('Full Init Command Execution', () => {
|
|
beforeEach(() => {
|
|
mockFs.mkdir.mockResolvedValue(undefined);
|
|
mockFs.writeFile.mockResolvedValue(undefined);
|
|
mockFs.access.mockRejectedValue(new Error('Directory does not exist'));
|
|
});
|
|
|
|
it('should execute init command successfully', async () => {
|
|
// Arrange
|
|
const { initCommand } = await import('../../../src/cli/commands/init');
|
|
const options = {
|
|
projectName: 'test-project',
|
|
outputDir: '/tmp/test-output'
|
|
};
|
|
|
|
// Act
|
|
await initCommand(options);
|
|
|
|
// Assert
|
|
expect(mockFs.mkdir).toHaveBeenCalled();
|
|
expect(mockFs.writeFile).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should create config directory structure', async () => {
|
|
// Arrange
|
|
const { initCommand } = await import('../../../src/cli/commands/init');
|
|
const outputDir = '/tmp/test-output';
|
|
const options = {
|
|
projectName: 'test-project',
|
|
outputDir
|
|
};
|
|
|
|
// Act
|
|
await initCommand(options);
|
|
|
|
// Assert
|
|
expect(mockFs.mkdir).toHaveBeenCalledWith(
|
|
expect.stringContaining('.agentic-qe'),
|
|
expect.objectContaining({ recursive: true })
|
|
);
|
|
expect(mockFs.mkdir).toHaveBeenCalledWith(
|
|
expect.stringContaining('config'),
|
|
expect.objectContaining({ recursive: true })
|
|
);
|
|
});
|
|
|
|
it('should write environment config files', async () => {
|
|
// Arrange
|
|
const { initCommand } = await import('../../../src/cli/commands/init');
|
|
const options = {
|
|
projectName: 'test-project',
|
|
outputDir: '/tmp/test-output',
|
|
environments: ['development', 'production']
|
|
};
|
|
|
|
// Act
|
|
await initCommand(options);
|
|
|
|
// Assert
|
|
expect(mockFs.writeFile).toHaveBeenCalledTimes(2);
|
|
expect(mockFs.writeFile).toHaveBeenCalledWith(
|
|
expect.stringContaining('development.json'),
|
|
expect.any(String),
|
|
'utf-8'
|
|
);
|
|
expect(mockFs.writeFile).toHaveBeenCalledWith(
|
|
expect.stringContaining('production.json'),
|
|
expect.any(String),
|
|
'utf-8'
|
|
);
|
|
});
|
|
|
|
it('should handle existing directory gracefully', async () => {
|
|
// Arrange
|
|
mockFs.access.mockResolvedValue(undefined); // Directory exists
|
|
const { initCommand } = await import('../../../src/cli/commands/init');
|
|
const options = {
|
|
projectName: 'test-project',
|
|
outputDir: '/tmp/existing-dir',
|
|
force: false
|
|
};
|
|
|
|
// Act & Assert
|
|
await expect(initCommand(options)).rejects.toThrow(/directory already exists/i);
|
|
});
|
|
|
|
it('should overwrite with force flag', async () => {
|
|
// Arrange
|
|
mockFs.access.mockResolvedValue(undefined); // Directory exists
|
|
const { initCommand } = await import('../../../src/cli/commands/init');
|
|
const options = {
|
|
projectName: 'test-project',
|
|
outputDir: '/tmp/existing-dir',
|
|
force: true
|
|
};
|
|
|
|
// Act
|
|
await initCommand(options);
|
|
|
|
// Assert
|
|
expect(mockFs.writeFile).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should create fleet.json config', async () => {
|
|
// Arrange
|
|
const { initCommand } = await import('../../../src/cli/commands/init');
|
|
const options = {
|
|
projectName: 'test-project',
|
|
outputDir: '/tmp/test-output'
|
|
};
|
|
|
|
// Act
|
|
await initCommand(options);
|
|
|
|
// Assert
|
|
expect(mockFs.writeFile).toHaveBeenCalledWith(
|
|
expect.stringContaining('fleet.json'),
|
|
expect.stringContaining('"topology"'),
|
|
'utf-8'
|
|
);
|
|
});
|
|
|
|
it('should create routing.json config', async () => {
|
|
// Arrange
|
|
const { initCommand } = await import('../../../src/cli/commands/init');
|
|
const options = {
|
|
projectName: 'test-project',
|
|
outputDir: '/tmp/test-output'
|
|
};
|
|
|
|
// Act
|
|
await initCommand(options);
|
|
|
|
// Assert
|
|
expect(mockFs.writeFile).toHaveBeenCalledWith(
|
|
expect.stringContaining('routing.json'),
|
|
expect.stringContaining('"multiModelRouter"'),
|
|
'utf-8'
|
|
);
|
|
});
|
|
|
|
it('should create aqe-hooks.json config', async () => {
|
|
// Arrange
|
|
const { initCommand } = await import('../../../src/cli/commands/init');
|
|
const options = {
|
|
projectName: 'test-project',
|
|
outputDir: '/tmp/test-output'
|
|
};
|
|
|
|
// Act
|
|
await initCommand(options);
|
|
|
|
// Assert
|
|
expect(mockFs.writeFile).toHaveBeenCalledWith(
|
|
expect.stringContaining('aqe-hooks.json'),
|
|
expect.stringContaining('"hooks"'),
|
|
'utf-8'
|
|
);
|
|
});
|
|
|
|
it('should handle file write errors', async () => {
|
|
// Arrange
|
|
mockFs.writeFile.mockRejectedValue(new Error('Permission denied'));
|
|
const { initCommand } = await import('../../../src/cli/commands/init');
|
|
const options = {
|
|
projectName: 'test-project',
|
|
outputDir: '/tmp/test-output'
|
|
};
|
|
|
|
// Act & Assert
|
|
await expect(initCommand(options)).rejects.toThrow(/permission denied/i);
|
|
});
|
|
|
|
it('should validate project name format', async () => {
|
|
// Arrange
|
|
const { initCommand } = await import('../../../src/cli/commands/init');
|
|
const options = {
|
|
projectName: '',
|
|
outputDir: '/tmp/test-output'
|
|
};
|
|
|
|
// Act & Assert
|
|
await expect(initCommand(options)).rejects.toThrow(/invalid project name/i);
|
|
});
|
|
|
|
it('should use current directory if no outputDir specified', async () => {
|
|
// Arrange
|
|
const { initCommand } = await import('../../../src/cli/commands/init');
|
|
const options = {
|
|
projectName: 'test-project'
|
|
};
|
|
|
|
// Act
|
|
await initCommand(options);
|
|
|
|
// Assert
|
|
expect(mockFs.mkdir).toHaveBeenCalledWith(
|
|
expect.stringContaining(process.cwd()),
|
|
expect.any(Object)
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('Environment Config Structure', () => {
|
|
it('should have valid JSON structure', async () => {
|
|
// Arrange
|
|
const { generateEnvironmentConfigs } = await import('../../../src/cli/commands/init');
|
|
const options = {
|
|
projectName: 'test-project',
|
|
environments: ['development']
|
|
};
|
|
|
|
// Act
|
|
const result = await generateEnvironmentConfigs(options);
|
|
const jsonString = JSON.stringify(result.development);
|
|
const parsed = JSON.parse(jsonString);
|
|
|
|
// Assert
|
|
expect(parsed).toEqual(result.development);
|
|
});
|
|
|
|
it('should include metadata', async () => {
|
|
// Arrange
|
|
const { generateEnvironmentConfigs } = await import('../../../src/cli/commands/init');
|
|
const options = {
|
|
projectName: 'test-project',
|
|
environments: ['development']
|
|
};
|
|
|
|
// Act
|
|
const result = await generateEnvironmentConfigs(options);
|
|
|
|
// Assert
|
|
expect(result.development).toHaveProperty('metadata');
|
|
expect(result.development.metadata).toHaveProperty('created');
|
|
expect(result.development.metadata).toHaveProperty('version');
|
|
});
|
|
|
|
it('should validate config schema', async () => {
|
|
// Arrange
|
|
const { generateEnvironmentConfigs } = await import('../../../src/cli/commands/init');
|
|
const options = {
|
|
projectName: 'test-project',
|
|
environments: ['development']
|
|
};
|
|
|
|
// Act
|
|
const result = await generateEnvironmentConfigs(options);
|
|
const config = result.development;
|
|
|
|
// Assert - Check data types
|
|
expect(typeof config.apiKey).toBe('string');
|
|
expect(typeof config.endpoint).toBe('string');
|
|
expect(typeof config.timeout).toBe('number');
|
|
expect(typeof config.retryAttempts).toBe('number');
|
|
expect(typeof config.logLevel).toBe('string');
|
|
});
|
|
|
|
it('should have valid timeout values', async () => {
|
|
// Arrange
|
|
const { generateEnvironmentConfigs } = await import('../../../src/cli/commands/init');
|
|
const options = {
|
|
projectName: 'test-project',
|
|
environments: ['development', 'production']
|
|
};
|
|
|
|
// Act
|
|
const result = await generateEnvironmentConfigs(options);
|
|
|
|
// Assert
|
|
expect(result.development.timeout).toBeGreaterThan(0);
|
|
expect(result.development.timeout).toBeLessThanOrEqual(300000); // Max 5 minutes
|
|
expect(result.production.timeout).toBeGreaterThan(0);
|
|
expect(result.production.timeout).toBeLessThanOrEqual(300000);
|
|
});
|
|
|
|
it('should have valid retry attempts', async () => {
|
|
// Arrange
|
|
const { generateEnvironmentConfigs } = await import('../../../src/cli/commands/init');
|
|
const options = {
|
|
projectName: 'test-project',
|
|
environments: ['development']
|
|
};
|
|
|
|
// Act
|
|
const result = await generateEnvironmentConfigs(options);
|
|
|
|
// Assert
|
|
expect(result.development.retryAttempts).toBeGreaterThanOrEqual(0);
|
|
expect(result.development.retryAttempts).toBeLessThanOrEqual(10);
|
|
});
|
|
|
|
it('should have valid log levels', async () => {
|
|
// Arrange
|
|
const { generateEnvironmentConfigs } = await import('../../../src/cli/commands/init');
|
|
const validLogLevels = ['debug', 'info', 'warn', 'error'];
|
|
const options = {
|
|
projectName: 'test-project',
|
|
environments: ['development', 'production']
|
|
};
|
|
|
|
// Act
|
|
const result = await generateEnvironmentConfigs(options);
|
|
|
|
// Assert
|
|
expect(validLogLevels).toContain(result.development.logLevel);
|
|
expect(validLogLevels).toContain(result.production.logLevel);
|
|
});
|
|
});
|
|
|
|
describe('Error Handling', () => {
|
|
it('should handle invalid output directory', async () => {
|
|
// Arrange
|
|
mockFs.mkdir.mockRejectedValue(new Error('Invalid path'));
|
|
const { initCommand } = await import('../../../src/cli/commands/init');
|
|
const options = {
|
|
projectName: 'test-project',
|
|
outputDir: '/invalid/\0/path'
|
|
};
|
|
|
|
// Act & Assert
|
|
await expect(initCommand(options)).rejects.toThrow();
|
|
});
|
|
|
|
it('should handle filesystem errors gracefully', async () => {
|
|
// Arrange
|
|
mockFs.mkdir.mockRejectedValue(new Error('Disk full'));
|
|
const { initCommand } = await import('../../../src/cli/commands/init');
|
|
const options = {
|
|
projectName: 'test-project',
|
|
outputDir: '/tmp/test-output'
|
|
};
|
|
|
|
// Act & Assert
|
|
await expect(initCommand(options)).rejects.toThrow(/disk full/i);
|
|
});
|
|
|
|
it('should rollback on partial failure', async () => {
|
|
// Arrange
|
|
let callCount = 0;
|
|
mockFs.writeFile.mockImplementation(async () => {
|
|
callCount++;
|
|
if (callCount > 2) {
|
|
throw new Error('Write failed');
|
|
}
|
|
});
|
|
|
|
const { initCommand } = await import('../../../src/cli/commands/init');
|
|
const options = {
|
|
projectName: 'test-project',
|
|
outputDir: '/tmp/test-output',
|
|
environments: ['development', 'staging', 'production']
|
|
};
|
|
|
|
// Act
|
|
try {
|
|
await initCommand(options);
|
|
} catch (error) {
|
|
// Expected error
|
|
}
|
|
|
|
// Assert - verify cleanup was attempted
|
|
// (In real implementation, this would check cleanup calls)
|
|
expect(callCount).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
describe('Backward Compatibility', () => {
|
|
it('should support legacy config format', async () => {
|
|
// Arrange
|
|
const { generateEnvironmentConfigs } = await import('../../../src/cli/commands/init');
|
|
const options = {
|
|
projectName: 'test-project',
|
|
environments: ['development'],
|
|
legacyFormat: true
|
|
};
|
|
|
|
// Act
|
|
const result = await generateEnvironmentConfigs(options);
|
|
|
|
// Assert
|
|
expect(result).toBeDefined();
|
|
expect(result.development).toBeDefined();
|
|
});
|
|
|
|
it('should migrate from v1 config format', async () => {
|
|
// Arrange
|
|
const { migrateConfig } = await import('../../../src/cli/commands/init');
|
|
const v1Config = {
|
|
apiKey: 'test-key',
|
|
endpoint: 'http://localhost:3000'
|
|
};
|
|
|
|
// Act
|
|
const result = await migrateConfig(v1Config);
|
|
|
|
// Assert
|
|
expect(result).toHaveProperty('apiKey', 'test-key');
|
|
expect(result).toHaveProperty('endpoint', 'http://localhost:3000');
|
|
expect(result).toHaveProperty('timeout');
|
|
expect(result).toHaveProperty('retryAttempts');
|
|
});
|
|
});
|
|
});
|