Skip to content

Commit c9bbc3a

Browse files
committed
Claude review
1 parent 3d4d639 commit c9bbc3a

3 files changed

Lines changed: 38 additions & 17 deletions

File tree

test/asynchronous/test_async_network_layer.py

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from pymongo.errors import ProtocolError
2727
from pymongo.network_layer import PyMongoProtocol, _async_socket_receive
2828
from test.asynchronous import AsyncUnitTest, unittest
29-
from test.utils_shared import make_msg_header
29+
from test.utils_shared import pack_msg_header
3030

3131

3232
def _make_protocol(timeout=None):
@@ -43,7 +43,7 @@ async def asyncSetUp(self):
4343

4444
def test_op_msg_returns_body_len_and_op_code(self):
4545
self.protocol._header = memoryview(
46-
bytearray(make_msg_header(length=32, request_id=1, response_to=99, op_code=2013))
46+
bytearray(pack_msg_header(length=32, request_id=1, response_to=99, op_code=2013))
4747
)
4848
body_len, op_code, response_to, expecting_compression = self.protocol.process_header()
4949
self.assertEqual(body_len, 16)
@@ -56,7 +56,7 @@ def test_op_compressed_sets_expecting_compression(self):
5656
# (op code + uncompressed size + compressor id), then the 16-byte standard header.
5757
# length=35 → after compression sub-header: 26 → body: 10
5858
self.protocol._header = memoryview(
59-
bytearray(make_msg_header(length=35, request_id=1, response_to=0, op_code=2012))
59+
bytearray(pack_msg_header(length=35, request_id=1, response_to=0, op_code=2012))
6060
)
6161
body_len, op_code, _response_to, expecting_compression = self.protocol.process_header()
6262
self.assertEqual(body_len, 10)
@@ -65,22 +65,22 @@ def test_op_compressed_sets_expecting_compression(self):
6565

6666
def test_op_compressed_length_too_small_raises(self):
6767
self.protocol._header = memoryview(
68-
bytearray(make_msg_header(length=25, request_id=1, response_to=0, op_code=2012))
68+
bytearray(pack_msg_header(length=25, request_id=1, response_to=0, op_code=2012))
6969
)
7070
with self.assertRaisesRegex(ProtocolError, "not longer than standard OP_COMPRESSED"):
7171
self.protocol.process_header()
7272

7373
def test_non_compressed_length_too_small_raises(self):
7474
self.protocol._header = memoryview(
75-
bytearray(make_msg_header(length=16, request_id=1, response_to=0, op_code=2013))
75+
bytearray(pack_msg_header(length=16, request_id=1, response_to=0, op_code=2013))
7676
)
7777
with self.assertRaisesRegex(ProtocolError, "not longer than standard message header size"):
7878
self.protocol.process_header()
7979

