11from __future__ import annotations
22
3+ import urllib .request
34from collections import deque
45from test .unit .test_proxies import (
56 DbRequestFlags ,
1213
1314import aiohttp
1415import pytest
16+ from aiohttp import BasicAuth
17+ from aiohttp .helpers import proxies_from_env
18+ from yarl import URL
1519
1620from snowflake .connector .aio import connect as async_connect
1721
@@ -314,6 +318,57 @@ async def test_no_proxy_basic_param_proxy_bypass_backend(
314318 assert flags .proxy_saw_storage is True
315319
316320
321+ @pytest .fixture
322+ def fix_aiohttp_proxy_bypass (monkeypatch ):
323+ """Fix aiohttp's proxy bypass to check host:port instead of just host.
324+
325+ This fixture implements a two-step fix:
326+ 1. Override get_env_proxy_for_url to use host_port_subcomponent for proxy_bypass
327+ 2. Override urllib.request._splitport to return (host:port, port) for proper matching
328+ """
329+
330+ # Step 1: Override get_env_proxy_for_url to pass host:port to proxy_bypass
331+ def get_env_proxy_for_url_with_port (url : URL ) -> tuple [URL , BasicAuth | None ]:
332+ """Get a permitted proxy for the given URL from the env, checking host:port."""
333+ from urllib .request import proxy_bypass
334+
335+ # Check proxy bypass using host:port combination
336+ if url .host is not None :
337+ # Use host_port_subcomponent which includes port
338+ host_port = f"{ url .host } :{ url .port } " if url .port else url .host
339+ if proxy_bypass (host_port ):
340+ raise LookupError (f"Proxying is disallowed for `{ host_port !r} `" )
341+
342+ proxies_in_env = proxies_from_env ()
343+ try :
344+ proxy_info = proxies_in_env [url .scheme ]
345+ except KeyError :
346+ raise LookupError (f"No proxies found for `{ url !s} ` in the env" )
347+ else :
348+ return proxy_info .proxy , proxy_info .proxy_auth
349+
350+ # Step 2: Override _splitport to return host:port as first element
351+ original_splitport = urllib .request ._splitport
352+
353+ def _splitport_with_port (host ):
354+ """Override to return (host:port, port) instead of (host, port)."""
355+ result = original_splitport (host )
356+ if result is None :
357+ return (host , None )
358+ host_only , port = result
359+ # If port was found, return the original host (with port) as first element
360+ if port is not None :
361+ return (host , port ) # Return original host:port string
362+ return (host_only , port )
363+
364+ monkeypatch .setattr (
365+ aiohttp .client , "get_env_proxy_for_url" , get_env_proxy_for_url_with_port
366+ )
367+ monkeypatch .setattr (urllib .request , "_splitport" , _splitport_with_port )
368+
369+ yield
370+
371+
317372@pytest .mark .skipolddriver
318373@pytest .mark .parametrize ("proxy_method" , ["explicit_args" , "env_vars" ])
319374@pytest .mark .parametrize ("no_proxy_source" , ["param" , "env" ])
@@ -325,6 +380,7 @@ async def test_no_proxy_source_vs_proxy_method_matrix(
325380 proxy_method ,
326381 no_proxy_source ,
327382 host_port_pooling ,
383+ fix_aiohttp_proxy_bypass ,
328384):
329385 if proxy_method == "env_vars" and no_proxy_source == "param" :
330386 pytest .xfail (
@@ -366,6 +422,7 @@ async def test_no_proxy_backend_matrix(
366422 proxy_method ,
367423 no_proxy_source ,
368424 host_port_pooling ,
425+ fix_aiohttp_proxy_bypass ,
369426):
370427 if proxy_method == "env_vars" and no_proxy_source == "param" :
371428 pytest .xfail (
@@ -420,7 +477,7 @@ async def test_no_proxy_multiple_values_param_only(
420477 wiremock_mapping_dir ,
421478 proxy_env_vars ,
422479 no_proxy_factory ,
423- host_port_pooling ,
480+ host_port_pooling , # Unlike in synch code - Session stores no_proxy setup so it would be reused for proxy and backend since they are both on localhost
424481):
425482 target_wm , storage_wm , proxy_wm = wiremock_backend_storage_proxy
426483 _setup_backend_storage_mappings (
0 commit comments