Full codebase including API server (32 modules + routes), dashboard frontend, DashCA certificate distribution, installer script, and deployment skills.
270 lines
8.8 KiB
JavaScript
270 lines
8.8 KiB
JavaScript
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
|
|
});
|