d5bae868a4
core/env.py dual-read helper (D-108); 21 ACDL_*→NOVA_* env vars migrated across core/scripts/adapters/tests/workflows + .env/.env.secrets (key rename, values stay). G-106 binding: run_platform.sh:288-289 + regression_verify.py:309-312 dual-read (NOVA first, ACDL fallback). G-108 binding: Gitea NOVA_* secrets created via API + workflow secrets: refs updated (deploy.yml + modules-lifecycle.yml, .gitea + .github). acdl_tagging.py→nova_tagging.py (D-109 warn mode, nova:* enforced). .acdl/→.nova/ consumer path (resolver + deploy workflow + schema + tests + docs). Test fixtures updated; pytest + run_ci.sh PASS. ---ci--- project: acdl phase: 2 milestone: v1.15 status: execute ---/ci---
107 lines
5.1 KiB
Python
107 lines
5.1 KiB
Python
"""Tests for the lifecycle test scripts' plan-only/full mode flag (REQ-134).
|
|
|
|
The lifecycle scripts (run_lifecycle_test.sh, run_lifecycle_destroy.sh,
|
|
run_l2_lifecycle_test.sh, run_l2_lifecycle_destroy.sh) wrap run_platform.sh.
|
|
REQ-134 (v1.12) adds the NOVA_LIFECYCLE_MODE env var (P2 renamed from
|
|
ACDL_LIFECYCLE_MODE, dual-read NOVA_* preferred / ACDL_* fallback until
|
|
P5): default "plan" runs `run_platform.sh --plan-only` (fast, no AWS
|
|
mutation); "full" runs the real `--apply`/`--destroy` against live AWS.
|
|
|
|
These tests verify the dispatch logic offline by inspecting script content
|
|
(running the scripts end-to-end requires AWS credentials in full mode).
|
|
"""
|
|
import re
|
|
|
|
import pytest
|
|
|
|
ROOT = __import__("pathlib").Path(__file__).resolve().parent.parent
|
|
|
|
SCRIPTS = [
|
|
"scripts/run_lifecycle_test.sh",
|
|
"scripts/run_lifecycle_destroy.sh",
|
|
"scripts/run_l2_lifecycle_test.sh",
|
|
"scripts/run_l2_lifecycle_destroy.sh",
|
|
]
|
|
|
|
|
|
def _read(path):
|
|
return (ROOT / path).read_text()
|
|
|
|
|
|
class TestLifecycleModeFlag:
|
|
"""REQ-134: the lifecycle scripts dispatch to plan-only by default."""
|
|
|
|
@pytest.mark.parametrize("script", SCRIPTS)
|
|
def test_script_reads_acdl_lifecycle_mode(self, script):
|
|
"""Every lifecycle script reads NOVA_LIFECYCLE_MODE (dual-read with
|
|
ACDL_LIFECYCLE_MODE fallback) with a 'plan' default."""
|
|
src = _read(script)
|
|
# P2 (REQ-159): dual-read NOVA_* preferred, ACDL_* fallback. The
|
|
# ACDL_LIFECYCLE_MODE:-plan substring is still present inside the
|
|
# nested dual-read expression (removed in P5).
|
|
assert "${NOVA_LIFECYCLE_MODE:-${ACDL_LIFECYCLE_MODE:-plan}}" in src, \
|
|
f"{script} must dual-read NOVA_/ACDL_LIFECYCLE_MODE defaulting to 'plan'"
|
|
assert "LIFECYCLE_MODE=" in src, \
|
|
f"{script} must assign LIFECYCLE_MODE from the env var"
|
|
|
|
@pytest.mark.parametrize("script", [
|
|
"scripts/run_lifecycle_test.sh",
|
|
"scripts/run_l2_lifecycle_test.sh",
|
|
])
|
|
def test_apply_scripts_dispatch_to_plan_only_by_default(self, script):
|
|
"""Apply/modify scripts run --plan-only when mode != full."""
|
|
src = _read(script)
|
|
assert "--plan-only" in src, f"{script} must support --plan-only (plan mode)"
|
|
assert "--apply" in src, f"{script} must support --apply (full mode)"
|
|
# The dispatch must branch on LIFECYCLE_MODE
|
|
assert "LIFECYCLE_MODE" in src and "full" in src, \
|
|
f"{script} must branch on LIFECYCLE_MODE == 'full'"
|
|
|
|
@pytest.mark.parametrize("script", [
|
|
"scripts/run_lifecycle_destroy.sh",
|
|
"scripts/run_l2_lifecycle_destroy.sh",
|
|
])
|
|
def test_destroy_scripts_noop_in_plan_mode(self, script):
|
|
"""Destroy scripts are a no-op (exit 0) in plan mode — nothing was applied."""
|
|
src = _read(script)
|
|
# The destroy script must check the mode and exit 0 in plan mode.
|
|
assert "nothing to destroy" in src, \
|
|
f"{script} must no-op (with a message) in plan mode"
|
|
assert "--destroy" in src, f"{script} must still support --destroy in full mode"
|
|
assert "LIFECYCLE_MODE" in src, f"{script} must read LIFECYCLE_MODE"
|
|
|
|
def test_apply_script_plan_mode_uses_plan_only(self):
|
|
"""In plan mode, run_lifecycle_test.sh dispatches to --plan-only."""
|
|
src = _read("scripts/run_lifecycle_test.sh")
|
|
# The if/else: full -> --apply, else -> --plan-only
|
|
assert re.search(r'if.*LIFECYCLE_MODE.*=.*"full".*;.*then', src)
|
|
assert re.search(r'else\s+bash scripts/run_platform\.sh --plan-only', src, re.DOTALL)
|
|
|
|
def test_apply_script_full_mode_uses_apply(self):
|
|
"""In full mode, run_lifecycle_test.sh dispatches to --apply."""
|
|
src = _read("scripts/run_lifecycle_test.sh")
|
|
assert re.search(r'LIFECYCLE_MODE.*=.*"full".*bash scripts/run_platform\.sh --apply', src, re.DOTALL)
|
|
|
|
def test_destroy_script_plan_mode_exits_zero(self):
|
|
"""In plan mode, the L1 destroy script exits 0 without calling run_platform."""
|
|
src = _read("scripts/run_lifecycle_destroy.sh")
|
|
assert re.search(r'LIFECYCLE_MODE.*!=.*"full".*exiting 0', src, re.DOTALL)
|
|
|
|
def test_l2_apply_script_dispatches_correctly(self):
|
|
"""The L2 apply script dispatches to plan-only by default, apply in full."""
|
|
src = _read("scripts/run_l2_lifecycle_test.sh")
|
|
assert "--plan-only" in src and "--apply" in src
|
|
assert re.search(r'if.*LIFECYCLE_MODE.*=.*"full"', src)
|
|
|
|
def test_l2_destroy_script_noop_in_plan_mode(self):
|
|
"""The L2 destroy script is a no-op in plan mode."""
|
|
src = _read("scripts/run_l2_lifecycle_destroy.sh")
|
|
assert "nothing to destroy" in src
|
|
assert re.search(r'LIFECYCLE_MODE.*!=.*"full".*exiting 0', src, re.DOTALL)
|
|
|
|
def test_vpc_injection_gated_on_full_mode(self):
|
|
"""VPC output injection is gated on full mode (plan mode skips it)."""
|
|
src = _read("scripts/run_lifecycle_test.sh")
|
|
# The VPC injection block must be gated on LIFECYCLE_MODE == full.
|
|
assert re.search(r'LIFECYCLE_MODE.*=.*"full".*&&.*echo.*VPC_DEPENDENT', src, re.DOTALL) or \
|
|
re.search(r'\[ "\$LIFECYCLE_MODE" = "full" \] && echo.*VPC_DEPENDENT', src, re.DOTALL) |