Files
wehub-resource-sync b4fbd6fe9f
Deploy Site / deploy-vercel (push) Has been skipped
Deploy Site / deploy-docs (push) Has been skipped
Build Skills Index / build-index (push) Has been skipped
CI / Deny unrelated histories (push) Has been skipped
CI / Detect affected areas (push) Successful in 27m35s
CI / OSV scan (push) Failing after 4s
CI / Build&Test Docker image (push) Successful in 9s
CI / Supply-chain scan (push) Has been skipped
CI / Lint Docker scripts (push) Failing after 5m13s
CI / Check contributors (push) Failing after 12m8s
CI / Docs Site (push) Failing after 12m8s
CI / TypeScript (push) Failing after 12m8s
CI / Python lints (push) Failing after 12m9s
CI / Python tests (push) Failing after 12m9s
CI / Check uv.lock (push) Failing after 23m22s
CI / CI timing report (push) Has been cancelled
Build Skills Index / trigger-deploy (push) Has been cancelled
CI / All required checks pass (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 11:56:03 +08:00

230 lines
10 KiB
Python

"""Tests for hermes_cli.copilot_auth — Copilot token validation and resolution."""
import pytest
from unittest.mock import patch
class TestTokenValidation:
"""Token type validation."""
def test_classic_pat_rejected(self):
from hermes_cli.copilot_auth import validate_copilot_token
valid, msg = validate_copilot_token("ghp_abcdefghijklmnop1234")
assert valid is False
assert "Classic Personal Access Tokens" in msg
assert "ghp_" in msg
def test_oauth_token_accepted(self):
from hermes_cli.copilot_auth import validate_copilot_token
valid, msg = validate_copilot_token("gho_abcdefghijklmnop1234")
assert valid is True
def test_fine_grained_pat_accepted(self):
from hermes_cli.copilot_auth import validate_copilot_token
valid, msg = validate_copilot_token("github_pat_abcdefghijklmnop1234")
assert valid is True
def test_github_app_token_accepted(self):
from hermes_cli.copilot_auth import validate_copilot_token
valid, msg = validate_copilot_token("ghu_abcdefghijklmnop1234")
assert valid is True
def test_empty_token_rejected(self):
from hermes_cli.copilot_auth import validate_copilot_token
valid, msg = validate_copilot_token("")
assert valid is False
class TestResolveToken:
"""Token resolution with env var priority."""
def test_copilot_github_token_first_priority(self, monkeypatch):
from hermes_cli.copilot_auth import resolve_copilot_token
monkeypatch.setenv("COPILOT_GITHUB_TOKEN", "gho_copilot_first")
monkeypatch.setenv("GH_TOKEN", "gho_gh_second")
monkeypatch.setenv("GITHUB_TOKEN", "gho_github_third")
token, source = resolve_copilot_token()
assert token == "gho_copilot_first"
assert source == "COPILOT_GITHUB_TOKEN"
def test_gh_token_second_priority(self, monkeypatch):
from hermes_cli.copilot_auth import resolve_copilot_token
monkeypatch.delenv("COPILOT_GITHUB_TOKEN", raising=False)
monkeypatch.setenv("GH_TOKEN", "gho_gh_second")
monkeypatch.setenv("GITHUB_TOKEN", "gho_github_third")
token, source = resolve_copilot_token()
assert token == "gho_gh_second"
assert source == "GH_TOKEN"
def test_github_token_third_priority(self, monkeypatch):
from hermes_cli.copilot_auth import resolve_copilot_token
monkeypatch.delenv("COPILOT_GITHUB_TOKEN", raising=False)
monkeypatch.delenv("GH_TOKEN", raising=False)
monkeypatch.setenv("GITHUB_TOKEN", "gho_github_third")
token, source = resolve_copilot_token()
assert token == "gho_github_third"
assert source == "GITHUB_TOKEN"
def test_classic_pat_in_env_skipped(self, monkeypatch):
"""Classic PATs in env vars should be skipped, not returned."""
from hermes_cli.copilot_auth import resolve_copilot_token
monkeypatch.setenv("COPILOT_GITHUB_TOKEN", "ghp_classic_pat_nope")
monkeypatch.delenv("GH_TOKEN", raising=False)
monkeypatch.setenv("GITHUB_TOKEN", "gho_valid_oauth")
token, source = resolve_copilot_token()
# Should skip the ghp_ token and find the gho_ one
assert token == "gho_valid_oauth"
assert source == "GITHUB_TOKEN"
def test_gh_cli_fallback(self, monkeypatch):
from hermes_cli.copilot_auth import resolve_copilot_token
monkeypatch.delenv("COPILOT_GITHUB_TOKEN", raising=False)
monkeypatch.delenv("GH_TOKEN", raising=False)
monkeypatch.delenv("GITHUB_TOKEN", raising=False)
with patch("hermes_cli.copilot_auth._try_gh_cli_token", return_value="gho_from_cli"):
token, source = resolve_copilot_token()
assert token == "gho_from_cli"
assert source == "gh auth token"
def test_gh_cli_classic_pat_raises(self, monkeypatch):
from hermes_cli.copilot_auth import resolve_copilot_token
monkeypatch.delenv("COPILOT_GITHUB_TOKEN", raising=False)
monkeypatch.delenv("GH_TOKEN", raising=False)
monkeypatch.delenv("GITHUB_TOKEN", raising=False)
with patch("hermes_cli.copilot_auth._try_gh_cli_token", return_value="ghp_classic"):
with pytest.raises(ValueError, match="classic PAT"):
resolve_copilot_token()
def test_no_token_returns_empty(self, monkeypatch):
from hermes_cli.copilot_auth import resolve_copilot_token
monkeypatch.delenv("COPILOT_GITHUB_TOKEN", raising=False)
monkeypatch.delenv("GH_TOKEN", raising=False)
monkeypatch.delenv("GITHUB_TOKEN", raising=False)
with patch("hermes_cli.copilot_auth._try_gh_cli_token", return_value=None):
token, source = resolve_copilot_token()
assert token == ""
assert source == ""
class TestRequestHeaders:
"""Copilot API header generation."""
def test_default_headers_include_openai_intent(self):
from hermes_cli.copilot_auth import copilot_request_headers
headers = copilot_request_headers()
assert headers["Openai-Intent"] == "conversation-edits"
assert headers["User-Agent"] == "HermesAgent/1.0"
assert "Editor-Version" in headers
def test_agent_turn_sets_initiator(self):
from hermes_cli.copilot_auth import copilot_request_headers
headers = copilot_request_headers(is_agent_turn=True)
assert headers["x-initiator"] == "agent"
def test_user_turn_sets_initiator(self):
from hermes_cli.copilot_auth import copilot_request_headers
headers = copilot_request_headers(is_agent_turn=False)
assert headers["x-initiator"] == "user"
def test_vision_header(self):
from hermes_cli.copilot_auth import copilot_request_headers
headers = copilot_request_headers(is_vision=True)
assert headers["Copilot-Vision-Request"] == "true"
def test_no_vision_header_by_default(self):
from hermes_cli.copilot_auth import copilot_request_headers
headers = copilot_request_headers()
assert "Copilot-Vision-Request" not in headers
class TestCopilotDefaultHeaders:
"""The models.py copilot_default_headers uses copilot_auth."""
def test_includes_openai_intent(self):
from hermes_cli.models import copilot_default_headers
headers = copilot_default_headers()
assert "Openai-Intent" in headers
assert headers["Openai-Intent"] == "conversation-edits"
def test_includes_x_initiator(self):
from hermes_cli.models import copilot_default_headers
headers = copilot_default_headers()
assert "x-initiator" in headers
def test_default_is_agent_turn(self):
"""Calling with no args preserves backward-compatible default (agent)."""
from hermes_cli.models import copilot_default_headers
headers = copilot_default_headers()
assert headers["x-initiator"] == "agent"
def test_user_turn_sets_user_initiator(self):
"""Passing is_agent_turn=False sets x-initiator to 'user'."""
from hermes_cli.models import copilot_default_headers
headers = copilot_default_headers(is_agent_turn=False)
assert headers["x-initiator"] == "user"
def test_agent_turn_explicit(self):
"""Explicitly passing is_agent_turn=True sets x-initiator to 'agent'."""
from hermes_cli.models import copilot_default_headers
headers = copilot_default_headers(is_agent_turn=True)
assert headers["x-initiator"] == "agent"
def test_param_passthrough_both_values(self):
"""is_agent_turn param correctly maps to x-initiator for both True and False."""
from hermes_cli.models import copilot_default_headers
for is_agent, expected in [(True, "agent"), (False, "user")]:
headers = copilot_default_headers(is_agent_turn=is_agent)
assert headers["x-initiator"] == expected, (
f"is_agent_turn={is_agent} should produce x-initiator={expected!r}, "
f"got {headers['x-initiator']!r}"
)
class TestApiModeSelection:
"""API mode selection matching opencode's shouldUseCopilotResponsesApi."""
def test_gpt5_uses_responses(self):
from hermes_cli.models import _should_use_copilot_responses_api
assert _should_use_copilot_responses_api("gpt-5.4") is True
assert _should_use_copilot_responses_api("gpt-5.4-mini") is True
assert _should_use_copilot_responses_api("gpt-5.3-codex") is True
assert _should_use_copilot_responses_api("gpt-5.2-codex") is True
assert _should_use_copilot_responses_api("gpt-5.2") is True
assert _should_use_copilot_responses_api("gpt-5.1-codex-max") is True
def test_gpt5_mini_excluded(self):
from hermes_cli.models import _should_use_copilot_responses_api
assert _should_use_copilot_responses_api("gpt-5-mini") is False
def test_gpt4_uses_chat(self):
from hermes_cli.models import _should_use_copilot_responses_api
assert _should_use_copilot_responses_api("gpt-4.1") is False
assert _should_use_copilot_responses_api("gpt-4o") is False
assert _should_use_copilot_responses_api("gpt-4o-mini") is False
def test_non_gpt_uses_chat(self):
from hermes_cli.models import _should_use_copilot_responses_api
assert _should_use_copilot_responses_api("claude-sonnet-4.6") is False
assert _should_use_copilot_responses_api("claude-opus-4.6") is False
assert _should_use_copilot_responses_api("gemini-2.5-pro") is False
assert _should_use_copilot_responses_api("grok-code-fast-1") is False
class TestEnvVarOrder:
"""PROVIDER_REGISTRY has correct env var order."""
def test_copilot_env_vars_include_copilot_github_token(self):
from hermes_cli.auth import PROVIDER_REGISTRY
copilot = PROVIDER_REGISTRY["copilot"]
assert "COPILOT_GITHUB_TOKEN" in copilot.api_key_env_vars
# COPILOT_GITHUB_TOKEN should be first
assert copilot.api_key_env_vars[0] == "COPILOT_GITHUB_TOKEN"
def test_copilot_env_vars_order_matches_docs(self):
from hermes_cli.auth import PROVIDER_REGISTRY
copilot = PROVIDER_REGISTRY["copilot"]
assert copilot.api_key_env_vars == (
"COPILOT_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"
)