Phase 1: public market + universe lookups via ESI
CI / lint (pull_request) Successful in 52s
CI / test (pull_request) Successful in 56s
CI / publish (pull_request) Has been skipped
CI / lint (push) Successful in 52s
CI / test (push) Successful in 56s
CI / publish (push) Failing after 34s

Add a thin async ESI client (httpx, descriptive User-Agent, uniform errors,
mockable transport) plus name<->id resolution. New read-only tools:

- universe: resolve_names, get_item, get_system, get_station, get_region, get_route
- market:   list_hubs, get_market_price, get_market_orders, compare_hubs

Trade-hub IDs (Jita/Amarr/Dodixie/Rens/Hek) are hardcoded constants. Market
aggregation (best buy/sell, spread, order book) lives in pure helpers. 30 unit
tests with mocked ESI; coverage 99%. SDE bulk-load deferred to a later phase.

Closes #2

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
LatteandClaude Opus 4.8 committed 2026-06-27 17:49:40 +02:00
1 parent 585acc766c
commit 4223141cf7
11 files changed
+839 -1

No files matched your search

+49
View File
@@ -0,0 +1,49 @@
"""Shared test helpers: a mock ESI client backed by httpx.MockTransport."""
from __future__ import annotations
from collections.abc import Callable
import httpx
import pytest
from familiar import esi
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}"})