99 lines
3.7 KiB
Python
99 lines
3.7 KiB
Python
|
|
"""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
|