clawdie-ai/setup/env-audit.test.ts
Clawdie AI abe3be71fd test: add freebsd-timezones and env-audit tests
54 new tests: pure timezone helpers (getTimezoneOptions, prioritizeTimezones,
findTimezoneOption, isValidTimezone) with no mocks, plus auditEnvFile covering
defaults, missing key detection, feature-flag warnings, GIT_MIRROR_URLS parsing,
and value masking — using real temp files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---
Build: FAIL | Tests: pass — Tests  1363 passed (1363)
2026-04-15 05:12:49 +00:00

271 lines
10 KiB
TypeScript

/**
* setup/env-audit.test.ts — auditEnvFile unit tests.
*
* Tests defaults, missing keys, warnings, and feature-flag interactions.
* Uses real temp files (simpler than mocking fs for a function that
* reads env file content line-by-line).
*
* Run with: npx vitest run setup/env-audit.test.ts
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { vi } from 'vitest';
import fs from 'fs';
import os from 'os';
import path from 'path';
// ---------------------------------------------------------------------------
// Mock non-auditEnvFile deps (only used by run(), not by auditEnvFile)
// ---------------------------------------------------------------------------
vi.mock('./status.js', () => ({ emitStatus: vi.fn() }));
vi.mock('./platform.js', () => ({ getPlatform: vi.fn().mockReturnValue('freebsd') }));
import { auditEnvFile } from './env-audit.js';
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
let tmpDir: string;
let envFile: string;
beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'env-audit-test-'));
envFile = path.join(tmpDir, '.env');
});
afterEach(() => {
fs.rmSync(tmpDir, { recursive: true, force: true });
});
function writeEnv(content: string): void {
fs.writeFileSync(envFile, content, 'utf-8');
}
// ---------------------------------------------------------------------------
// Missing env file
// ---------------------------------------------------------------------------
describe('auditEnvFile — no env file', () => {
it('uses defaults when file does not exist', () => {
const result = auditEnvFile(path.join(tmpDir, 'nonexistent.env'));
expect(result.values.AGENT_NAME).toBe('clawdie');
expect(result.values.AGENT_SUBNET_BASE).toBe('10.0.0');
});
it('ASSISTANT_NAME is in missing list when file is absent', () => {
const result = auditEnvFile(path.join(tmpDir, 'nonexistent.env'));
expect(result.missing).toContain('ASSISTANT_NAME');
});
});
// ---------------------------------------------------------------------------
// Defaults
// ---------------------------------------------------------------------------
describe('auditEnvFile — defaults', () => {
it('uses "clawdie" as default AGENT_NAME', () => {
writeEnv('');
const result = auditEnvFile(envFile);
expect(result.values.AGENT_NAME).toBe('clawdie');
});
it('defaults AGENT_SUBNET_BASE to 10.0.0', () => {
writeEnv('');
const result = auditEnvFile(envFile);
expect(result.values.AGENT_SUBNET_BASE).toBe('10.0.0');
});
it('derives WARDEN_GATEWAY from subnet base', () => {
writeEnv('AGENT_SUBNET_BASE=192.168.1\n');
const result = auditEnvFile(envFile);
expect(result.values.WARDEN_GATEWAY).toBe('192.168.1.1');
});
it('defaults WARDEN_DB_IP to subnet.3', () => {
writeEnv('AGENT_SUBNET_BASE=10.1.2\n');
const result = auditEnvFile(envFile);
expect(result.values.WARDEN_DB_IP).toBe('10.1.2.3');
});
it('defaults CODE_HOSTING_MODE to "git"', () => {
writeEnv('');
const result = auditEnvFile(envFile);
expect(result.values.CODE_HOSTING_MODE).toBe('git');
});
it('defaults DB_RUNTIME to "jail"', () => {
writeEnv('');
const result = auditEnvFile(envFile);
expect(result.values.DB_RUNTIME).toBe('jail');
});
it('lowercases AGENT_NAME value', () => {
writeEnv('AGENT_NAME=MyBot\n');
const result = auditEnvFile(envFile);
expect(result.values.AGENT_NAME).toBe('mybot');
});
});
// ---------------------------------------------------------------------------
// Missing key detection
// ---------------------------------------------------------------------------
describe('auditEnvFile — missing keys', () => {
it('adds ASSISTANT_NAME to missing when absent', () => {
writeEnv('AGENT_NAME=clawdie\n');
const result = auditEnvFile(envFile);
expect(result.missing).toContain('ASSISTANT_NAME');
});
it('ASSISTANT_NAME is not missing when set', () => {
writeEnv('ASSISTANT_NAME=Clawdie\n');
const result = auditEnvFile(envFile);
expect(result.missing).not.toContain('ASSISTANT_NAME');
});
it('returns empty missing array for a complete minimal env', () => {
writeEnv('ASSISTANT_NAME=Clawdie\n');
const result = auditEnvFile(envFile);
expect(result.missing).toHaveLength(0);
});
});
// ---------------------------------------------------------------------------
// Warnings
// ---------------------------------------------------------------------------
describe('auditEnvFile — warnings', () => {
it('warns when DISPLAY_LOCALE is not set', () => {
writeEnv('ASSISTANT_NAME=Clawdie\n');
const result = auditEnvFile(envFile);
expect(result.warnings.some((w) => w.includes('DISPLAY_LOCALE'))).toBe(true);
});
it('no DISPLAY_LOCALE warning when set', () => {
writeEnv('ASSISTANT_NAME=Clawdie\nDISPLAY_LOCALE=en-GB\n');
const result = auditEnvFile(envFile);
expect(result.warnings.some((w) => w.startsWith('DISPLAY_LOCALE') || w.includes('DISPLAY_LOCALE not set'))).toBe(false);
});
it('warns when FEATURE_TAILSCALE=YES but TAILSCALE_AUTHKEY not set', () => {
writeEnv('ASSISTANT_NAME=Clawdie\nFEATURE_TAILSCALE=YES\n');
const result = auditEnvFile(envFile);
expect(result.warnings.some((w) => w.includes('TAILSCALE_AUTHKEY'))).toBe(true);
});
it('no tailscale authkey warning when authkey is set', () => {
writeEnv('ASSISTANT_NAME=Clawdie\nFEATURE_TAILSCALE=YES\nTAILSCALE_AUTHKEY=tskey-abc\n');
const result = auditEnvFile(envFile);
expect(result.warnings.some((w) => w.includes('TAILSCALE_AUTHKEY'))).toBe(false);
});
it('warns when FEATURE_GITEA=YES but FEATURE_GIT is not enabled', () => {
writeEnv('ASSISTANT_NAME=Clawdie\nFEATURE_GITEA=YES\n');
const result = auditEnvFile(envFile);
expect(result.warnings.some((w) => w.includes('FEATURE_GITEA'))).toBe(true);
});
it('warns when LOCAL_LLM_PROVIDER=ollama but FEATURE_OLLAMA is not enabled', () => {
writeEnv('ASSISTANT_NAME=Clawdie\nLOCAL_LLM_PROVIDER=ollama\n');
const result = auditEnvFile(envFile);
expect(result.warnings.some((w) => w.includes('FEATURE_OLLAMA'))).toBe(true);
});
it('warns when LOCAL_LLM_PROVIDER=llama_cpp but FEATURE_LLAMA_CPP is not enabled', () => {
writeEnv('ASSISTANT_NAME=Clawdie\nLOCAL_LLM_PROVIDER=llama_cpp\n');
const result = auditEnvFile(envFile);
expect(result.warnings.some((w) => w.includes('FEATURE_LLAMA_CPP'))).toBe(true);
});
it('no LLM provider warning when provider=none', () => {
writeEnv('ASSISTANT_NAME=Clawdie\nLOCAL_LLM_PROVIDER=none\n');
const result = auditEnvFile(envFile);
expect(result.warnings.some((w) => w.includes('FEATURE_OLLAMA') || w.includes('FEATURE_LLAMA_CPP'))).toBe(false);
});
it('warns when SSH_PUBLIC_KEY not set', () => {
writeEnv('ASSISTANT_NAME=Clawdie\n');
const result = auditEnvFile(envFile);
expect(result.warnings.some((w) => w.includes('SSH_PUBLIC_KEY'))).toBe(true);
});
it('no SSH warning when SSH_PUBLIC_KEY is set', () => {
writeEnv('ASSISTANT_NAME=Clawdie\nSSH_PUBLIC_KEY=ssh-ed25519 AAAA...\n');
const result = auditEnvFile(envFile);
expect(result.warnings.some((w) => w.includes('SSH_PUBLIC_KEY'))).toBe(false);
});
});
// ---------------------------------------------------------------------------
// Feature flags (isTruthyFlag)
// ---------------------------------------------------------------------------
describe('auditEnvFile — feature flags', () => {
it('recognises YES as truthy', () => {
writeEnv('ASSISTANT_NAME=Clawdie\nFEATURE_GIT=YES\n');
const result = auditEnvFile(envFile);
expect(result.values.FEATURE_GIT).toBe('YES');
});
it('recognises true as truthy', () => {
writeEnv('ASSISTANT_NAME=Clawdie\nFEATURE_GIT=true\n');
const result = auditEnvFile(envFile);
expect(result.values.FEATURE_GIT).toBe('YES');
});
it('recognises 1 as truthy', () => {
writeEnv('ASSISTANT_NAME=Clawdie\nFEATURE_GIT=1\n');
const result = auditEnvFile(envFile);
expect(result.values.FEATURE_GIT).toBe('YES');
});
it('treats NO as falsy for feature flags', () => {
writeEnv('ASSISTANT_NAME=Clawdie\nFEATURE_GIT=NO\n');
const result = auditEnvFile(envFile);
expect(result.values.FEATURE_GIT).toBe('NO');
});
});
// ---------------------------------------------------------------------------
// GIT_MIRROR_URLS parsing
// ---------------------------------------------------------------------------
describe('auditEnvFile — GIT_MIRROR_URLS', () => {
it('parses comma-separated mirror URLs', () => {
writeEnv('ASSISTANT_NAME=Clawdie\nGIT_MIRROR_URLS=https://a.com,https://b.com\n');
const result = auditEnvFile(envFile);
expect(result.values.GIT_MIRROR_URLS).toContain('https://a.com');
expect(result.values.GIT_MIRROR_URLS).toContain('https://b.com');
});
it('shows (unset) when GIT_MIRROR_URLS is empty', () => {
writeEnv('ASSISTANT_NAME=Clawdie\n');
const result = auditEnvFile(envFile);
expect(result.values.GIT_MIRROR_URLS).toBe('(unset)');
});
});
// ---------------------------------------------------------------------------
// TAILSCALE_AUTHKEY masking
// ---------------------------------------------------------------------------
describe('auditEnvFile — value masking', () => {
it('shows "(set)" for TAILSCALE_AUTHKEY when present', () => {
writeEnv('ASSISTANT_NAME=Clawdie\nTAILSCALE_AUTHKEY=tskey-secret\n');
const result = auditEnvFile(envFile);
expect(result.values.TAILSCALE_AUTHKEY).toBe('(set)');
});
it('shows "(unset)" for TAILSCALE_AUTHKEY when absent', () => {
writeEnv('ASSISTANT_NAME=Clawdie\n');
const result = auditEnvFile(envFile);
expect(result.values.TAILSCALE_AUTHKEY).toBe('(unset)');
});
it('shows "(set)" for SSH_PUBLIC_KEY when present', () => {
writeEnv('ASSISTANT_NAME=Clawdie\nSSH_PUBLIC_KEY=ssh-ed25519 AAAA...\n');
const result = auditEnvFile(envFile);
expect(result.values.SSH_PUBLIC_KEY).toBe('(set)');
});
});