Skip to content

Commit f7f61e8

Browse files
test(wire): pin oversize-message rejection boundary (ISSUE-105)
Adds three boundary tests for the envelope ``max_message_size`` cap: - ``test_huge_sql_frame_exceeds_buffer_cap`` constructs a ~1.5 MB SQL frame and feeds it into a 1 MiB-cap ReadBuffer; asserts a clear ``DecodeError("... exceeds maximum ...")`` instead of silent truncation. - ``test_huge_sql_within_cap_decodes`` guards against a regression that over-tightens the check: a 6 KB SQL encodes and round-trips cleanly. - ``test_just_over_default_cap_is_rejected`` exercises the real 64 MiB default by constructing a 65 MiB frame. Realistic triggers in production: dynamically-generated ``INSERT VALUES (...), (...), ...`` rows, ``WHERE col IN (?, ?, ...)`` with thousands of placeholders. Previously nothing pinned the boundary; a change to the cap behavior would go undetected.
1 parent b9d64df commit f7f61e8

1 file changed

Lines changed: 78 additions & 0 deletions

File tree

tests/test_oversize_message.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
"""Pin the oversize-message rejection boundary (ISSUE-105).
2+
3+
``max_message_size`` caps the total envelope size of a single wire
4+
frame. Realistic triggers in production are dynamically-generated SQL:
5+
many-row ``INSERT VALUES (...), (...), ...`` or ``WHERE col IN (?, ?, ...)``
6+
with thousands of placeholders. The encoder must reject at construction
7+
time with a clear error rather than silently truncate or produce bytes
8+
the server will refuse.
9+
10+
Pre-ISSUE-105, no test pinned this boundary. Changes to the envelope
11+
cap behavior would go undetected.
12+
"""
13+
14+
from __future__ import annotations
15+
16+
import pytest
17+
18+
from dqlitewire.buffer import ReadBuffer
19+
from dqlitewire.codec import decode_message, encode_message
20+
from dqlitewire.exceptions import DecodeError
21+
from dqlitewire.messages.requests import QuerySqlRequest
22+
23+
24+
class TestOversizeSqlEncode:
25+
"""An encoder-constructed frame whose total size exceeds the
26+
buffer's max cap must be rejected at decode — the wire format
27+
itself does not refuse oversize frames at encode time, but the
28+
decoder's envelope check is the first thing any real peer does
29+
to our bytes."""
30+
31+
def test_huge_sql_frame_exceeds_buffer_cap(self) -> None:
32+
"""A frame encoded from a multi-MB SQL string must be rejected
33+
by the decoder's envelope cap."""
34+
# Construct a SQL string whose encoded frame exceeds a small
35+
# buffer cap. Use a cap small enough that the test is cheap;
36+
# the real cap is 64 MiB.
37+
small_cap = 1024 * 1024 # 1 MiB
38+
# Each "?," is 2 bytes; aim for ~1.5 MB of SQL.
39+
big_sql = "SELECT " + ",".join(["?"] * 750_000)
40+
msg = QuerySqlRequest(db_id=0, sql=big_sql)
41+
encoded = encode_message(msg)
42+
assert len(encoded) > small_cap, (
43+
f"test setup: encoded frame must exceed cap ({len(encoded)} vs {small_cap})"
44+
)
45+
46+
buf = ReadBuffer(max_message_size=small_cap)
47+
with pytest.raises(DecodeError, match="exceeds maximum"):
48+
buf.feed(encoded)
49+
50+
def test_huge_sql_within_cap_decodes(self) -> None:
51+
"""A frame that fits within the cap must decode cleanly —
52+
guards against a regression that over-tightens the check."""
53+
big_sql = "SELECT " + ",".join(["?"] * 1_000) # ~6 KB
54+
msg = QuerySqlRequest(db_id=0, sql=big_sql)
55+
encoded = encode_message(msg)
56+
assert len(encoded) < ReadBuffer.DEFAULT_MAX_MESSAGE_SIZE
57+
decoded = decode_message(encoded, is_request=True)
58+
assert isinstance(decoded, QuerySqlRequest)
59+
assert decoded.sql == big_sql
60+
61+
def test_just_over_default_cap_is_rejected(self) -> None:
62+
"""Boundary test against the real 64 MiB default. Construct a
63+
SQL string whose frame size is slightly over the default cap.
64+
The test uses a custom buffer at the default size so the
65+
assertion is explicit."""
66+
# Each '?' + comma = 2 bytes; padding overhead is small. Aim
67+
# for ~64 MiB + a margin.
68+
target_bytes = ReadBuffer.DEFAULT_MAX_MESSAGE_SIZE + 1024 * 1024 # 65 MiB
69+
n_placeholders = target_bytes // 2
70+
# Build the string in one go to avoid quadratic concatenation.
71+
big_sql = "SELECT " + ",".join(["?"] * n_placeholders)
72+
msg = QuerySqlRequest(db_id=0, sql=big_sql)
73+
encoded = encode_message(msg)
74+
assert len(encoded) > ReadBuffer.DEFAULT_MAX_MESSAGE_SIZE
75+
76+
buf = ReadBuffer() # default cap
77+
with pytest.raises(DecodeError, match="exceeds maximum"):
78+
buf.feed(encoded)

0 commit comments

Comments
 (0)