Phase 1: public market + universe lookups via ESI
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:
11 files changed
+839
-1
No files matched your search
@@ -0,0 +1,106 @@
|
||||
"""Universe lookup tools."""
|
||||
|
||||
import asyncio
|
||||
|
||||
import httpx
|
||||
|
||||
from familiar import universe
|
||||
from familiar.server import mcp
|
||||
from conftest import route
|
||||
|
||||
|
||||
def test_get_item_resolves_name_then_fetches_type(mock_esi):
|
||||
fixtures = {
|
||||
"/universe/ids/": {"inventory_types": [{"id": 34, "name": "Tritanium"}]},
|
||||
"/universe/types/": {
|
||||
"type_id": 34,
|
||||
"name": "Tritanium",
|
||||
"group_id": 18,
|
||||
"volume": 0.01,
|
||||
"packaged_volume": 0.01,
|
||||
"description": "The most common ore type.",
|
||||
},
|
||||
}
|
||||
mock_esi(lambda req: route(req, fixtures))
|
||||
result = asyncio.run(universe.get_item("Tritanium"))
|
||||
assert result["type_id"] == 34
|
||||
assert result["name"] == "Tritanium"
|
||||
assert result["volume"] == 0.01
|
||||
|
||||
|
||||
def test_get_system_rounds_security_and_counts(mock_esi):
|
||||
fixtures = {
|
||||
"/universe/systems/": {
|
||||
"system_id": 30000142,
|
||||
"name": "Jita",
|
||||
"constellation_id": 20000020,
|
||||
"security_status": 0.945913,
|
||||
"security_class": "D",
|
||||
"planets": [{}, {}, {}],
|
||||
"stargates": [1, 2],
|
||||
},
|
||||
}
|
||||
mock_esi(lambda req: route(req, fixtures))
|
||||
result = asyncio.run(universe.get_system(30000142))
|
||||
assert result["security_status"] == 0.95
|
||||
assert result["planets"] == 3
|
||||
assert result["stargates"] == 2
|
||||
|
||||
|
||||
def test_get_station(mock_esi):
|
||||
fixtures = {
|
||||
"/universe/stations/": {
|
||||
"station_id": 60003760,
|
||||
"name": "Jita IV - Moon 4 - Caldari Navy Assembly Plant",
|
||||
"system_id": 30000142,
|
||||
"type_id": 52678,
|
||||
"services": ["market", "repair-facilities"],
|
||||
},
|
||||
}
|
||||
mock_esi(lambda req: route(req, fixtures))
|
||||
result = asyncio.run(universe.get_station(60003760))
|
||||
assert result["system_id"] == 30000142
|
||||
assert "market" in result["services"]
|
||||
|
||||
|
||||
def test_get_region(mock_esi):
|
||||
fixtures = {
|
||||
"/universe/regions/": {
|
||||
"region_id": 10000002,
|
||||
"name": "The Forge",
|
||||
"constellations": [1, 2, 3, 4],
|
||||
"description": "A region.",
|
||||
},
|
||||
}
|
||||
mock_esi(lambda req: route(req, fixtures))
|
||||
result = asyncio.run(universe.get_region(10000002))
|
||||
assert result["name"] == "The Forge"
|
||||
assert result["constellations"] == 4
|
||||
|
||||
|
||||
def test_get_route_counts_jumps(mock_esi):
|
||||
fixtures = {"/route/": [30000142, 30000144, 30002187]}
|
||||
mock_esi(lambda req: route(req, fixtures))
|
||||
result = asyncio.run(universe.get_route(30000142, 30002187, prefer="secure"))
|
||||
assert result["jumps"] == 2
|
||||
assert result["flag"] == "secure"
|
||||
|
||||
|
||||
def test_get_route_rejects_unknown_flag(mock_esi):
|
||||
fixtures = {"/route/": [30000142]}
|
||||
mock_esi(lambda req: route(req, fixtures))
|
||||
result = asyncio.run(universe.get_route(30000142, 30000142, prefer="banana"))
|
||||
assert result["flag"] == "shortest"
|
||||
assert result["jumps"] == 0
|
||||
|
||||
|
||||
def test_resolve_names_passthrough(mock_esi):
|
||||
body = {"systems": [{"id": 30000142, "name": "Jita"}]}
|
||||
mock_esi(lambda req: httpx.Response(200, json=body))
|
||||
result = asyncio.run(universe.resolve_names(["Jita"]))
|
||||
assert result["systems"][0]["id"] == 30000142
|
||||
|
||||
|
||||
def test_universe_tools_are_registered():
|
||||
tools = {t.name for t in asyncio.run(mcp.list_tools())}
|
||||
assert {"get_item", "get_system", "get_station", "get_region", "get_route"} <= tools
|
||||
Reference in new issue
Block a user