#!/usr/bin/env node import { Command } from 'commander' import chalk from 'chalk' import ora from 'ora' import inquirer from 'inquirer' import { loadConfig, setGlobalConfig } from '../../core/config' import { DataRelationManager } from '../../core/utils/data-relations' import { WebFixtures } from '../../services/web' import { ChatServerFixtures } from '../../services/chat-server' import { StreamServerFixtures } from '../../services/stream-server' import { NewUserOnboardingScenario } from '../../scenarios/user-journey/new-user-onboarding' import { HighLoadScenario } from '../../scenarios/performance/high-load' import { CrossServiceCommunicationScenario } from '../../scenarios/integration/cross-service-communication' /** * Veza Fixtures CLI Tool * * Comprehensive command-line interface for managing fixtures across the entire Veza Platform */ const program = new Command() // CLI Configuration program .name('veza-fixtures') .description('๐ŸŽต Veza Platform Fixtures Management CLI') .version('1.0.0') /** * Generate Command - Create fixture data */ program .command('generate') .alias('gen') .description('Generate fixture data for Veza Platform') .option('-e, --env ', 'Target environment', 'development') .option('-u, --users ', 'Number of users to generate', '50') .option('-t, --tracks ', 'Number of tracks to generate', '200') .option('-p, --playlists ', 'Number of playlists to generate', '30') .option('-c, --conversations ', 'Number of conversations to generate', '20') .option('-s, --seed ', 'Random seed for reproducible data') .option('--dry-run', 'Show what would be generated without creating data') .option('--format ', 'Output format (json|sql|csv)', 'json') .option('--output ', 'Output directory path', './fixtures/exports') .action(async (options) => { const spinner = ora('๐ŸŽฏ Initializing fixture generation...').start() try { // Load configuration const config = loadConfig(options.env) if (options.seed) { config.seed = options.seed } setGlobalConfig(config) spinner.text = '๐Ÿ“Š Generating comprehensive dataset...' // Generate complete dataset with relationships const dataset = DataRelationManager.generateCompleteDataset() spinner.succeed(`โœ… Generated complete dataset:`) console.log(chalk.green(` ๐Ÿ‘ฅ Users: ${dataset.users.length}`)) console.log(chalk.green(` ๐ŸŽต Tracks: ${dataset.tracks.length}`)) console.log(chalk.green(` ๐Ÿ“‹ Playlists: ${dataset.playlists.length}`)) console.log(chalk.green(` ๐Ÿ’ฌ Conversations: ${dataset.conversations.length}`)) console.log(chalk.green(` ๐Ÿ“ Messages: ${dataset.messages.length}`)) // Validate data relationships const validation = DataRelationManager.validateRelations() if (validation.isValid) { console.log(chalk.green('โœ… Data validation passed')) } else { console.log(chalk.yellow('โš ๏ธ Data validation warnings:')) validation.errors.forEach(error => console.log(chalk.red(` โŒ ${error}`))) validation.warnings.forEach(warning => console.log(chalk.yellow(` โš ๏ธ ${warning}`))) } // Export data if not dry run if (!options.dryRun) { await exportData(dataset, options.format, options.output) } else { console.log(chalk.blue('๐Ÿ” Dry run - no data exported')) } // Show statistics const stats = DataRelationManager.getStatistics() console.log(chalk.blue('\n๐Ÿ“Š Generation Statistics:')) console.log(` Average tracks per artist: ${stats.relationships.avgTracksPerArtist}`) console.log(` Average playlists per user: ${stats.relationships.avgPlaylistsPerUser}`) console.log(` Average messages per conversation: ${stats.relationships.avgMessagesPerConversation}`) } catch (error) { spinner.fail(`โŒ Generation failed: ${error}`) process.exit(1) } }) /** * Seed Command - Populate services with fixture data */ program .command('seed') .description('Seed services with fixture data') .option('-e, --env ', 'Target environment', 'development') .option('-s, --service ', 'Services to seed (web, chat, stream, all)', ['all']) .option('--scenario ', 'Use specific scenario data') .option('--clean', 'Clean existing data before seeding') .option('--validate', 'Validate data after seeding') .action(async (options) => { const spinner = ora('๐ŸŒฑ Initializing seeding process...').start() try { // Load configuration const config = loadConfig(options.env) setGlobalConfig(config) // Determine services to seed const servicesToSeed = options.service.includes('all') ? ['web', 'chat', 'stream'] : options.service console.log(chalk.blue(`๐ŸŽฏ Seeding services: ${servicesToSeed.join(', ')}`)) // Generate or load scenario data let dataset if (options.scenario) { dataset = await loadScenarioData(options.scenario, spinner) } else { spinner.text = '๐Ÿ“Š Generating base dataset...' dataset = DataRelationManager.generateCompleteDataset() } // Seed each service for (const service of servicesToSeed) { await seedService(service, dataset, options, spinner) } // Validate if requested if (options.validate) { spinner.text = '๐Ÿ” Validating seeded data...' const validation = DataRelationManager.validateRelations() if (!validation.isValid) { spinner.fail('โŒ Validation failed') validation.errors.forEach(error => console.error(` โŒ ${error}`)) process.exit(1) } console.log('โœ… Validation passed') } spinner.succeed('โœ… Seeding completed successfully!') } catch (error) { spinner.fail(`โŒ Seeding failed: ${error}`) process.exit(1) } }) /** * Clean Command - Remove fixture data */ program .command('clean') .description('Clean fixture data from services') .option('-e, --env ', 'Target environment', 'development') .option('-s, --service ', 'Services to clean (web, chat, stream, all)', ['all']) .option('--force', 'Skip confirmation prompt') .action(async (options) => { const servicesToClean = options.service.includes('all') ? ['web', 'chat', 'stream'] : options.service // Confirmation prompt if (!options.force) { const { confirmed } = await inquirer.prompt([{ type: 'confirm', name: 'confirmed', message: `Are you sure you want to clean fixture data from: ${servicesToClean.join(', ')}?`, default: false }]) if (!confirmed) { console.log(chalk.yellow('๐Ÿšซ Operation cancelled')) return } } const spinner = ora('๐Ÿงน Cleaning fixture data...').start() try { // Load configuration const config = loadConfig(options.env) setGlobalConfig(config) // Clean each service for (const service of servicesToClean) { await cleanService(service, spinner) } // Clear in-memory data DataRelationManager.clearAll() spinner.succeed('โœ… Cleanup completed successfully!') } catch (error) { spinner.fail(`โŒ Cleanup failed: ${error}`) process.exit(1) } }) /** * Validate Command - Validate fixture data integrity */ program .command('validate') .alias('check') .description('Validate fixture data integrity and relationships') .option('-e, --env ', 'Target environment', 'development') .option('-s, --service ', 'Services to validate (web, chat, stream, all)', ['all']) .option('--check-relations', 'Check data relationships') .option('--check-performance', 'Check performance metrics') .option('--report ', 'Generate validation report') .action(async (options) => { const spinner = ora('๐Ÿ” Starting validation...').start() try { // Load configuration const config = loadConfig(options.env) setGlobalConfig(config) const servicesToValidate = options.service.includes('all') ? ['web', 'chat', 'stream'] : options.service const validationResults: ValidationResult[] = [] // Validate data relationships if (options.checkRelations) { spinner.text = '๐Ÿ”— Validating data relationships...' const relationValidation = DataRelationManager.validateRelations() validationResults.push({ type: 'relationships', service: 'data-manager', passed: relationValidation.isValid, errors: relationValidation.errors, warnings: relationValidation.warnings }) } // Validate each service for (const service of servicesToValidate) { const result = await validateService(service, spinner) validationResults.push(result) } // Performance checks if (options.checkPerformance) { const perfResults = await performanceChecks(servicesToValidate, spinner) validationResults.push(...perfResults) } // Display results displayValidationResults(validationResults) // Generate report if requested if (options.report) { await generateValidationReport(validationResults, options.report) } const hasErrors = validationResults.some(r => !r.passed) if (hasErrors) { spinner.fail('โŒ Validation completed with errors') process.exit(1) } else { spinner.succeed('โœ… Validation passed successfully!') } } catch (error) { spinner.fail(`โŒ Validation failed: ${error}`) process.exit(1) } }) /** * Scenario Command - Work with test scenarios */ program .command('scenario') .description('Manage test scenarios') .option('-l, --list', 'List available scenarios') .option('-r, --run ', 'Run a specific scenario') .option('-c, --create ', 'Create a new scenario') .option('--interactive', 'Interactive scenario builder') .action(async (options) => { if (options.list) { listAvailableScenarios() } else if (options.run) { await runScenario(options.run) } else if (options.create) { await createScenario(options.create, options.interactive) } else { console.log(chalk.yellow('Please specify an action. Use --help for more information.')) } }) /** * Status Command - Show current fixture status */ program .command('status') .alias('info') .description('Show current fixture status and statistics') .option('-e, --env ', 'Target environment', 'development') .option('-v, --verbose', 'Show detailed information') .action(async (options) => { const spinner = ora('๐Ÿ“Š Gathering status information...').start() try { // Load configuration const config = loadConfig(options.env) setGlobalConfig(config) spinner.stop() // Display configuration console.log(chalk.blue('๐Ÿ”ง Configuration:')) console.log(` Environment: ${chalk.green(config.environment)}`) console.log(` Seed: ${chalk.green(config.seed)}`) console.log(` Locale: ${chalk.green(config.locale)}`) // Display generation settings console.log(chalk.blue('\n๐Ÿ“Š Generation Settings:')) console.log(` Users: ${chalk.green(config.generation.users.count)}`) console.log(` Tracks: ${chalk.green(config.generation.audio.trackCount)}`) console.log(` Conversations: ${chalk.green(config.generation.conversations.directCount + config.generation.conversations.groupCount)}`) // Check service health console.log(chalk.blue('\n๐Ÿฅ Service Health:')) await checkServiceHealth(options.verbose) // Show in-memory statistics const stats = DataRelationManager.getStatistics() if (stats.users > 0) { console.log(chalk.blue('\n๐Ÿ’พ In-Memory Data:')) console.log(` Users: ${chalk.green(stats.users)}`) console.log(` Tracks: ${chalk.green(stats.tracks)}`) console.log(` Playlists: ${chalk.green(stats.playlists)}`) console.log(` Conversations: ${chalk.green(stats.conversations)}`) console.log(` Messages: ${chalk.green(stats.messages)}`) } } catch (error) { spinner.fail(`โŒ Status check failed: ${error}`) process.exit(1) } }) /** * Helper Functions */ async function exportData(dataset: any, format: string, outputPath: string): Promise { const fs = await import('fs/promises') const path = await import('path') // Ensure output directory exists await fs.mkdir(outputPath, { recursive: true }) switch (format) { case 'json': await fs.writeFile( path.join(outputPath, 'fixtures-export.json'), JSON.stringify(dataset, null, 2) ) break case 'sql': // Generate SQL inserts (simplified example) const sqlContent = generateSQLInserts(dataset) await fs.writeFile( path.join(outputPath, 'fixtures-export.sql'), sqlContent ) break case 'csv': // Generate CSV files for each entity type await generateCSVFiles(dataset, outputPath) break default: throw new Error(`Unsupported export format: ${format}`) } console.log(chalk.green(`๐Ÿ“ Data exported to: ${outputPath}`)) } async function seedService(service: string, dataset: any, options: any, spinner: any): Promise { spinner.text = `๐ŸŒฑ Seeding ${service} service...` switch (service) { case 'web': await WebFixtures.initialize() break case 'chat': await ChatServerFixtures.initialize() await ChatServerFixtures.seedDatabase() await ChatServerFixtures.seedRedis() break case 'stream': await StreamServerFixtures.initialize() await StreamServerFixtures.seedStreamingData() break default: throw new Error(`Unknown service: ${service}`) } console.log(chalk.green(` โœ… ${service} seeded successfully`)) } async function cleanService(service: string, spinner: any): Promise { spinner.text = `๐Ÿงน Cleaning ${service} service...` switch (service) { case 'web': WebFixtures.reset() break case 'chat': await ChatServerFixtures.cleanup() break case 'stream': await StreamServerFixtures.cleanup() break default: throw new Error(`Unknown service: ${service}`) } console.log(chalk.green(` โœ… ${service} cleaned successfully`)) } async function validateService(service: string, spinner: any): Promise { spinner.text = `๐Ÿ” Validating ${service} service...` switch (service) { case 'web': // Validate web fixtures return { type: 'service', service: 'web', passed: true, errors: [], warnings: [] } case 'chat': const chatHealth = await ChatServerFixtures.healthCheck() return { type: 'service', service: 'chat', passed: chatHealth.database && chatHealth.redis && chatHealth.dataConsistency, errors: [ ...(chatHealth.database ? [] : ['Database connection failed']), ...(chatHealth.redis ? [] : ['Redis connection failed']), ...(chatHealth.dataConsistency ? [] : ['Data consistency check failed']) ], warnings: [] } case 'stream': const streamHealth = await StreamServerFixtures.healthCheck() return { type: 'service', service: 'stream', passed: streamHealth.redis, errors: streamHealth.redis ? [] : ['Redis connection failed'], warnings: streamHealth.sessionsActive === 0 ? ['No active streaming sessions'] : [] } default: throw new Error(`Unknown service: ${service}`) } } async function loadScenarioData(scenario: string, spinner: any): Promise { spinner.text = `๐Ÿ“‹ Loading scenario: ${scenario}...` switch (scenario) { case 'onboarding': const onboardingContext = await NewUserOnboardingScenario.setup() return DataRelationManager.exportForDatabase() case 'high-load': const loadContext = await HighLoadScenario.setup() return DataRelationManager.exportForDatabase() case 'integration': const integrationContext = await CrossServiceCommunicationScenario.setup() return DataRelationManager.exportForDatabase() default: throw new Error(`Unknown scenario: ${scenario}`) } } function listAvailableScenarios(): void { console.log(chalk.blue('๐Ÿ“‹ Available Scenarios:')) console.log(' ๐ŸŽฏ onboarding - New user onboarding journey') console.log(' ๐Ÿš€ high-load - High load performance testing') console.log(' ๐Ÿ”— integration - Cross-service communication testing') } async function runScenario(scenarioName: string): Promise { const spinner = ora(`๐ŸŽฌ Running scenario: ${scenarioName}...`).start() try { switch (scenarioName) { case 'onboarding': const context = await NewUserOnboardingScenario.setup() const simulation = NewUserOnboardingScenario.simulateOnboardingCompletion(context) const validation = NewUserOnboardingScenario.validateScenario(context) spinner.succeed('โœ… Onboarding scenario completed') console.log(chalk.green(` User: ${context.user.username}`)) console.log(chalk.green(` Validation: ${validation.isValid ? 'PASSED' : 'FAILED'}`)) break case 'high-load': const loadContext = await HighLoadScenario.setup() const loadTest = HighLoadScenario.simulateLoadTest(loadContext) spinner.succeed('โœ… High load scenario completed') console.log(chalk.green(` Peak users: ${loadContext.loadProfile.peakUsers}`)) console.log(chalk.green(` Expected throughput: ${loadContext.loadProfile.expectedThroughput} req/s`)) break case 'integration': const integrationContext = await CrossServiceCommunicationScenario.setup() const integrationTest = await CrossServiceCommunicationScenario.simulateIntegrationTest(integrationContext) spinner.succeed('โœ… Integration scenario completed') console.log(chalk.green(` Services tested: ${integrationContext.testFlow.services.length}`)) console.log(chalk.green(` Interactions: ${integrationContext.expectedInteractions.length}`)) break default: throw new Error(`Unknown scenario: ${scenarioName}`) } } catch (error) { spinner.fail(`โŒ Scenario failed: ${error}`) process.exit(1) } } async function createScenario(name: string, interactive: boolean): Promise { console.log(chalk.blue(`๐Ÿ“ Creating new scenario: ${name}`)) if (interactive) { const answers = await inquirer.prompt([ { type: 'list', name: 'type', message: 'Scenario type:', choices: ['user-journey', 'performance', 'integration', 'edge-cases'] }, { type: 'input', name: 'description', message: 'Scenario description:' }, { type: 'checkbox', name: 'services', message: 'Services involved:', choices: ['web', 'chat-server', 'stream-server', 'backend-api', 'docs'] } ]) console.log(chalk.green('โœ… Scenario template created')) console.log(chalk.blue('๐Ÿ“ Location: ') + `fixtures/scenarios/${answers.type}/${name}.ts`) } else { console.log(chalk.yellow('Use --interactive for guided scenario creation')) } } async function performanceChecks(services: string[], spinner: any): Promise { spinner.text = 'โšก Running performance checks...' const results: ValidationResult[] = [] for (const service of services) { // Simulate performance check const responseTime = Math.random() * 500 + 100 // 100-600ms const passed = responseTime < 400 // Pass if under 400ms results.push({ type: 'performance', service, passed, errors: passed ? [] : [`Response time too high: ${responseTime.toFixed(0)}ms`], warnings: responseTime > 300 ? [`Response time warning: ${responseTime.toFixed(0)}ms`] : [] }) } return results } function displayValidationResults(results: ValidationResult[]): void { console.log(chalk.blue('\n๐Ÿ“‹ Validation Results:')) results.forEach(result => { const status = result.passed ? chalk.green('โœ… PASS') : chalk.red('โŒ FAIL') console.log(` ${status} ${result.service} (${result.type})`) if (result.errors.length > 0) { result.errors.forEach(error => { console.log(chalk.red(` โŒ ${error}`)) }) } if (result.warnings.length > 0) { result.warnings.forEach(warning => { console.log(chalk.yellow(` โš ๏ธ ${warning}`)) }) } }) const totalTests = results.length const passedTests = results.filter(r => r.passed).length const passRate = ((passedTests / totalTests) * 100).toFixed(1) console.log(chalk.blue(`\n๐Ÿ“Š Summary: ${passedTests}/${totalTests} tests passed (${passRate}%)`)) } async function generateValidationReport(results: ValidationResult[], reportPath: string): Promise { const fs = await import('fs/promises') const path = await import('path') const report = { timestamp: new Date().toISOString(), summary: { total: results.length, passed: results.filter(r => r.passed).length, failed: results.filter(r => !r.passed).length }, results } await fs.writeFile(reportPath, JSON.stringify(report, null, 2)) console.log(chalk.green(`๐Ÿ“„ Validation report saved to: ${reportPath}`)) } async function checkServiceHealth(verbose: boolean): Promise { try { // Check chat server const chatHealth = await ChatServerFixtures.healthCheck() const chatStatus = chatHealth.database && chatHealth.redis ? '๐ŸŸข' : '๐Ÿ”ด' console.log(` ${chatStatus} Chat Server`) if (verbose) { console.log(` Database: ${chatHealth.database ? 'โœ…' : 'โŒ'}`) console.log(` Redis: ${chatHealth.redis ? 'โœ…' : 'โŒ'}`) console.log(` Data Consistency: ${chatHealth.dataConsistency ? 'โœ…' : 'โŒ'}`) } // Check stream server const streamHealth = await StreamServerFixtures.healthCheck() const streamStatus = streamHealth.redis ? '๐ŸŸข' : '๐Ÿ”ด' console.log(` ${streamStatus} Stream Server`) if (verbose) { console.log(` Redis: ${streamHealth.redis ? 'โœ…' : 'โŒ'}`) console.log(` Active Sessions: ${streamHealth.sessionsActive}`) console.log(` Queue Size: ${streamHealth.queueSize}`) } // Web service is always available (no external dependencies) console.log(' ๐ŸŸข Web Service') } catch (error) { console.log(' ๐Ÿ”ด Service health check failed') if (verbose) { console.log(` Error: ${error}`) } } } function generateSQLInserts(dataset: any): string { // Simplified SQL generation let sql = '-- Veza Platform Fixtures SQL Export\n\n' // Users sql += '-- Users\n' dataset.users.forEach((user: any) => { sql += `INSERT INTO users (id, username, email, first_name, last_name, role, created_at) VALUES ('${user.id}', '${user.username}', '${user.email}', '${user.firstName}', '${user.lastName}', '${user.role}', '${user.createdAt}');\n` }) return sql } async function generateCSVFiles(dataset: any, outputPath: string): Promise { const fs = await import('fs/promises') const path = await import('path') // Users CSV const usersCSV = [ 'id,username,email,firstName,lastName,role,status,createdAt', ...dataset.users.map((user: any) => `${user.id},${user.username},${user.email},${user.firstName},${user.lastName},${user.role},${user.status},${user.createdAt}` ) ].join('\n') await fs.writeFile(path.join(outputPath, 'users.csv'), usersCSV) console.log(chalk.green(' ๐Ÿ“„ users.csv exported')) } interface ValidationResult { type: string service: string passed: boolean errors: string[] warnings: string[] } // Parse command line arguments program.parse() // Show help if no command provided if (!process.argv.slice(2).length) { program.outputHelp() }