import pytest import respx from httpx import Response from traderai.cornerstone_client import CornerstoneClient, parse_cornerstone_item_page from traderai.tools import ToolRegistry from traderai.uex_client import UEXClient class FakeUEX: def __init__(self): self.posts = [] self.get_calls = [] async def get(self, path, params=None, authenticated=False): self.get_calls.append({"path": path, "params": params, "authenticated": authenticated}) if path == "commodities_prices_history": return { "status": "ok", "data": [ { "id": 1, "id_terminal": 7, "id_commodity": 3, "commodity_name": "Gold", "terminal_name": "Port Tressler", "price_buy": 4000, "price_sell": 5000, "scu_buy": 100, "scu_sell": 20, "date_added": 100, }, { "id": 2, "id_terminal": 7, "id_commodity": 3, "commodity_name": "Gold", "terminal_name": "Port Tressler", "price_buy": 4200, "price_sell": 4800, "scu_buy": 80, "scu_sell": 30, "date_added": 200, }, ], } if path == "marketplace_prices_history": return { "status": "ok", "data": [ {"id": 1, "item_name": "Widget", "operation": "sell", "price": 1000, "currency": "UEC", "date_added": 100}, {"id": 2, "item_name": "Widget", "operation": "sell", "price": 1250, "currency": "UEC", "date_added": 200}, ], } if path == "currencies_index_history": return { "status": "ok", "data": [ {"id": 1, "currency": "UEC", "index_value": 100.0, "basket_value": 5000.0, "date_added": 100}, {"id": 2, "currency": "UEC", "index_value": 110.0, "basket_value": 5500.0, "date_added": 200}, ], } if path == "commodities_prices": return { "status": "ok", "data": [ { "id": 10, "commodity_name": "Gold", "terminal_name": "Port Tressler", "price_buy": 4120, "price_sell": 5020, "scu_buy": 1200, "verbose_note": "x" * 300, }, { "id": 11, "commodity_name": "Beryl", "terminal_name": "Area18", "price_buy": 2500, "price_sell": 3100, }, ], } if path == "marketplace_trends": return { "status": "ok", "data": [ { "id_item": 2791, "item_name": "\"Quantanium\" Water Bottle", "item_slug": "quantanium-water-bottle", "currency": "UEC", "price_avg_sell": "937500", "price_avg_month_sell": "1072222", "price_min_sell": "750000", "price_max_sell": "1200000", "listings_count_sell": 4, "price_avg_buy": "500000", "price_avg_month_buy": "525000", "price_min_buy": "450000", "price_max_buy": "550000", "listings_count_buy": 2, "total_listings_count": 6, "negotiations_count": 18, "negotiations_open": 7, "negotiations_success": 9, "link_prices": "https://uexcorp.space/marketplace/home/?id_item=2791&mode=list", "link_prices_history": "https://uexcorp.space/marketplace/averages/?id_item=2791&quality_tier=q0&unit=unit", } ], } assert path == "marketplace_listings" return { "data": [ { "id": 1, "slug": "gold-haul", "title": "Gold haul escort", "description": "Escort service", "operation": "sell", "type": "service", "price": 5000, "currency": "UEC", "unit": "run", "location": "Port Tressler", "user_username": "pilot_a", "date_expiration": 123, }, { "id": 2, "slug": "armor-set", "title": "Armor set", "description": "Clean set", "operation": "sell", "type": "item", "price": 15000, "currency": "UEC", "unit": "set", "location": "Area18", "user_username": "pilot_b", "date_expiration": 456, }, ] } async def delete(self, path, params=None, authenticated=True): return {"status": "ok", "deleted": {"path": path, "params": params, "authenticated": authenticated}} async def post(self, path, payload, authenticated=True): self.posts.append({"path": path, "payload": payload, "authenticated": authenticated}) return {"status": "ok", "posted": self.posts[-1]} class FakeSCMDB: base_url = "https://scmdb.test" async def list_versions(self): return [ {"version": "4.8.0-ptu.1", "file": "merged-4.8.0-ptu.1.json"}, {"version": "4.7.2-live.1", "file": "merged-4.7.2-live.1.json"}, ] async def get_data(self, version=None, channel="live"): return { "version": version or "4.7.2-live.1", "factions": { "fac-haul": {"name": "Covalex"}, "fac-bounty": {"name": "Bounty Hunters Guild"}, }, "scopes": { "scope-rep": {"scopeName": "FactionReputation"}, }, "factionRewardsPools": [ [{"factionGuid": "fac-haul", "scopeGuid": "scope-rep", "amount": 125}], [{"factionGuid": "fac-bounty", "scopeGuid": "scope-rep", "amount": 250}], ], "partialRewardPayoutPools": [ [], [{"minPercentage": 50, "maxPercentage": 99, "currencyRewardMultiplier": 0.75, "reputationMultipliers": None}], ], "resourcePools": { "res-tungsten": {"name": "Tungsten"}, }, "blueprintPools": { "bp-pool": { "name": "Ship Salvage Rewards", "blueprints": [{"name": "Abrade Scraper Module"}], }, }, "locationPools": { "loc-a18": {"name": "Area18"}, "loc-baijini": {"name": "Baijini Point"}, }, "contracts": [ { "id": "mission-haul", "debugName": "Haul_Tungsten_Test", "title": "Move Tungsten", "description": "Move Tungsten to Baijini Point.", "missionType": "Hauling", "category": "career", "factionGuid": "fac-haul", "rewardUEC": 50250, "factionRewardsIndex": 0, "partialRewardPayoutIndex": 1, "haulingOrders": [{"resource": "res-tungsten", "minSCU": 6, "maxSCU": 6, "maxContainerSize": 1}], "locations": ["loc-a18"], "destinations": ["loc-baijini"], "systems": ["Stanton"], "illegal": False, "canBeShared": False, }, { "id": "mission-bounty", "debugName": "Bounty_Blueprint_Test", "title": "Ambush Op", "description": "Clean out targets.", "missionType": "Bounty Hunter", "factionGuid": "fac-bounty", "rewardUEC": 120000, "factionRewardsIndex": 1, "partialRewardPayoutIndex": 0, "itemRewards": [{"name": "Council Scrip", "amount": 5}], "blueprintRewards": [{"blueprintPool": "bp-pool", "chance": 1, "trigger": "complete"}], "systems": ["Pyro"], "illegal": True, "canBeShared": True, }, ], "legacyContracts": [ { "id": "legacy-delivery", "debugName": "Legacy_Delivery_Test", "title": "Old Box Run", "missionType": "Delivery", "factionGuid": "fac-haul", "rewardUEC": 1000, "factionRewardsIndex": 0, "partialRewardPayoutIndex": 0, "systems": ["Stanton"], } ], } class FakeCornerstone: base_url = "https://finder.cstone.test" async def list_items(self): return [ {"id": "item-abrade", "name": "Abrade Scraper Module", "sold": True}, {"id": "item-cinch", "name": "Cinch Scraper Module", "sold": True}, {"id": "item-poster", "name": "Zeus 2955 Ship Showdown Poster", "sold": False}, ] async def get_item_page(self, item_id): assert item_id == "item-abrade" return { "url": f"{self.base_url}/ShipSalvageMods1/{item_id}", "html": """ Star Citizen - Salvage modifier - Abrade Scraper Module
NAMEAbrade Scraper Module
MANUFACTURERGreycat Industrial
LOCATIONBASE PRICEVERIFIED
Stanton - ArcCorp - Area18 - Dumper's Depot21 2502956-01-29
Stanton - microTech - Port Tressler - Platinum Bay21 2502956-01-04
""", } async def get_image_data(self, url, max_bytes=10_000_000): assert url == f"{self.base_url}/images/abrade.png" return { "url": url, "content_type": "image/png", "size_bytes": 12, "image_data": "ZmFrZS1pbWFnZQ==", } class FakeSCWiki: base_url = "https://starcitizen.tools" api_base_url = "https://api.star-citizen.wiki" async def search_pages(self, query, limit=5): assert query == "Carrack" return [ { "pageid": 415, "title": "Carrack", "description": "Deep-space multi-crew explorer manufactured by Anvil Aerospace", "extract": "The Anvil Carrack is a multi-crew explorer.", "thumbnail": "https://media.starcitizen.tools/carrack.webp", "url": "https://starcitizen.tools/Carrack", } ][:limit] async def get_page_summary(self, title=None, pageid=None, chars=700): assert title == "Carrack" or pageid == 415 return { "pageid": 415, "title": "Carrack", "description": "Deep-space multi-crew explorer manufactured by Anvil Aerospace", "extract": "The Anvil Carrack is a multi-crew explorer.", "thumbnail": "https://media.starcitizen.tools/carrack.webp", "url": "https://starcitizen.tools/Carrack", } async def search_verse(self, query): assert query == "Carrack" return [ { "type": "vehicles", "label": "Vehicles", "results": [ { "name": "Anvil Carrack", "class_name": "ANVL_Carrack", "extra_label": "Exploration", "web_url": "https://api.star-citizen.wiki/vehicles/anvl-carrack", "api_url": "https://api.star-citizen.wiki/api/vehicles/anvl-carrack", } ], } ] async def get_vehicle(self, slug): assert slug == "anvl-carrack" return { "name": "Carrack", "game_name": "Anvil Carrack", "slug": "anvl-carrack", "manufacturer": {"name": "Anvil Aerospace"}, "career": "Exploration", "role": "Expedition", "size_class": 5, "cargo_capacity": 456, "crew": {"min": 6, "max": 6}, "msrp": 600, "pledge_url": "https://robertsspaceindustries.com/pledge/ships/carrack/Carrack", "uex_prices": { "purchase": [ { "price_buy": 34398000, "terminal_name": "Astro Armada - Area 18", "starmap_location": {"name": "Area18", "parent_name": "ArcCorp", "star_system_name": "Stanton"}, "game_version": "4.8.1-LIVE.11952564", "date_updated": "2026-05-20T18:39:37-04:00", "uex_link": "https://uexcorp.space/vehicles/home/list/in_game_sell/?id_terminal=148", } ] }, "description": {"en_EN": "The Anvil Carrack features reinforced fuel tanks for long-duration flight."}, "web_url": "https://api.star-citizen.wiki/vehicles/anvl-carrack", "updated_at": "2026-06-08T00:34:00Z", "version": "4.8.1-LIVE.11952564", } class FakeWikelo: base_url = "https://wikelo-projects.test" async def list_ship_projects(self): return [ { "id": "ship-1", "ship_name": "Polaris Wikelo Special", "description": "Now make Polaris. Short Time Deal", "status": "planning", "privacy": "public", "owner_name": "Chimpanz33", "required_materials": [ {"material_name": "Wikelo Favor", "quantity_needed": 50.0, "quantity_collected": 0.0}, {"material_name": "Polaris Bit", "quantity_needed": 15.0, "quantity_collected": 2.0}, ], }, { "id": "ship-2", "ship_name": "Guardian", "description": "Guardian Fight Mod", "status": "planning", "privacy": "public", "owner_name": "Chimpanz33", "required_materials": [ {"material_name": "Wikelo Favor", "quantity_needed": 20.0, "quantity_collected": 0.0}, ], }, ] @pytest.mark.asyncio async def test_search_marketplace_listings_filters_locally(): registry = ToolRegistry(FakeUEX()) result = await registry.search_marketplace_listings(query="gold", type="service", max_price=6000) assert result["count"] == 1 assert result["listings"][0]["slug"] == "gold-haul" @pytest.mark.asyncio async def test_draft_message_creates_pending_action(): registry = ToolRegistry(FakeUEX()) result = await registry.draft_negotiation_message(hash="abc", message="Would you take 4500 UEC?") pending = result["pending_action"] assert pending["endpoint"] == "marketplace_negotiations_messages" assert pending["payload"]["message"] == "Would you take 4500 UEC?" assert pending["id"] in registry.pending_actions @pytest.mark.asyncio async def test_decline_pending_action_removes_without_sending(): registry = ToolRegistry(FakeUEX()) result = await registry.draft_negotiation_message(hash="abc", message="Would you take 4500 UEC?") action_id = result["pending_action"]["id"] declined = await registry.decline(action_id) assert declined["declined"] is True assert declined["pending_action"]["id"] == action_id assert action_id not in registry.pending_actions @pytest.mark.asyncio async def test_approve_negotiation_message_forces_production_send(): fake = FakeUEX() registry = ToolRegistry(fake) result = await registry.draft_negotiation_message(hash="abc", message="Ready to close", is_production=0) action_id = result["pending_action"]["id"] approved = await registry.approve(action_id) assert approved["posted"]["path"] == "marketplace_negotiations_messages" assert approved["posted"]["payload"]["is_production"] == 1 def test_uex_client_uses_bearer_and_secret_headers(): client = UEXClient("https://api.uexcorp.space/2.0", secret_key="secret", bearer_token="bearer") headers = client._headers(authenticated=True) assert headers["secret-key"] == "secret" assert headers["Authorization"] == "Bearer bearer" @pytest.mark.asyncio async def test_uex_get_projects_and_limits_results(): registry = ToolRegistry(FakeUEX()) result = await registry.execute( "get_uex_commodities_prices", { "commodity_name": "Gold", "ignored": "drop", "fields": ["id", "commodity_name", "price_buy"], "limit": 1, }, ) assert result["resource"] == "commodities_prices" assert result["params"] == {"commodity_name": "Gold"} assert result["returned"] == 1 assert result["truncated"] is True assert result["items"] == [{"id": 10, "commodity_name": "Gold", "price_buy": 4120}] @pytest.mark.asyncio async def test_uex_get_marketplace_listings_accepts_item_and_operation_filters(): fake = FakeUEX() registry = ToolRegistry(fake) result = await registry.execute( "get_uex_marketplace_listings", { "id_item": 2791, "operation": "sell", "fields": ["id", "slug", "operation"], }, ) assert result["params"] == {"id_item": 2791, "operation": "sell"} assert fake.get_calls[-1]["path"] == "marketplace_listings" assert fake.get_calls[-1]["params"] == {"id_item": 2791, "operation": "sell"} @pytest.mark.asyncio async def test_get_marketplace_trends_returns_compact_wts_wtb_and_negotiation_metrics(): fake = FakeUEX() registry = ToolRegistry(fake) result = await registry.get_marketplace_trends(item_name="Quantanium", currency="UEC", quality_tier=0) assert result["status"] == "ok" assert result["count"] == 1 assert result["filters"] == {"item_name": "Quantanium", "currency": "UEC", "quality_tier": 0} assert fake.get_calls[-1]["path"] == "marketplace_trends" assert fake.get_calls[-1]["params"] == {"id_item": None, "item_name": "Quantanium", "item_slug": None, "id_category": None, "currency": "UEC", "quality_tier": 0} assert result["trends"][0] == { "id_item": 2791, "item_name": "\"Quantanium\" Water Bottle", "item_slug": "quantanium-water-bottle", "currency": "UEC", "sell": { "avg_price": "937500", "avg_price_month": "1072222", "min_price": "750000", "max_price": "1200000", "listings_count": 4, }, "buy": { "avg_price": "500000", "avg_price_month": "525000", "min_price": "450000", "max_price": "550000", "listings_count": 2, }, "total_listings_count": 6, "negotiations_count": 18, "negotiations_open": 7, "negotiations_success": 9, "link_prices": "https://uexcorp.space/marketplace/home/?id_item=2791&mode=list", "link_prices_history": "https://uexcorp.space/marketplace/averages/?id_item=2791&quality_tier=q0&unit=unit", } @pytest.mark.asyncio async def test_uex_api_catalog_exposes_resources_without_live_call(): registry = ToolRegistry(FakeUEX()) result = await registry.uex_api_catalog(group="vehicles") resources = [item["resource"] for item in result["get"]["vehicles"]] assert "vehicles" in resources assert "vehicles_prices" in resources assert "wallet_add" in result["post"] @pytest.mark.asyncio async def test_draft_delete_approves_with_delete_method(): registry = ToolRegistry(FakeUEX()) result = await registry.execute("delete_uex_marketplace_listings", {"id": 123, "label": "Remove listing"}) action_id = result["pending_action"]["id"] approved = await registry.approve(action_id) assert result["pending_action"]["method"] == "DELETE" assert approved["deleted"] == { "path": "marketplace_listings", "params": {"id": 123}, "authenticated": True, } def test_schemas_expose_specific_uex_tools_instead_of_generic_api_tool(): registry = ToolRegistry(FakeUEX()) names = {schema["function"]["name"] for schema in registry.schemas} assert "get_uex_commodities_prices" in names assert "get_uex_vehicles" in names assert "get_marketplace_trends" in names assert "draft_uex_marketplace_advertise" in names assert "delete_uex_marketplace_listings" in names assert "uex_get" not in names assert "uex_draft_post" not in names def test_schemas_expose_scmdb_mission_tools(): registry = ToolRegistry(FakeUEX(), scmdb=FakeSCMDB()) names = {schema["function"]["name"] for schema in registry.schemas} assert "list_scmdb_versions" in names assert "search_scmdb_missions" in names assert "get_scmdb_mission_rewards" in names def test_schemas_expose_cornerstone_item_tools(): registry = ToolRegistry(FakeUEX(), cornerstone=FakeCornerstone()) names = {schema["function"]["name"] for schema in registry.schemas} assert "search_cornerstone_items" in names assert "get_cornerstone_item_locations" in names assert "get_cornerstone_item_media" in names assert "draft_marketplace_listing_with_cornerstone_image" in names def test_schemas_expose_scwiki_tools(): registry = ToolRegistry(FakeUEX(), scwiki=FakeSCWiki()) names = {schema["function"]["name"] for schema in registry.schemas} assert "search_scwiki_pages" in names assert "get_scwiki_page" in names assert "search_scwiki_vehicles" in names assert "get_scwiki_vehicle" in names @pytest.mark.asyncio async def test_search_scmdb_missions_returns_reward_summary(): registry = ToolRegistry(FakeUEX(), scmdb=FakeSCMDB()) result = await registry.search_scmdb_missions(query="tungsten", mission_type="hauling") assert result["version"] == "4.7.2-live.1" assert result["matched"] == 1 mission = result["missions"][0] assert mission["title"] == "Move Tungsten" assert mission["rewards"]["uec"] == 50250 assert mission["rewards"]["reputation"] == [{"faction": "Covalex", "scope": "FactionReputation", "amount": 125}] assert mission["rewards"]["hauling"] == [ {"resource": "Tungsten", "min_scu": 6, "max_scu": 6, "max_container_size_scu": 1} ] @pytest.mark.asyncio async def test_get_scmdb_mission_rewards_enriches_items_blueprints_and_locations(): registry = ToolRegistry(FakeUEX(), scmdb=FakeSCMDB()) result = await registry.get_scmdb_mission_rewards(debug_name="Bounty_Blueprint_Test") mission = result["mission"] assert mission["title"] == "Ambush Op" assert mission["faction"] == "Bounty Hunters Guild" assert mission["rewards"]["items"] == [{"name": "Council Scrip", "amount": 5}] assert mission["rewards"]["blueprints"][0]["blueprints"] == ["Abrade Scraper Module"] @pytest.mark.asyncio async def test_search_cornerstone_items_filters_sold_items(): registry = ToolRegistry(FakeUEX(), cornerstone=FakeCornerstone()) result = await registry.search_cornerstone_items(query="scraper", sold_only=True) assert result["matched"] == 2 assert {item["name"] for item in result["items"]} == {"Abrade Scraper Module", "Cinch Scraper Module"} assert result["items"][0]["url"].startswith("https://finder.cstone.test/Search/item-") @pytest.mark.asyncio async def test_get_cornerstone_item_locations_parses_store_prices(): registry = ToolRegistry(FakeUEX(), cornerstone=FakeCornerstone()) result = await registry.get_cornerstone_item_locations(query="abrade", location="Area18") assert result["item"]["name"] == "Abrade Scraper Module" assert result["item"]["general"]["manufacturer"] == "Greycat Industrial" assert result["matched_locations"] == 1 assert result["locations"] == [ { "location": "Stanton - ArcCorp - Area18 - Dumper's Depot", "base_price": 21250, "base_price_display": "21 250", "verified": "2956-01-29", } ] @pytest.mark.asyncio async def test_get_cornerstone_item_media_returns_absolute_image_urls(): registry = ToolRegistry(FakeUEX(), cornerstone=FakeCornerstone()) result = await registry.get_cornerstone_item_media(query="abrade") assert result["media"] == [ { "url": "https://finder.cstone.test/images/abrade.png", "source": "og:image", } ] @pytest.mark.asyncio async def test_search_scwiki_pages_returns_general_knowledge_matches(): registry = ToolRegistry(FakeUEX(), scwiki=FakeSCWiki()) result = await registry.search_scwiki_pages(query="Carrack") assert result["source"] == "https://starcitizen.tools" assert result["matched"] == 1 assert result["pages"][0]["title"] == "Carrack" assert result["pages"][0]["url"] == "https://starcitizen.tools/Carrack" @pytest.mark.asyncio async def test_get_scwiki_vehicle_returns_ship_prices_and_store_context(): registry = ToolRegistry(FakeUEX(), scwiki=FakeSCWiki()) result = await registry.get_scwiki_vehicle(query="Carrack") assert result["source"] == "https://api.star-citizen.wiki" vehicle = result["vehicle"] assert vehicle["name"] == "Carrack" assert vehicle["manufacturer"] == "Anvil Aerospace" assert vehicle["msrp"] == 600 assert vehicle["purchase_locations"] == [ { "price_buy": 34398000, "terminal_name": "Astro Armada - Area 18", "location": "Area18", "parent_location": "ArcCorp", "star_system": "Stanton", "game_version": "4.8.1-LIVE.11952564", "date_updated": "2026-05-20T18:39:37-04:00", "uex_link": "https://uexcorp.space/vehicles/home/list/in_game_sell/?id_terminal=148", } ] @pytest.mark.asyncio async def test_search_wikelo_ship_projects_returns_material_matches(): registry = ToolRegistry(FakeUEX(), wikelo=FakeWikelo()) result = await registry.search_wikelo_ship_projects(query="Polaris") assert result["source"] == "https://wikelo-projects.test/Ships" assert result["matched"] == 1 assert result["projects"][0]["ship_name"] == "Polaris Wikelo Special" assert result["projects"][0]["required_materials"][0]["material_name"] == "Wikelo Favor" @pytest.mark.asyncio async def test_get_wikelo_ship_project_returns_full_requirements(): registry = ToolRegistry(FakeUEX(), wikelo=FakeWikelo()) result = await registry.get_wikelo_ship_project(ship_name="Guardian") assert result["project"]["ship_name"] == "Guardian" assert result["project"]["materials_count"] == 1 assert result["project"]["required_materials"] == [ { "material_name": "Wikelo Favor", "quantity_needed": 20, "quantity_collected": 0, } ] @pytest.mark.asyncio async def test_draft_marketplace_listing_with_cornerstone_image_adds_image_data_and_redacts_display(): registry = ToolRegistry(FakeUEX(), cornerstone=FakeCornerstone()) result = await registry.draft_marketplace_listing_with_cornerstone_image( item_query="abrade", id_category=3, operation="sell", type="item", unit="unit", title="Abrade Scraper Module", description="Clean module, ready for pickup.", price=21250, currency="UEC", language="en_US", source="purchased_in_game", in_stock=1, ) pending = result["pending_action"] stored = registry.pending_actions[pending["id"]] assert pending["endpoint"] == "marketplace_advertise" assert pending["payload"]["image_data"].startswith("Star Citizen - Food - Whamburger
NAMEWhamburger
Whamburger
LOCATIONBASE PRICEVERIFIED
Stanton - Area18 - Cubby Blast92956-01-01
""", "https://finder.cstone.test/Search/item-wham", ) assert parsed["name"] == "Whamburger" assert parsed["locations"][0]["base_price"] == 9 assert parsed["media"][0]["url"] == "https://finder.cstone.test/img/wham.png" assert parsed["media"][1]["url"] == "https://example.test/extra.png" @pytest.mark.asyncio @respx.mock async def test_cornerstone_client_accepts_json_encoded_string_payload(): respx.get("https://finder.cstone.space/GetSearch").mock( return_value=Response( 200, json='[{"id":"item-1","name":"Abrade Scraper Module","Sold":1}]', ) ) client = CornerstoneClient("https://finder.cstone.space") assert await client.list_items() == [{"id": "item-1", "name": "Abrade Scraper Module", "sold": True}] @pytest.mark.asyncio async def test_search_uex_api_index_finds_history_tools(): registry = ToolRegistry(FakeUEX()) result = await registry.execute("search_uex_api_index", {"query": "history", "history_only": True}) tools = {item["tool"] for item in result["get"]} assert "get_uex_commodities_prices_history" in tools assert "get_uex_marketplace_prices_history" in tools assert "get_uex_currencies_index_history" in tools @pytest.mark.asyncio async def test_summarize_commodity_price_history_returns_trend_metrics(): registry = ToolRegistry(FakeUEX()) result = await registry.execute( "summarize_uex_commodity_price_history", {"id_terminal": 7, "id_commodity": 3}, ) assert result["resource"] == "commodities_prices_history" assert result["count"] == 2 assert result["labels"] == {"commodity_name": "Gold", "terminal_name": "Port Tressler"} assert result["metrics"]["price_buy"]["change"] == 200 assert result["metrics"]["price_sell"]["pct_change"] == -4.0 @pytest.mark.asyncio async def test_summarize_marketplace_and_currency_history(): registry = ToolRegistry(FakeUEX()) market = await registry.execute("summarize_uex_marketplace_price_history", {"item_name": "Widget"}) currency = await registry.execute("summarize_uex_currency_index_history", {"currency": "UEC"}) assert market["metrics"]["price"]["pct_change"] == 25.0 assert currency["metrics"]["index_value"]["change"] == 10.0 @pytest.mark.asyncio @respx.mock async def test_uex_client_get_user_normalizes_user_payload(): respx.get("https://api.uexcorp.space/2.0/user/").mock( return_value=Response(200, json={"status": "ok", "data": [{"username": "pilot_hudson"}]}) ) client = UEXClient("https://api.uexcorp.space/2.0", bearer_token="bearer") result = await client.get_user(authenticated=True) assert result == {"status": "ok", "user": {"username": "pilot_hudson"}} @pytest.mark.asyncio @respx.mock async def test_uex_client_get_user_notifications_normalizes_payload(): respx.get("https://api.uexcorp.space/2.0/user_notifications/").mock( return_value=Response(200, json={"status": "ok", "data": {"id": 7, "message": "Reply waiting", "date_read": 0}}) ) client = UEXClient("https://api.uexcorp.space/2.0", bearer_token="bearer") result = await client.get_user_notifications() assert result == {"status": "ok", "notifications": [{"id": 7, "message": "Reply waiting", "date_read": 0}]}