Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 142 additions & 0 deletions packages/blueprint-tester/__tests__/hcl-parser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { describe, expect, it } from 'vitest';
import { parseTerraformFile } from '../src/hcl-parser.js';

describe('parseTerraformFile', () => {
it('parses a simple resource block', () => {
const content = `
resource "epilot_journey" "sample_journey_abc123" {
name = "My Journey"
access_mode = "PUBLIC"
}`;
const result = parseTerraformFile('main.tf', content);

expect(result.resources).toHaveLength(1);
expect(result.resources[0].type).toBe('epilot_journey');
expect(result.resources[0].name).toBe('sample_journey_abc123');
expect(result.resources[0].address).toBe('epilot_journey.sample_journey_abc123');
expect(result.resources[0].attributes.name).toBe('My Journey');
expect(result.resources[0].attributes.access_mode).toBe('PUBLIC');
});

it('parses multiple resource blocks', () => {
const content = `
resource "epilot_journey" "journey_1" {
name = "Journey 1"
}

resource "epilot_automation" "auto_1" {
name = "Automation 1"
}`;
const result = parseTerraformFile('main.tf', content);
expect(result.resources).toHaveLength(2);
expect(result.resources[0].type).toBe('epilot_journey');
expect(result.resources[1].type).toBe('epilot_automation');
});

it('parses depends_on arrays', () => {
const content = `
resource "epilot_automation" "auto_1" {
name = "Automation"
depends_on = [epilot_journey.journey_1, epilot_schema.contact]
}`;
const result = parseTerraformFile('main.tf', content);
expect(result.resources[0].dependsOn).toEqual([
'epilot_journey.journey_1',
'epilot_schema.contact',
]);
});

it('parses nested blocks', () => {
const content = `
resource "epilot_automation" "auto_1" {
trigger {
type = "journey_submission"
journey_id = "abc-123"
}
name = "Automation"
}`;
const result = parseTerraformFile('main.tf', content);
expect(result.resources[0].attributes.trigger).toBeDefined();
expect(result.resources[0].attributes.name).toBe('Automation');
});

it('parses boolean and null values', () => {
const content = `
resource "epilot_journey" "j1" {
is_active = true
is_deleted = false
description = null
}`;
const result = parseTerraformFile('main.tf', content);
expect(result.resources[0].attributes.is_active).toBe(true);
expect(result.resources[0].attributes.is_deleted).toBe(false);
expect(result.resources[0].attributes.description).toBe(null);
});

it('preserves terraform references as strings', () => {
const content = `
resource "epilot_automation" "auto_1" {
journey_id = epilot_journey.sample.journey_id
}`;
const result = parseTerraformFile('main.tf', content);
expect(result.resources[0].attributes.journey_id).toBe(
'epilot_journey.sample.journey_id',
);
});

it('parses variables', () => {
const content = `
variable "manifest_id" {
type = string
}

variable "target_org_id" {
type = string
}

resource "epilot_journey" "j1" {
name = "test"
}`;
const result = parseTerraformFile('main.tf', content);
expect(result.variables).toHaveProperty('manifest_id');
expect(result.variables).toHaveProperty('target_org_id');
});

it('tracks line numbers', () => {
const content = `
resource "epilot_journey" "j1" {
name = "test"
}

resource "epilot_automation" "a1" {
name = "auto"
}`;
const result = parseTerraformFile('main.tf', content);
expect(result.resources[0].lineStart).toBe(2);
expect(result.resources[1].lineStart).toBe(6);
});

it('handles lifecycle blocks', () => {
const content = `
resource "epilot-taxonomy_taxonomy_classification" "tax_abc123" {
lifecycle {
prevent_destroy = true
}
manifest = distinct([var.manifest_id])
name = "Test Classification"
}`;
const result = parseTerraformFile('main.tf', content);
expect(result.resources).toHaveLength(1);
expect(result.resources[0].attributes.name).toBe('Test Classification');
});

it('handles jsonencode function calls', () => {
const content = `
resource "epilot_journey" "j1" {
journey = jsonencode({"steps": [{"name": "step1"}]})
name = "test"
}`;
const result = parseTerraformFile('main.tf', content);
expect(result.resources[0].attributes.journey).toContain('jsonencode');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { describe, expect, it } from 'vitest';
import { parseTerraformFile } from '../../src/hcl-parser.js';
import { crossRefIntegrityRule } from '../../src/rules/cross-ref-integrity.js';
import { buildResourceIndex } from '../../src/utils/resource-index.js';

function runRule(tfContent: string) {
const file = parseTerraformFile('main.tf', tfContent);
const files = [file];
const resourceIndex = buildResourceIndex(files);
return crossRefIntegrityRule.validate({ files, resourceIndex, options: {} });
}

describe('cross-ref-integrity rule', () => {
it('flags depends_on referencing non-existent resource', () => {
const tf = `
resource "epilot_automation" "auto_1" {
name = "Automation"
depends_on = [epilot_journey.missing_journey]
}`;
const issues = runRule(tf);
expect(issues).toHaveLength(1);
expect(issues[0].ruleId).toBe('cross-ref-integrity');
expect(issues[0].attributePath).toBe('depends_on');
expect(issues[0].value).toBe('epilot_journey.missing_journey');
});

it('does NOT flag depends_on referencing existing resource', () => {
const tf = `
resource "epilot_journey" "journey_1" {
name = "Journey"
}

resource "epilot_automation" "auto_1" {
name = "Automation"
depends_on = [epilot_journey.journey_1]
}`;
const issues = runRule(tf);
expect(issues).toHaveLength(0);
});

it('flags terraform reference to non-existent resource', () => {
const tf = `
resource "epilot_automation" "auto_1" {
journey_id = "\${epilot_journey.missing.journey_id}"
}`;
const issues = runRule(tf);
expect(issues).toHaveLength(1);
expect(issues[0].value).toBe('epilot_journey.missing');
});

it('does NOT flag terraform reference to existing resource', () => {
const tf = `
resource "epilot_journey" "sample" {
name = "Journey"
}

resource "epilot_automation" "auto_1" {
journey_id = "\${epilot_journey.sample.journey_id}"
}`;
const issues = runRule(tf);
expect(issues).toHaveLength(0);
});

it('does NOT flag variable references', () => {
const tf = `
resource "epilot_journey" "j1" {
manifest = "\${var.manifest_id}"
}`;
const issues = runRule(tf);
expect(issues).toHaveLength(0);
});
});
91 changes: 91 additions & 0 deletions packages/blueprint-tester/__tests__/rules/dangling-uuids.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { describe, expect, it } from 'vitest';
import { parseTerraformFile } from '../../src/hcl-parser.js';
import { danglingUuidsRule } from '../../src/rules/dangling-uuids.js';
import type { ValidatorOptions } from '../../src/types.js';
import { buildResourceIndex } from '../../src/utils/resource-index.js';

function runRule(tfContent: string, options: ValidatorOptions = {}) {
const file = parseTerraformFile('main.tf', tfContent);
const files = [file];
const resourceIndex = buildResourceIndex(files);
return danglingUuidsRule.validate({ files, resourceIndex, options });
}

describe('dangling-uuids rule', () => {
it('flags a hardcoded UUID in an attribute', () => {
const tf = `
resource "epilot_automation" "auto_abc123def456abc123def456abc123de" {
journey_id = "d11995ae-368e-4ad3-bf1c-51d6449f8afc"
}`;
const issues = runRule(tf);
expect(issues).toHaveLength(1);
expect(issues[0].ruleId).toBe('dangling-uuid');
expect(issues[0].severity).toBe('error');
expect(issues[0].attributePath).toBe('journey_id');
expect(issues[0].value).toBe('d11995ae-368e-4ad3-bf1c-51d6449f8afc');
});

it('does NOT flag a terraform reference', () => {
const tf = `
resource "epilot_automation" "auto_1" {
journey_id = epilot_journey.sample.journey_id
}`;
const issues = runRule(tf);
expect(issues).toHaveLength(0);
});

it('does NOT flag a UUID that matches another resource identifier', () => {
const tf = `
resource "epilot_journey" "journey_d11995ae368e4ad3bf1c51d6449f8afc" {
name = "My Journey"
}

resource "epilot_automation" "auto_abc123def456abc123def456abc123de" {
journey_id = "d11995ae-368e-4ad3-bf1c-51d6449f8afc"
}`;
const issues = runRule(tf);
expect(issues).toHaveLength(0);
});

it('does NOT flag UUIDs in knownSafeUuids', () => {
const tf = `
resource "epilot_automation" "auto_abc123def456abc123def456abc123de" {
some_id = "d11995ae-368e-4ad3-bf1c-51d6449f8afc"
}`;
const issues = runRule(tf, {
knownSafeUuids: ['d11995ae-368e-4ad3-bf1c-51d6449f8afc'],
});
expect(issues).toHaveLength(0);
});

it('does NOT flag variable references', () => {
const tf = `
resource "epilot_journey" "j1" {
manifest = "distinct([var.manifest_id])"
}`;
// This is stored as a string that contains "var." — the rule checks containsTerraformRef
// Actually the value would be stored as the raw function call string
const issues = runRule(tf);
// The UUID regex won't match "var.manifest_id" since it's not a UUID format
expect(issues).toHaveLength(0);
});

it('flags multiple dangling UUIDs in the same resource', () => {
const tf = `
resource "epilot_automation" "auto_abc123def456abc123def456abc123de" {
journey_id = "d11995ae-368e-4ad3-bf1c-51d6449f8afc"
mapping_id = "e22fb48b-479f-5be4-cf2d-62e7550a9bfd"
}`;
const issues = runRule(tf);
expect(issues).toHaveLength(2);
});

it('does NOT flag terraform interpolation expressions', () => {
const tf = `
resource "epilot_automation" "auto_1" {
journey_id = "\${epilot_journey.sample.journey_id}"
}`;
const issues = runRule(tf);
expect(issues).toHaveLength(0);
});
});
52 changes: 52 additions & 0 deletions packages/blueprint-tester/__tests__/rules/email-addresses.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { describe, expect, it } from 'vitest';
import { parseTerraformFile } from '../../src/hcl-parser.js';
import { emailAddressesRule } from '../../src/rules/email-addresses.js';
import { buildResourceIndex } from '../../src/utils/resource-index.js';

function runRule(tfContent: string) {
const file = parseTerraformFile('main.tf', tfContent);
const files = [file];
const resourceIndex = buildResourceIndex(files);
return emailAddressesRule.validate({ files, resourceIndex, options: {} });
}

describe('email-addresses rule', () => {
it('detects hardcoded email addresses', () => {
const tf = `
resource "epilot_emailtemplate" "template1" {
to = "john.doe@company.com"
subject = "Welcome"
}`;
const issues = runRule(tf);
expect(issues.length).toBeGreaterThanOrEqual(1);
expect(issues[0].ruleId).toBe('email-address');
expect(issues[0].value).toBe('john.doe@company.com');
});

it('does NOT flag noreply addresses', () => {
const tf = `
resource "epilot_emailtemplate" "template1" {
from = "noreply@epilot.cloud"
}`;
const issues = runRule(tf);
expect(issues).toHaveLength(0);
});

it('does NOT flag no-reply addresses', () => {
const tf = `
resource "epilot_emailtemplate" "template1" {
from = "no-reply@company.com"
}`;
const issues = runRule(tf);
expect(issues).toHaveLength(0);
});

it('does NOT flag resources without emails', () => {
const tf = `
resource "epilot_journey" "j1" {
name = "My Journey"
}`;
const issues = runRule(tf);
expect(issues).toHaveLength(0);
});
});
Loading
Loading