Files
LatteandClaude Opus 4.8 eb6440db40
CI / lint (pull_request) Successful in 51s
CI / lint (push) Successful in 50s
CI / test (pull_request) Successful in 57s
CI / publish (pull_request) Has been skipped
CI / test (push) Successful in 57s
CI / publish (push) Successful in 50s
Phase 2: EVE SSO auth + read-only account tools
Add PKCE OAuth2 login against EVE SSO with an encrypted, auto-refreshing
token store, driven by `familiar login` / `status` / `logout` CLI commands.
The MCP server gains authenticated account tools:

- account_status, get_wallet, get_skills, get_skill_queue
- get_location, get_clones, get_assets (paginated)

ESI client extended with Bearer auth and X-Pages pagination. 51 tests
(auth + account mocked), coverage 88%. Client id/callback/scopes are
env-overridable; refresh token Fernet-encrypted on disk.

Closes #7

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 18:32:47 +02:00

63 lines
1.7 KiB
Python

"""Shared test helpers: a mock ESI client backed by httpx.MockTransport."""
from __future__ import annotations
import base64
import json
from collections.abc import Callable
import httpx
import pytest
from familiar import esi
def make_jwt(claims: dict) -> str:
"""Build a fake JWT (header.payload.signature) whose payload is `claims`.
Only the payload segment is ever decoded by familiar, so the header and
signature can be arbitrary.
"""
payload = base64.urlsafe_b64encode(json.dumps(claims).encode()).rstrip(b"=").decode()
return f"header.{payload}.signature"
Handler = Callable[[httpx.Request], httpx.Response]
@pytest.fixture
def mock_esi():
"""Install a mock ESI client driven by a request handler.
Usage::
def test_x(mock_esi):
def handler(request):
return httpx.Response(200, json={...})
mock_esi(handler)
...
The shared client is reset after the test.
"""
def _make(handler: Handler) -> esi.EsiClient:
client = esi.EsiClient(transport=httpx.MockTransport(handler))
esi.set_client(client)
return client
yield _make
esi.set_client(None)
def route(request: httpx.Request, fixtures: dict[str, object]) -> httpx.Response:
"""Match a request against ``{substring_in_path: json_body}`` and respond.
The special key ``"/universe/ids/"`` is matched for the name-resolution POST.
A missing match returns 404 so tests fail loudly.
"""
path = request.url.path
for needle, body in fixtures.items():
if needle in path:
return httpx.Response(200, json=body)
return httpx.Response(404, json={"error": f"no fixture for {path}"})