8080
def test_length_exceeds_max_raises(self):
8181
self.protocol._header = memoryview(
8282
bytearray(
83-
make_msg_header(
83+
pack_msg_header(
8484
length=MAX_MESSAGE_SIZE + 1, request_id=1, response_to=0, op_code=2013
8585
)
8686
)
@@ -111,15 +111,31 @@ class TestBufferUpdated(AsyncUnitTest):
111111
async def asyncSetUp(self):
112112
self.protocol = _make_protocol()
113113

114-
def test_zero_bytes_closes_connection(self):
114+
async def test_zero_bytes_closes_connection(self):
115+
# A zero-byte buffer_updated (connection closed mid-read) must surface to
116+
# a waiting reader as OSError("connection closed"), not just abort the transport.
117+
read_task = asyncio.create_task(
118+
self.protocol.read(request_id=None, max_message_size=MAX_MESSAGE_SIZE)
119+
)
120+
await asyncio.sleep(0)
115121
self.protocol.buffer_updated(0)
116122
self.assertTrue(self.protocol.transport.abort.called)
123+
with self.assertRaisesRegex(OSError, "connection closed"):
124+
await read_task
117125

118-
def test_protocol_error_closes_connection(self):
126+
async def test_protocol_error_closes_connection(self):
127+
# A malformed header must surface to a waiting reader as a ProtocolError,
128+
# not just abort the transport.
129+
read_task = asyncio.create_task(
130+
self.protocol.read(request_id=None, max_message_size=MAX_MESSAGE_SIZE)
131+
)
132+
await asyncio.sleep(0)
119133
buf = self.protocol.get_buffer(16)
120-
buf[:16] = make_msg_header(length=16, request_id=1, response_to=0, op_code=2013)
134+
buf[:16] = pack_msg_header(length=16, request_id=1, response_to=0, op_code=2013)
121135
self.protocol.buffer_updated(16)
122136
self.assertTrue(self.protocol.transport.abort.called)
137+
with self.assertRaisesRegex(ProtocolError, "not longer than standard message header"):
138+
await read_task
123139

124140
async def test_resolves_pending_read(self):
125141
read_task = asyncio.create_task(
@@ -128,7 +144,7 @@ async def test_resolves_pending_read(self):
128144
await asyncio.sleep(0)
129145

130146
# Feed a valid 32-byte OP_MSG header (16-byte header + 16-byte body).
131-
header = make_msg_header(length=32, request_id=1, response_to=99, op_code=2013)
147+
header = pack_msg_header(length=32, request_id=1, response_to=99, op_code=2013)
132148
buf = self.protocol.get_buffer(16)
133149
buf[:16] = header
134150
self.protocol.buffer_updated(16)

test/test_network_layer.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from pymongo.common import MAX_MESSAGE_SIZE
3131
from pymongo.errors import ProtocolError
3232
from test import UnitTest, unittest
33-
from test.utils_shared import make_msg_header
33+
from test.utils_shared import pack_msg_header
3434

3535

3636
def _make_conn():
@@ -48,7 +48,7 @@ def test_request_id_mismatch_raises(self):
4848
with patch.object(
4949
network_layer,
5050
"receive_data",
51-
return_value=make_msg_header(length=32, request_id=0, response_to=99, op_code=2013),
51+
return_value=pack_msg_header(length=32, request_id=0, response_to=99, op_code=2013),
5252
):
5353
with self.assertRaisesRegex(ProtocolError, "Got response id"):
5454
network_layer.receive_message(_make_conn(), request_id=1)
@@ -57,7 +57,7 @@ def test_length_too_small_raises(self):
5757
with patch.object(
5858
network_layer,
5959
"receive_data",
60-
return_value=make_msg_header(length=16, request_id=0, response_to=0, op_code=2013),
60+
return_value=pack_msg_header(length=16, request_id=0, response_to=0, op_code=2013),
6161
):
6262
with self.assertRaisesRegex(ProtocolError, "not longer than standard message header"):
6363
network_layer.receive_message(_make_conn(), request_id=None)
@@ -66,7 +66,7 @@ def test_length_exceeds_max_raises(self):
6666
with patch.object(
6767
network_layer,
6868
"receive_data",
69-
return_value=make_msg_header(
69+
return_value=pack_msg_header(
7070
length=MAX_MESSAGE_SIZE + 1, request_id=0, response_to=0, op_code=2013
7171
),
7272
):
@@ -78,7 +78,7 @@ def test_unknown_opcode_raises(self):
7878
network_layer,
7979
"receive_data",
8080
side_effect=[
81-
make_msg_header(length=20, request_id=0, response_to=0, op_code=9999),
81+
pack_msg_header(length=20, request_id=0, response_to=0, op_code=9999),
8282
b"data",
8383
],
8484
):

test/utils_shared.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,11 @@ def barrier_wait(barrier, timeout: float | None = None):
746746
barrier.wait(timeout=timeout)
747747

748748

749-
def make_msg_header(length: int, request_id: int, response_to: int, op_code: int) -> bytes:
750-
"""Pack a MongoDB wire-protocol message header."""
749+
def pack_msg_header(length: int, request_id: int, response_to: int, op_code: int) -> bytes:
750+
"""Pack a MongoDB wire-protocol message header (``<iiii``: length,
751+
request_id, response_to, op_code).
752+
753+
Lets tests set an arbitrary ``length`` (including invalid values), which
754+
production header-packing never does.
755+
"""
751756
return struct.pack("<iiii", length, request_id, response_to, op_code)

0 commit comments

Comments
 (0)