import { afterEach, beforeEach, describe, expect, it } from 'bun:test'; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { closeDb, getDb } from '../db/client'; import { runMigrations } from '../db/migrate'; import { modules } from '../db/schema'; import { resetTestDbPath } from '../test-utils/db-path'; import { NonEmptyRestoreTargetError, assertRestoreTargetEmpty, surveyRestoreTarget, } from './restore-preflight'; describe('restore pre-flight', () => { let dir: string; beforeEach(async () => { dir = mkdtempSync(join(tmpdir(), 'celilo-preflight-test-')); process.env.CELILO_DB_PATH = join(dir, 'celilo.db'); process.env.CELILO_DATA_DIR = dir; await runMigrations(process.env.CELILO_DB_PATH); }); afterEach(() => { closeDb(); resetTestDbPath(); delete process.env.CELILO_DATA_DIR; try { rmSync(dir, { recursive: true, force: true }); } catch { /* ignore */ } }); it('reports empty when DB has no modules and no TF state on disk', () => { const report = surveyRestoreTarget(); expect(report.empty).toBe(true); expect(report.modulesInDb).toEqual([]); expect(report.modulesWithTerraformState).toEqual([]); }); it('reports modulesInDb when modules exist in the DB', () => { const db = getDb(); db.insert(modules) .values({ id: 'homebridge', name: 'homebridge', version: '0.1.0', sourcePath: '/tmp/fake', manifestData: {} as Record, }) .run(); const report = surveyRestoreTarget(); expect(report.empty).toBe(false); expect(report.modulesInDb).toEqual(['homebridge']); }); it('reports modulesWithTerraformState when a tfstate file exists on disk', () => { const tfDir = join(dir, 'modules', 'caddy', 'generated', 'terraform'); mkdirSync(tfDir, { recursive: true }); writeFileSync(join(tfDir, 'terraform.tfstate'), '{}'); const report = surveyRestoreTarget(); expect(report.empty).toBe(false); expect(report.modulesWithTerraformState).toEqual(['caddy']); }); it('treats an empty terraform/ dir as no state', () => { const tfDir = join(dir, 'modules', 'half-installed', 'generated', 'terraform'); mkdirSync(tfDir, { recursive: true }); const report = surveyRestoreTarget(); expect(report.empty).toBe(true); expect(report.modulesWithTerraformState).toEqual([]); }); describe('assertRestoreTargetEmpty', () => { it('does not throw on a fresh target', () => { expect(() => assertRestoreTargetEmpty()).not.toThrow(); }); it('throws NonEmptyRestoreTargetError on a populated DB', () => { const db = getDb(); db.insert(modules) .values({ id: 'homebridge', name: 'homebridge', version: '0.1.0', sourcePath: '/tmp/fake', manifestData: {} as Record, }) .run(); expect(() => assertRestoreTargetEmpty()).toThrow(NonEmptyRestoreTargetError); }); it('error message names the conflicting modules + suggests --force', () => { const db = getDb(); db.insert(modules) .values({ id: 'homebridge', name: 'homebridge', version: '0.1.0', sourcePath: '/tmp/fake', manifestData: {} as Record, }) .run(); const tfDir = join(dir, 'modules', 'caddy', 'generated', 'terraform'); mkdirSync(tfDir, { recursive: true }); writeFileSync(join(tfDir, 'terraform.tfstate'), '{}'); try { assertRestoreTargetEmpty(); } catch (err) { expect(err).toBeInstanceOf(NonEmptyRestoreTargetError); const msg = (err as Error).message; expect(msg).toContain('homebridge'); expect(msg).toContain('caddy'); expect(msg).toContain('--force'); return; } throw new Error('expected assertRestoreTargetEmpty to throw'); }); }); });