import { describe, expect, test } from 'bun:test'; import { type TerraformPlanRunner, auditTerraformPlan, parsePlanSummary } from './terraform-plan'; describe('parsePlanSummary', () => { test('parses a typical Plan summary line', () => { const stdout = ` Terraform will perform the following actions: ... Plan: 2 to add, 1 to change, 3 to destroy. `; expect(parsePlanSummary(stdout)).toEqual({ add: 2, change: 1, destroy: 3 }); }); test('treats "No changes." as zero plan', () => { expect(parsePlanSummary('No changes. Your infrastructure matches the configuration.')).toEqual({ add: 0, change: 0, destroy: 0, }); }); test('strips ANSI color codes', () => { const stdout = 'Plan: 1 to add, 0 to change, 0 to destroy.'; expect(parsePlanSummary(stdout)).toEqual({ add: 1, change: 0, destroy: 0 }); }); test('returns null when no recognized pattern is present', () => { expect(parsePlanSummary('something went sideways')).toBeNull(); }); }); describe('auditTerraformPlan', () => { const noChange: TerraformPlanRunner = async () => ({ exitCode: 0, stdout: 'No changes. Your infrastructure matches the configuration.', stderr: '', }); test('skips modules without a terraform dir', async () => { const result = await auditTerraformPlan({ modules: [{ id: 'caddy', terraformDir: null }], run: noChange, }); expect(result).toEqual([]); }); test('no finding when plan is empty', async () => { const result = await auditTerraformPlan({ modules: [{ id: 'caddy', terraformDir: '/tf/caddy' }], run: noChange, }); expect(result).toEqual([]); }); test('drift finding for additive plan (no destroys)', async () => { const run: TerraformPlanRunner = async () => ({ exitCode: 2, stdout: 'Plan: 1 to add, 0 to change, 0 to destroy.', stderr: '', }); const result = await auditTerraformPlan({ modules: [{ id: 'caddy', terraformDir: '/tf/caddy' }], run, }); expect(result).toHaveLength(1); expect(result[0]).toMatchObject({ severity: 'drift', code: 'terraform_plan_pending', subject: 'caddy', }); expect(result[0].message).toContain('+1 ~0'); }); test('blocked finding for destructive plan', async () => { const run: TerraformPlanRunner = async () => ({ exitCode: 2, stdout: 'Plan: 0 to add, 1 to change, 2 to destroy.', stderr: '', }); const result = await auditTerraformPlan({ modules: [{ id: 'caddy', terraformDir: '/tf/caddy' }], run, }); expect(result).toHaveLength(1); expect(result[0]).toMatchObject({ severity: 'blocked', code: 'terraform_plan_destructive', }); expect(result[0].message).toContain('would destroy 2 resources'); expect(result[0].remediation).toContain('--allow-destructive'); }); test('drift finding when terraform plan command fails', async () => { const run: TerraformPlanRunner = async () => ({ exitCode: 1, stdout: '', stderr: 'Error: provider authentication failed', }); const result = await auditTerraformPlan({ modules: [{ id: 'caddy', terraformDir: '/tf/caddy' }], run, }); expect(result).toHaveLength(1); expect(result[0]).toMatchObject({ severity: 'drift', code: 'terraform_plan_failed', }); expect(result[0].details).toContain('provider authentication failed'); }); test('singular wording for exactly one destroy', async () => { const run: TerraformPlanRunner = async () => ({ exitCode: 2, stdout: 'Plan: 0 to add, 0 to change, 1 to destroy.', stderr: '', }); const result = await auditTerraformPlan({ modules: [{ id: 'caddy', terraformDir: '/tf/caddy' }], run, }); expect(result[0].message).toContain('would destroy 1 resource ('); expect(result[0].message).not.toContain('1 resources'); }); });