import { existsSync, rmSync } from 'node:fs'; import { mkdtemp } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { setupTestDatabaseFile } from './database'; /** * Integration test context with isolated database and data directory */ export interface IntegrationTestContext { /** Path to test database file */ dbPath: string; /** Path to test data directory */ dataDir: string; /** * Path to the bus event sqlite db. Tests that need to attach a * bus responder fixture can pass this to `startBusResponderFixture` * so the responder shares the bus with the celilo CLI subprocess. */ busDbPath: string; /** CLI command prefix with environment variables */ cli: string; /** Cleanup function (closes DB and removes temp directories) */ cleanup: () => Promise; } /** * Setup complete integration test environment * * Creates: * - Isolated test database (file-based for CLI access) * - Isolated data directory * - Runs migrations * - Returns CLI command prefix with proper env vars * * @returns Integration test context with cleanup function * * @example * ```typescript * let ctx: IntegrationTestContext; * * beforeEach(async () => { * ctx = await setupIntegrationTest(); * }); * * afterEach(async () => { * await ctx.cleanup(); * }); * * test('my test', () => { * const output = runCli(ctx.cli, 'system config get'); * expect(output).toContain('...'); * }); * ``` */ export async function setupIntegrationTest(): Promise { // celilo#1246: the shell on a dev host carries CELILO_HOME pointing at the // live install (DB and master.key included). Every CLI child this harness // spawns would inherit it, and isolation would then rest on the per-run // CELILO_DB_PATH / CELILO_DATA_DIR overrides below never being missed. // Unset it once, here, so no child ever sees the live home. delete process.env.CELILO_HOME; // Setup isolated database const testDb = await setupTestDatabaseFile(); const dbPath = testDb.path; const dbCleanup = testDb.cleanup; // Setup isolated data directory (use mkdtemp to avoid timestamp collisions in parallel tests) const dataDir = await mkdtemp(join(tmpdir(), 'celilo-test-')); // The celilo CLI's getEventBusPath() falls back to /events.db // when EVENT_BUS_DB isn't set. Tests that attach a responder need // the same path so subprocess + responder share the bus DB. const busDbPath = join(dataDir, 'events.db'); // CLI with isolated environment. CELILO_SUPPRESS_DEPRECATION silences // the legacy-path banners (e.g. on `celilo system init`) so they // don't pollute test output — the banners are an operator-UX concern, // not a CI signal. const cli = `CELILO_DB_PATH="${dbPath}" CELILO_DATA_DIR="${dataDir}" CELILO_SUPPRESS_DEPRECATION=1 bun run src/cli/index.ts`; // Note: Migrations are auto-run by createDbClient() in setupTestDatabaseFile() // Unified cleanup function const cleanup = async () => { // Cleanup data directory if (existsSync(dataDir)) { rmSync(dataDir, { recursive: true, force: true }); } // Cleanup database await dbCleanup(); }; return { dbPath, dataDir, busDbPath, cli, cleanup, }; }