Maintainer edit: add mocked coverage for the enterprise endpoint and client ID override paths, and document the environment variables users must set before OAuth login.
144 lines
4.7 KiB
Python
144 lines
4.7 KiB
Python
"""Regression tests for GitHub Enterprise / Copilot for Business endpoint overrides (#4220)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
from nanobot.providers import github_copilot_provider as gc
|
|
|
|
|
|
def test_resolve_falls_back_to_default_without_env(monkeypatch):
|
|
monkeypatch.delenv("NANOBOT_COPILOT_BASE_URL", raising=False)
|
|
assert gc._resolve("NANOBOT_COPILOT_BASE_URL", gc.DEFAULT_COPILOT_BASE_URL) == (
|
|
gc.DEFAULT_COPILOT_BASE_URL
|
|
)
|
|
|
|
|
|
def test_resolve_uses_env_override_and_strips(monkeypatch):
|
|
monkeypatch.setenv("NANOBOT_COPILOT_TOKEN_URL", " https://api.acme.ghe.com/copilot_internal/v2/token ")
|
|
assert gc._resolve("NANOBOT_COPILOT_TOKEN_URL", gc.DEFAULT_COPILOT_TOKEN_URL) == (
|
|
"https://api.acme.ghe.com/copilot_internal/v2/token"
|
|
)
|
|
|
|
|
|
def test_blank_env_override_falls_back_to_default(monkeypatch):
|
|
monkeypatch.setenv("NANOBOT_COPILOT_BASE_URL", " ")
|
|
assert gc._resolve("NANOBOT_COPILOT_BASE_URL", gc.DEFAULT_COPILOT_BASE_URL) == (
|
|
gc.DEFAULT_COPILOT_BASE_URL
|
|
)
|
|
|
|
|
|
def test_provider_api_base_honors_env_override(monkeypatch):
|
|
monkeypatch.setenv("NANOBOT_COPILOT_BASE_URL", "https://copilot-api.acme.ghe.com")
|
|
provider = gc.GitHubCopilotProvider()
|
|
assert provider.api_base == "https://copilot-api.acme.ghe.com"
|
|
|
|
|
|
def test_login_uses_enterprise_endpoint_overrides(monkeypatch):
|
|
monkeypatch.setenv("NANOBOT_GITHUB_COPILOT_CLIENT_ID", "enterprise-client-id")
|
|
monkeypatch.setenv("NANOBOT_GITHUB_DEVICE_CODE_URL", "https://ghe.example/login/device/code")
|
|
monkeypatch.setenv(
|
|
"NANOBOT_GITHUB_ACCESS_TOKEN_URL",
|
|
"https://ghe.example/login/oauth/access_token",
|
|
)
|
|
monkeypatch.setenv("NANOBOT_GITHUB_USER_URL", "https://api.ghe.example/user")
|
|
monkeypatch.setattr(gc.webbrowser, "open", lambda _url: None)
|
|
|
|
calls = []
|
|
saved = []
|
|
|
|
class FakeResponse:
|
|
def __init__(self, payload):
|
|
self._payload = payload
|
|
|
|
def raise_for_status(self):
|
|
pass
|
|
|
|
def json(self):
|
|
return self._payload
|
|
|
|
class FakeClient:
|
|
def __init__(self, *args, **kwargs):
|
|
pass
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, *args):
|
|
return False
|
|
|
|
def post(self, url, *, headers, data):
|
|
calls.append(("post", url, data))
|
|
if url.endswith("/device/code"):
|
|
return FakeResponse(
|
|
{
|
|
"device_code": "device-code",
|
|
"user_code": "user-code",
|
|
"verification_uri": "https://ghe.example/device",
|
|
"interval": 1,
|
|
"expires_in": 60,
|
|
}
|
|
)
|
|
return FakeResponse({"access_token": "github-token", "expires_in": 3600})
|
|
|
|
def get(self, url, *, headers):
|
|
calls.append(("get", url, headers))
|
|
return FakeResponse({"login": "enterprise-user"})
|
|
|
|
monkeypatch.setattr(gc.httpx, "Client", FakeClient)
|
|
monkeypatch.setattr(gc, "get_storage", lambda: SimpleNamespace(save=saved.append))
|
|
|
|
token = gc.login_github_copilot(print_fn=lambda _message: None)
|
|
|
|
assert token.access == "github-token"
|
|
assert saved[0].account_id == "enterprise-user"
|
|
assert calls[0] == (
|
|
"post",
|
|
"https://ghe.example/login/device/code",
|
|
{"client_id": "enterprise-client-id", "scope": gc.GITHUB_COPILOT_SCOPE},
|
|
)
|
|
assert calls[1][0:2] == ("post", "https://ghe.example/login/oauth/access_token")
|
|
assert calls[1][2]["client_id"] == "enterprise-client-id"
|
|
assert calls[2][0:2] == ("get", "https://api.ghe.example/user")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_copilot_token_exchange_uses_enterprise_endpoint_override(monkeypatch):
|
|
monkeypatch.setenv(
|
|
"NANOBOT_COPILOT_TOKEN_URL",
|
|
"https://api.ghe.example/copilot_internal/v2/token",
|
|
)
|
|
monkeypatch.setattr(gc, "_load_github_token", lambda: SimpleNamespace(access="github-token"))
|
|
|
|
calls = []
|
|
|
|
class FakeResponse:
|
|
def raise_for_status(self):
|
|
pass
|
|
|
|
def json(self):
|
|
return {"token": "copilot-token", "refresh_in": 120}
|
|
|
|
class FakeAsyncClient:
|
|
def __init__(self, *args, **kwargs):
|
|
pass
|
|
|
|
async def __aenter__(self):
|
|
return self
|
|
|
|
async def __aexit__(self, *args):
|
|
return False
|
|
|
|
async def get(self, url, *, headers):
|
|
calls.append((url, headers))
|
|
return FakeResponse()
|
|
|
|
monkeypatch.setattr(gc.httpx, "AsyncClient", FakeAsyncClient)
|
|
|
|
provider = gc.GitHubCopilotProvider()
|
|
|
|
assert await provider._get_copilot_access_token() == "copilot-token"
|
|
assert calls[0][0] == "https://api.ghe.example/copilot_internal/v2/token"
|