test: cover Copilot enterprise overrides
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.
This commit is contained in:
@@ -677,6 +677,16 @@ nanobot agent -c ~/.nanobot-telegram/config.json -w /tmp/nanobot-telegram-test -
|
|||||||
|
|
||||||
GitHub Copilot uses OAuth instead of API keys. Requires a [GitHub account with a plan](https://github.com/features/copilot/plans) configured. No `providers.githubCopilot` block is needed in `config.json`; `nanobot provider login` stores the OAuth session outside config.
|
GitHub Copilot uses OAuth instead of API keys. Requires a [GitHub account with a plan](https://github.com/features/copilot/plans) configured. No `providers.githubCopilot` block is needed in `config.json`; `nanobot provider login` stores the OAuth session outside config.
|
||||||
|
|
||||||
|
For GitHub Enterprise / Copilot for Business, set the endpoint overrides you need before login:
|
||||||
|
```bash
|
||||||
|
export NANOBOT_GITHUB_COPILOT_CLIENT_ID="your-enterprise-client-id"
|
||||||
|
export NANOBOT_GITHUB_DEVICE_CODE_URL="https://ghe.example/login/device/code"
|
||||||
|
export NANOBOT_GITHUB_ACCESS_TOKEN_URL="https://ghe.example/login/oauth/access_token"
|
||||||
|
export NANOBOT_GITHUB_USER_URL="https://api.ghe.example/user"
|
||||||
|
export NANOBOT_COPILOT_TOKEN_URL="https://api.ghe.example/copilot_internal/v2/token"
|
||||||
|
export NANOBOT_COPILOT_BASE_URL="https://copilot-api.ghe.example"
|
||||||
|
```
|
||||||
|
|
||||||
**1. Login:**
|
**1. Login:**
|
||||||
```bash
|
```bash
|
||||||
nanobot provider login github-copilot
|
nanobot provider login github-copilot
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from nanobot.providers import github_copilot_provider as gc
|
from nanobot.providers import github_copilot_provider as gc
|
||||||
|
|
||||||
|
|
||||||
@@ -30,3 +34,110 @@ def test_provider_api_base_honors_env_override(monkeypatch):
|
|||||||
monkeypatch.setenv("NANOBOT_COPILOT_BASE_URL", "https://copilot-api.acme.ghe.com")
|
monkeypatch.setenv("NANOBOT_COPILOT_BASE_URL", "https://copilot-api.acme.ghe.com")
|
||||||
provider = gc.GitHubCopilotProvider()
|
provider = gc.GitHubCopilotProvider()
|
||||||
assert provider.api_base == "https://copilot-api.acme.ghe.com"
|
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"
|
||||||
|
|||||||
Reference in New Issue
Block a user