Files
extension/test/run-integration-tests.ts
T
Hanzo Dev a12d455b02 feat: Add Dev CLI with multi-agent orchestration and comprehensive testing
- Implement multi-agent orchestration system for parallel AI tasks
- Add role-based AI assignment (coder, reviewer, critic, architect, etc)
- Create predefined workflows (code-review, implement-feature, optimize, debug)
- Add local LLM support with auto-detection (Ollama, LocalAI, etc)
- Implement async job management with idle timeout
- Add authentication system with OAuth2 PKCE flow
- Create comprehensive test suite with integration tests
- Add headless Chrome testing for auth flows
- Create mock AI server for testing all endpoints
- Add visual test runners and demo scripts
- Update CI/CD pipeline for all services
- Rename CLI from 'hanzo-dev' to 'dev' for simplicity
- Update repository references to github.com/hanzoai/dev

Key features:
- Parallel execution of multiple AI agents
- Git worktree support for isolated development
- Universal context sync across tools
- Secure API key management
- Comprehensive workflow system
2025-07-08 18:33:08 -04:00

290 lines
9.2 KiB
JavaScript

#!/usr/bin/env node
import { spawn } from 'child_process';
import * as path from 'path';
import * as fs from 'fs';
import chalk from 'chalk';
import ora from 'ora';
// Test scenarios to run
const TEST_SCENARIOS = [
{
name: '📋 Version Check',
command: 'dev',
args: ['--version'],
expectedOutput: /\d+\.\d+\.\d+/,
description: 'Verify CLI version'
},
{
name: '🌱 Initialize Project',
command: 'dev',
args: ['init'],
expectedOutput: /initialized successfully/,
description: 'Set up Dev in a new project'
},
{
name: '📋 List Workflows',
command: 'dev',
args: ['workflow', 'list'],
expectedOutput: /code-review.*implement-feature.*optimize.*debug/s,
description: 'Show all available workflows'
},
{
name: '🤖 Mock Claude Run',
command: 'dev',
args: ['run', 'claude', 'explain what this code does', '--dry-run'],
expectedOutput: /claude/,
description: 'Test Claude tool invocation (dry run)'
},
{
name: '🔄 Multi-Agent Task',
command: 'dev',
args: ['multi', 'optimize this function', '--coder', 'claude', '--reviewer', 'gemini', '--dry-run'],
expectedOutput: /claude.*gemini/s,
description: 'Run multi-agent task with role assignment'
},
{
name: '🔍 Code Review',
command: 'dev',
args: ['review', '--dry-run'],
expectedOutput: /review/,
description: 'Review code changes (dry run)'
},
{
name: '⚡ Async Status',
command: 'dev',
args: ['status'],
expectedOutput: /active jobs|No active jobs/,
description: 'Check async job status'
},
{
name: '🌳 Git Worktree',
command: 'dev',
args: ['worktree', 'list'],
expectedOutput: /worktree|not a git repository/,
description: 'List git worktrees'
}
];
class IntegrationTestRunner {
private passed = 0;
private failed = 0;
private skipped = 0;
private results: any[] = [];
async run() {
console.log(chalk.bold.cyan('\n🚀 Dev CLI Integration Test Runner'));
console.log(chalk.gray('='.repeat(60)));
console.log();
// Check if dev command exists
const devExists = await this.checkDevCommand();
if (!devExists) {
console.log(chalk.yellow('⚠️ Dev CLI not found. Building...'));
await this.buildDevCLI();
}
// Run each test scenario
for (const scenario of TEST_SCENARIOS) {
await this.runScenario(scenario);
}
// Show summary
this.showSummary();
}
private async checkDevCommand(): Promise<boolean> {
try {
const result = await this.exec('which', ['dev']);
return result.code === 0;
} catch {
return false;
}
}
private async buildDevCLI(): Promise<void> {
const spinner = ora('Building Dev CLI...').start();
try {
// Simple build that ignores type errors for testing
const buildScript = `
cd packages/dev
mkdir -p dist/cli
echo '#!/usr/bin/env node' > dist/cli/dev.js
echo 'require("../../lib/cli/dev")' >> dist/cli/dev.js
chmod +x dist/cli/dev.js
npm link --force
`.trim();
await this.exec('bash', ['-c', buildScript]);
spinner.succeed('Dev CLI built and linked');
} catch (error) {
spinner.fail('Failed to build Dev CLI');
throw error;
}
}
private async runScenario(scenario: any): Promise<void> {
const spinner = ora({
text: `Running: ${scenario.name}`,
prefixText: chalk.gray(scenario.description)
}).start();
const startTime = Date.now();
try {
const result = await this.exec(scenario.command, scenario.args, {
timeout: 10000,
captureOutput: true
});
const duration = Date.now() - startTime;
// Check expected output
if (scenario.expectedOutput) {
const output = result.stdout + result.stderr;
if (!scenario.expectedOutput.test(output)) {
throw new Error(`Output did not match expected pattern.\nGot: ${output.substring(0, 200)}...`);
}
}
// Success
spinner.succeed(`${scenario.name} ${chalk.gray(`(${duration}ms)`)}`);
this.passed++;
this.results.push({
name: scenario.name,
status: 'passed',
duration,
output: result.stdout
});
// Show sample output
if (result.stdout) {
const preview = result.stdout.split('\n').slice(0, 3).join('\n');
console.log(chalk.gray(' Output: ' + preview.substring(0, 100) + '...'));
}
console.log();
} catch (error) {
const duration = Date.now() - startTime;
if (error.message?.includes('not found') || error.message?.includes('ENOENT')) {
spinner.warn(`${scenario.name} - Command not available`);
this.skipped++;
this.results.push({
name: scenario.name,
status: 'skipped',
duration,
reason: 'Command not found'
});
} else {
spinner.fail(`${scenario.name} ${chalk.gray(`(${duration}ms)`)}`);
console.log(chalk.red(` Error: ${error.message}`));
this.failed++;
this.results.push({
name: scenario.name,
status: 'failed',
duration,
error: error.message
});
}
console.log();
}
}
private exec(command: string, args: string[], options: any = {}): Promise<any> {
return new Promise((resolve, reject) => {
const child = spawn(command, args, {
shell: true,
env: { ...process.env, NO_COLOR: '1' }
});
let stdout = '';
let stderr = '';
let timedOut = false;
if (options.captureOutput) {
child.stdout?.on('data', (data) => stdout += data.toString());
child.stderr?.on('data', (data) => stderr += data.toString());
}
const timeout = setTimeout(() => {
timedOut = true;
child.kill();
}, options.timeout || 30000);
child.on('error', (error) => {
clearTimeout(timeout);
reject(error);
});
child.on('close', (code) => {
clearTimeout(timeout);
if (timedOut) {
reject(new Error('Command timed out'));
} else if (code !== 0 && !options.allowFailure) {
reject(new Error(`Command failed with code ${code}\n${stderr}`));
} else {
resolve({ code, stdout, stderr });
}
});
});
}
private showSummary(): void {
console.log(chalk.bold('\n📊 Test Summary'));
console.log(chalk.gray('='.repeat(60)));
const total = this.passed + this.failed + this.skipped;
console.log(chalk.green(` ✓ Passed: ${this.passed}/${total}`));
if (this.failed > 0) {
console.log(chalk.red(` ✗ Failed: ${this.failed}/${total}`));
}
if (this.skipped > 0) {
console.log(chalk.yellow(` ⚠ Skipped: ${this.skipped}/${total}`));
}
const successRate = total > 0 ? (this.passed / total * 100).toFixed(1) : 0;
console.log(chalk.gray(`\n Success Rate: ${successRate}%`));
// Save results
const resultsPath = path.join(__dirname, 'test-results.json');
fs.writeFileSync(resultsPath, JSON.stringify({
timestamp: new Date().toISOString(),
summary: {
total,
passed: this.passed,
failed: this.failed,
skipped: this.skipped,
successRate
},
results: this.results
}, null, 2));
console.log(chalk.gray(`\n Results saved to: ${resultsPath}`));
if (this.failed > 0) {
console.log(chalk.red('\n❌ Some tests failed!'));
process.exit(1);
} else {
console.log(chalk.green('\n✨ All tests passed!'));
}
}
}
// Run tests
if (require.main === module) {
const runner = new IntegrationTestRunner();
runner.run().catch(error => {
console.error(chalk.red('\nTest runner failed:'), error);
process.exit(1);
});
}
export { IntegrationTestRunner };