"""Unit tests for the phase-prevb `previous/` overlay surface + COMPOSE_FILE layering (lifecycle). Covers: the version-guarded apply/skip/stale decision (`previous_status`), the VERSION-marker reader (`previous_target_version`), folder discovery (`has_previous`), and the pure COMPOSE_FILE add/remove helpers used to layer compose.previous.yml onto the base deploy and strip it before the head redeploy. """ from __future__ import annotations import os import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner")) from harness import lifecycle # noqa: E402 from harness import meta as meta_mod # noqa: E402 # ---- pure COMPOSE_FILE helpers -------------------------------------------------------------------- def test_compose_file_add_appends_when_absent(): assert ( lifecycle.compose_file_add("compose.yml:compose.ccci.yml", "compose.previous.yml") == "compose.yml:compose.ccci.yml:compose.previous.yml" ) def test_compose_file_add_idempotent(): cf = "compose.yml:compose.previous.yml" assert lifecycle.compose_file_add(cf, "compose.previous.yml") == cf def test_compose_file_add_defaults_missing_base(): assert ( lifecycle.compose_file_add("", "compose.previous.yml") == "compose.yml:compose.previous.yml" ) def test_compose_file_remove_strips_only_overlay(): assert ( lifecycle.compose_file_remove( "compose.yml:compose.ccci.yml:compose.previous.yml", "compose.previous.yml" ) == "compose.yml:compose.ccci.yml" ) def test_compose_file_remove_keeps_compose_yml_when_emptied(): assert ( lifecycle.compose_file_remove("compose.previous.yml", "compose.previous.yml") == "compose.yml" ) # ---- marker reader + discovery (filesystem, via a temp TESTS_DIR) --------------------------------- def _stage(monkeypatch, tmp_path, recipe, *, compose=True, marker=None): monkeypatch.setattr(meta_mod, "TESTS_DIR", str(tmp_path)) prev = tmp_path / recipe / "previous" prev.mkdir(parents=True) if compose: (prev / "compose.previous.yml").write_text("services: {}\n") if marker is not None: (prev / "VERSION").write_text(marker) return prev def test_has_previous_true_only_with_compose(monkeypatch, tmp_path): _stage(monkeypatch, tmp_path, "rx", compose=True) assert lifecycle.has_previous("rx") is True assert lifecycle.has_previous("other") is False def test_previous_target_version_reads_first_real_line(monkeypatch, tmp_path): _stage(monkeypatch, tmp_path, "rx", marker="# the previous published version\n\n0.7.0+3.3.1\n") assert lifecycle.previous_target_version("rx") == "0.7.0+3.3.1" def test_previous_target_version_none_without_marker(monkeypatch, tmp_path): _stage(monkeypatch, tmp_path, "rx", marker=None) assert lifecycle.previous_target_version("rx") is None # ---- the apply/skip/stale decision matrix --------------------------------------------------------- def test_previous_status_no_folder(monkeypatch): monkeypatch.setattr(lifecycle, "has_previous", lambda r: False) st = lifecycle.previous_status("rx", "version", "0.7.0+3.3.1") assert st == {"apply": False, "stale": False, "reason": ""} def test_previous_status_applies_on_version_match(monkeypatch): monkeypatch.setattr(lifecycle, "has_previous", lambda r: True) monkeypatch.setattr(lifecycle, "previous_target_version", lambda r: "0.7.0+3.3.1") st = lifecycle.previous_status("rx", "version", "0.7.0+3.3.1") assert st["apply"] is True and st["stale"] is False def test_previous_status_stale_on_version_mismatch(monkeypatch): monkeypatch.setattr(lifecycle, "has_previous", lambda r: True) monkeypatch.setattr(lifecycle, "previous_target_version", lambda r: "0.6.0+3.1.1") st = lifecycle.previous_status("rx", "version", "0.7.0+3.3.1") assert st["apply"] is False and st["stale"] is True and "stale" in st["reason"] def test_previous_status_stale_on_main_tip_base(monkeypatch): # previous/ can only repair a pinned published base; a main-tip (ref) base flags it for review. monkeypatch.setattr(lifecycle, "has_previous", lambda r: True) st = lifecycle.previous_status("rx", "ref", None) assert st["apply"] is False and st["stale"] is True def test_previous_status_stale_without_marker(monkeypatch): monkeypatch.setattr(lifecycle, "has_previous", lambda r: True) monkeypatch.setattr(lifecycle, "previous_target_version", lambda r: None) st = lifecycle.previous_status("rx", "version", "0.7.0+3.3.1") assert st["apply"] is False and st["stale"] is True and "marker" in st["reason"]