[grade=A] installer: fix 17 failing tests across 4 suites + tld data-loss bug

Batch 2 (installer). Root causes and fixes:
1. config-manager tests asserted stale layout (<path>/config/config.json);
   canonical implementation writes flat <path>/config.json matching the
   Docker volume-mount contract + REQUIRED_DIRS. Tests aligned to the
   production contract (not the other way) + create testDir in beforeEach.
2. saveConfig ENOENT when the install dir didn't exist: now mkdirs the
   parent before writing (implementation fix; wizard passes user-typed paths).
3. REAL BUG: saveDNSCredentials/loadDNSCredentials silently dropped the tld
   field on round-trip (property test caught it). Now persisted plaintext
   (non-secret, like server/username) and restored on load; type-normalized
   to string-or-null (judge polish #4).
4. installDocker/installCaddy unit tests hit the real network via
   DownloadManager (only child_process mocked) -> 5s timeouts. Now
   jest.mock('./download-manager') with fail-fast stubs.
5. dependency-checker.property.test.js ran real exec/downloads (caddy is
   installed on this box). Now hermetic: child_process + download-manager
   mocked at file scope.
6. installDocker fallback message parroted raw downloader error; now steers
   user to the manual instructions returned alongside (satisfies the test
   contract AND improves UX).

Also: mkdtemp test dirs (judge polish #1), nested-mkdir regression test
(polish #7). Installer suite: 119/119 green, 7/7 suites (was 17 failed /
4 suites red). Judge: Qwen lane grade A, 0 blocking, 8 polish (1,3,4,7
applied here), verdict /tmp/judge-batch2-verdict.json.
This commit is contained in:
Hermes
2026-08-31 23:37:38 -07:00
parent c28322eb46
commit b08de2955b
6 changed files with 125 additions and 33 deletions
@@ -1,5 +1,31 @@
const fc = require('fast-check');
const DependencyChecker = require('./dependency-checker');
const { exec } = require('child_process');
// These property tests must be hermetic: this repo's build box has caddy
// (and often docker) actually installed, and installDocker/installCaddy
// construct a real DownloadManager that would hit docker.com / GitHub.
// Mock child_process + DownloadManager so every property exercises the
// same deterministic code paths regardless of host state.
jest.mock('child_process');
jest.mock('./download-manager', () => {
return jest.fn().mockImplementation(() => ({
downloadDocker: jest.fn().mockResolvedValue({
success: false,
message: 'Download unavailable in test environment'
}),
downloadCaddy: jest.fn().mockResolvedValue({
success: false,
message: 'Download unavailable in test environment'
}),
extractCaddy: jest.fn().mockResolvedValue({
success: false,
message: 'Extraction unavailable in test environment'
}),
cleanup: jest.fn().mockResolvedValue(undefined)
}));
});
/**
* Feature: dashcaddy-installer, Property 2: Dependency Verification
@@ -12,6 +38,14 @@ describe('Property 2: Dependency Verification', () => {
beforeEach(() => {
checker = new DependencyChecker();
jest.clearAllMocks();
// Default: all commands succeed with empty output. This keeps
// checkDocker/checkCaddy/executeCommand/detectLinuxDistro deterministic
// (version parsing degrades to 'unknown'/'') and makes the
// installCaddy('macos') brew path reach the automated branch.
exec.mockImplementation((cmd, opts, callback) => {
callback(null, { stdout: '', stderr: '' });
});
});
test('checkDocker always returns valid structure', async () => {