Resolve 51 findings from the I/RUF/BLE/TRY002/S110/PLR0402 rule set: - Sort imports and __all__ (I001, RUF022, PLR0402). The sys.path.insert calls in check_public_calls.py and tests/test_e2e.py still precede the openttd imports that depend on them. - Replace unused unpacked values with _ (RUF059) and annotate the two timetable lookup tables as ClassVar (RUF012). - Narrow the best-effort excepts in OpenTTDClient.quit and OpenTTDAdminClient.quit to (OSError, SocketClosed) and log at debug rather than swallowing silently (BLE001, S110). The test doubles now raise an OSError subclass so they still exercise that branch. - Narrow the gamescript JSON fallback to json.JSONDecodeError. The broad catch in receive_packet keeps a noqa: it guards untrusted wire data and must degrade to a no-op packet instead of killing the connection. - Use contextlib.suppress instead of try/except/pass in tests. ruff check . is clean, 102 tests pass, coverage stays at 100%. Co-Authored-By: Claude <[email protected]>
71 lines
2.4 KiB
Python
71 lines
2.4 KiB
Python
import asyncio
|
|
import struct
|
|
|
|
import pytest
|
|
from openttd.client import OpenTTDClient
|
|
from openttd.protocol import OpenTTDProtocol, PacketGameType
|
|
|
|
|
|
class MockTransport:
|
|
def __init__(self): self._closing = False
|
|
def is_closing(self): return self._closing
|
|
def close(self): self._closing = True
|
|
def write(self, data): return len(data)
|
|
|
|
class FakeNetworkError(OSError):
|
|
"""Stand-in for a socket-level failure, so it matches the client's narrowed handlers."""
|
|
|
|
class MockProtocol:
|
|
async def send_packet(self, data):
|
|
raise FakeNetworkError("Send failed")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_client_connect_exception(monkeypatch):
|
|
# Coverage for client.py:51-53
|
|
client = OpenTTDClient(host="127.0.0.1")
|
|
async def mock_fail(*args, **kwargs):
|
|
raise FakeNetworkError("Async Failure")
|
|
monkeypatch.setattr(asyncio.get_running_loop(), "create_connection", mock_fail)
|
|
with pytest.raises(Exception, match="Async Failure"):
|
|
await client.connect()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_client_quit_exception():
|
|
# Coverage for client.py:76
|
|
client = OpenTTDClient(host="127.0.0.1")
|
|
client._transport = MockTransport()
|
|
client._protocol = MockProtocol() # send_packet raises
|
|
await client.quit() # Should hit the except: pass
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_client_no_company_password():
|
|
# Coverage for client.py:126-127
|
|
client = OpenTTDClient(host="127.0.0.1")
|
|
# _company_password is empty by default
|
|
await client.receive_ServerNeedCompanyPassword(None, 0, "srv")
|
|
# Should log error and return
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_client_chat_no_callback():
|
|
# Coverage for client.py:162
|
|
client = OpenTTDClient(host="127.0.0.1")
|
|
# on_chat is None
|
|
await client.receive_ServerChat(None, 1, "Hello")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_protocol_receive_exception():
|
|
# Coverage for protocol.py:74-75
|
|
# Trigger exception in receive_packet loop
|
|
class BadHandler:
|
|
encryption_enabled = False
|
|
proto = OpenTTDProtocol(BadHandler())
|
|
# data too short for read_uint16
|
|
res = proto.receive_packet(None, memoryview(b"\x01"))
|
|
assert res == (PacketGameType.ServerUnused, {})
|
|
|
|
def test_protocol_welcome_parser():
|
|
# Coverage for protocol.py:110-112
|
|
data = memoryview(struct.pack("<I", 42))
|
|
res = OpenTTDProtocol.receive_ServerWelcome(None, data)
|
|
assert res == {"client_id": 42}
|