UNPKG

aios-core

Version:

Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework

352 lines (290 loc) 13.9 kB
const fs = require('fs'); const path = require('path'); const { runWizard, getProjectType, confirmProjectType } = require('../../src/wizard/wizard'); const { detectProjectType } = require('../../src/detection/detect-project-type'); // Mock fs module jest.mock('fs'); // Mock console methods to avoid test output pollution global.console = { ...console, log: jest.fn(), error: jest.fn(), }; describe('Wizard Integration with Project Type Detection', () => { beforeEach(() => { jest.clearAllMocks(); }); // ============================================================================ // Task 1.3.5.2: Test full wizard flow with GREENFIELD detection // ============================================================================ describe('GREENFIELD Detection Flow', () => { test('wizard correctly detects and processes GREENFIELD project', async () => { // Setup: Empty directory (directory exists but no markers) const targetPath = path.resolve('/test-greenfield'); fs.existsSync.mockImplementation((checkPath) => { if (checkPath === targetPath) return true; // Directory exists // No markers exist if (checkPath.includes('.aios-core')) return false; if (checkPath.includes('package.json')) return false; if (checkPath.includes('.git')) return false; return false; }); fs.readdirSync.mockReturnValue([]); const result = await runWizard({ targetDir: '/test-greenfield' }); expect(result.projectType).toBe('GREENFIELD'); expect(result.targetDir).toBe('/test-greenfield'); // Output uses lowercase format for display expect(console.log).toHaveBeenCalledWith(expect.stringContaining('greenfield')); }); test('getProjectType helper returns GREENFIELD for empty directory', () => { const targetPath = path.resolve('/test/empty'); fs.existsSync.mockImplementation((checkPath) => { if (checkPath === targetPath) return true; if (checkPath.includes('.aios-core')) return false; if (checkPath.includes('package.json')) return false; if (checkPath.includes('.git')) return false; return false; }); fs.readdirSync.mockReturnValue([]); const type = getProjectType('/test/empty'); expect(type).toBe('GREENFIELD'); }); }); // ============================================================================ // Task 1.3.5.3: Test full wizard flow with BROWNFIELD detection // ============================================================================ describe('BROWNFIELD Detection Flow', () => { test('wizard correctly detects and processes BROWNFIELD project with package.json', async () => { // Setup: Directory with package.json const targetPath = path.resolve('/test-brownfield'); fs.existsSync.mockImplementation((checkPath) => { if (checkPath === targetPath) return true; if (checkPath.includes('.aios-core')) return false; // No AIOS if (checkPath.includes('package.json')) return true; // Has package.json if (checkPath.includes('.git')) return false; return false; }); fs.readdirSync.mockReturnValue(['package.json', 'src', 'README.md']); const result = await runWizard({ targetDir: '/test-brownfield' }); expect(result.projectType).toBe('BROWNFIELD'); expect(result.targetDir).toBe('/test-brownfield'); expect(console.log).toHaveBeenCalledWith(expect.stringContaining('brownfield')); }); test('wizard correctly detects and processes BROWNFIELD project with .git', async () => { // Setup: Directory with .git const targetPath = path.resolve('/test-git'); fs.existsSync.mockImplementation((checkPath) => { if (checkPath === targetPath) return true; if (checkPath.includes('.aios-core')) return false; // No AIOS if (checkPath.includes('package.json')) return false; if (checkPath.includes('.git')) return true; // Has .git return false; }); fs.readdirSync.mockReturnValue(['.git', 'README.md']); const result = await runWizard({ targetDir: '/test-git' }); expect(result.projectType).toBe('BROWNFIELD'); expect(console.log).toHaveBeenCalledWith(expect.stringContaining('brownfield')); }); test('getProjectType helper returns BROWNFIELD for existing project', () => { const targetPath = path.resolve('/test/brownfield'); fs.existsSync.mockImplementation((checkPath) => { if (checkPath === targetPath) return true; if (checkPath.includes('.aios-core')) return false; if (checkPath.includes('package.json')) return true; if (checkPath.includes('.git')) return false; return false; }); fs.readdirSync.mockReturnValue(['package.json', 'src']); const type = getProjectType('/test/brownfield'); expect(type).toBe('BROWNFIELD'); }); }); // ============================================================================ // Task 1.3.5.4: Test full wizard flow with EXISTING_AIOS detection // ============================================================================ describe('EXISTING_AIOS Detection Flow', () => { test('wizard correctly detects and processes EXISTING_AIOS installation', async () => { // Setup: Directory with .aios-core fs.existsSync.mockImplementation((checkPath) => { if (checkPath.includes('test-existing')) return true; if (checkPath.endsWith('.aios-core')) return true; return true; // Other files exist }); fs.readdirSync.mockReturnValue(['.aios-core', 'package.json', '.git']); const result = await runWizard({ targetDir: '/test-existing' }); expect(result.projectType).toBe('EXISTING_AIOS'); expect(result.targetDir).toBe('/test-existing'); // Output shows "brownfield" since EXISTING_AIOS is treated as brownfield update expect(console.log).toHaveBeenCalledWith(expect.stringContaining('brownfield')); }); test('confirmProjectType shows update/reinstall message for EXISTING_AIOS', async () => { const confirmed = await confirmProjectType('EXISTING_AIOS'); expect(confirmed).toBe('EXISTING_AIOS'); expect(console.log).toHaveBeenCalledWith( expect.stringContaining('update or reinstall'), ); }); test('getProjectType helper returns EXISTING_AIOS when .aios-core exists', () => { fs.existsSync.mockImplementation((checkPath) => { if (checkPath.includes('existing')) return true; if (checkPath.endsWith('.aios-core')) return true; return false; }); fs.readdirSync.mockReturnValue(['.aios-core']); const type = getProjectType('/test/existing'); expect(type).toBe('EXISTING_AIOS'); }); }); // ============================================================================ // Task 1.3.5.5: Test user override of detection result // ============================================================================ describe('User Override Functionality', () => { test('confirmProjectType accepts detected type', async () => { const confirmed = await confirmProjectType('GREENFIELD'); expect(confirmed).toBe('GREENFIELD'); expect(console.log).toHaveBeenCalledWith(expect.stringContaining('GREENFIELD')); }); test('confirmProjectType shows appropriate message for each type', async () => { const types = ['GREENFIELD', 'BROWNFIELD', 'EXISTING_AIOS', 'UNKNOWN']; for (const type of types) { jest.clearAllMocks(); const confirmed = await confirmProjectType(type); expect(confirmed).toBe(type); expect(console.log).toHaveBeenCalledWith(expect.stringContaining(type)); } }); test('confirmProjectType provides description for GREENFIELD', async () => { await confirmProjectType('GREENFIELD'); expect(console.log).toHaveBeenCalledWith( expect.stringContaining('New project'), ); }); test('confirmProjectType provides description for BROWNFIELD', async () => { await confirmProjectType('BROWNFIELD'); expect(console.log).toHaveBeenCalledWith( expect.stringContaining('Existing project'), ); }); }); // ============================================================================ // Task 1.3.5.6: Test error handling and fallback to manual selection // ============================================================================ describe('Error Handling in Wizard Flow', () => { test('wizard propagates detection errors correctly', async () => { // Setup: Invalid directory fs.existsSync.mockImplementation((checkPath) => { if (checkPath.includes('invalid')) return false; return false; }); await expect(runWizard({ targetDir: '/invalid' })).rejects.toThrow( 'Directory does not exist', ); }); test('wizard logs error message on detection failure', async () => { // Setup: Permission error fs.existsSync.mockImplementation(() => { throw new Error('EACCES: permission denied'); }); await expect(runWizard({ targetDir: '/denied' })).rejects.toThrow(); // Check that console.error was called with the error message const errorCalls = console.error.mock.calls; const hasInstallationFailed = errorCalls.some(call => call.some(arg => String(arg).includes('Installation failed')), ); expect(hasInstallationFailed).toBe(true); }); test('confirmProjectType handles UNKNOWN type gracefully', async () => { const confirmed = await confirmProjectType('UNKNOWN'); expect(confirmed).toBe('UNKNOWN'); expect(console.log).toHaveBeenCalledWith( expect.stringContaining('Manual selection required'), ); }); }); // ============================================================================ // Task 1.3.5.7: Verify detection result flows correctly to Story 1.4 // ============================================================================ describe('Integration with Downstream Stories', () => { test('wizard returns configuration object with projectType for downstream use', async () => { const targetPath = path.resolve('/test/downstream'); fs.existsSync.mockImplementation((checkPath) => { if (checkPath === targetPath) return true; if (checkPath.includes('.aios-core')) return false; if (checkPath.includes('package.json')) return false; if (checkPath.includes('.git')) return false; return false; }); fs.readdirSync.mockReturnValue([]); const config = await runWizard({ targetDir: '/test/downstream' }); // Verify config has required fields for downstream stories expect(config).toHaveProperty('projectType'); expect(config).toHaveProperty('targetDir'); expect(config.projectType).toBe('GREENFIELD'); expect(config.targetDir).toBe('/test/downstream'); }); test('getProjectType can be called independently by downstream stories', () => { const targetPath = path.resolve('/test/standalone'); fs.existsSync.mockImplementation((checkPath) => { if (checkPath === targetPath) return true; if (checkPath.includes('.aios-core')) return false; if (checkPath.includes('package.json')) return false; if (checkPath.includes('.git')) return false; return false; }); fs.readdirSync.mockReturnValue([]); // Story 1.4 (IDE Selection) can call this directly const type = getProjectType('/test/standalone'); expect(type).toBe('GREENFIELD'); expect(typeof type).toBe('string'); }); test('detection result remains consistent across multiple calls', () => { const targetPath = path.resolve('/test/consistent'); fs.existsSync.mockImplementation((checkPath) => { if (checkPath === targetPath) return true; if (checkPath.includes('.aios-core')) return false; if (checkPath.includes('package.json')) return true; if (checkPath.includes('.git')) return false; return false; }); fs.readdirSync.mockReturnValue(['package.json']); const type1 = getProjectType('/test/consistent'); const type2 = getProjectType('/test/consistent'); const type3 = detectProjectType('/test/consistent'); expect(type1).toBe(type2); expect(type2).toBe(type3); expect(type1).toBe('BROWNFIELD'); }); }); // ============================================================================ // Default Behavior Tests // ============================================================================ describe('Default Wizard Behavior', () => { test('wizard uses process.cwd() when no targetDir provided', async () => { const mockCwd = process.cwd(); fs.existsSync.mockImplementation((checkPath) => { if (checkPath === path.resolve(mockCwd)) return true; return false; }); fs.readdirSync.mockReturnValue([]); const result = await runWizard(); expect(result.targetDir).toBe(mockCwd); expect(result.projectType).toBe('GREENFIELD'); }); test('wizard displays welcome message', async () => { fs.existsSync.mockImplementation(() => true); fs.readdirSync.mockReturnValue([]); await runWizard({ targetDir: '/test' }); expect(console.log).toHaveBeenCalledWith( expect.stringContaining('Welcome to AIOS Installer'), ); }); test('wizard displays detection step message', async () => { fs.existsSync.mockImplementation(() => true); fs.readdirSync.mockReturnValue([]); await runWizard({ targetDir: '/test' }); expect(console.log).toHaveBeenCalledWith( expect.stringContaining('Analyzing project directory'), ); }); }); });