"""ORCH-089 — src/labels.py: auto-mode pure logic (never-raise, fail-safe). Covers (04-test-plan.yaml): TC-01 has_label True when the label is present on the issue. TC-02 has_label False when the label is absent. TC-03 has_label fail-safe (no auto, never-raise) on Plane API error/timeout. TC-04 label-name matching is normalized (case/space); ambiguity -> no auto. TC-05 auto_approve_applies / auto_deploy_applies: CSV scope + self-hosting. TC-06 auto_label_enabled=False -> applies() False -> has_label never reached (no network call on the gate). """ import os import tempfile import pytest os.environ.setdefault("ORCH_DB_PATH", os.path.join(tempfile.gettempdir(), "test_labels.db")) os.environ.setdefault("ORCH_GITEA_TOKEN", "test-token") os.environ.setdefault("ORCH_PLANE_API_TOKEN", "test-token") from src import labels # noqa: E402 from src import plane_sync # noqa: E402 from src import config as cfg # noqa: E402 @pytest.fixture(autouse=True) def enabled_self_hosting(monkeypatch): monkeypatch.setattr(cfg.settings, "auto_label_enabled", True, raising=False) monkeypatch.setattr(cfg.settings, "auto_approve_label", "autoApprove", raising=False) monkeypatch.setattr(cfg.settings, "auto_deploy_label", "autoDeploy", raising=False) monkeypatch.setattr(cfg.settings, "auto_label_repos", "", raising=False) # Keep _resolve_project_id offline-deterministic. monkeypatch.setattr(plane_sync, "_resolve_project_id", lambda w=None, p=None: "proj-1") yield # --- TC-01 / TC-02 --------------------------------------------------------- def test_tc01_has_label_present(monkeypatch): monkeypatch.setattr(plane_sync, "fetch_issue_labels", lambda w, p=None: ["uuid-A"]) monkeypatch.setattr(plane_sync, "get_project_labels", lambda pid: {"autoapprove": "uuid-A"}) assert labels.has_label("ORCH-1", "autoApprove") is True def test_tc02_has_label_absent(monkeypatch): # Issue carries a different label uuid than the project's autoApprove uuid. monkeypatch.setattr(plane_sync, "fetch_issue_labels", lambda w, p=None: ["uuid-OTHER"]) monkeypatch.setattr(plane_sync, "get_project_labels", lambda pid: {"autoapprove": "uuid-A"}) assert labels.has_label("ORCH-1", "autoApprove") is False def test_tc02_has_label_empty_issue_labels(monkeypatch): monkeypatch.setattr(plane_sync, "fetch_issue_labels", lambda w, p=None: []) monkeypatch.setattr(plane_sync, "get_project_labels", lambda pid: {"autoapprove": "uuid-A"}) assert labels.has_label("ORCH-1", "autoApprove") is False # --- TC-03: fail-safe / never-raise ---------------------------------------- def test_tc03_fetch_none_failsafe(monkeypatch): # fetch returns None (could-not-read) -> False, no auto. monkeypatch.setattr(plane_sync, "fetch_issue_labels", lambda w, p=None: None) monkeypatch.setattr(plane_sync, "get_project_labels", lambda pid: {"autoapprove": "uuid-A"}) assert labels.has_label("ORCH-1", "autoApprove") is False def test_tc03_fetch_raises_failsafe(monkeypatch): def boom(*a, **k): raise RuntimeError("plane down") monkeypatch.setattr(plane_sync, "fetch_issue_labels", boom) monkeypatch.setattr(plane_sync, "get_project_labels", lambda pid: {"autoapprove": "uuid-A"}) # Never raises; degrades to no auto. assert labels.has_label("ORCH-1", "autoApprove") is False def test_tc03_project_map_empty_failsafe(monkeypatch): monkeypatch.setattr(plane_sync, "fetch_issue_labels", lambda w, p=None: ["uuid-A"]) monkeypatch.setattr(plane_sync, "get_project_labels", lambda pid: {}) assert labels.has_label("ORCH-1", "autoApprove") is False # --- TC-04: normalization + ambiguity -------------------------------------- def test_tc04_normalized_match(monkeypatch): # Issue label uuid-A; project maps a differently-cased/spaced name to it. monkeypatch.setattr(plane_sync, "fetch_issue_labels", lambda w, p=None: ["uuid-A"]) monkeypatch.setattr(plane_sync, "get_project_labels", lambda pid: {"autoapprove": "uuid-A"}) # Sought name has different case + surrounding spaces. assert labels.has_label("ORCH-1", " AUTOapprove ") is True def test_tc04_ambiguous_name_no_auto(monkeypatch): # Two distinct project labels collapse to the same normalized name -> ambiguous # sentinel from get_project_labels -> has_label False. monkeypatch.setattr(plane_sync, "fetch_issue_labels", lambda w, p=None: ["uuid-A"]) monkeypatch.setattr( plane_sync, "get_project_labels", lambda pid: {"autoapprove": "__AMBIGUOUS__"}, ) assert labels.has_label("ORCH-1", "autoApprove") is False def test_tc04_empty_label_name(monkeypatch): monkeypatch.setattr(plane_sync, "fetch_issue_labels", lambda w, p=None: ["uuid-A"]) monkeypatch.setattr(plane_sync, "get_project_labels", lambda pid: {"autoapprove": "uuid-A"}) assert labels.has_label("ORCH-1", "") is False # --- TC-05: scope (CSV + self-hosting) ------------------------------------- def test_tc05_empty_csv_self_hosting_only(monkeypatch): monkeypatch.setattr(cfg.settings, "auto_label_repos", "", raising=False) assert labels.auto_approve_applies("orchestrator") is True assert labels.auto_deploy_applies("orchestrator") is True # Non-self repo with empty CSV -> not in scope. assert labels.auto_approve_applies("enduro-trails") is False assert labels.auto_deploy_applies("enduro-trails") is False def test_tc05_csv_membership(monkeypatch): monkeypatch.setattr(cfg.settings, "auto_label_repos", "enduro-trails, foo", raising=False) assert labels.auto_approve_applies("enduro-trails") is True assert labels.auto_deploy_applies("foo") is True # orchestrator is NOT in the explicit CSV -> out of scope. assert labels.auto_approve_applies("orchestrator") is False # --- TC-06: kill-switch -> applies False, no network ----------------------- def test_tc06_killswitch_applies_false(monkeypatch): monkeypatch.setattr(cfg.settings, "auto_label_enabled", False, raising=False) assert labels.auto_approve_applies("orchestrator") is False assert labels.auto_deploy_applies("orchestrator") is False def test_tc06_killswitch_gate_makes_no_network(monkeypatch): """The gate idiom `applies(repo) and has_label(...)` short-circuits before any network call when the kill-switch is off (AC-8).""" monkeypatch.setattr(cfg.settings, "auto_label_enabled", False, raising=False) called = {"fetch": 0} def spy(*a, **k): called["fetch"] += 1 return ["uuid-A"] monkeypatch.setattr(plane_sync, "fetch_issue_labels", spy) repo = "orchestrator" fired = labels.auto_approve_applies(repo) and labels.has_label("ORCH-1", "autoApprove") assert fired is False assert called["fetch"] == 0 # has_label never reached -> zero network def test_snapshot_never_raises(): snap = labels.snapshot() assert set(snap) >= {"enabled", "approve_label", "deploy_label", "repos"}