Add vehicle timetable get/set support
Timetables have no GameScript API surface, so this implements real DoCommands over the game port (ClientCommand/ServerCommand) instead of the Admin GameScript relay used for list_vehicles(): change_timetable(), autofill_timetable(), set_timetable_start(), and set_vehicle_on_time() send commands, while get_vehicle_timetable() reconstructs state purely by observing ServerCommand broadcasts, since no query command exists. Includes the custom varuint wire codec these commands require, a full usage guide (docs/TIMETABLES.md), and a worked demo in main.py. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,9 +13,17 @@ from openttd.protocol import (
|
||||
OpenTTDAdminProtocol,
|
||||
AdminUpdateType,
|
||||
AdminUpdateFrequency,
|
||||
PacketGameType
|
||||
PacketGameType,
|
||||
ModifyTimetableFlags
|
||||
)
|
||||
|
||||
# These identify a vehicle/order that already exists in the local dev server's persisted
|
||||
# save (company 0, unprotected, owns vehicle 7 with 2 orders) -- required for the timetable
|
||||
# command tests below, since DoCommands are rejected unless issued by the owning company.
|
||||
TIMETABLE_COMPANY_ID = 0
|
||||
TIMETABLE_VEHICLE_ID = 7
|
||||
TIMETABLE_ORDER_POSITION = 0
|
||||
|
||||
|
||||
# --- Pytest Fixtures ---
|
||||
|
||||
@@ -52,6 +60,22 @@ async def connected_client(server_config):
|
||||
if hasattr(client, '_transport') and not client.shutdown_event.is_set():
|
||||
await client.quit()
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def connected_owner_client(server_config):
|
||||
"""Fixture to yield a client joined to TIMETABLE_COMPANY_ID (owns a real vehicle for command tests)."""
|
||||
client_name = f"E2E_Owner_{random.randint(1000, 9999)}"
|
||||
client = OpenTTDClient(
|
||||
host=server_config["host"],
|
||||
port=server_config["game_port"],
|
||||
username=client_name
|
||||
)
|
||||
await client.connect(server_password=server_config["password"])
|
||||
await client.join_company(company_id=TIMETABLE_COMPANY_ID, company_password="")
|
||||
await asyncio.wait_for(client.joined.wait(), timeout=15.0)
|
||||
yield client
|
||||
if hasattr(client, '_transport') and not client.shutdown_event.is_set():
|
||||
await client.quit()
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# --- End-to-End Tests (Covering all public functions with multiple inputs) ---
|
||||
@@ -129,6 +153,110 @@ async def test_e2e_client_quit_and_disconnect_multiple_inputs(server_config):
|
||||
# Input 2: quit already inactive client
|
||||
await client2.quit()
|
||||
|
||||
@pytest.mark.e2e
|
||||
@pytest.mark.asyncio
|
||||
async def test_e2e_client_change_timetable_wait_time(connected_owner_client):
|
||||
# Public function: change_timetable()
|
||||
# Input 1: set wait time
|
||||
await connected_owner_client.change_timetable(TIMETABLE_VEHICLE_ID, TIMETABLE_ORDER_POSITION, ModifyTimetableFlags.WaitTime, 42)
|
||||
await asyncio.sleep(1.0)
|
||||
assert not connected_owner_client.shutdown_event.is_set()
|
||||
entry = connected_owner_client.get_vehicle_timetable(TIMETABLE_VEHICLE_ID)
|
||||
assert entry["orders"][TIMETABLE_ORDER_POSITION]["wait_time"] == 42
|
||||
|
||||
@pytest.mark.e2e
|
||||
@pytest.mark.asyncio
|
||||
async def test_e2e_client_change_timetable_travel_time(connected_owner_client):
|
||||
# Public function: change_timetable()
|
||||
# Input 2: set travel time
|
||||
await connected_owner_client.change_timetable(TIMETABLE_VEHICLE_ID, TIMETABLE_ORDER_POSITION, ModifyTimetableFlags.TravelTime, 99)
|
||||
await asyncio.sleep(1.0)
|
||||
assert not connected_owner_client.shutdown_event.is_set()
|
||||
entry = connected_owner_client.get_vehicle_timetable(TIMETABLE_VEHICLE_ID)
|
||||
assert entry["orders"][TIMETABLE_ORDER_POSITION]["travel_time"] == 99
|
||||
|
||||
@pytest.mark.e2e
|
||||
@pytest.mark.asyncio
|
||||
async def test_e2e_client_autofill_timetable_enable(connected_owner_client):
|
||||
# Public function: autofill_timetable()
|
||||
# Input 1: enable autofill
|
||||
await connected_owner_client.autofill_timetable(TIMETABLE_VEHICLE_ID, autofill=True, preserve_wait_time=False)
|
||||
await asyncio.sleep(1.0)
|
||||
assert not connected_owner_client.shutdown_event.is_set()
|
||||
assert connected_owner_client.get_vehicle_timetable(TIMETABLE_VEHICLE_ID)["autofill"] is True
|
||||
|
||||
@pytest.mark.e2e
|
||||
@pytest.mark.asyncio
|
||||
async def test_e2e_client_autofill_timetable_disable(connected_owner_client):
|
||||
# Public function: autofill_timetable()
|
||||
# Input 2: disable autofill, preserve wait time
|
||||
await connected_owner_client.autofill_timetable(TIMETABLE_VEHICLE_ID, autofill=False, preserve_wait_time=True)
|
||||
await asyncio.sleep(1.0)
|
||||
assert not connected_owner_client.shutdown_event.is_set()
|
||||
entry = connected_owner_client.get_vehicle_timetable(TIMETABLE_VEHICLE_ID)
|
||||
assert entry["autofill"] is False
|
||||
assert entry["autofill_preserve_wait_time"] is True
|
||||
|
||||
@pytest.mark.e2e
|
||||
@pytest.mark.asyncio
|
||||
async def test_e2e_client_set_timetable_start_single_vehicle(connected_owner_client):
|
||||
# Public function: set_timetable_start()
|
||||
# Input 1: this vehicle only
|
||||
await connected_owner_client.set_timetable_start(TIMETABLE_VEHICLE_ID, False, 500000)
|
||||
await asyncio.sleep(1.0)
|
||||
assert not connected_owner_client.shutdown_event.is_set()
|
||||
entry = connected_owner_client.get_vehicle_timetable(TIMETABLE_VEHICLE_ID)
|
||||
assert entry["timetable_start"] == 500000
|
||||
assert entry["timetable_all"] is False
|
||||
|
||||
@pytest.mark.e2e
|
||||
@pytest.mark.asyncio
|
||||
async def test_e2e_client_set_timetable_start_all_shared(connected_owner_client):
|
||||
# Public function: set_timetable_start()
|
||||
# Input 2: all vehicles sharing this order list
|
||||
await connected_owner_client.set_timetable_start(TIMETABLE_VEHICLE_ID, True, 600000)
|
||||
await asyncio.sleep(1.0)
|
||||
assert not connected_owner_client.shutdown_event.is_set()
|
||||
entry = connected_owner_client.get_vehicle_timetable(TIMETABLE_VEHICLE_ID)
|
||||
assert entry["timetable_start"] == 600000
|
||||
assert entry["timetable_all"] is True
|
||||
|
||||
@pytest.mark.e2e
|
||||
@pytest.mark.asyncio
|
||||
async def test_e2e_client_set_vehicle_on_time_single_vehicle(connected_owner_client):
|
||||
# Public function: set_vehicle_on_time()
|
||||
# Input 1: reset lateness for this vehicle only
|
||||
await connected_owner_client.set_vehicle_on_time(TIMETABLE_VEHICLE_ID, apply_to_group=False)
|
||||
await asyncio.sleep(1.0)
|
||||
assert not connected_owner_client.shutdown_event.is_set()
|
||||
assert connected_owner_client.get_vehicle_timetable(TIMETABLE_VEHICLE_ID)["on_time_apply_to_group"] is False
|
||||
|
||||
@pytest.mark.e2e
|
||||
@pytest.mark.asyncio
|
||||
async def test_e2e_client_set_vehicle_on_time_apply_to_group(connected_owner_client):
|
||||
# Public function: set_vehicle_on_time()
|
||||
# Input 2: reset lateness for every vehicle sharing these orders
|
||||
await connected_owner_client.set_vehicle_on_time(TIMETABLE_VEHICLE_ID, apply_to_group=True)
|
||||
await asyncio.sleep(1.0)
|
||||
assert not connected_owner_client.shutdown_event.is_set()
|
||||
assert connected_owner_client.get_vehicle_timetable(TIMETABLE_VEHICLE_ID)["on_time_apply_to_group"] is True
|
||||
|
||||
@pytest.mark.e2e
|
||||
@pytest.mark.asyncio
|
||||
async def test_e2e_client_get_vehicle_timetable_after_change(connected_owner_client):
|
||||
# Public function: get_vehicle_timetable()
|
||||
# Input 1: a vehicle with observed state
|
||||
await connected_owner_client.change_timetable(TIMETABLE_VEHICLE_ID, TIMETABLE_ORDER_POSITION, ModifyTimetableFlags.WaitTime, 15)
|
||||
await asyncio.sleep(1.0)
|
||||
assert connected_owner_client.get_vehicle_timetable(TIMETABLE_VEHICLE_ID) is not None
|
||||
|
||||
@pytest.mark.e2e
|
||||
@pytest.mark.asyncio
|
||||
async def test_e2e_client_get_vehicle_timetable_unknown_vehicle(connected_owner_client):
|
||||
# Public function: get_vehicle_timetable()
|
||||
# Input 2: a vehicle id with no observed state
|
||||
assert connected_owner_client.get_vehicle_timetable(999999) is None
|
||||
|
||||
|
||||
# --- Admin Client Public Functions ---
|
||||
|
||||
|
||||
@@ -118,11 +118,10 @@ async def test_fallback_handlers():
|
||||
await client.receive_ServerConfigurationUpdate(None)
|
||||
await client.receive_ServerClientInfo(None)
|
||||
await client.receive_ServerExternalChat(None)
|
||||
await client.receive_ServerCommand(None)
|
||||
await client.receive_ClientAck(None)
|
||||
await client.receive_ClientIdentify(None)
|
||||
await client.receive_ServerCompanyUpdate(None)
|
||||
|
||||
|
||||
client.joined.set()
|
||||
await client.join_company(0)
|
||||
|
||||
@@ -224,7 +223,6 @@ async def test_unit_client_noop_callbacks(server_config):
|
||||
await client.receive_ServerMapData(None)
|
||||
await client.receive_ServerConfigurationUpdate(None)
|
||||
await client.receive_ServerExternalChat(None)
|
||||
await client.receive_ServerCommand(None)
|
||||
await client.receive_ServerFull(None)
|
||||
await client.receive_ServerBanned(None)
|
||||
await client.receive_ClientAck(None)
|
||||
|
||||
@@ -38,7 +38,6 @@ def test_protocol_static_parsers():
|
||||
assert res["token"] == 7
|
||||
|
||||
assert OpenTTDProtocol.receive_ServerExternalChat(None, b"") == {}
|
||||
assert OpenTTDProtocol.receive_ServerCommand(None, b"") == {}
|
||||
assert OpenTTDProtocol.receive_ServerFull(None, b"") == {}
|
||||
assert OpenTTDProtocol.receive_ServerBanned(None, b"") == {}
|
||||
assert OpenTTDProtocol.receive_ClientIdentify(None, b"") == {}
|
||||
|
||||
276
tests/test_timetable.py
Normal file
276
tests/test_timetable.py
Normal file
@@ -0,0 +1,276 @@
|
||||
import pytest
|
||||
from openttd import OpenTTDClient
|
||||
from openttd.protocol import (
|
||||
OpenTTDProtocol, GameCommand, ModifyTimetableFlags, ModifyTimetableCtrlFlag,
|
||||
write_varuint, read_varuint, write_varuint_signed, read_varuint_signed
|
||||
)
|
||||
from openttd_protocol.wire.read import read_uint8, read_uint16
|
||||
|
||||
|
||||
class MockTransport:
|
||||
def is_closing(self):
|
||||
return False
|
||||
def write(self, data):
|
||||
return len(data)
|
||||
|
||||
class MockProtocol:
|
||||
def __init__(self):
|
||||
self.sent = []
|
||||
async def send_packet(self, data):
|
||||
self.sent.append(data)
|
||||
return len(data)
|
||||
|
||||
|
||||
def decode_sent_command(packet):
|
||||
"""Decode a ClientCommand packet by reusing the ServerCommand parser, padding on the
|
||||
frame/my_cmd trailer that only ServerCommand carries on the wire (ClientCommand doesn't)."""
|
||||
padded = bytes(packet)[3:] + b"\x00\x00\x00\x00\x00"
|
||||
return OpenTTDProtocol.receive_ServerCommand(None, memoryview(padded))
|
||||
|
||||
def new_client():
|
||||
client = OpenTTDClient("127.0.0.1")
|
||||
client._protocol = MockProtocol()
|
||||
client._transport = MockTransport()
|
||||
client._target_company = 0
|
||||
return client
|
||||
|
||||
|
||||
# --- Varuint codec ---
|
||||
|
||||
@pytest.mark.parametrize("value", [
|
||||
0, 1, 127,
|
||||
128, 16383,
|
||||
16384, 2097151,
|
||||
2097152, 268435455,
|
||||
268435456, 34359738367,
|
||||
34359738368, 4398046511103,
|
||||
4398046511104, 562949953421311,
|
||||
562949953421312, 72057594037927935,
|
||||
72057594037927936, 18446744073709551615,
|
||||
])
|
||||
def test_varuint_roundtrip_boundaries(value):
|
||||
buf = bytearray()
|
||||
write_varuint(buf, value)
|
||||
decoded, rest = read_varuint(memoryview(bytes(buf)))
|
||||
assert decoded == value
|
||||
assert bytes(rest) == b""
|
||||
|
||||
def test_varuint_rejects_negative():
|
||||
with pytest.raises(ValueError):
|
||||
write_varuint(bytearray(), -1)
|
||||
|
||||
@pytest.mark.parametrize("value", [0, 1, -1, 2, -2, 1000000, -1000000, 9223372036854775807, -9223372036854775808])
|
||||
def test_varuint_signed_roundtrip(value):
|
||||
buf = bytearray()
|
||||
write_varuint_signed(buf, value)
|
||||
decoded, rest = read_varuint_signed(memoryview(bytes(buf)))
|
||||
assert decoded == value
|
||||
assert bytes(rest) == b""
|
||||
|
||||
|
||||
# --- OpenTTDProtocol.receive_ServerCommand ---
|
||||
|
||||
def build_server_command_bytes(company, cmd, payload, callback=0, callback_param=0, frame=42, my_cmd=True):
|
||||
import struct
|
||||
body = bytearray()
|
||||
body += struct.pack("<B", company)
|
||||
body += struct.pack("<H", cmd)
|
||||
body += struct.pack("<H", 0)
|
||||
body += struct.pack("<I", 0)
|
||||
body += struct.pack("<H", len(payload))
|
||||
body += payload
|
||||
body += struct.pack("<B", callback)
|
||||
if callback != 0:
|
||||
body += struct.pack("<I", callback_param)
|
||||
body += struct.pack("<I", frame)
|
||||
body += struct.pack("<B", 1 if my_cmd else 0)
|
||||
return bytes(body)
|
||||
|
||||
def test_protocol_receive_server_command_no_callback():
|
||||
payload = bytearray()
|
||||
write_varuint(payload, 7)
|
||||
data = build_server_command_bytes(1, GameCommand.ChangeTimetable, payload, callback=0, frame=42, my_cmd=True)
|
||||
res = OpenTTDProtocol.receive_ServerCommand(None, memoryview(data))
|
||||
assert res["company"] == 1
|
||||
assert res["cmd"] == GameCommand.ChangeTimetable
|
||||
assert res["callback"] == 0
|
||||
assert res["callback_param"] == 0
|
||||
assert res["frame"] == 42
|
||||
assert res["my_cmd"] is True
|
||||
assert bytes(res["payload"]) == bytes(payload)
|
||||
|
||||
def test_protocol_receive_server_command_with_callback():
|
||||
payload = bytearray()
|
||||
write_varuint(payload, 9)
|
||||
data = build_server_command_bytes(2, GameCommand.SetVehicleOnTime, payload, callback=5, callback_param=999, frame=100, my_cmd=False)
|
||||
res = OpenTTDProtocol.receive_ServerCommand(None, memoryview(data))
|
||||
assert res["callback"] == 5
|
||||
assert res["callback_param"] == 999
|
||||
assert res["my_cmd"] is False
|
||||
|
||||
|
||||
# --- OpenTTDClient outgoing command methods ---
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_client_send_command_with_callback_includes_callback_param():
|
||||
client = new_client()
|
||||
await client._send_command(GameCommand.ChangeTimetable, bytearray(), callback=5)
|
||||
parsed = decode_sent_command(client._protocol.sent[0])
|
||||
assert parsed["callback"] == 5
|
||||
assert parsed["callback_param"] == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_client_change_timetable_sends_expected_payload():
|
||||
client = new_client()
|
||||
await client.change_timetable(7, 3, ModifyTimetableFlags.WaitTime, 120)
|
||||
assert len(client._protocol.sent) == 1
|
||||
parsed = decode_sent_command(client._protocol.sent[0])
|
||||
assert parsed["cmd"] == GameCommand.ChangeTimetable
|
||||
assert parsed["company"] == 0
|
||||
vehicle_id, rest = read_varuint(parsed["payload"])
|
||||
order_position, rest = read_uint16(rest)
|
||||
flag, rest = read_uint8(rest)
|
||||
value, rest = read_varuint(rest)
|
||||
ctrl_flags, _ = read_uint8(rest)
|
||||
assert (vehicle_id, order_position, flag, value, ctrl_flags) == (7, 3, ModifyTimetableFlags.WaitTime, 120, 0)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_client_change_timetable_clear_field_sets_ctrl_flag():
|
||||
client = new_client()
|
||||
await client.change_timetable(7, 3, ModifyTimetableFlags.TravelTime, 0, clear_field=True)
|
||||
parsed = decode_sent_command(client._protocol.sent[0])
|
||||
vehicle_id, rest = read_varuint(parsed["payload"])
|
||||
order_position, rest = read_uint16(rest)
|
||||
flag, rest = read_uint8(rest)
|
||||
value, rest = read_varuint(rest)
|
||||
ctrl_flags, _ = read_uint8(rest)
|
||||
assert flag == ModifyTimetableFlags.TravelTime
|
||||
assert ctrl_flags == ModifyTimetableCtrlFlag.ClearField
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_client_autofill_timetable_sends_expected_payload():
|
||||
client = new_client()
|
||||
await client.autofill_timetable(7, autofill=True, preserve_wait_time=False)
|
||||
parsed = decode_sent_command(client._protocol.sent[0])
|
||||
assert parsed["cmd"] == GameCommand.AutofillTimetable
|
||||
vehicle_id, rest = read_varuint(parsed["payload"])
|
||||
autofill, rest = read_uint8(rest)
|
||||
preserve_wait_time, _ = read_uint8(rest)
|
||||
assert (vehicle_id, autofill, preserve_wait_time) == (7, 1, 0)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_client_set_timetable_start_sends_expected_payload():
|
||||
client = new_client()
|
||||
await client.set_timetable_start(7, True, -12345)
|
||||
parsed = decode_sent_command(client._protocol.sent[0])
|
||||
assert parsed["cmd"] == GameCommand.SetTimetableStart
|
||||
vehicle_id, rest = read_varuint(parsed["payload"])
|
||||
timetable_all, rest = read_uint8(rest)
|
||||
start_date, _ = read_varuint_signed(rest)
|
||||
assert (vehicle_id, timetable_all, start_date) == (7, 1, -12345)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_client_set_vehicle_on_time_sends_expected_payload():
|
||||
client = new_client()
|
||||
await client.set_vehicle_on_time(7, apply_to_group=True)
|
||||
parsed = decode_sent_command(client._protocol.sent[0])
|
||||
assert parsed["cmd"] == GameCommand.SetVehicleOnTime
|
||||
vehicle_id, rest = read_varuint(parsed["payload"])
|
||||
apply_to_group, _ = read_uint8(rest)
|
||||
assert (vehicle_id, apply_to_group) == (7, 1)
|
||||
|
||||
|
||||
# --- OpenTTDClient.receive_ServerCommand dispatch ---
|
||||
|
||||
async def feed_command(client, cmd, payload):
|
||||
parsed = OpenTTDProtocol.receive_ServerCommand(None, memoryview(build_server_command_bytes(0, cmd, payload)))
|
||||
await client.receive_ServerCommand(None, **parsed)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_receive_change_timetable_updates_order_state():
|
||||
client = new_client()
|
||||
payload = bytearray()
|
||||
write_varuint(payload, 7)
|
||||
payload += (3).to_bytes(2, "little")
|
||||
payload.append(ModifyTimetableFlags.WaitTime)
|
||||
write_varuint(payload, 120)
|
||||
payload.append(0)
|
||||
await feed_command(client, GameCommand.ChangeTimetable, payload)
|
||||
assert client.get_vehicle_timetable(7) == {"orders": {3: {"wait_time": 120}}}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_receive_change_timetable_clear_field_sets_none():
|
||||
client = new_client()
|
||||
payload = bytearray()
|
||||
write_varuint(payload, 7)
|
||||
payload += (3).to_bytes(2, "little")
|
||||
payload.append(ModifyTimetableFlags.TravelTime)
|
||||
write_varuint(payload, 0)
|
||||
payload.append(ModifyTimetableCtrlFlag.ClearField)
|
||||
await feed_command(client, GameCommand.ChangeTimetable, payload)
|
||||
assert client.get_vehicle_timetable(7)["orders"][3]["travel_time"] is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_receive_change_timetable_wait_fixed_stores_bool():
|
||||
client = new_client()
|
||||
payload = bytearray()
|
||||
write_varuint(payload, 7)
|
||||
payload += (0).to_bytes(2, "little")
|
||||
payload.append(ModifyTimetableFlags.SetWaitFixed)
|
||||
write_varuint(payload, 1)
|
||||
payload.append(0)
|
||||
await feed_command(client, GameCommand.ChangeTimetable, payload)
|
||||
assert client.get_vehicle_timetable(7)["orders"][0]["wait_time_fixed"] is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_receive_autofill_timetable_updates_state():
|
||||
client = new_client()
|
||||
payload = bytearray()
|
||||
write_varuint(payload, 7)
|
||||
payload.append(1)
|
||||
payload.append(0)
|
||||
await feed_command(client, GameCommand.AutofillTimetable, payload)
|
||||
entry = client.get_vehicle_timetable(7)
|
||||
assert entry["autofill"] is True
|
||||
assert entry["autofill_preserve_wait_time"] is False
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_receive_set_timetable_start_updates_state():
|
||||
client = new_client()
|
||||
payload = bytearray()
|
||||
write_varuint(payload, 7)
|
||||
payload.append(1)
|
||||
write_varuint_signed(payload, 555)
|
||||
await feed_command(client, GameCommand.SetTimetableStart, payload)
|
||||
entry = client.get_vehicle_timetable(7)
|
||||
assert entry["timetable_all"] is True
|
||||
assert entry["timetable_start"] == 555
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_receive_set_vehicle_on_time_updates_state():
|
||||
client = new_client()
|
||||
payload = bytearray()
|
||||
write_varuint(payload, 7)
|
||||
payload.append(1)
|
||||
await feed_command(client, GameCommand.SetVehicleOnTime, payload)
|
||||
assert client.get_vehicle_timetable(7)["on_time_apply_to_group"] is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_receive_unknown_command_is_ignored():
|
||||
client = new_client()
|
||||
await feed_command(client, 999, bytearray())
|
||||
assert client.vehicle_timetables == {}
|
||||
|
||||
|
||||
# --- get_vehicle_timetable ---
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_vehicle_timetable_known_and_unknown():
|
||||
client = new_client()
|
||||
assert client.get_vehicle_timetable(7) is None
|
||||
payload = bytearray()
|
||||
write_varuint(payload, 7)
|
||||
payload.append(1)
|
||||
await feed_command(client, GameCommand.SetVehicleOnTime, payload)
|
||||
assert client.get_vehicle_timetable(7) is not None
|
||||
assert client.get_vehicle_timetable(42) is None
|
||||
Reference in New Issue
Block a user