diff --git a/tests/test_adapter.py b/tests/test_adapter.py index ffd0bde..a1ee5f4 100644 --- a/tests/test_adapter.py +++ b/tests/test_adapter.py @@ -9,7 +9,7 @@ import pytest sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) -from adapters.terraform.adapter import adapt, _tf_value, _ref_expr, _module_name +from adapters.terraform.adapter import adapt, _tf_value, _ref_expr, _module_name, _child_id ROOT = Path(__file__).resolve().parent.parent @@ -241,4 +241,93 @@ class TestAdapterEmitsValidTerraform: ["terraform", "validate"], cwd=str(tmp_path), capture_output=True, text=True ) - assert result.returncode == 0, f"terraform validate failed: {result.stderr}" \ No newline at end of file + assert result.returncode == 0, f"terraform validate failed: {result.stderr}" + + +class TestAdapterDedupMultiResourceL1: + """CAP-013 (v1.12, REQ-129): multi-resource L1s (ecs-service, alb) with + stack outputs + cross-module refs must dedup to ONE module block named by + the composition child id, with all expanded sub-ids rewritten. This is the + regression test that locks in the fix — a future refactor that re-breaks + the dedup must turn this red.""" + + def test_microservice_dedup_names_modules_by_child_id(self, tmp_path): + import sys + sys.path.insert(0, str(ROOT)) + from core.contract_resolver import resolve + stack = resolve(str(ROOT / "contracts/microservice.yml")) + adapt(stack, str(tmp_path)) + main_tf = (tmp_path / "main.tf").read_text() + # The merged modules are named by the child id (alb, service), not the + # expanded sub-ids (alb-loadbalancer, service-service, ...). + assert 'module "alb" {' in main_tf + assert 'module "service" {' in main_tf + # Expanded sub-ids must NOT appear as module names. + assert 'module "alb-loadbalancer"' not in main_tf + assert 'module "alb-targetgroup"' not in main_tf + assert 'module "alb-listener"' not in main_tf + assert 'module "service-task-definition"' not in main_tf + assert 'module "service-service"' not in main_tf + + def test_microservice_dedup_rewrites_stack_outputs(self, tmp_path): + """Stack outputs reference the expanded sub-ids; after dedup they + must resolve to the child-id module name.""" + import sys + sys.path.insert(0, str(ROOT)) + from core.contract_resolver import resolve + stack = resolve(str(ROOT / "contracts/microservice.yml")) + adapt(stack, str(tmp_path)) + main_tf = (tmp_path / "main.tf").read_text() + # service_arn output references service-service -> module.service + assert 'output "service_arn" {\n value = module.service.service_arn' in main_tf + # lb_arn output references alb-loadbalancer -> module.alb + assert 'output "lb_arn" {\n value = module.alb.lb_arn' in main_tf + + def test_microservice_dedup_rewrites_cross_module_refs(self, tmp_path): + """A ref: input that targets an expanded sub-id (e.g. + ref:alb-targetgroup.target_group_arn) must be rewritten to the + child-id module (module.alb.target_group_arn).""" + import sys + sys.path.insert(0, str(ROOT)) + from core.contract_resolver import resolve + stack = resolve(str(ROOT / "contracts/microservice.yml")) + adapt(stack, str(tmp_path)) + main_tf = (tmp_path / "main.tf").read_text() + assert "lb_target_group_arn = module.alb.target_group_arn" in main_tf + # The un-rewritten expanded form must NOT appear. + assert "module.alb-targetgroup" not in main_tf + assert "module.service-service" not in main_tf + + def test_microservice_emits_valid_terraform(self, tmp_path): + """CAP-013 end-to-end: the microservice (multi-resource L1s) main.tf + passes terraform init + validate. This is the real regression — the + v1.11 dedup produced 'No module call name' here.""" + import sys + sys.path.insert(0, str(ROOT)) + from core.contract_resolver import resolve + stack = resolve(str(ROOT / "contracts/microservice.yml")) + adapt(stack, str(tmp_path)) + result = subprocess.run( + ["terraform", "init", "-backend=false", "-input=false"], + cwd=str(tmp_path), capture_output=True, text=True + ) + assert result.returncode == 0, f"terraform init failed: {result.stderr}" + result = subprocess.run( + ["terraform", "validate"], + cwd=str(tmp_path), capture_output=True, text=True + ) + assert result.returncode == 0, f"terraform validate failed (CAP-013 regression): {result.stderr}" + + +class TestChildIdHelper: + """Unit tests for _child_id (the common-prefix heuristic).""" + + def test_single_resource_returns_id_verbatim(self): + assert _child_id(["cluster"]) == "cluster" + assert _child_id(["kms-key"]) == "kms-key" + + def test_multi_resource_returns_common_prefix(self): + # ecs-service expands to service-task-definition + service-service + assert _child_id(["service-task-definition", "service-service"]) == "service" + # alb expands to alb-loadbalancer + alb-targetgroup + alb-listener + assert _child_id(["alb-loadbalancer", "alb-targetgroup", "alb-listener"]) == "alb" \ No newline at end of file