clawdie-ai/setup/tailscale.test.ts

135 lines
4.9 KiB
TypeScript
Raw Permalink Normal View History

/**
* setup/tailscale.test.ts maybeEnableTailscaleInJail unit tests.
*
* Run with: npx vitest run setup/tailscale.test.ts
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
// ---------------------------------------------------------------------------
// Mocks
// ---------------------------------------------------------------------------
vi.mock('../src/env.js', () => ({
readEnvFile: vi.fn().mockReturnValue({}),
}));
vi.mock('../src/logger.js', () => ({
logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },
}));
import { maybeEnableTailscaleInJail } from './tailscale.js';
import { readEnvFile } from '../src/env.js';
const mockReadEnvFile = vi.mocked(readEnvFile);
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function makeRunner(results: Record<string, { ok: boolean; output: string }> = {}) {
return vi.fn().mockImplementation((args: string[]) => {
const key = args[0];
return results[key] ?? { ok: true, output: '' };
});
}
// ---------------------------------------------------------------------------
// maybeEnableTailscaleInJail
// ---------------------------------------------------------------------------
describe('maybeEnableTailscaleInJail — disabled', () => {
beforeEach(() => {
mockReadEnvFile.mockReset().mockReturnValue({ FEATURE_TAILSCALE: 'NO', TAILSCALE_AUTHKEY: '' });
delete process.env.FEATURE_TAILSCALE;
delete process.env.TAILSCALE_AUTHKEY;
});
it('does nothing when FEATURE_TAILSCALE is off', () => {
const runner = makeRunner();
maybeEnableTailscaleInJail(runner, 'test-jail', 'test-jail');
expect(runner).not.toHaveBeenCalled();
});
it('does nothing when FEATURE_TAILSCALE is not set', () => {
mockReadEnvFile.mockReturnValue({});
const runner = makeRunner();
maybeEnableTailscaleInJail(runner, 'test-jail', 'test-jail');
expect(runner).not.toHaveBeenCalled();
});
});
describe('maybeEnableTailscaleInJail — enabled but no authkey', () => {
beforeEach(() => {
mockReadEnvFile.mockReset().mockReturnValue({ FEATURE_TAILSCALE: 'YES', TAILSCALE_AUTHKEY: '' });
delete process.env.TAILSCALE_AUTHKEY;
});
it('does nothing when auth key is missing', () => {
const runner = makeRunner();
maybeEnableTailscaleInJail(runner, 'test-jail', 'test-jail');
expect(runner).not.toHaveBeenCalled();
});
});
describe('maybeEnableTailscaleInJail — enabled with authkey', () => {
beforeEach(() => {
mockReadEnvFile.mockReset().mockReturnValue({
FEATURE_TAILSCALE: 'YES',
TAILSCALE_AUTHKEY: 'tskey-abc123',
});
delete process.env.FEATURE_TAILSCALE;
delete process.env.TAILSCALE_AUTHKEY;
});
it('calls pkg install tailscale', () => {
const runner = makeRunner();
maybeEnableTailscaleInJail(runner, 'my-jail', 'my-jail');
const pkgCall = runner.mock.calls.find((c: string[][]) => c[0].includes('tailscale') && c[0][0] === 'pkg');
expect(pkgCall).toBeDefined();
});
it('calls tailscale up with the auth key', () => {
const runner = makeRunner();
maybeEnableTailscaleInJail(runner, 'my-jail', 'my-jail');
const upCall = runner.mock.calls.find((c: string[][]) =>
c[0].includes('tailscale') && c[0].includes('up'),
);
expect(upCall).toBeDefined();
expect(upCall![0]).toContain('tskey-abc123');
});
it('passes normalized hostname to tailscale up', () => {
const runner = makeRunner();
maybeEnableTailscaleInJail(runner, 'my-jail', 'My_Jail Host!');
const upCall = runner.mock.calls.find((c: string[][]) =>
c[0].includes('up'),
);
// normalizeHostname: lowercase, strip non [a-z0-9-]
expect(upCall![0]).toContain('myjailhost');
});
it('stops after pkg install fails (no tailscale up call)', () => {
const runner = makeRunner({ pkg: { ok: false, output: 'install failed' } });
maybeEnableTailscaleInJail(runner, 'my-jail', 'my-jail');
const upCall = runner.mock.calls.find((c: string[][]) => c[0].includes('up'));
expect(upCall).toBeUndefined();
});
it('uses process.env.TAILSCALE_AUTHKEY when set', () => {
process.env.TAILSCALE_AUTHKEY = 'env-override-key';
mockReadEnvFile.mockReturnValue({ FEATURE_TAILSCALE: 'YES', TAILSCALE_AUTHKEY: 'file-key' });
const runner = makeRunner();
maybeEnableTailscaleInJail(runner, 'my-jail', 'my-jail');
const upCall = runner.mock.calls.find((c: string[][]) => c[0].includes('up'));
expect(upCall![0]).toContain('env-override-key');
delete process.env.TAILSCALE_AUTHKEY;
});
it('falls back to jail name as hostname when hostnameHint is empty', () => {
const runner = makeRunner();
maybeEnableTailscaleInJail(runner, 'my-jail', '');
const upCall = runner.mock.calls.find((c: string[][]) => c[0].includes('up'));
expect(upCall![0]).toContain('my-jail');
});
});