Follow-up ORCH-040: legacy root:root files in /repos broke worktree creation under uid 1000 with a raw "Permission denied" (agent never started, no diagnosis). Three additive, kill-switch-reversible layers; STAGE_TRANSITIONS / QG_CHECKS / check_* / machine-verdict keys / DB schema are byte-for-byte unchanged. - D1: ensure_worktree classifies the permission class and raises an actionable RuntimeError (cause + chown command + INFRA.md ref); non-permission errors keep the prior raw-stderr contract; kill-switch off -> contract 1:1 as before ORCH-057. - D2: new never-raise leaf src/fs_normalize.py — scan_ownership (TTL-cached, early-exit per root), applies()-first scope (empty CSV -> self-hosting only), opt-in normalize() that chowns ONLY when privileged (no-op under uid 1000). - D3: best-effort startup detect in main.lifespan (WARNING + Telegram on mismatch, never-fatal); read-only fs_ownership block in GET /queue; POST /fs-normalize/check. Claim is NOT blocked — the clear early outcome is delivered by D1 at launch. - Docs/config: .env.example flags + CHANGELOG (architecture README / adr-0031 / INFRA.md procedure already landed on the branch). - Tests: test_fs_normalize.py, test_git_worktree_perm.py, test_fs_normalize_startup.py, test_api_queue.py (TC-01..TC-12). Full suite green. Refs: ORCH-057 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
69 lines
2.2 KiB
Python
69 lines
2.2 KiB
Python
"""ORCH-057 TC-12: GET /queue exposes the read-only fs_ownership block.
|
|
|
|
The block carries {enabled, target_uid, mismatch, roots_checked, roots_mismatch,
|
|
sample_path, checked_at, ...} and /queue must not 5xx whether the layer is on or off.
|
|
"""
|
|
import os
|
|
import tempfile
|
|
|
|
import pytest
|
|
|
|
_test_db = os.path.join(tempfile.gettempdir(), "test_orchestrator_apiq.db")
|
|
os.environ["ORCH_DB_PATH"] = _test_db
|
|
os.environ["ORCH_REPOS_DIR"] = tempfile.gettempdir()
|
|
os.environ["ORCH_GITEA_TOKEN"] = "test-token"
|
|
os.environ["ORCH_PLANE_API_TOKEN"] = "test-token"
|
|
os.environ["ORCH_PLANE_WEBHOOK_SECRET"] = ""
|
|
os.environ["ORCH_GITEA_WEBHOOK_SECRET"] = ""
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
from src import fs_normalize
|
|
from src.main import app
|
|
from src.db import init_db
|
|
|
|
client = TestClient(app)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _db():
|
|
if os.path.exists(_test_db):
|
|
os.unlink(_test_db)
|
|
init_db()
|
|
fs_normalize.reset_cache()
|
|
yield
|
|
if os.path.exists(_test_db):
|
|
os.unlink(_test_db)
|
|
|
|
|
|
def test_tc12_queue_exposes_fs_ownership_block(monkeypatch):
|
|
"""TC-12: GET /queue returns the fs_ownership block with the documented shape."""
|
|
monkeypatch.setattr(fs_normalize.settings, "fs_normalize_enabled", True)
|
|
r = client.get("/queue")
|
|
assert r.status_code == 200
|
|
body = r.json()
|
|
assert "fs_ownership" in body
|
|
block = body["fs_ownership"]
|
|
for k in ("enabled", "target_uid", "mismatch", "roots_checked",
|
|
"roots_mismatch", "sample_path", "checked_at"):
|
|
assert k in block
|
|
|
|
|
|
def test_tc12_queue_no_5xx_when_disabled(monkeypatch):
|
|
"""TC-12: with the kill-switch off /queue still returns 200 (no 5xx)."""
|
|
monkeypatch.setattr(fs_normalize.settings, "fs_normalize_enabled", False)
|
|
fs_normalize.reset_cache()
|
|
r = client.get("/queue")
|
|
assert r.status_code == 200
|
|
assert r.json()["fs_ownership"]["enabled"] is False
|
|
|
|
|
|
def test_fs_normalize_check_endpoint():
|
|
"""The optional POST /fs-normalize/check force-rescans and returns the snapshot."""
|
|
r = client.post("/fs-normalize/check")
|
|
assert r.status_code == 200
|
|
body = r.json()
|
|
assert body["ok"] is True
|
|
assert "scan" in body and "mismatch" in body["scan"]
|
|
assert "healing" in body
|