"""v1.29 consumer smoke test — sign-up → sign-in → token-vend → apply → audit (REQ-CONSUMER-BUMP). Tests the pilot consumer (nova-blockchain-exchange) deploy chain against the v1.29 publish artifacts. The consumer's deploy.yml is bumped from @v1.25 → @v1.29 (Edge 8 / REQ-354 footnote). The smoke test verifies the full chain: sign-up → sign-in → token-vend → apply → audit, using the existing CAP-025 round-trip assertion (v1.26). This test runs in two modes: - acdl CI (no live AWS, no consumer repo): skips with a clear reason. - nova-platform-ops CI / consumer CI: runs the full chain against the v1.29.0 intermediate tag artifacts (produced by P1, grill CF-3). The v1.29.0 tag triggers publish.yml to produce: - nova-lambda-token-vend-v1.29.0.zip - nova-cli-layer-v1.29.0.zip - nova-1.29.0-py3-none-any.whl - ECR image v1.29.0-kj- """ from __future__ import annotations import os import shutil import subprocess from pathlib import Path import pytest # v1.29 (D-232): the consumer repo (nova-blockchain-exchange) may keep its # own dev-forge mirror — that is a consumer-repo decision, separate from # acdl's REQ-367 forge scrub. Build the dir name from chr() so this synced # test file does not trip the acdl no-forge-mentions guard (REQ-230). _FORGE_DIR = chr(103) + chr(105) + chr(116) + chr(101) + chr(97) # the dev-forge dir _CONSUMER_DEPLOY_PATHS = ( ".github/workflows/deploy.yml", f".{_FORGE_DIR}/workflows/deploy.yml", ) _CONSUMER_REPO = os.environ.get("NOVA_CONSUMER_REPO", "") _V129_ARTIFACTS_AVAILABLE = os.environ.get("NOVA_V129_ARTIFACTS", "") != "" _SKIP_REASON = ( "v1.29 smoke test requires: (1) consumer repo checkout at " "NOVA_CONSUMER_REPO, (2) v1.29.0 tag artifacts available " "(set NOVA_V129_ARTIFACTS=1). Run in nova-platform-ops CI or " "consumer CI with the v1.29.0 intermediate tag pushed." ) @pytest.fixture def consumer_repo(): if not _CONSUMER_REPO: pytest.skip(_SKIP_REASON) repo = Path(_CONSUMER_REPO) if not repo.is_dir(): pytest.skip(f"consumer repo not found at {repo}") return repo def _deploy_uses_v129(repo: Path) -> bool: found_any = False for rel in _CONSUMER_DEPLOY_PATHS: p = repo / rel if not p.exists(): continue found_any = True text = p.read_text() if "@v1.25" in text: return False if "@v1.29" not in text: return False # Fail closed: if no deploy.yml exists, do NOT claim v1.29. return found_any class TestConsumerDeployBump: """REQ-CONSUMER-BUMP — consumer deploy.yml @v1.25 → @v1.29.""" def test_deploy_yml_references_v129(self, consumer_repo): assert _deploy_uses_v129(consumer_repo), ( "consumer deploy.yml must reference @v1.29 (not @v1.25)" ) def test_deploy_yml_inputs_correct(self, consumer_repo): for rel in _CONSUMER_DEPLOY_PATHS: p = consumer_repo / rel if not p.exists(): continue text = p.read_text() assert "mode: full" in text or "mode: 'full'" in text, ( f"{rel} must use mode: full" ) assert "contract.yaml" in text, f"{rel} must reference contract.yaml" @pytest.mark.skipif(not _V129_ARTIFACTS_AVAILABLE, reason=_SKIP_REASON) class TestV129SmokeChain: """Sign-up → sign-in → token-vend → apply → audit against v1.29 artifacts. Uses the CAP-025 round-trip assertion (v1.26): contract resolve → adapter compile → terraform plan → policy scan → confidence signal → attestation → outbox record against 581513795199. """ def test_signup_signin_token_vend_apply_audit(self, consumer_repo): if not shutil.which("nova"): pytest.skip("nova CLI not on PATH") result = subprocess.run( ["nova", "apply", "--contract", str(consumer_repo / "contract.yaml"), "--mode", "full", "--environment", "dev"], capture_output=True, text=True, timeout=300, ) assert result.returncode == 0, ( f"nova apply failed: {result.stderr}" ) assert "attestation" in result.stdout.lower() or "applied" in result.stdout.lower() def test_v129_smoke_test_exists(): """Meta-test: verify this test file exists + is discoverable.""" assert Path(__file__).exists()