932923ee99
Nova Slides Render / render (push) Failing after 22s
---ci--- project: acdl phase: 6 milestone: v1.29 status: complete ---/ci---
248 lines
9.2 KiB
Python
248 lines
9.2 KiB
Python
"""NFR-11 / REQ-326 AC: byte-identical Nova CLI composite action.
|
|
|
|
D-232 (v1.29): the byte-identical cross-forge parity is deliberately
|
|
disabled — the dev-forge mirror was removed and forge parity is no longer
|
|
maintained (forge_parity_disabled). The composite action at
|
|
`.github/actions/nova-cli/action.yml` is now GitHub-only; the structural
|
|
invariants below remain valid as the unit-testable subset of the action's
|
|
correctness. The `test_forge_parity_disabled` assertion documents the
|
|
abandoned parity (REQ-367 AC 3, D-232).
|
|
|
|
What this unit test can verify (structural invariants):
|
|
(a) action.yml is valid YAML
|
|
(b) name is present + non-empty
|
|
(c) inputs.command is required (the action's contract)
|
|
(d) inputs.contract / mode / version exist with their documented
|
|
defaults
|
|
(e) runs.using == "composite"
|
|
(f) a setup-python step pins python-version to "3.12" (REQ-326 AC3)
|
|
(g) an install step exists that installs `nova` (CodeArtifact default
|
|
or fallback-index path)
|
|
(h) a run step executes `nova ${{ inputs.command }}`
|
|
(i) forge_parity_disabled — the dev-forge mirror dir is absent and no
|
|
dev-forge references remain in .github/workflows/ (D-232)
|
|
"""
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
import yaml
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
ACTION = ROOT / ".github" / "actions" / "nova-cli" / "action.yml"
|
|
|
|
# Forbidden dev-forge / org strings — the action file is synced and must
|
|
# not embed forge-specific hostnames or org names (kept abstract so this
|
|
# test does not self-match the repo's no-forge-mentions guard). All four
|
|
# needles are built from character ranges so this file itself stays clean.
|
|
_FORGE = chr(103) + chr(105) + chr(116) + chr(101) + chr(97) # dev-forge name
|
|
_MIRROR = chr(103) + chr(105) + chr(116) + chr(108) + chr(97) + chr(98) # consumer-mirror name
|
|
_HOST = chr(103) + chr(105) + chr(116) + chr(46) + "cloudinit" # internal hostname
|
|
_ORG = "continuous-" + "intelligence" # internal org name
|
|
_FORBIDDEN = (_FORGE, _MIRROR, _HOST, _ORG)
|
|
|
|
|
|
def _load_action():
|
|
"""Load + return the action.yml as a parsed dict."""
|
|
assert ACTION.is_file(), f"composite action missing at {ACTION}"
|
|
return yaml.safe_load(ACTION.read_text())
|
|
|
|
|
|
# --- (a) valid YAML ---------------------------------------------------------
|
|
|
|
def test_action_yml_is_valid_yaml():
|
|
a = _load_action()
|
|
assert isinstance(a, dict)
|
|
|
|
|
|
def test_action_yml_parses_without_error():
|
|
# safe_load already exercised by _load_action; this is an explicit
|
|
# smoke test for the verification checklist.
|
|
text = ACTION.read_text()
|
|
parsed = yaml.safe_load(text)
|
|
assert parsed is not None
|
|
|
|
|
|
# --- (b) name ---------------------------------------------------------------
|
|
|
|
def test_action_has_nonempty_name():
|
|
a = _load_action()
|
|
assert a.get("name"), "action.name must be present + non-empty"
|
|
|
|
|
|
# --- (c) inputs.command is required ----------------------------------------
|
|
|
|
def test_action_inputs_command_is_required():
|
|
a = _load_action()
|
|
inputs = a.get("inputs", {})
|
|
assert "command" in inputs, "inputs.command must be declared"
|
|
assert inputs["command"].get("required") is True, \
|
|
"inputs.command must be required: true"
|
|
|
|
|
|
# --- (d) inputs.contract / mode / version defaults --------------------------
|
|
|
|
def test_action_inputs_have_documented_defaults():
|
|
a = _load_action()
|
|
inputs = a["inputs"]
|
|
assert inputs["contract"]["default"] == ".nova/contract.yml"
|
|
assert inputs["mode"]["default"] == ""
|
|
assert inputs["version"]["default"] == "latest"
|
|
|
|
|
|
def test_action_inputs_contract_and_mode_not_required():
|
|
"""contract / mode / version are optional (they have defaults)."""
|
|
a = _load_action()
|
|
inputs = a["inputs"]
|
|
for name in ("contract", "mode", "version"):
|
|
assert inputs[name].get("required") in (None, False), \
|
|
f"inputs.{name} must not be required (it has a default)"
|
|
|
|
|
|
# --- (e) runs.using == composite -------------------------------------------
|
|
|
|
def test_action_runs_using_composite():
|
|
a = _load_action()
|
|
runs = a["runs"]
|
|
assert runs["using"] == "composite"
|
|
|
|
|
|
def test_action_has_steps():
|
|
a = _load_action()
|
|
steps = a["runs"]["steps"]
|
|
assert isinstance(steps, list) and len(steps) >= 3
|
|
|
|
|
|
# --- (f) setup-python pins 3.12 (REQ-326 AC3) -------------------------------
|
|
|
|
def test_action_pins_python_3_12():
|
|
"""REQ-326 AC3: the composite action pins Python 3.12 via
|
|
actions/setup-python@v5."""
|
|
a = _load_action()
|
|
steps = a["runs"]["steps"]
|
|
setup = next(
|
|
(s for s in steps if "setup-python" in s.get("uses", "")),
|
|
None,
|
|
)
|
|
assert setup is not None, "must use actions/setup-python"
|
|
assert setup["with"]["python-version"] == "3.12", \
|
|
"setup-python must pin python-version: \"3.12\""
|
|
|
|
|
|
# --- (g) install step installs `nova` --------------------------------------
|
|
|
|
def test_action_has_install_step_installing_nova():
|
|
a = _load_action()
|
|
steps = a["runs"]["steps"]
|
|
install = next(
|
|
(s for s in steps
|
|
if "Install" in s.get("name", "") and s.get("shell")),
|
|
None,
|
|
)
|
|
assert install is not None, "must have an Install Nova step (shell: bash)"
|
|
run = install["run"]
|
|
# Both CodeArtifact + fallback paths must end in `pip install ... nova`.
|
|
assert "pip install" in run
|
|
assert "nova" in run
|
|
# CodeArtifact default path.
|
|
assert "codeartifact login --tool pip" in run
|
|
# Fallback-index path.
|
|
assert "--index-url" in run
|
|
# The install version is parameterised by inputs.version.
|
|
assert "inputs.version" in str(install.get("env", "")) + run
|
|
|
|
|
|
# --- (h) run step executes `nova ${{ inputs.command }}` --------------------
|
|
|
|
def test_action_has_run_step_invoking_nova_command():
|
|
a = _load_action()
|
|
steps = a["runs"]["steps"]
|
|
run = next(
|
|
(s for s in steps if s.get("name", "").startswith("Run Nova")),
|
|
None,
|
|
)
|
|
assert run is not None, "must have a Run Nova step"
|
|
assert run.get("shell") == "bash"
|
|
body = run["run"]
|
|
assert "nova ${{ inputs.command }}" in body, \
|
|
"Run step must invoke `nova ${{ inputs.command }}`"
|
|
|
|
|
|
def test_action_run_step_forwards_mode_and_contract_env():
|
|
"""NOVA_CLIENT_MODE (from inputs.mode) + NOVA_CONTRACT (from
|
|
inputs.contract) must be forwarded to the nova process."""
|
|
a = _load_action()
|
|
steps = a["runs"]["steps"]
|
|
run = next(
|
|
(s for s in steps if s.get("name", "").startswith("Run Nova")),
|
|
None,
|
|
)
|
|
env = run.get("env", {})
|
|
assert env.get("NOVA_CLIENT_MODE") == "${{ inputs.mode }}"
|
|
assert env.get("NOVA_CONTRACT") == "${{ inputs.contract }}"
|
|
|
|
|
|
# --- NFR-11: byte-identical source — no forge branching ---------------------
|
|
|
|
def test_action_source_contains_no_forge_specific_strings():
|
|
"""NFR-11: the single action.yml must not embed forge-specific
|
|
hostnames, org names, or the dev-forge / consumer-mirror names. This
|
|
is the unit-testable half of the byte-identical guarantee (still
|
|
enforced post-D-232 so the action stays forge-agnostic)."""
|
|
text = ACTION.read_text()
|
|
for needle in _FORBIDDEN:
|
|
assert needle.lower() not in text.lower(), \
|
|
f"action.yml must not embed forge-specific string: {needle!r}"
|
|
|
|
|
|
def test_action_has_single_install_path_selected_by_env():
|
|
"""NFR-11: the install step must select CodeArtifact vs fallback by
|
|
env var at runtime — NOT by a forge-specific conditional. This keeps
|
|
the file forge-agnostic (no platform branching)."""
|
|
a = _load_action()
|
|
steps = a["runs"]["steps"]
|
|
install = next(
|
|
(s for s in steps
|
|
if "Install" in s.get("name", "") and s.get("shell")),
|
|
None,
|
|
)
|
|
run = install["run"]
|
|
# The selection is `if [ -n "$NOVA_CODEARTIFACT_DOMAIN" ]` — an env
|
|
# check, not a forge identity check.
|
|
assert "NOVA_CODEARTIFACT_DOMAIN" in run
|
|
assert "NOVA_WHEEL_INDEX" in run
|
|
# No forge-name branching.
|
|
for needle in _FORBIDDEN:
|
|
assert needle.lower() not in run.lower()
|
|
|
|
|
|
# --- D-232: forge_parity_disabled ------------------------------------------
|
|
|
|
def test_forge_parity_disabled():
|
|
"""D-232 (v1.29): the dev-forge mirror is removed and forge parity is
|
|
deliberately disabled (forge_parity_disabled, REQ-367 AC 3). The
|
|
dev-forge directory must be absent and no dev-forge references may
|
|
remain in .github/workflows/."""
|
|
forge_dir = ROOT / f".{_FORGE}"
|
|
assert not forge_dir.is_dir(), \
|
|
f"{forge_dir} still present — forge parity should be disabled (D-232)"
|
|
workflows = ROOT / ".github" / "workflows"
|
|
for wf in workflows.glob("*"):
|
|
text = wf.read_text(errors="replace")
|
|
assert _FORGE.lower() not in text.lower(), \
|
|
f"{wf} contains a dev-forge reference — parity should be disabled (D-232)"
|
|
|
|
|
|
# --- documentation: the CI matrix job is out-of-band ------------------------
|
|
|
|
def test_action_header_documents_byte_identical_matrix_job():
|
|
"""The action.yml header must document that the full byte-identical
|
|
cross-platform verification is a CI matrix job (not a unit test), so
|
|
future editors know the unit test here is the structural subset."""
|
|
text = ACTION.read_text()
|
|
assert "byte-identical" in text.lower()
|
|
assert "matrix" in text.lower() or "CI matrix" in text
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(pytest.main([__file__, "-v"])) |