const fc = require('fast-check'); const DependencyChecker = require('./dependency-checker'); /** * Feature: dashcaddy-installer, Property 2: Dependency Verification * For any system state, the installer should accurately report whether Docker * is installed and running, and whether Caddy is installed. * Validates: Requirements 1.2, 1.3 */ describe('Property 2: Dependency Verification', () => { let checker; beforeEach(() => { checker = new DependencyChecker(); }); test('checkDocker always returns valid structure', async () => { // Skip if Docker commands are too slow const quickCheck = await checker.checkDocker(); if (!quickCheck.installed) { // If Docker not installed, just verify the structure is correct expect(quickCheck.installed).toBe(false); expect(quickCheck.running).toBe(false); return; } await fc.assert( fc.asyncProperty( fc.constant(null), async () => { const result = await checker.checkDocker(); // Must have required fields const hasRequiredFields = typeof result.installed === 'boolean' && typeof result.running === 'boolean' && (result.version === null || typeof result.version === 'string'); // If installed, version should be present const versionConsistent = !result.installed || result.version !== null; // If not installed, cannot be running const runningConsistent = !result.running || result.installed; return hasRequiredFields && versionConsistent && runningConsistent; } ), { numRuns: 5 } // Very few runs since Docker commands are slow ); }, 60000); // 60 second timeout test('checkCaddy always returns valid structure', async () => { await fc.assert( fc.asyncProperty( fc.constant(null), async () => { const result = await checker.checkCaddy(); // Must have required fields const hasRequiredFields = typeof result.installed === 'boolean' && (result.version === null || typeof result.version === 'string') && (result.path === null || typeof result.path === 'string'); // If installed, version should be present const versionConsistent = !result.installed || result.version !== null; return hasRequiredFields && versionConsistent; } ), { numRuns: 50 } ); }); test('dependency check results are deterministic', async () => { // Skip if Docker commands are too slow const quickCheck = await checker.checkDocker(); if (!quickCheck.installed) { // If Docker not installed, just verify consistency const check2 = await checker.checkDocker(); expect(check2.installed).toBe(false); return; } await fc.assert( fc.asyncProperty( fc.constant(null), async () => { const result1 = await checker.checkDocker(); const result2 = await checker.checkDocker(); // Same system state should return same results return ( result1.installed === result2.installed && result1.running === result2.running && result1.version === result2.version ); } ), { numRuns: 3 } // Very few runs since this is expensive ); }, 60000); // 60 second timeout test('getDockerInstallInstructions returns valid structure for any platform', () => { fc.assert( fc.property( fc.constantFrom('windows', 'macos', 'linux', 'unknown'), (platform) => { const instructions = checker.getDockerInstallInstructions(platform); return ( typeof instructions === 'object' && typeof instructions.title === 'string' && Array.isArray(instructions.steps) && instructions.steps.length > 0 && typeof instructions.url === 'string' && typeof instructions.requiresWSL2 === 'boolean' ); } ), { numRuns: 100 } ); }); test('getCaddyInstallInstructions returns valid structure for any platform', () => { fc.assert( fc.property( fc.constantFrom('windows', 'macos', 'linux', 'unknown'), (platform) => { const instructions = checker.getCaddyInstallInstructions(platform); return ( typeof instructions === 'object' && typeof instructions.title === 'string' && Array.isArray(instructions.steps) && instructions.steps.length > 0 && typeof instructions.url === 'string' && typeof instructions.automated === 'boolean' ); } ), { numRuns: 100 } ); }); test('Windows Docker instructions always mention WSL2', () => { const instructions = checker.getDockerInstallInstructions('windows'); expect(instructions.requiresWSL2).toBe(true); expect(instructions.wsl2Instructions).toBeDefined(); expect(Array.isArray(instructions.wsl2Instructions)).toBe(true); }); test('macOS Caddy instructions support automation', () => { const instructions = checker.getCaddyInstallInstructions('macos'); expect(instructions.automated).toBe(true); expect(instructions.command).toBeDefined(); expect(instructions.command).toContain('brew'); }); test('Linux instructions include package manager options', () => { fc.assert( fc.property( fc.constantFrom('docker', 'caddy'), (tool) => { const instructions = tool === 'docker' ? checker.getDockerInstallInstructions('linux') : checker.getCaddyInstallInstructions('linux'); return ( typeof instructions.packageManagers === 'object' && Object.keys(instructions.packageManagers).length > 0 ); } ), { numRuns: 100 } ); }); test('executeCommand handles any command string', async () => { await fc.assert( fc.asyncProperty( fc.string({ minLength: 1, maxLength: 50 }), async (command) => { try { const result = await checker.executeCommand(command); // Must return valid structure return ( typeof result === 'object' && typeof result.success === 'boolean' && typeof result.stdout === 'string' && typeof result.stderr === 'string' ); } catch (error) { // Some commands might fail, that's okay return true; } } ), { numRuns: 50 } // Reduced since this executes actual commands ); }); test('detectLinuxDistro returns valid distro name', async () => { await fc.assert( fc.asyncProperty( fc.constant(null), async () => { const distro = await checker.detectLinuxDistro(); const validDistros = ['ubuntu', 'fedora', 'arch', 'unknown']; return ( typeof distro === 'string' && validDistros.includes(distro) ); } ), { numRuns: 50 } ); }); test('installDocker always returns result with instructions', async () => { await fc.assert( fc.asyncProperty( fc.constantFrom('windows', 'macos', 'linux'), async (platform) => { const result = await checker.installDocker(platform); return ( typeof result === 'object' && typeof result.success === 'boolean' && typeof result.automated === 'boolean' && typeof result.message === 'string' && typeof result.instructions === 'object' ); } ), { numRuns: 100 } ); }); test('installCaddy returns appropriate result for platform', async () => { await fc.assert( fc.asyncProperty( fc.constantFrom('windows', 'macos', 'linux'), async (platform) => { const result = await checker.installCaddy(platform); const hasRequiredFields = typeof result === 'object' && typeof result.success === 'boolean' && typeof result.automated === 'boolean' && typeof result.message === 'string'; // Windows should not be automated const windowsCorrect = platform !== 'windows' || result.automated === false; // macOS should attempt automation (Linux may or may not depending on distro detection) const macCorrect = platform !== 'macos' || result.automated === true; return hasRequiredFields && windowsCorrect && macCorrect; } ), { numRuns: 100 } ); }, 30000); // 30 second timeout });