import { afterEach, beforeEach, describe, expect, test } from 'bun:test'; import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { isPreservedGeneratedPath, pruneGeneratedProject } from './module-deploy'; const ROOT = '/tmp/celilo-prune-test'; const GENERATED = join(ROOT, 'generated'); /** * Mirrors the layout observed on celilo-mgr at 2026-09-16T00:51Z: * `generated/{ansible,deploy.log,terraform}` with state inside terraform/. * Derived from the real tree rather than invented — a hand-written mirror gives * confident results about a world that does not exist. */ function buildGeneratedTree(): void { mkdirSync(join(GENERATED, 'ansible', 'roles'), { recursive: true }); mkdirSync(join(GENERATED, 'terraform', '.terraform', 'providers'), { recursive: true }); writeFileSync(join(GENERATED, 'deploy.log'), 'deploy output'); writeFileSync(join(GENERATED, 'ansible', 'secrets.yml'), 'vault-encrypted secrets'); writeFileSync(join(GENERATED, 'ansible', 'roles', 'task.yml'), 'rendered task'); writeFileSync(join(GENERATED, 'terraform', 'main.tf'), 'rendered terraform'); writeFileSync(join(GENERATED, 'terraform', 'terraform.tfvars'), 'rendered vars'); writeFileSync(join(GENERATED, 'terraform', 'terraform.tfstate'), '{"lxc":207}'); writeFileSync(join(GENERATED, 'terraform', 'terraform.tfstate.backup'), '{"lxc":207,"old":1}'); writeFileSync(join(GENERATED, 'terraform', '.terraform.lock.hcl'), 'provider lock'); writeFileSync(join(GENERATED, 'terraform', '.terraform', 'providers', 'bin'), 'huge binary'); } beforeEach(() => { rmSync(ROOT, { recursive: true, force: true }); buildGeneratedTree(); }); afterEach(() => { rmSync(ROOT, { recursive: true, force: true }); }); describe('pruneGeneratedProject', () => { // The recurrence gate for celilo#1374's root cause. Deleting this file is // what armed every container module for destruction on its next deploy. test('keeps terraform state and its lock', () => { pruneGeneratedProject(GENERATED); expect(existsSync(join(GENERATED, 'terraform', 'terraform.tfstate'))).toBe(true); expect(existsSync(join(GENERATED, 'terraform', 'terraform.tfstate.backup'))).toBe(true); expect(existsSync(join(GENERATED, 'terraform', '.terraform.lock.hcl'))).toBe(true); // Intact, not merely present. expect(readFileSync(join(GENERATED, 'terraform', 'terraform.tfstate'), 'utf-8')).toBe( '{"lxc":207}', ); }); // D4's actual concern. If this ever passes, the fix has quietly become // "persist the rendered project", which D4 refused for good reasons. test('still deletes every rendered template and secret', () => { pruneGeneratedProject(GENERATED); expect(existsSync(join(GENERATED, 'ansible'))).toBe(false); expect(existsSync(join(GENERATED, 'deploy.log'))).toBe(false); expect(existsSync(join(GENERATED, 'terraform', 'main.tf'))).toBe(false); expect(existsSync(join(GENERATED, 'terraform', 'terraform.tfvars'))).toBe(false); }); // ~19 MB per provider, rebuilt by `terraform init`; excluded from the backup // envelope for the same reason (cross-module-read.ts). test('drops the provider cache', () => { pruneGeneratedProject(GENERATED); expect(existsSync(join(GENERATED, 'terraform', '.terraform'))).toBe(false); }); // A deploy that failed before rendering never made one. test('is a no-op when generated/ is absent', () => { rmSync(GENERATED, { recursive: true, force: true }); expect(() => pruneGeneratedProject(GENERATED)).not.toThrow(); }); }); describe('isPreservedGeneratedPath', () => { test('preserves only state files directly under terraform/', () => { expect(isPreservedGeneratedPath('terraform/terraform.tfstate')).toBe(true); expect(isPreservedGeneratedPath('terraform/main.tf')).toBe(false); expect(isPreservedGeneratedPath('ansible/terraform.tfstate')).toBe(false); // A nested lookalike must not smuggle a whole tree through. expect(isPreservedGeneratedPath('terraform/nested/terraform.tfstate')).toBe(false); }); });