"""Phase 2 of MuseHub issue #128 -- deploy/publish_muse_release.sh must use prune_releases.py (PEP 440 precedence) instead of sort -V's lexical comparison, for both the S3 cleanup and the server (SSM) cleanup blocks. Source-level assertions, matching this repo's existing convention in test_deployment.py for validating deploy scripts without executing real AWS calls -- live verification against the actual staging instance/S3 bucket is Phase 3 (PRUNE_10), a deliberate, human-directed action, not something a test suite should trigger. """ from __future__ import annotations from pathlib import Path _ROOT = Path(__file__).resolve().parents[1] _PUBLISH_SH = _ROOT / "deploy" / "publish_muse_release.sh" _SRC = _PUBLISH_SH.read_text() def _executable_lines(src: str) -> str: """Strip full-line comments -- isolates actual bash code from prose that may legitimately mention "sort -V" while explaining why it was removed.""" return "\n".join( line for line in src.splitlines() if not line.strip().startswith("#") ) _CODE = _executable_lines(_SRC) class TestPruneReleasesWiredIntoPublishScript: def test_PRUNE_06_sort_dash_v_removed_entirely(self) -> None: """sort -V's lexical comparison must not execute anywhere in the script. Explanatory comments are allowed to mention "sort -V" by name (e.g. documenting why it was removed) -- this checks executable code only. """ assert "sort -V" not in _CODE def test_PRUNE_06b_s3_cleanup_calls_prune_releases_py(self) -> None: """S3 cleanup must pipe candidate filenames through prune_releases.py.""" assert "prune_releases.py" in _SRC assert '--keep "$KEEP_RELEASES"' in _SRC def test_PRUNE_06c_s3_cleanup_still_greps_muse_tarball_pattern(self) -> None: """The existing muse-*.tar.gz filter must survive the rewrite -- prune_releases.py raises on unparseable names, so anything that isn't a muse release tarball must never reach it.""" assert "grep '^muse-.*\\.tar\\.gz$'" in _SRC def test_PRUNE_07_server_cleanup_has_no_sort_in_remote_command(self) -> None: """The remote SSM command string must never invoke sort or awk-based version comparison -- only prune_releases.py (run locally) decides what's stale; the remote host receives an explicit rm list. Checks executable code only (comments may legitimately say "the remote shell never sorts" while explaining the design).""" # Isolate the server-cleanup section (list + rm), not the whole file, # so an unrelated "sort" elsewhere in the script wouldn't false-pass. start = _CODE.index("LIST_CMD_ID=") end = _CODE.index("HTTP=$(curl") server_section = _CODE[start:end] assert "sort" not in server_section assert "awk" not in server_section def test_PRUNE_07b_server_cleanup_computes_stale_set_locally(self) -> None: """The stale set for the server must be computed via a local prune_releases.py call against the SSM-fetched file listing, not inside the remote SSM command string.""" start = _CODE.index("LIST_CMD_ID=") end = _CODE.index("HTTP=$(curl") server_section = _CODE[start:end] assert "prune_releases.py" in server_section assert 'REMOTE_FILES' in server_section def test_PRUNE_07c_server_rm_command_targets_explicit_filenames(self) -> None: """The rm sent to the remote host must be an explicit filename list, not a re-derived sort/keep-N pipeline.""" start = _CODE.index("LIST_CMD_ID=") end = _CODE.index("HTTP=$(curl") server_section = _CODE[start:end] assert "rm -v" in server_section def test_script_dir_used_for_prune_releases_reference(self) -> None: """prune_releases.py must be referenced via $SCRIPT_DIR, not a bare relative path -- the script must work regardless of the caller's cwd.""" assert '$SCRIPT_DIR/prune_releases.py' in _SRC def test_syntax_is_valid_bash(self) -> None: """The script must still parse as valid bash after the rewrite.""" import subprocess result = subprocess.run( ["bash", "-n", str(_PUBLISH_SH)], capture_output=True, text=True, ) assert result.returncode == 0, result.stderr