repo-manager/tests/test_repo_onboard.py

99 lines
3.7 KiB
Python
Raw Normal View History

"""Onboarding guards (CUST-WP-0067-T04).
The guards matter more than the happy path: onboarding that silently accepts an
unpushed or unclassified repository is what produces hub records nobody can
re-derive.
"""
from __future__ import annotations
import subprocess
from pathlib import Path
import pytest
from repo_manager.commands import repo_onboard as ro
def _git(repo: Path, *args: str) -> None:
subprocess.run(["git", "-C", str(repo), *args], check=True, capture_output=True)
def _repo(tmp_path: Path, *, classification: str | None = None) -> Path:
repo = tmp_path / "demo-repo"
repo.mkdir()
_git(repo, "init", "-q", "-b", "main")
_git(repo, "config", "user.email", "t@example.com")
_git(repo, "config", "user.name", "t")
(repo / "README.md").write_text("x\n", encoding="utf-8")
if classification is not None:
(repo / ro.CLASSIFICATION_FILENAME).write_text(classification, encoding="utf-8")
_git(repo, "add", "-A")
_git(repo, "commit", "-qm", "init")
return repo
VALID = """repo_classification:
category: tooling
domain: infotech
"""
def test_missing_classification_is_blocked(tmp_path):
repo = _repo(tmp_path)
with pytest.raises(ro.OnboardError, match="is missing"):
ro.onboard_repo(repo, api_base="http://hub.invalid", dry_run=True)
def test_invalid_classification_is_blocked(tmp_path):
repo = _repo(tmp_path, classification="repo_classification:\n category: nope\n domain: bogus\n")
with pytest.raises(ro.OnboardError, match="invalid"):
ro.onboard_repo(repo, api_base="http://hub.invalid", dry_run=True)
def test_no_upstream_is_blocked(tmp_path):
"""Central derives from what it can fetch, so a local-only branch cannot onboard."""
repo = _repo(tmp_path, classification=VALID)
with pytest.raises(ro.OnboardError, match="upstream"):
ro.onboard_repo(repo, api_base="http://hub.invalid", dry_run=True)
def test_uncommitted_classification_is_blocked(tmp_path):
repo = _repo(tmp_path, classification=VALID)
(repo / ro.CLASSIFICATION_FILENAME).write_text(VALID + " capability_tags: [extra]\n", encoding="utf-8")
with pytest.raises(ro.OnboardError, match="uncommitted"):
ro.onboard_repo(repo, api_base="http://hub.invalid", dry_run=True)
def test_unpushed_commits_are_blocked(tmp_path):
"""A record whose backing file is only local cannot be re-derived by anyone."""
origin = tmp_path / "origin.git"
subprocess.run(["git", "init", "-q", "--bare", str(origin)], check=True, capture_output=True)
repo = _repo(tmp_path, classification=VALID)
_git(repo, "remote", "add", "origin", str(origin))
_git(repo, "push", "-q", "-u", "origin", "main")
(repo / "extra.txt").write_text("y\n", encoding="utf-8")
_git(repo, "add", "-A")
_git(repo, "commit", "-qm", "later")
with pytest.raises(ro.OnboardError, match="not pushed"):
ro.onboard_repo(repo, api_base="http://hub.invalid", dry_run=True)
def test_dry_run_reports_without_writing(tmp_path, monkeypatch):
origin = tmp_path / "origin.git"
subprocess.run(["git", "init", "-q", "--bare", str(origin)], check=True, capture_output=True)
repo = _repo(tmp_path, classification=VALID)
_git(repo, "remote", "add", "origin", str(origin))
_git(repo, "push", "-q", "-u", "origin", "main")
calls: list[tuple[str, str]] = []
def fake_hub(api_base, method, path, body=None):
calls.append((method, path))
return 404, {}
monkeypatch.setattr(ro, "_hub", fake_hub)
report = ro.onboard_repo(repo, api_base="http://hub.invalid", dry_run=True, slug="demo")
assert report["action"] == "would-create"
assert all(method == "GET" for method, _ in calls), calls