Add list_cargo() to the admin client
The station queries and the cargo events name a cargo only by a bare numeric id -- get_station()'s and get_station_cargo()'s cargo_id, the cargo_waiting events, the per-cargo load on vehicle events. Those ids index the cargo table the loaded NewGRFs build for the running game, so the same id is coal in one save and grain in another and callers had no way to resolve them. The AdminBridge GameScript has answered a list_cargo command all along; no method on OpenTTDAdminClient sent it. This adds the missing half, so no GameScript change is needed for it to work. It goes through _gs_query() like get_station(), inheriting the request_id correlation and the Gamescript auto-subscribe, with one difference worth knowing: the GS handler defines no error reply for this command, so unlike the other queries it can time out but can never raise ValueError. The reply lists cargo in GSCargoList order rather than by id -- against the dev server the ids come back 10 down to 0 -- so the docstring and PROTOCOL.md both warn to index the list by cargo_id and not by position. Also teaches the main_admin.py demo to resolve the labels before printing a station's cargo, which is what the bare ids in its output were asking for all along. Co-Authored-By: Claude <[email protected]>
This commit is contained in:
@@ -388,6 +388,40 @@ async def test_admin_get_station_cargo_error_response():
|
||||
await task
|
||||
assert client._gs_futures == {}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_admin_list_cargo_request_and_response():
|
||||
client = OpenTTDAdminClient("127.0.0.1", port=3977, admin_name="TestAdmin")
|
||||
proto = MockProtocol()
|
||||
client._protocol = proto
|
||||
client._transport = MockTransport()
|
||||
|
||||
task = asyncio.ensure_future(client.list_cargo())
|
||||
await asyncio.sleep(0) # let the task send the request
|
||||
|
||||
# First use auto-subscribes to Gamescript updates, then sends the query.
|
||||
assert len(proto.sent) == 2
|
||||
assert proto.sent[0][2] == PacketAdminType.AdminUpdateFrequency
|
||||
assert decode_gamescript_payload(proto.sent[1]) == {
|
||||
"command": "list_cargo", "request_id": 1,
|
||||
}
|
||||
|
||||
response = {"command": "list_cargo", "request_id": 1,
|
||||
"cargo": [{"cargo_id": 0, "label": "PASS", "freight": 0},
|
||||
{"cargo_id": 1, "label": "COAL", "freight": 1}]}
|
||||
await client.receive_ServerGamescript(None, data=response)
|
||||
assert await task == response
|
||||
assert client._gs_futures == {}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_admin_list_cargo_timeout():
|
||||
client = OpenTTDAdminClient("127.0.0.1", port=3977, admin_name="TestAdmin")
|
||||
client._protocol = MockProtocol()
|
||||
client._transport = MockTransport()
|
||||
|
||||
with pytest.raises(asyncio.TimeoutError):
|
||||
await client.list_cargo(timeout=0.05)
|
||||
assert client._gs_futures == {}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_admin_get_dispatch_request_and_response():
|
||||
client = OpenTTDAdminClient("127.0.0.1", port=3977, admin_name="TestAdmin")
|
||||
|
||||
@@ -722,6 +722,44 @@ async def test_e2e_admin_get_station_cargo_invalid_cargo(connected_admin):
|
||||
with pytest.raises(ValueError, match="invalid_cargo"):
|
||||
await connected_admin.get_station_cargo(stations[0]["id"], 250, timeout=10.0)
|
||||
|
||||
@pytest.mark.e2e
|
||||
@pytest.mark.asyncio
|
||||
async def test_e2e_admin_list_cargo_table(connected_admin):
|
||||
# Public function: list_cargo()
|
||||
# Input 1: an explicit timeout. Every game has a cargo table, so an empty list would be a bug.
|
||||
data = await connected_admin.list_cargo(timeout=10.0)
|
||||
assert isinstance(data["cargo"], list) and data["cargo"]
|
||||
for cargo in data["cargo"]:
|
||||
for key in ("cargo_id", "label", "freight"):
|
||||
assert key in cargo
|
||||
assert cargo["freight"] in (0, 1)
|
||||
ids = [cargo["cargo_id"] for cargo in data["cargo"]]
|
||||
assert len(ids) == len(set(ids))
|
||||
# Any cargo set carries passengers as well as freight, so both kinds must show up.
|
||||
assert any(cargo["freight"] == 0 for cargo in data["cargo"])
|
||||
assert any(cargo["freight"] == 1 for cargo in data["cargo"])
|
||||
|
||||
@pytest.mark.e2e
|
||||
@pytest.mark.asyncio
|
||||
async def test_e2e_admin_list_cargo_resolves_station_cargo_ids(connected_admin):
|
||||
# Public function: list_cargo()
|
||||
# Input 2: the default timeout. The point of the call: naming the bare ids get_station() returns.
|
||||
responses = []
|
||||
connected_admin.on_gamescript = lambda data: responses.append(data)
|
||||
await connected_admin.update_frequency(AdminUpdateType.Gamescript, AdminUpdateFrequency.Automatic)
|
||||
await connected_admin.list_stations()
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
assert responses and "stations" in responses[-1]
|
||||
stations = responses[-1]["stations"]
|
||||
if not stations:
|
||||
pytest.skip("No stations on the test server to query.")
|
||||
|
||||
labels = {cargo["cargo_id"]: cargo["label"] for cargo in (await connected_admin.list_cargo())["cargo"]}
|
||||
detail = await connected_admin.get_station(stations[0]["id"], timeout=10.0)
|
||||
for cargo in detail["cargo"]:
|
||||
assert cargo["cargo_id"] in labels
|
||||
|
||||
|
||||
# --- Game Events ---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user