73 lines
3.0 KiB
Python
73 lines
3.0 KiB
Python
"""ORCH-036 TC-01/02/03: deterministic exit-code -> deploy_status mapping.
|
|
|
|
The finalizer (Phase C) maps the host-hook exit-code to the machine verdict via a
|
|
PURE function (no LLM, no I/O), so it is unit-testable in isolation. Contract
|
|
(hook exit-code 0/1/2, AC-1/AC-3): 0 -> SUCCESS; 1 (rolled back), 2 (rollback also
|
|
failed), and anything else -> FAILED (fail-closed).
|
|
"""
|
|
|
|
import os
|
|
|
|
os.environ.setdefault("ORCH_PLANE_API_TOKEN", "test-token")
|
|
os.environ.setdefault("ORCH_GITEA_TOKEN", "test-token")
|
|
|
|
from src import self_deploy # noqa: E402
|
|
from src.self_deploy import map_exit_code_to_status, build_deploy_log # noqa: E402
|
|
|
|
|
|
def test_tc01_exit0_maps_to_success():
|
|
assert map_exit_code_to_status(0) == "SUCCESS"
|
|
|
|
|
|
def test_tc02_exit1_rolled_back_maps_to_failed():
|
|
assert map_exit_code_to_status(1) == "FAILED"
|
|
|
|
|
|
def test_tc03_exit2_rollback_also_failed_maps_to_failed():
|
|
assert map_exit_code_to_status(2) == "FAILED"
|
|
|
|
|
|
def test_tc09_provenance_fail_closed_exit1_maps_to_failed():
|
|
"""ORCH-058 TC-09: the Strategy-B hook fail-close uses `exit 1`; that must map
|
|
to FAILED so the existing БАГ-8 rollback path triggers (prod never left stale)."""
|
|
assert map_exit_code_to_status(1) == "FAILED"
|
|
|
|
|
|
def test_other_exit_codes_map_to_failed():
|
|
for code in (3, 127, 255, -1):
|
|
assert map_exit_code_to_status(code) == "FAILED"
|
|
|
|
|
|
def test_non_int_or_none_maps_to_failed_fail_closed():
|
|
assert map_exit_code_to_status(None) == "FAILED"
|
|
assert map_exit_code_to_status("garbage") == "FAILED"
|
|
|
|
|
|
def test_deploy_log_frontmatter_carries_status():
|
|
"""The rendered log must expose deploy_status in YAML frontmatter so the
|
|
existing _parse_deploy_status contract (AC-10) reads the right verdict."""
|
|
body_ok = build_deploy_log("ORCH-036", 0, "SUCCESS")
|
|
assert body_ok.startswith("---\n")
|
|
assert "deploy_status: SUCCESS" in body_ok
|
|
body_fail = build_deploy_log("ORCH-036", 2, "FAILED")
|
|
assert "deploy_status: FAILED" in body_fail
|
|
assert "hook_exit_code: 2" in body_fail
|
|
|
|
|
|
def test_clear_state_removes_all_markers_and_is_idempotent(monkeypatch, tmp_path):
|
|
"""clear_state wipes the whole work-item state dir (all sentinels) and treats a
|
|
missing dir as success, so a re-deploy after rollback starts from a clean slate."""
|
|
monkeypatch.setattr(self_deploy.settings, "repos_dir", str(tmp_path))
|
|
repo, wi = "orchestrator", "ORCH-036"
|
|
self_deploy.write_marker(repo, wi, self_deploy.APPROVE_REQUESTED, "t")
|
|
self_deploy.write_marker(repo, wi, self_deploy.INITIATED, "t")
|
|
self_deploy.write_marker(repo, wi, self_deploy.RESULT, "1")
|
|
assert self_deploy.has_marker(repo, wi, self_deploy.INITIATED) is True
|
|
|
|
assert self_deploy.clear_state(repo, wi) is True
|
|
assert self_deploy.has_marker(repo, wi, self_deploy.APPROVE_REQUESTED) is False
|
|
assert self_deploy.has_marker(repo, wi, self_deploy.INITIATED) is False
|
|
assert self_deploy.has_marker(repo, wi, self_deploy.RESULT) is False
|
|
# Idempotent: clearing an already-absent dir is still success (never raises).
|
|
assert self_deploy.clear_state(repo, wi) is True
|