Skip to content

Commit f2dbf16

Browse files
committed
test: add coverage tests for psycopg proxy timeout, handshake failure, and remote EOF
1 parent 375feff commit f2dbf16

1 file changed

Lines changed: 105 additions & 0 deletions

File tree

‎tests/unit/test_psycopg.py‎

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,108 @@ def mock_connect_impl(*args: Any, **kwargs: Any) -> MagicMock:
154154

155155
# Verify temp dir was cleaned up
156156
assert not os.path.exists(kwargs["host"])
157+
158+
159+
def test_proxy_timeout() -> None:
160+
"""Test that _proxy exits and cleans up on selectors timeout."""
161+
local_client, local_server = socket.socketpair()
162+
remote_client, remote_server = socket.socketpair()
163+
164+
# Mock selectors.DefaultSelector.select to return empty list (timeout)
165+
with patch(
166+
"google.cloud.sql.connector.psycopg.selectors.DefaultSelector"
167+
) as mock_selector_cls:
168+
mock_selector = MagicMock()
169+
mock_selector.select.return_value = [] # Timeout
170+
mock_selector_cls.return_value = mock_selector
171+
172+
# Run proxy
173+
_proxy(local_server, remote_client)
174+
175+
# Sockets should be closed
176+
assert local_server.fileno() == -1
177+
assert remote_client.fileno() == -1
178+
179+
# Clean up outer sockets
180+
local_client.close()
181+
remote_server.close()
182+
183+
184+
@patch("psycopg.connect")
185+
def test_connect_wrapper_failure(mock_psycopg_connect: MagicMock) -> None:
186+
"""Test that connect wrapper cleans up correctly when psycopg.connect fails."""
187+
mock_remote_sock = MagicMock(spec=ssl.SSLSocket)
188+
mock_psycopg_connect.side_effect = Exception("connection failed simulated")
189+
190+
# Call the connect wrapper and expect it to raise
191+
import pytest
192+
193+
with pytest.raises(Exception, match="connection failed simulated"):
194+
connect(
195+
"127.0.0.1",
196+
mock_remote_sock,
197+
user="test_user",
198+
db="test_db",
199+
password="test_password",
200+
)
201+
202+
# Verify remote socket was closed
203+
assert mock_remote_sock.close.called
204+
205+
# Verify cleanup with mocked paths
206+
with patch(
207+
"google.cloud.sql.connector.psycopg.tempfile.mkdtemp"
208+
) as mock_mkdtemp:
209+
mock_mkdtemp.return_value = "/tmp/mock_temp_dir_failure"
210+
211+
with patch(
212+
"google.cloud.sql.connector.psycopg.os.rmdir"
213+
) as mock_rmdir, patch(
214+
"google.cloud.sql.connector.psycopg.os.remove"
215+
) as mock_remove, patch(
216+
"google.cloud.sql.connector.psycopg.socket.socket"
217+
) as mock_socket_cls:
218+
# Mock the local socket to avoid real OS bind/listen
219+
mock_local_sock = MagicMock()
220+
mock_socket_cls.return_value = mock_local_sock
221+
222+
with pytest.raises(Exception, match="connection failed simulated"):
223+
connect(
224+
"127.0.0.1",
225+
mock_remote_sock,
226+
user="test_user",
227+
db="test_db",
228+
password="test_password",
229+
)
230+
231+
# Verify rmdir and remove were called for cleanup
232+
mock_rmdir.assert_called_once_with("/tmp/mock_temp_dir_failure")
233+
mock_remove.assert_called_once()
234+
235+
236+
def test_proxy_remote_eof() -> None:
237+
"""Test that _proxy exits when remote socket receives EOF."""
238+
local_client, local_server = socket.socketpair()
239+
remote_client, remote_server = socket.socketpair()
240+
241+
# Start proxy in background
242+
proxy_thread = threading.Thread(
243+
target=_proxy, args=(local_server, remote_client), daemon=True
244+
)
245+
proxy_thread.start()
246+
247+
# Close remote server to trigger EOF on remote_client
248+
remote_server.close()
249+
250+
# local_client should receive EOF (b"")
251+
assert local_client.recv(1024) == b""
252+
253+
# Wait for proxy thread to finish
254+
proxy_thread.join(timeout=2.0)
255+
assert not proxy_thread.is_alive()
256+
257+
# Sockets should be closed
258+
assert local_server.fileno() == -1
259+
assert remote_client.fileno() == -1
260+
261+
local_client.close()

0 commit comments

Comments
 (0)