2443909362
---ci--- project: nova-platform milestone: v1.0 status: complete requirements: covered: [REQ-01,REQ-02,REQ-03,REQ-04,REQ-05,REQ-06,REQ-07,REQ-08,REQ-09,REQ-10,REQ-11,REQ-12,REQ-13,REQ-14,REQ-15,REQ-16,REQ-17,REQ-18,REQ-19,REQ-20,REQ-21,REQ-22,REQ-23,REQ-24,REQ-25,REQ-26,REQ-27,REQ-28,REQ-29,REQ-30,REQ-31,REQ-32,REQ-33,REQ-34,REQ-35,REQ-36,REQ-37,REQ-38] partial: [] ---/ci--- v1.0 milestone complete: simplified infrastructure-delivery platform derived from Nova (acdl). 6 phases (P0-P5 + P6 final). 38 REQ-IDs. 38 decisions (D-001..D-038). 76 tests pass. Engine boundary holds. Happy paths green (check-only + CI). 13 L1 + 2 L2 modules. 5 terraform roots. Shell reproducibility. Zero OOS files. Tags: v0.1.0 (P0) → v0.1.1..v0.1.5 (P1..P5) → v0.1.6 (P6 = milestone release on v0.1 patch line).
128 lines
4.4 KiB
Python
128 lines
4.4 KiB
Python
"""Engine-boundary test — REQ-09, D-034.
|
|
|
|
Verifies no engine-specific *logic* (HCL strings, aws_ resource types,
|
|
terraform CLI calls, module/provider/resource block declarations) leaks
|
|
outside adapters/terraform/.
|
|
|
|
Scans .py files in core/, schemas/, contracts/, tests/, scripts/, root.
|
|
EXCLUDES adapters/terraform/ (the boundary), modules/, .tf/.md/.json data
|
|
files (per D-034).
|
|
|
|
To avoid false positives on docstrings/comments that *mention* "Terraform"
|
|
conceptually, the test strips comments + docstrings before scanning.
|
|
The forbidden terms are checked as *code-level* tokens: aws_<word>,
|
|
`module "`, `provider "`, `resource "` (HCL block declarations that
|
|
would indicate actual HCL emission outside the adapter). The bare word
|
|
"terraform" is NOT forbidden (it appears in import paths like
|
|
`adapters.terraform` and docstrings); only `terraform ` followed by a
|
|
block brace or CLI invocation is.
|
|
"""
|
|
|
|
import ast
|
|
import re
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from tests.conftest import ROOT
|
|
|
|
# Forbidden as code-level patterns (not in strings/comments):
|
|
# - aws_<word>: AWS resource type prefixes (e.g. aws_s3_bucket)
|
|
# - 'module "': HCL module block declaration
|
|
# - 'provider "': HCL provider block declaration
|
|
# - 'resource "': HCL resource block declaration
|
|
# - terraform init/plan/apply: CLI invocations
|
|
FORBIDDEN_PATTERNS = [
|
|
re.compile(r'\baws_[a-z_]+'),
|
|
re.compile(r'module\s+"'),
|
|
re.compile(r'provider\s+"'),
|
|
re.compile(r'resource\s+"'),
|
|
re.compile(r'\bterraform\s+(init|plan|apply|validate|destroy)\b'),
|
|
]
|
|
|
|
EXCLUDE_DIRS = {
|
|
"adapters/terraform",
|
|
".venv",
|
|
"__pycache__",
|
|
".ciagent",
|
|
".git",
|
|
"modules",
|
|
"terraform",
|
|
"docs",
|
|
}
|
|
|
|
# Test files that legitimately reference engine terms to verify the
|
|
# boundary/adapter (they assert HCL output contains 'module "' etc.).
|
|
# These are part of the boundary enforcement, not engine logic leaks.
|
|
EXCLUDE_FILES = {
|
|
"tests/test_terraform_adapter.py",
|
|
"tests/test_engine_boundary.py",
|
|
}
|
|
|
|
|
|
def _strip_docstrings_and_comments(source):
|
|
"""Remove docstrings + comments from Python source, return code only."""
|
|
try:
|
|
tree = ast.parse(source)
|
|
except SyntaxError:
|
|
return source
|
|
lines = source.splitlines(keepends=True)
|
|
# Collect line ranges of docstring nodes
|
|
docstring_ranges = set()
|
|
for node in ast.walk(tree):
|
|
if isinstance(node, (ast.Expr,)) and isinstance(node.value, ast.Constant) and isinstance(node.value.value, str):
|
|
for ln in range(node.lineno, node.end_lineno + 1):
|
|
docstring_ranges.add(ln)
|
|
if isinstance(node, ast.Constant) and isinstance(node.value, str) and node.lineno == getattr(node, "end_lineno", None):
|
|
# standalone string used as docstring at module/class level
|
|
pass
|
|
out = []
|
|
for i, line in enumerate(lines, start=1):
|
|
if i in docstring_ranges:
|
|
continue
|
|
# strip inline comments
|
|
stripped = re.sub(r'#.*$', '', line)
|
|
out.append(stripped)
|
|
return "".join(out)
|
|
|
|
|
|
def _scan_files():
|
|
for path in ROOT.rglob("*.py"):
|
|
rel = path.relative_to(ROOT)
|
|
rel_str = str(rel)
|
|
if any(rel_str.startswith(ex) for ex in EXCLUDE_DIRS):
|
|
continue
|
|
if any(part in EXCLUDE_DIRS for part in rel.parts):
|
|
continue
|
|
if rel_str in EXCLUDE_FILES:
|
|
continue
|
|
yield path
|
|
|
|
|
|
def _forbidden_matches(code):
|
|
matches = []
|
|
for pat in FORBIDDEN_PATTERNS:
|
|
found = pat.findall(code)
|
|
if found:
|
|
matches.extend(found)
|
|
return matches
|
|
|
|
|
|
class TestEngineBoundary:
|
|
@pytest.mark.parametrize("path", list(_scan_files()),
|
|
ids=[str(p.relative_to(ROOT)) for p in _scan_files()])
|
|
def test_no_engine_logic(self, path):
|
|
source = path.read_text()
|
|
code = _strip_docstrings_and_comments(source)
|
|
matches = _forbidden_matches(code)
|
|
assert not matches, (
|
|
f"{path.relative_to(ROOT)} contains forbidden engine logic: {matches}. "
|
|
f"Engine-specific code must live ONLY in adapters/terraform/."
|
|
)
|
|
|
|
def test_boundary_scans_files(self):
|
|
files = list(_scan_files())
|
|
assert len(files) > 0, "engine-boundary test must scan at least one .py file"
|
|
for f in files:
|
|
assert "adapters/terraform" not in str(f.relative_to(ROOT)), \
|
|
f"adapters/terraform/ must be excluded but found {f}" |