Skip to content

Commit 05d2bb7

Browse files
Add short-body truncation tests for fixed-width response decoders
The fixed-width response decoders (FailureResponse, WelcomeResponse, MetadataResponse, ResultResponse, DbResponse) all rely on the primitive decode helpers (decode_uint32 / decode_uint64) raising DecodeError on short input. That behaviour is well-covered by the helpers' own tests, but the message-class wrappers have no direct coverage — a future refactor that swaps a helper for a looser one would regress silently. Parametrise a short-body test over the five fixed-width response classes, one iteration per length below the minimum body. Add a targeted test for LeaderResponse.decode_body_legacy (no NUL terminator must raise) and pin the current lax EmptyResponse behaviour explicitly so any future tightening is a deliberate decision, not a silent refactor side-effect. Variable-size response bodies (RowsResponse, ServersResponse, FilesResponse) are intentionally out of scope — they already have targeted truncation tests and cover their own edge cases directly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 8aea406 commit 05d2bb7

1 file changed

Lines changed: 42 additions & 0 deletions

File tree

tests/test_messages_responses.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1182,3 +1182,45 @@ def test_roundtrip(self) -> None:
11821182
decoded = MetadataResponse.decode_body(encoded[HEADER_SIZE:])
11831183
assert decoded.failure_domain == 1
11841184
assert decoded.weight == 50
1185+
1186+
1187+
class TestShortBodyDecoding:
1188+
"""Fixed-width response decoders must raise ``DecodeError`` on a body
1189+
shorter than the declared field layout. The primitive helpers
1190+
(``decode_uint32`` / ``decode_uint64``) already raise on short input —
1191+
these tests pin the contract at the message-class boundary so a
1192+
future refactor that swaps a helper for a looser one surfaces here
1193+
rather than producing silently truncated values.
1194+
"""
1195+
1196+
@pytest.mark.parametrize(
1197+
"cls,min_body",
1198+
[
1199+
(FailureResponse, 8), # uint64 code
1200+
(WelcomeResponse, 8), # uint64 heartbeat_timeout
1201+
(MetadataResponse, 16), # 2 x uint64
1202+
(ResultResponse, 16), # 2 x uint64
1203+
(DbResponse, 4), # uint32 db_id
1204+
],
1205+
)
1206+
def test_decode_body_raises_on_short(self, cls: type, min_body: int) -> None:
1207+
for length in range(min_body):
1208+
with pytest.raises(DecodeError):
1209+
cls.decode_body(b"\x00" * length)
1210+
1211+
def test_leader_response_legacy_missing_null_terminator_raises(self) -> None:
1212+
"""``decode_body_legacy`` expects NUL-terminated text; bytes without
1213+
a NUL must be rejected rather than produce an unterminated string.
1214+
"""
1215+
with pytest.raises(DecodeError):
1216+
LeaderResponse.decode_body_legacy(b"abc")
1217+
1218+
def test_empty_response_accepts_any_body(self) -> None:
1219+
"""``EmptyResponse`` is documented as having an 8-byte reserved
1220+
field but its decoder accepts any body length today. Pin the
1221+
current (lax) behaviour so any future tightening is a deliberate
1222+
decision — not a silent refactor side-effect.
1223+
"""
1224+
assert isinstance(EmptyResponse.decode_body(b""), EmptyResponse)
1225+
assert isinstance(EmptyResponse.decode_body(b"\x00" * 8), EmptyResponse)
1226+
assert isinstance(EmptyResponse.decode_body(b"\xff" * 64), EmptyResponse)

0 commit comments

Comments
 (0)