test: add setup and upstream module unit tests
103 new tests across 7 files:
- setup/status: emitStatus output structure
- setup/packages: loadPackageList (real files), loadAllPackageLists
- setup/tailscale: maybeEnableTailscaleInJail enabled/disabled/pkg-fail paths
- setup/bastille-helpers: bastille(), jailRoot, jailExists, resolveJailName
- setup/hosts: syncLocalHosts with jail discovery and skip logic
- src/upstream/classify: classifyUpstreamFiles, classificationHint, summarizeCommit
- src/upstream/git: getUpstreamConfig, isGitRepo, listRemotes, readEnvFlag/writeEnvFlag, countCommitsBetween, listCommitHashes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---
Build: FAIL | Tests: pass — Tests 1493 passed (1493)
2026-04-15 05:32:50 +00:00
|
|
|
/**
|
|
|
|
|
* setup/hosts.test.ts — syncLocalHosts() unit tests.
|
|
|
|
|
*
|
|
|
|
|
* Tests that /etc/hosts is always synced and that jail hosts
|
|
|
|
|
* are synced/skipped based on whether bastille jail dirs exist.
|
|
|
|
|
*
|
|
|
|
|
* Run with: npx vitest run setup/hosts.test.ts
|
|
|
|
|
*/
|
|
|
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Mocks
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
vi.mock('../src/config.js', () => ({
|
2026-05-04 06:31:21 +02:00
|
|
|
RUNTIME_ID: 'clawdie',
|
test: add setup and upstream module unit tests
103 new tests across 7 files:
- setup/status: emitStatus output structure
- setup/packages: loadPackageList (real files), loadAllPackageLists
- setup/tailscale: maybeEnableTailscaleInJail enabled/disabled/pkg-fail paths
- setup/bastille-helpers: bastille(), jailRoot, jailExists, resolveJailName
- setup/hosts: syncLocalHosts with jail discovery and skip logic
- src/upstream/classify: classifyUpstreamFiles, classificationHint, summarizeCommit
- src/upstream/git: getUpstreamConfig, isGitRepo, listRemotes, readEnvFlag/writeEnvFlag, countCommitsBetween, listCommitHashes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---
Build: FAIL | Tests: pass — Tests 1493 passed (1493)
2026-04-15 05:32:50 +00:00
|
|
|
AGENT_INTERNAL_DOMAIN: 'clawdie.home.arpa',
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
vi.mock('../src/logger.js', () => ({
|
|
|
|
|
logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
vi.mock('./platform.js', () => ({
|
|
|
|
|
getPlatform: vi.fn().mockReturnValue('freebsd'),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
vi.mock('./status.js', () => ({ emitStatus: vi.fn() }));
|
|
|
|
|
|
|
|
|
|
vi.mock('../src/local-hosts.js', () => ({
|
|
|
|
|
renderLocalHostsBlock: vi.fn().mockReturnValue('# managed block\n'),
|
2026-04-24 07:49:09 +02:00
|
|
|
upsertLocalHostsBlock: vi
|
|
|
|
|
.fn()
|
|
|
|
|
.mockImplementation((content: string) => content + '\n# updated'),
|
test: add setup and upstream module unit tests
103 new tests across 7 files:
- setup/status: emitStatus output structure
- setup/packages: loadPackageList (real files), loadAllPackageLists
- setup/tailscale: maybeEnableTailscaleInJail enabled/disabled/pkg-fail paths
- setup/bastille-helpers: bastille(), jailRoot, jailExists, resolveJailName
- setup/hosts: syncLocalHosts with jail discovery and skip logic
- src/upstream/classify: classifyUpstreamFiles, classificationHint, summarizeCommit
- src/upstream/git: getUpstreamConfig, isGitRepo, listRemotes, readEnvFlag/writeEnvFlag, countCommitsBetween, listCommitHashes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---
Build: FAIL | Tests: pass — Tests 1493 passed (1493)
2026-04-15 05:32:50 +00:00
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
const fsMocks = vi.hoisted(() => ({
|
|
|
|
|
existsSync: vi.fn<[string], boolean>(() => false),
|
|
|
|
|
readFileSync: vi.fn<[string, string], string>(() => ''),
|
|
|
|
|
writeFileSync: vi.fn(),
|
|
|
|
|
mkdirSync: vi.fn(),
|
2026-04-24 07:49:09 +02:00
|
|
|
readdirSync: vi.fn<
|
|
|
|
|
[string, object],
|
|
|
|
|
Array<{ name: string; isDirectory: () => boolean }>
|
|
|
|
|
>(() => []),
|
test: add setup and upstream module unit tests
103 new tests across 7 files:
- setup/status: emitStatus output structure
- setup/packages: loadPackageList (real files), loadAllPackageLists
- setup/tailscale: maybeEnableTailscaleInJail enabled/disabled/pkg-fail paths
- setup/bastille-helpers: bastille(), jailRoot, jailExists, resolveJailName
- setup/hosts: syncLocalHosts with jail discovery and skip logic
- src/upstream/classify: classifyUpstreamFiles, classificationHint, summarizeCommit
- src/upstream/git: getUpstreamConfig, isGitRepo, listRemotes, readEnvFlag/writeEnvFlag, countCommitsBetween, listCommitHashes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---
Build: FAIL | Tests: pass — Tests 1493 passed (1493)
2026-04-15 05:32:50 +00:00
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
vi.mock('fs', async () => {
|
|
|
|
|
const actual = await vi.importActual<typeof import('fs')>('fs');
|
|
|
|
|
const mocked = { ...actual, ...fsMocks };
|
|
|
|
|
return { ...mocked, default: mocked };
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
import { syncLocalHosts } from './hosts.js';
|
|
|
|
|
|
|
|
|
|
const HOSTS_FILE = '/etc/hosts';
|
|
|
|
|
const BASTILLE_ROOT = '/usr/local/bastille';
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// syncLocalHosts
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
describe('syncLocalHosts — no bastille jails', () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
fsMocks.existsSync.mockReset().mockReturnValue(false);
|
|
|
|
|
fsMocks.readFileSync.mockReset().mockReturnValue('');
|
|
|
|
|
fsMocks.writeFileSync.mockReset();
|
|
|
|
|
fsMocks.readdirSync.mockReset().mockReturnValue([]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('always writes /etc/hosts', () => {
|
|
|
|
|
const result = syncLocalHosts();
|
|
|
|
|
expect(fsMocks.writeFileSync).toHaveBeenCalled();
|
|
|
|
|
const hostsWrite = fsMocks.writeFileSync.mock.calls.find(
|
|
|
|
|
(c) => c[0] === HOSTS_FILE,
|
|
|
|
|
);
|
|
|
|
|
expect(hostsWrite).toBeDefined();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('returns HOSTS_FILE as hostFile', () => {
|
|
|
|
|
const result = syncLocalHosts();
|
|
|
|
|
expect(result.hostFile).toBe(HOSTS_FILE);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('includes HOSTS_FILE in syncedTargets', () => {
|
|
|
|
|
const result = syncLocalHosts();
|
|
|
|
|
expect(result.syncedTargets).toContain(HOSTS_FILE);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('returns empty skippedTargets when no jails dir', () => {
|
2026-04-24 07:49:09 +02:00
|
|
|
fsMocks.existsSync.mockImplementation((p: string) => p === HOSTS_FILE);
|
test: add setup and upstream module unit tests
103 new tests across 7 files:
- setup/status: emitStatus output structure
- setup/packages: loadPackageList (real files), loadAllPackageLists
- setup/tailscale: maybeEnableTailscaleInJail enabled/disabled/pkg-fail paths
- setup/bastille-helpers: bastille(), jailRoot, jailExists, resolveJailName
- setup/hosts: syncLocalHosts with jail discovery and skip logic
- src/upstream/classify: classifyUpstreamFiles, classificationHint, summarizeCommit
- src/upstream/git: getUpstreamConfig, isGitRepo, listRemotes, readEnvFlag/writeEnvFlag, countCommitsBetween, listCommitHashes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---
Build: FAIL | Tests: pass — Tests 1493 passed (1493)
2026-04-15 05:32:50 +00:00
|
|
|
const result = syncLocalHosts();
|
|
|
|
|
expect(result.skippedTargets).toHaveLength(0);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('syncLocalHosts — with bastille jails', () => {
|
|
|
|
|
const JAILS_ROOT = `${BASTILLE_ROOT}/jails`;
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
fsMocks.existsSync.mockReset();
|
|
|
|
|
fsMocks.readFileSync.mockReset().mockReturnValue('127.0.0.1 localhost\n');
|
|
|
|
|
fsMocks.writeFileSync.mockReset();
|
|
|
|
|
fsMocks.readdirSync.mockReset();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('syncs a jail when its /etc/hosts directory exists', () => {
|
|
|
|
|
fsMocks.readdirSync.mockImplementation((p: string) => {
|
|
|
|
|
if ((p as string) === JAILS_ROOT) {
|
|
|
|
|
return [{ name: 'clawdie-db', isDirectory: () => true }] as never;
|
|
|
|
|
}
|
|
|
|
|
return [] as never;
|
|
|
|
|
});
|
|
|
|
|
fsMocks.existsSync.mockImplementation((p: string) => {
|
|
|
|
|
// jails root exists + jail's /etc dir exists
|
|
|
|
|
if ((p as string) === JAILS_ROOT) return true;
|
|
|
|
|
return (p as string).includes('clawdie-db/root/etc');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const result = syncLocalHosts();
|
|
|
|
|
expect(result.syncedTargets).toContain('clawdie-db');
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-24 16:50:08 +02:00
|
|
|
it('syncs a normalized underscore-form jail name', () => {
|
|
|
|
|
fsMocks.readdirSync.mockImplementation((p: string) => {
|
|
|
|
|
if ((p as string) === JAILS_ROOT) {
|
|
|
|
|
return [{ name: 'clawdie_db', isDirectory: () => true }] as never;
|
|
|
|
|
}
|
|
|
|
|
return [] as never;
|
|
|
|
|
});
|
|
|
|
|
fsMocks.existsSync.mockImplementation((p: string) => {
|
|
|
|
|
if ((p as string) === JAILS_ROOT) return true;
|
|
|
|
|
return (p as string).includes('clawdie_db/root/etc');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const result = syncLocalHosts();
|
|
|
|
|
expect(result.syncedTargets).toContain('clawdie_db');
|
|
|
|
|
});
|
|
|
|
|
|
test: add setup and upstream module unit tests
103 new tests across 7 files:
- setup/status: emitStatus output structure
- setup/packages: loadPackageList (real files), loadAllPackageLists
- setup/tailscale: maybeEnableTailscaleInJail enabled/disabled/pkg-fail paths
- setup/bastille-helpers: bastille(), jailRoot, jailExists, resolveJailName
- setup/hosts: syncLocalHosts with jail discovery and skip logic
- src/upstream/classify: classifyUpstreamFiles, classificationHint, summarizeCommit
- src/upstream/git: getUpstreamConfig, isGitRepo, listRemotes, readEnvFlag/writeEnvFlag, countCommitsBetween, listCommitHashes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---
Build: FAIL | Tests: pass — Tests 1493 passed (1493)
2026-04-15 05:32:50 +00:00
|
|
|
it('skips a jail when its /etc/hosts directory does not exist', () => {
|
|
|
|
|
fsMocks.readdirSync.mockImplementation((p: string) => {
|
|
|
|
|
if ((p as string) === JAILS_ROOT) {
|
|
|
|
|
return [{ name: 'clawdie-db', isDirectory: () => true }] as never;
|
|
|
|
|
}
|
|
|
|
|
return [] as never;
|
|
|
|
|
});
|
2026-04-24 07:49:09 +02:00
|
|
|
fsMocks.existsSync.mockImplementation(
|
|
|
|
|
(p: string) => (p as string) === JAILS_ROOT,
|
test: add setup and upstream module unit tests
103 new tests across 7 files:
- setup/status: emitStatus output structure
- setup/packages: loadPackageList (real files), loadAllPackageLists
- setup/tailscale: maybeEnableTailscaleInJail enabled/disabled/pkg-fail paths
- setup/bastille-helpers: bastille(), jailRoot, jailExists, resolveJailName
- setup/hosts: syncLocalHosts with jail discovery and skip logic
- src/upstream/classify: classifyUpstreamFiles, classificationHint, summarizeCommit
- src/upstream/git: getUpstreamConfig, isGitRepo, listRemotes, readEnvFlag/writeEnvFlag, countCommitsBetween, listCommitHashes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---
Build: FAIL | Tests: pass — Tests 1493 passed (1493)
2026-04-15 05:32:50 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const result = syncLocalHosts();
|
|
|
|
|
expect(result.skippedTargets).toContain('clawdie-db');
|
|
|
|
|
expect(result.syncedTargets).not.toContain('clawdie-db');
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-24 16:50:08 +02:00
|
|
|
it('only syncs tenant-owned jail names', () => {
|
test: add setup and upstream module unit tests
103 new tests across 7 files:
- setup/status: emitStatus output structure
- setup/packages: loadPackageList (real files), loadAllPackageLists
- setup/tailscale: maybeEnableTailscaleInJail enabled/disabled/pkg-fail paths
- setup/bastille-helpers: bastille(), jailRoot, jailExists, resolveJailName
- setup/hosts: syncLocalHosts with jail discovery and skip logic
- src/upstream/classify: classifyUpstreamFiles, classificationHint, summarizeCommit
- src/upstream/git: getUpstreamConfig, isGitRepo, listRemotes, readEnvFlag/writeEnvFlag, countCommitsBetween, listCommitHashes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---
Build: FAIL | Tests: pass — Tests 1493 passed (1493)
2026-04-15 05:32:50 +00:00
|
|
|
fsMocks.readdirSync.mockImplementation((p: string) => {
|
|
|
|
|
if ((p as string) === JAILS_ROOT) {
|
|
|
|
|
return [
|
|
|
|
|
{ name: 'clawdie-db', isDirectory: () => true },
|
2026-04-24 16:50:08 +02:00
|
|
|
{ name: 'clawdie_db', isDirectory: () => true },
|
test: add setup and upstream module unit tests
103 new tests across 7 files:
- setup/status: emitStatus output structure
- setup/packages: loadPackageList (real files), loadAllPackageLists
- setup/tailscale: maybeEnableTailscaleInJail enabled/disabled/pkg-fail paths
- setup/bastille-helpers: bastille(), jailRoot, jailExists, resolveJailName
- setup/hosts: syncLocalHosts with jail discovery and skip logic
- src/upstream/classify: classifyUpstreamFiles, classificationHint, summarizeCommit
- src/upstream/git: getUpstreamConfig, isGitRepo, listRemotes, readEnvFlag/writeEnvFlag, countCommitsBetween, listCommitHashes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---
Build: FAIL | Tests: pass — Tests 1493 passed (1493)
2026-04-15 05:32:50 +00:00
|
|
|
{ name: 'other-jail', isDirectory: () => true },
|
|
|
|
|
] as never;
|
|
|
|
|
}
|
|
|
|
|
return [] as never;
|
|
|
|
|
});
|
|
|
|
|
fsMocks.existsSync.mockImplementation((p: string) => {
|
|
|
|
|
if ((p as string) === JAILS_ROOT) return true;
|
2026-04-24 07:49:09 +02:00
|
|
|
return (
|
|
|
|
|
(p as string).includes('clawdie-db/root/etc') ||
|
2026-04-24 16:50:08 +02:00
|
|
|
(p as string).includes('clawdie_db/root/etc') ||
|
2026-04-24 07:49:09 +02:00
|
|
|
(p as string).includes('other-jail/root/etc')
|
|
|
|
|
);
|
test: add setup and upstream module unit tests
103 new tests across 7 files:
- setup/status: emitStatus output structure
- setup/packages: loadPackageList (real files), loadAllPackageLists
- setup/tailscale: maybeEnableTailscaleInJail enabled/disabled/pkg-fail paths
- setup/bastille-helpers: bastille(), jailRoot, jailExists, resolveJailName
- setup/hosts: syncLocalHosts with jail discovery and skip logic
- src/upstream/classify: classifyUpstreamFiles, classificationHint, summarizeCommit
- src/upstream/git: getUpstreamConfig, isGitRepo, listRemotes, readEnvFlag/writeEnvFlag, countCommitsBetween, listCommitHashes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---
Build: FAIL | Tests: pass — Tests 1493 passed (1493)
2026-04-15 05:32:50 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const result = syncLocalHosts();
|
|
|
|
|
// 'other-jail' should not appear in results at all (not agent-owned)
|
2026-04-24 07:49:09 +02:00
|
|
|
expect(result.syncedTargets.concat(result.skippedTargets)).not.toContain(
|
|
|
|
|
'other-jail',
|
|
|
|
|
);
|
2026-04-24 16:50:08 +02:00
|
|
|
expect(result.syncedTargets).toContain('clawdie-db');
|
|
|
|
|
expect(result.syncedTargets).toContain('clawdie_db');
|
test: add setup and upstream module unit tests
103 new tests across 7 files:
- setup/status: emitStatus output structure
- setup/packages: loadPackageList (real files), loadAllPackageLists
- setup/tailscale: maybeEnableTailscaleInJail enabled/disabled/pkg-fail paths
- setup/bastille-helpers: bastille(), jailRoot, jailExists, resolveJailName
- setup/hosts: syncLocalHosts with jail discovery and skip logic
- src/upstream/classify: classifyUpstreamFiles, classificationHint, summarizeCommit
- src/upstream/git: getUpstreamConfig, isGitRepo, listRemotes, readEnvFlag/writeEnvFlag, countCommitsBetween, listCommitHashes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---
Build: FAIL | Tests: pass — Tests 1493 passed (1493)
2026-04-15 05:32:50 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('returns HOSTS_FILE always in syncedTargets even with jails', () => {
|
|
|
|
|
fsMocks.readdirSync.mockReturnValue([]);
|
|
|
|
|
const result = syncLocalHosts();
|
|
|
|
|
expect(result.syncedTargets).toContain(HOSTS_FILE);
|
|
|
|
|
});
|
|
|
|
|
});
|