11# test_net.py
22import socket
3+ import sys
34from unittest .mock import Mock , patch
45
6+ import pytest
7+
58from chordnet import _Net
69
710
@@ -12,6 +15,10 @@ def test_net_initialization():
1215 assert net ._ip == "localhost"
1316 assert net ._port == 8000
1417 assert net ._request_handler == mock_handler
18+ assert net ._running is False
19+ assert net ._network_thread is None
20+ assert net .server_socket is None # Now initialized in __init__
21+ assert net .network_thread is None # Now initialized in __init__
1522
1623
1724@patch ("socket.socket" )
@@ -22,12 +29,17 @@ def test_net_start(mock_thread, mock_socket):
2229
2330 net .start ()
2431
32+ assert net ._running is True
33+ assert net .server_socket == mock_socket .return_value
34+ assert net .network_thread == mock_thread .return_value
35+
2536 # Verify socket was created and bound
2637 mock_socket .return_value .bind .assert_called_once_with (("localhost" , 8000 ))
2738 mock_socket .return_value .listen .assert_called_once_with (5 )
2839
2940 # Verify thread was started
3041 mock_thread .assert_called_once_with (target = net ._listen_for_connections , daemon = True )
42+ mock_thread .return_value .start .assert_called_once ()
3143
3244
3345def test_net_handle_connection ():
@@ -59,9 +71,12 @@ def test_net_stop():
5971 # Mock the thread and socket
6072 net .network_thread = Mock ()
6173 net .server_socket = Mock ()
74+ net ._running = True # Simulate that it was running
6275
6376 net .stop ()
6477
78+ assert net ._running is False # Should be set to False
79+
6580 # Verify socket was closed
6681 net .server_socket .close .assert_called_once ()
6782
@@ -92,12 +107,15 @@ def test_send_request_success():
92107 assert response == "RESPONSE"
93108
94109 # Verify socket methods were called correctly
110+ mock_socket_instance .settimeout .assert_called_once_with (5 )
95111 mock_socket_instance .connect .assert_called_once_with (("localhost" , 8001 ))
96- mock_socket_instance .send .assert_called_once ( )
97- mock_socket_instance .recv .assert_called_once ( )
112+ mock_socket_instance .send .assert_called_once_with ( b"TEST:arg1:arg2" )
113+ mock_socket_instance .recv .assert_called_once_with ( 1024 )
98114
99115
100- def test_send_request_timeout ():
116+ @patch .object (sys .stderr , 'write' )
117+ @patch .object (sys .stderr , 'flush' )
118+ def test_send_request_timeout (mock_stderr_flush , mock_stderr_write ):
101119 # Create a mock network instance
102120 mock_handler = Mock ()
103121 net = _Net ("localhost" , 8000 , mock_handler )
@@ -110,16 +128,22 @@ def test_send_request_timeout():
110128 # Simulate a timeout
111129 mock_socket_instance .connect .side_effect = socket .timeout
112130
113- # Call send_request and check for None return
114131 response = net .send_request (
115132 Mock (ip = "localhost" , port = 8001 ), "TEST" , "arg1" , "arg2"
116133 )
117134
118135 # Assertions
119136 assert response is None
120137
138+ # Assert that 'write' was called and contained the message
139+ assert mock_stderr_write .call_count >= 1
140+ messages_written = [call_args [0 ][0 ] for call_args in mock_stderr_write .call_args_list ]
141+ assert any ("Request timed out" in msg for msg in messages_written )
121142
122- def test_send_request_connection_refused ():
143+
144+ @patch .object (sys .stderr , 'write' )
145+ @patch .object (sys .stderr , 'flush' )
146+ def test_send_request_connection_refused (mock_stderr_flush , mock_stderr_write ):
123147 # Create a mock network instance
124148 mock_handler = Mock ()
125149 net = _Net ("localhost" , 8000 , mock_handler )
@@ -132,10 +156,149 @@ def test_send_request_connection_refused():
132156 # Simulate connection refused
133157 mock_socket_instance .connect .side_effect = ConnectionRefusedError
134158
135- # Call send_request and check for None return
136159 response = net .send_request (
137160 Mock (ip = "localhost" , port = 8001 ), "TEST" , "arg1" , "arg2"
138161 )
139162
140163 # Assertions
141164 assert response is None
165+ # Assert that 'write' was called and contained the message
166+ assert mock_stderr_write .call_count >= 1
167+ messages_written = [call_args [0 ][0 ] for call_args in mock_stderr_write .call_args_list ]
168+ assert any ("Connection refused" in msg for msg in messages_written )
169+
170+
171+ # tests/test_net.py (only the relevant test function is shown)
172+
173+ @patch .object (sys .stderr , 'write' )
174+ @patch .object (sys .stderr , 'flush' )
175+ def test_listen_for_connections_accept_exception (mock_stderr_flush , mock_stderr_write ):
176+ mock_handler = Mock ()
177+ net = _Net ("localhost" , 8000 , mock_handler )
178+
179+ mock_server_socket = Mock ()
180+ net .server_socket = mock_server_socket # Manually set the mock server_socket
181+ net ._running = True # Ensure the loop runs at least once
182+
183+ # Configure side_effect using a generator.
184+ # First call to accept(): raises exception, but net._running is still True.
185+ # Second call to accept() (if loop continued): sets net._running to False, allowing loop to exit.
186+ def controlled_accept_side_effect_generator ():
187+ yield Exception ("Simulated accept error" ) # First time accept is called, it raises this.
188+ # If _listen_for_connections tried to accept again (loop continues after logging error),
189+ # this next line would execute, making the loop terminate.
190+ net ._running = False
191+ yield Mock (), Mock () # Return a valid connection, so it doesn't raise another error if loop tried once more.
192+
193+ mock_server_socket .accept .side_effect = controlled_accept_side_effect_generator ()
194+
195+ # Call _listen_for_connections directly to control its execution for testing this error path
196+ net ._listen_for_connections ()
197+
198+ # Assertions for stderr output
199+ assert mock_stderr_write .call_count >= 1
200+ messages_written = [call_args [0 ][0 ] for call_args in mock_stderr_write .call_args_list ]
201+ assert any ("Error accepting connection: Simulated accept error" in msg for msg in messages_written )
202+ assert net ._running is False # Verify the loop has stopped
203+
204+
205+ def test_net_handle_connection_no_args ():
206+ mock_socket = Mock ()
207+ mock_handler = Mock (return_value = "test_response_no_args" )
208+
209+ net = _Net ("localhost" , 8000 , mock_handler )
210+
211+ # Simulate receiving a request with no args
212+ mock_socket .recv .return_value = b"TEST"
213+
214+ net ._handle_connection (mock_socket )
215+
216+ # Verify handler was called correctly with an empty list for args
217+ mock_handler .assert_called_once_with ("TEST" , [])
218+
219+ # Verify response was sent
220+ mock_socket .send .assert_called_once_with (b"test_response_no_args" )
221+
222+ # Verify socket was closed
223+ mock_socket .close .assert_called_once ()
224+
225+
226+ @patch .object (sys .stderr , 'write' )
227+ @patch .object (sys .stderr , 'flush' )
228+ def test_net_handle_connection_handler_exception (mock_stderr_flush , mock_stderr_write ):
229+ mock_socket = Mock ()
230+ mock_handler = Mock (side_effect = Exception ("Handler error" ))
231+
232+ net = _Net ("localhost" , 8000 , mock_handler )
233+
234+ mock_socket .recv .return_value = b"TEST:arg1"
235+
236+ net ._handle_connection (mock_socket )
237+
238+ mock_handler .assert_called_once_with ("TEST" , ["arg1" ])
239+ mock_socket .send .assert_not_called () # Response should not be sent
240+
241+ mock_socket .close .assert_called_once () # Socket should still be closed
242+
243+ assert mock_stderr_write .call_count >= 1
244+ messages_written = [call_args [0 ][0 ] for call_args in mock_stderr_write .call_args_list ]
245+ assert any ("Error handling connection: Handler error" in msg for msg in messages_written )
246+ mock_stderr_flush .assert_called_once ()
247+
248+
249+ def test_send_request_no_args ():
250+ mock_handler = Mock ()
251+ net = _Net ("localhost" , 8000 , mock_handler )
252+
253+ with patch ("socket.socket" ) as mock_socket :
254+ mock_socket_instance = Mock ()
255+ mock_socket .return_value .__enter__ .return_value = mock_socket_instance
256+ mock_socket_instance .recv .return_value = b"RESPONSE_NO_ARGS"
257+
258+ response = net .send_request (
259+ Mock (ip = "localhost" , port = 8001 ), "NO_ARGS_METHOD"
260+ )
261+
262+ assert response == "RESPONSE_NO_ARGS"
263+ mock_socket_instance .send .assert_called_once_with (b"NO_ARGS_METHOD:" )
264+
265+
266+ @patch .object (sys .stderr , 'write' )
267+ @patch .object (sys .stderr , 'flush' )
268+ def test_send_request_general_exception (mock_stderr_flush , mock_stderr_write ):
269+ mock_handler = Mock ()
270+ net = _Net ("localhost" , 8000 , mock_handler )
271+
272+ with patch ("socket.socket" ) as mock_socket :
273+ mock_socket_instance = Mock ()
274+ mock_socket .return_value .__enter__ .return_value = mock_socket_instance
275+
276+ # Simulate a generic exception during connect
277+ mock_socket_instance .connect .side_effect = Exception ("Generic network error" )
278+
279+ response = net .send_request (
280+ Mock (ip = "localhost" , port = 8001 ), "TEST" , "arg1"
281+ )
282+
283+ assert response is None
284+ assert mock_stderr_write .call_count >= 1
285+ messages_written = [call_args [0 ][0 ] for call_args in mock_stderr_write .call_args_list ]
286+ assert any ("Network request error: Generic network error" in msg for msg in messages_written )
287+
288+
289+ def test_net_stop_before_start ():
290+ mock_handler = Mock ()
291+ net = _Net ("localhost" , 8000 , mock_handler )
292+
293+ # Initially, server_socket and network_thread are None due to __init__ changes
294+ assert net ._running is False
295+ assert net .server_socket is None
296+ assert net .network_thread is None
297+
298+ # Calling stop should not raise an error
299+ try :
300+ net .stop ()
301+ except Exception as e :
302+ pytest .fail (f"Calling stop before start raised an exception: { e } " )
303+
304+ # No assertions on mocks needed as server_socket and network_thread are None
0 commit comments