from __future__ import annotations import subprocess import pytest from skillsbench_agentbeats import ghcr_preflight as ghcr_preflight_module from skillsbench_agentbeats.ghcr_preflight import ( active_account_scopes, ensure_ghcr_package_scopes, missing_ghcr_package_scopes, run_gh_token_scope_status, ) GH_AUTH_STATUS = """github.com - Logged in to github.com account benchflow-bot (keyring) - Active account: true - Git operations protocol: https - Token: gho_************************************ - Token scopes: 'gist', 'read:org', 'repo', 'workflow', 'read:packages', 'write:packages' - Logged in to github.com account FayeZC (keyring) - Active account: false - Git operations protocol: https - Token: gho_************************************ - Token scopes: 'gist', 'read:org', 'repo', 'workflow' """ def test_active_account_scopes_parses_only_active_account() -> None: scopes = active_account_scopes(GH_AUTH_STATUS) assert scopes == {"gist", "read:org", "repo", "workflow", "read:packages", "write:packages"} def test_missing_ghcr_package_scopes_detects_required_package_scopes() -> None: missing = missing_ghcr_package_scopes({"gist", "read:org", "repo", "workflow"}) assert missing == {"read:packages", "write:packages"} def test_ensure_ghcr_package_scopes_accepts_complete_scope_set() -> None: assert "write:packages" in ensure_ghcr_package_scopes(GH_AUTH_STATUS) def test_ensure_ghcr_package_scopes_rejects_missing_package_scopes() -> None: output = GH_AUTH_STATUS.replace(", 'read:packages', 'write:packages'", "") with pytest.raises(ValueError, match="gh auth refresh"): ensure_ghcr_package_scopes(output) def test_active_account_scopes_rejects_missing_active_account() -> None: with pytest.raises(ValueError, match="active GitHub account"): active_account_scopes("github.com\n - no active account") def test_run_gh_token_scope_status_reads_pat_from_environment(monkeypatch: pytest.MonkeyPatch) -> None: seen: dict[str, object] = {} def fake_run( cmd: list[str], *, check: bool, capture_output: bool, text: bool, env: dict[str, str] | None = None, ) -> subprocess.CompletedProcess[str]: seen["cmd"] = cmd seen["env"] = env return subprocess.CompletedProcess(cmd, 0, stdout="x-oauth-scopes: repo, read:packages, write:packages\n", stderr="") monkeypatch.setenv("GH_TOKEN", "github_pat_secret_value") monkeypatch.setattr(ghcr_preflight_module.subprocess, "run", fake_run) assert run_gh_token_scope_status() == {"repo", "read:packages", "write:packages"} assert seen["cmd"] == ["gh", "api", "-i", "user"] assert seen["env"]["GH_TOKEN"] == "github_pat_secret_value" def test_run_gh_token_scope_status_prefers_gh_token_over_github_token(monkeypatch: pytest.MonkeyPatch) -> None: seen_env: dict[str, str] = {} def fake_run( cmd: list[str], *, check: bool, capture_output: bool, text: bool, env: dict[str, str] | None = None, ) -> subprocess.CompletedProcess[str]: assert env is not None seen_env.update(env) return subprocess.CompletedProcess(cmd, 0, stdout="x-oauth-scopes: read:packages, write:packages\n", stderr="") monkeypatch.setenv("GH_TOKEN", "gh_token_secret") monkeypatch.setenv("GITHUB_TOKEN", "github_token_secret") monkeypatch.setattr(ghcr_preflight_module.subprocess, "run", fake_run) run_gh_token_scope_status() assert seen_env["GH_TOKEN"] == "gh_token_secret" assert "GITHUB_TOKEN" not in seen_env def test_run_gh_token_scope_status_rejects_missing_env_token(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.delenv("GH_TOKEN", raising=False) monkeypatch.delenv("GITHUB_TOKEN", raising=False) with pytest.raises(ValueError, match="GH_TOKEN"): run_gh_token_scope_status() def test_run_gh_token_scope_status_redacts_token_on_error(monkeypatch: pytest.MonkeyPatch) -> None: def fake_run( cmd: list[str], *, check: bool, capture_output: bool, text: bool, env: dict[str, str] | None = None, ) -> subprocess.CompletedProcess[str]: return subprocess.CompletedProcess(cmd, 1, stdout="", stderr="bad github_pat_secret_value") monkeypatch.setenv("GH_TOKEN", "github_pat_secret_value") monkeypatch.setattr(ghcr_preflight_module.subprocess, "run", fake_run) with pytest.raises(RuntimeError) as exc_info: run_gh_token_scope_status() assert "github_pat_secret_value" not in str(exc_info.value) assert "[REDACTED_TOKEN]" in str(exc_info.value)