Files
orchestrator/tests/test_config.py
claude-bot 00d69d9e27
All checks were successful
CI / test (push) Successful in 15s
CI / test (pull_request) Successful in 17s
feat(merge-gate): auto-rebase onto current main + re-test + serialise merges
Deterministic (no-LLM) sub-gate on the deploy-staging -> deploy edge that
catches a feature branch up to the CURRENT origin/main, re-tests the combined
tree, and serialises merges with a per-repo file lease — so two green parallel
branches can no longer break main (self-hosting safety for the orchestrator repo).

- src/merge_gate.py: branch_is_behind_main, auto_rebase_onto_main (push
  --force-with-lease ONLY the task branch, NEVER main), retest_branch, and a
  file merge-lease (atomic O_CREAT|O_EXCL, holder-aware release, stale reclaim).
  Strict never-raise contract; all git ops in the per-branch worktree.
- src/qg/checks.py: check_branch_mergeable composes the primitives under the
  lease; registered in QG_CHECKS. Conditional rollout (merge_gate_enabled /
  merge_gate_repos, default self-hosting only).
- src/stage_engine.py: sub-gate hook on deploy-staging (not a new stage). PASS ->
  advance; "merge-lock busy" -> DEFER (re-queue with available_at, anti-deadlock
  at max_concurrency=1, capped); conflict/red re-test -> rollback to development
  + developer retry (capped by MAX_DEVELOPER_RETRIES). Lease released on
  deploy->done / rollback / PR-merged webhook.
- src/db.py: enqueue_job(available_at_delay_s=...) for the defer (no schema change).
- src/webhooks/gitea.py: holder-aware lease release on PR-merged.
- src/config.py + .env.example: ORCH_MERGE_* settings.

Docs: README + adr-0006 (architect) already cover the design; CHANGELOG updated.
Tests: test_merge_gate.py, test_qg_merge_gate.py, test_merge_gate_race.py,
test_stage_engine.py::TestMergeGate, test_config.py, QG-registry snapshot.
Full suite: 535 passed.

Refs: ORCH-043

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 17:32:50 +00:00

75 lines
2.8 KiB
Python

"""ORCH-042: Settings.tracker_mode config field.
AC-1: tracker_mode defaults to "edit" and is read from env ORCH_TRACKER_MODE.
Settings is a Pydantic BaseSettings reading env at instantiation, so each case
builds a FRESH Settings() (the process-wide singleton is not mutated).
"""
from src.config import Settings
def test_tracker_mode_defaults_to_edit(monkeypatch):
# No env var -> default "edit" (TC-01 / AC-1).
monkeypatch.delenv("ORCH_TRACKER_MODE", raising=False)
assert Settings().tracker_mode == "edit"
def test_tracker_mode_reads_env_bump(monkeypatch):
# ORCH_TRACKER_MODE=bump -> "bump" (TC-01 / AC-1).
monkeypatch.setenv("ORCH_TRACKER_MODE", "bump")
assert Settings().tracker_mode == "bump"
def test_tracker_mode_reads_env_arbitrary(monkeypatch):
# The field is read verbatim from env; mode RESOLUTION (anything != "bump"
# -> edit) happens in notifications, not here (AC-1/AC-2 split).
monkeypatch.setenv("ORCH_TRACKER_MODE", "garbage")
assert Settings().tracker_mode == "garbage"
# ---------------------------------------------------------------------------
# ORCH-043 / TC-25: merge-gate settings defaults + env override.
# ---------------------------------------------------------------------------
_MERGE_ENV = (
"ORCH_MERGE_GATE_ENABLED",
"ORCH_MERGE_GATE_REPOS",
"ORCH_MERGE_RETEST_TIMEOUT_S",
"ORCH_MERGE_RETEST_TARGET",
"ORCH_MERGE_LOCK_TIMEOUT_S",
"ORCH_MERGE_DEFER_DELAY_S",
"ORCH_MERGE_DEFER_MAX_ATTEMPTS",
)
def test_merge_gate_settings_defaults(monkeypatch):
"""TC-25 / AC-10: documented defaults when no env is set."""
for name in _MERGE_ENV:
monkeypatch.delenv(name, raising=False)
s = Settings()
assert s.merge_gate_enabled is True
assert s.merge_gate_repos == ""
assert s.merge_retest_timeout_s == 600
assert s.merge_retest_target == "tests/"
assert s.merge_lock_timeout_s == 300
assert s.merge_defer_delay_s == 60
assert s.merge_defer_max_attempts == 5
def test_merge_gate_settings_env_override(monkeypatch):
"""TC-25 / AC-10: each field is read from its ORCH_* env var."""
monkeypatch.setenv("ORCH_MERGE_GATE_ENABLED", "false")
monkeypatch.setenv("ORCH_MERGE_GATE_REPOS", "orchestrator,enduro-trails")
monkeypatch.setenv("ORCH_MERGE_RETEST_TIMEOUT_S", "120")
monkeypatch.setenv("ORCH_MERGE_RETEST_TARGET", "tests/unit")
monkeypatch.setenv("ORCH_MERGE_LOCK_TIMEOUT_S", "90")
monkeypatch.setenv("ORCH_MERGE_DEFER_DELAY_S", "5")
monkeypatch.setenv("ORCH_MERGE_DEFER_MAX_ATTEMPTS", "9")
s = Settings()
assert s.merge_gate_enabled is False
assert s.merge_gate_repos == "orchestrator,enduro-trails"
assert s.merge_retest_timeout_s == 120
assert s.merge_retest_target == "tests/unit"
assert s.merge_lock_timeout_s == 90
assert s.merge_defer_delay_s == 5
assert s.merge_defer_max_attempts == 9