2020from indisoluble .a_healthy_dns .records .zone_origins import ZoneOrigins
2121
2222
23+ def _make_multi_question_wire (* question_names ):
24+ queries = [
25+ dns .message .make_query (question_name , dns .rdatatype .A )
26+ for question_name in question_names
27+ ]
28+ wire = bytearray (queries [0 ].to_wire ())
29+ wire [4 :6 ] = len (queries ).to_bytes (2 )
30+
31+ for query in queries [1 :]:
32+ wire .extend (query .to_wire ()[12 :])
33+
34+ return bytes (wire )
35+
36+
2337@pytest .fixture
2438def mock_reader ():
2539 reader = MagicMock (spec = dns .zone .Transaction )
@@ -75,7 +89,6 @@ def mock_soa_rdataset(mock_soa_rdata):
7589 return mock_soa_rdataset
7690
7791
78-
7992@pytest .fixture
8093def mock_server (mock_zone , mock_zone_origins ):
8194 server = MagicMock ()
@@ -286,11 +299,7 @@ def test_handle_exception_parsing_query(
286299 mock_from_wire .side_effect = dns .exception .DNSException ("Test exception" )
287300
288301 # No need to call handle() as it's called automatically by the constructor
289- with patch ("logging.warning" ) as mock_logging :
290- _ = DnsServerUdpHandler (dns_request , dns_client_address , mock_server )
291-
292- mock_logging .assert_called_once ()
293- assert "Failed to parse DNS query" in mock_logging .call_args [0 ][0 ]
302+ _ = DnsServerUdpHandler (dns_request , dns_client_address , mock_server )
294303
295304 # Assertions
296305 query_data = dns_request [0 ]
@@ -302,64 +311,24 @@ def test_handle_exception_parsing_query(
302311
303312
304313@patch ("indisoluble.a_healthy_dns.dns_server_udp_handler._update_response" )
305- def test_handle_query_without_question (
306- mock_update_response , dns_client_address , mock_server
314+ @pytest .mark .parametrize (
315+ "query_data" ,
316+ [
317+ dns .message .Message ().to_wire (),
318+ _make_multi_question_wire ("example.com." , "test.com." ),
319+ ],
320+ )
321+ def test_handle_query_with_invalid_question_count_returns_formerr (
322+ mock_update_response , query_data , dns_client_address , mock_server
307323):
308- # Create a request with an empty question section
309- query = dns .message .Message ()
310324 mock_sock = MagicMock ()
311- request = (query .to_wire (), mock_sock )
312-
313- # No need to call handle() as it's called automatically by the constructor
314- with patch ("logging.warning" ) as mock_logging :
315- _ = DnsServerUdpHandler (request , dns_client_address , mock_server )
325+ request = (query_data , mock_sock )
316326
317- mock_logging .assert_called_once ()
318- assert "0 questions, expected exactly 1" in (
319- mock_logging .call_args [0 ][0 ] % mock_logging .call_args [0 ][1 ]
320- )
327+ _ = DnsServerUdpHandler (request , dns_client_address , mock_server )
321328
322- # Assertions
323329 mock_update_response .assert_not_called ()
324-
325- # Check response was sent with FORMERR
326330 mock_sock .sendto .assert_called_once ()
327331
328332 sent_data = mock_sock .sendto .call_args [0 ][0 ]
329333 response = dns .message .from_wire (sent_data )
330334 assert response .rcode () == dns .rcode .FORMERR
331-
332-
333- @patch ("indisoluble.a_healthy_dns.dns_server_udp_handler._update_response" )
334- def test_handle_query_with_multiple_questions (
335- mock_update_response , dns_client_address , mock_server
336- ):
337- # Build a wire message with QDCOUNT=2 by crafting the bytes directly:
338- # start from a valid single-question query, patch QDCOUNT to 2, then
339- # append the question bytes from a second query.
340- q1 = dns .message .make_query ("example.com." , dns .rdatatype .A )
341- wire = bytearray (q1 .to_wire ())
342- # QDCOUNT is at bytes 4-5 of the DNS header
343- wire [4 ] = 0
344- wire [5 ] = 2
345- q2 = dns .message .make_query ("test.com." , dns .rdatatype .A )
346- # Append the question section of q2 (everything after the 12-byte header)
347- wire .extend (q2 .to_wire ()[12 :])
348-
349- mock_sock = MagicMock ()
350- request = (bytes (wire ), mock_sock )
351-
352- with patch ("logging.warning" ) as mock_logging :
353- _ = DnsServerUdpHandler (request , dns_client_address , mock_server )
354-
355- mock_logging .assert_called_once ()
356- assert "2 questions, expected exactly 1" in (
357- mock_logging .call_args [0 ][0 ] % mock_logging .call_args [0 ][1 ]
358- )
359-
360- mock_update_response .assert_not_called ()
361-
362- mock_sock .sendto .assert_called_once ()
363- sent_data = mock_sock .sendto .call_args [0 ][0 ]
364- response = dns .message .from_wire (sent_data )
365- assert response .rcode () == dns .rcode .FORMERR
0 commit comments