clawdie-ai/setup/git.test.ts
Operator & Codex 6de0ed87ab Remove legacy Mevy references (Sam & Codex)
Sweep active code, tests, identity files, public docs, CMS seed content, and stale handoffs so old assistant-name fixtures no longer leak into current Clawdie/system-namespace behavior. Keep the skills-memory SQL artifact unchanged per regeneration policy.

---

Build: pass

Tests: pass — 2197 passed (164 files)

---
Build: pass | Tests: pass — 2197 passed (650 files)
2026-05-07 11:16:40 +02:00

235 lines
6 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
const fsMocks = vi.hoisted(() => ({
mkdirSync: vi.fn(),
writeFileSync: vi.fn(),
existsSync: vi.fn(() => false),
readFileSync: vi.fn(() => ''),
appendFileSync: vi.fn(),
}));
const bastilleMocks = vi.hoisted(() => ({
bastille: vi.fn(),
bastilleTimed: vi.fn(),
jailExists: vi.fn(),
jailRoot: vi.fn(),
detectFreeBSDRelease: vi.fn(),
}));
const packageMocks = vi.hoisted(() => ({
loadPackageList: vi.fn(),
mountPkgCacheInJail: vi.fn(),
}));
const statusMocks = vi.hoisted(() => ({
emitStatus: vi.fn(),
}));
const childProcessMocks = vi.hoisted(() => ({
execSync: vi.fn(),
spawnSync: vi.fn(),
}));
const tailscaleMocks = vi.hoisted(() => ({
maybeEnableTailscaleInJail: vi.fn(),
}));
const envMocks = vi.hoisted(() => ({
readEnvFile: vi.fn(),
}));
vi.mock('child_process', () => childProcessMocks);
vi.mock('fs', async () => {
const actual = await vi.importActual<typeof import('fs')>('fs');
const mocked = { ...actual, ...fsMocks };
return { ...mocked, default: mocked };
});
vi.mock('./bastille-helpers.js', () => bastilleMocks);
vi.mock('./packages.js', () => packageMocks);
vi.mock('./status.js', () => statusMocks);
vi.mock('./tailscale.js', () => tailscaleMocks);
vi.mock('../src/env.js', () => envMocks);
vi.mock('./platform.js', () => ({
getPlatform: vi.fn(() => 'freebsd'),
}));
vi.mock('../src/logger.js', () => ({
logger: {
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
debug: vi.fn(),
},
}));
function mockConfig(overrides: Record<string, unknown> = {}) {
vi.doMock('../src/config.js', () => ({
CODE_SERVICE_INTERNAL_DOMAIN: 'git.home.arpa',
CODE_HOSTING_MODE: 'local',
FEATURE_GIT: true,
GIT_DEFAULT_REPO_NAME: 'alpha-ai.git',
GIT_JAIL_NAME: 'alpha-git',
GIT_MIRROR_URLS: [],
GIT_JAIL_IP: '10.0.0.6',
GIT_STORAGE_ROOT: '/srv/git',
PLATFORM_RUNTIME_HOME: '/home/alpha',
REMOTE_GIT_URL: '',
SUBNET_BASE: '10.0.0',
...overrides,
}));
}
function ok(output = '') {
return { ok: true, output };
}
describe('setup/git.ts', () => {
beforeEach(() => {
vi.resetModules();
vi.clearAllMocks();
bastilleMocks.bastille.mockReturnValue(ok());
bastilleMocks.bastilleTimed.mockReturnValue(ok());
bastilleMocks.jailExists.mockReturnValue(true);
bastilleMocks.jailRoot.mockReturnValue('/jails/alpha-git/root');
bastilleMocks.detectFreeBSDRelease.mockReturnValue('15.0-RELEASE');
packageMocks.loadPackageList.mockReturnValue(['git', 'rsync']);
packageMocks.mountPkgCacheInJail.mockImplementation(() => {});
tailscaleMocks.maybeEnableTailscaleInJail.mockImplementation(() => {});
envMocks.readEnvFile.mockReturnValue({});
childProcessMocks.execSync.mockImplementation(() => Buffer.from(''));
fsMocks.existsSync.mockReturnValue(false);
});
it('skips when git feature is disabled', async () => {
mockConfig({ FEATURE_GIT: false });
const mod = await import('./git.js');
await mod.run([]);
expect(statusMocks.emitStatus).toHaveBeenCalledWith(
'SETUP_GIT',
expect.objectContaining({
STATUS: 'skipped',
REASON: 'feature_disabled',
}),
);
expect(bastilleMocks.bastille).not.toHaveBeenCalled();
});
it('falls back to git init --bare when mirror clone fails', async () => {
mockConfig({ REMOTE_GIT_URL: 'https://codeberg.org/Clawdie/pi.git' });
bastilleMocks.bastille.mockImplementation((...args: string[]) => {
if (
args[0] === 'cmd' &&
args[1] === 'alpha-git' &&
args[2] === 'test' &&
args[3] === '-d'
) {
return { ok: false, output: '' };
}
if (
args[0] === 'cmd' &&
args.includes('git') &&
args.includes('--version')
) {
return ok('git version 2.46.0');
}
if (args[0] === 'cmd' && args.includes('rev-parse')) {
return ok('true\n');
}
return ok();
});
bastilleMocks.bastilleTimed.mockReturnValue({
ok: false,
output: 'mirror failed',
});
const mod = await import('./git.js');
await mod.run([]);
expect(bastilleMocks.bastilleTimed).toHaveBeenCalledWith(
expect.any(Number),
'cmd',
'alpha-git',
'git',
'clone',
'--mirror',
'https://codeberg.org/Clawdie/pi.git',
'/srv/git/alpha-ai.git',
);
expect(bastilleMocks.bastille).toHaveBeenCalledWith(
'cmd',
'alpha-git',
'git',
'init',
'--bare',
'/srv/git/alpha-ai.git',
);
expect(statusMocks.emitStatus).toHaveBeenCalledWith(
'SETUP_GIT',
expect.objectContaining({ STATUS: 'success' }),
);
});
it('clones only unique extra mirror repos', async () => {
mockConfig({
REMOTE_GIT_URL: 'https://codeberg.org/Clawdie/pi.git',
GIT_MIRROR_URLS: [
'https://codeberg.org/Clawdie/pi.git',
'git@codeberg.org:Clawdie/docs.git',
],
});
bastilleMocks.bastille.mockImplementation((...args: string[]) => {
if (
args[0] === 'cmd' &&
args[1] === 'alpha-git' &&
args[2] === 'test' &&
args[3] === '-d'
) {
return { ok: false, output: '' };
}
if (
args[0] === 'cmd' &&
args.includes('git') &&
args.includes('--version')
) {
return ok('git version 2.46.0');
}
if (args[0] === 'cmd' && args.includes('rev-parse')) {
return ok('true\n');
}
return ok();
});
const mod = await import('./git.js');
await mod.run([]);
const cloneCalls = bastilleMocks.bastilleTimed.mock.calls;
expect(cloneCalls).toHaveLength(2);
expect(cloneCalls[0].slice(1)).toEqual([
'cmd',
'alpha-git',
'git',
'clone',
'--mirror',
'https://codeberg.org/Clawdie/pi.git',
'/srv/git/alpha-ai.git',
]);
expect(cloneCalls[1].slice(1)).toEqual([
'cmd',
'alpha-git',
'git',
'clone',
'--mirror',
'git@codeberg.org:Clawdie/docs.git',
'/srv/git/docs.git',
]);
});
});