@@ -18,8 +18,8 @@ def make_client(server: MockServer, **kwargs: object) -> Webshare:
1818
1919def test_auth_header_and_defaults (server : MockServer ) -> None :
2020 server .enqueue (json_body = PROFILE )
21- client = make_client (server )
22- profile = client .profile .get ()
21+ with make_client (server ) as client :
22+ profile = client .profile .get ()
2323 assert profile .id == 1
2424 assert profile .email == "user@webshare.io"
2525 request = server .requests [0 ]
@@ -33,8 +33,8 @@ def test_auth_header_and_defaults(server: MockServer) -> None:
3333def test_api_key_from_environment (server : MockServer , monkeypatch : pytest .MonkeyPatch ) -> None :
3434 monkeypatch .setenv ("WEBSHARE_API_KEY" , "env-key" )
3535 server .enqueue (json_body = PROFILE )
36- client = Webshare (base_url = server .base_url )
37- client .profile .get ()
36+ with Webshare (base_url = server .base_url ) as client :
37+ client .profile .get ()
3838 assert server .requests [0 ].headers ["Authorization" ] == "Token env-key"
3939
4040
@@ -46,13 +46,20 @@ def test_missing_credentials_raises(monkeypatch: pytest.MonkeyPatch) -> None:
4646 AsyncWebshare ()
4747
4848
49+ def test_conflicting_credentials_raise (server : MockServer ) -> None :
50+ with pytest .raises (webshare .WebshareError , match = "not both" ):
51+ Webshare (base_url = server .base_url , api_key = "k" , credentials_provider = lambda : "t" )
52+ with pytest .raises (webshare .WebshareError , match = "not both" ):
53+ AsyncWebshare (base_url = server .base_url , api_key = "k" , credentials_provider = lambda : "t" )
54+
55+
4956def test_credentials_provider_called_per_request (server : MockServer ) -> None :
5057 tokens = iter (["token-1" , "token-2" ])
51- client = Webshare (base_url = server .base_url , credentials_provider = lambda : next (tokens ))
52- server .enqueue (json_body = PROFILE )
53- server .enqueue (json_body = PROFILE )
54- client .profile .get ()
55- client .profile .get ()
58+ with Webshare (base_url = server .base_url , credentials_provider = lambda : next (tokens )) as client :
59+ server .enqueue (json_body = PROFILE )
60+ server .enqueue (json_body = PROFILE )
61+ client .profile .get ()
62+ client .profile .get ()
5663 assert server .requests [0 ].headers ["Authorization" ] == "Token token-1"
5764 assert server .requests [1 ].headers ["Authorization" ] == "Token token-2"
5865
@@ -61,41 +68,40 @@ async def test_async_credentials_provider(server: MockServer) -> None:
6168 async def provider () -> str :
6269 return "async-token"
6370
64- client = AsyncWebshare (base_url = server .base_url , credentials_provider = provider )
65- server .enqueue (json_body = PROFILE )
66- profile = await client .profile .get ()
71+ async with AsyncWebshare (base_url = server .base_url , credentials_provider = provider ) as client :
72+ server .enqueue (json_body = PROFILE )
73+ profile = await client .profile .get ()
6774 assert profile .email == "user@webshare.io"
6875 assert server .requests [0 ].headers ["Authorization" ] == "Token async-token"
69- await client .close ()
7076
7177
7278def test_default_and_per_request_headers (server : MockServer ) -> None :
73- client = make_client (server , default_headers = {"X-Team" : "infra" })
74- server .enqueue (json_body = PROFILE )
75- client .profile .get (headers = {"X-Trace" : "abc" })
79+ with make_client (server , default_headers = {"X-Team" : "infra" }) as client :
80+ server .enqueue (json_body = PROFILE )
81+ client .profile .get (headers = {"X-Trace" : "abc" })
7682 request = server .requests [0 ]
7783 assert request .headers ["X-Team" ] == "infra"
7884 assert request .headers ["X-Trace" ] == "abc"
7985
8086
8187def test_subuser_and_federated_headers (server : MockServer ) -> None :
82- client = make_client (server , subuser_id = 7 , federated_user_id = 99 )
83- server .enqueue (json_body = {"count" : 0 , "next" : None , "previous" : None , "results" : []})
84- server .enqueue (json_body = {"count" : 0 , "next" : None , "previous" : None , "results" : []})
85- client .proxies .list (mode = "direct" )
86- assert server .requests [0 ].headers ["X-Subuser" ] == "7"
87- assert server .requests [0 ].headers ["X-Webshare-Federated-Access" ] == "99"
88- # Per-request values override client-level values.
89- client .proxies .list (mode = "direct" , subuser_id = 8 , federated_user_id = 100 )
90- assert server .requests [1 ].headers ["X-Subuser" ] == "8"
91- assert server .requests [1 ].headers ["X-Webshare-Federated-Access" ] == "100"
88+ with make_client (server , subuser_id = 7 , federated_user_id = 99 ) as client :
89+ server .enqueue (json_body = {"count" : 0 , "next" : None , "previous" : None , "results" : []})
90+ server .enqueue (json_body = {"count" : 0 , "next" : None , "previous" : None , "results" : []})
91+ client .proxies .list (mode = "direct" )
92+ assert server .requests [0 ].headers ["X-Subuser" ] == "7"
93+ assert server .requests [0 ].headers ["X-Webshare-Federated-Access" ] == "99"
94+ # Per-request values override client-level values.
95+ client .proxies .list (mode = "direct" , subuser_id = 8 , federated_user_id = 100 )
96+ assert server .requests [1 ].headers ["X-Subuser" ] == "8"
97+ assert server .requests [1 ].headers ["X-Webshare-Federated-Access" ] == "100"
9298
9399
94100def test_timeout_raises_api_timeout_error (server : MockServer ) -> None :
95101 server .enqueue (json_body = PROFILE , delay = 1.0 )
96- client = make_client (server , timeout = 0.1 , max_retries = 0 )
97- with pytest .raises (webshare .APITimeoutError ):
98- client .profile .get ()
102+ with make_client (server , timeout = 0.1 , max_retries = 0 ) as client :
103+ with pytest .raises (webshare .APITimeoutError ):
104+ client .profile .get ()
99105
100106
101107async def test_async_timeout (server : MockServer ) -> None :
@@ -105,6 +111,25 @@ async def test_async_timeout(server: MockServer) -> None:
105111 await client .profile .get (max_retries = 0 )
106112
107113
114+ def test_timeout_none_is_distinct_from_omitted () -> None :
115+ # Omitted -> the 60s default; explicit None -> no timeout at all.
116+ with Webshare (api_key = "k" ) as default_client :
117+ assert default_client .timeout == 60.0
118+ with Webshare (api_key = "k" , timeout = None ) as no_timeout_client :
119+ assert no_timeout_client .timeout is None
120+
121+
122+ def test_redirects_are_followed (server : MockServer ) -> None :
123+ # Owned httpx clients follow redirects, so a 3xx surfaces the real
124+ # response instead of a confusing decode error.
125+ server .enqueue (status = 302 , headers = {"Location" : f"{ server .base_url } /api/v2/profile/" })
126+ server .enqueue (json_body = PROFILE )
127+ with make_client (server ) as client :
128+ assert client .profile .get ().id == 1
129+ assert len (server .requests ) == 2
130+ assert server .requests [1 ].path == "/api/v2/profile/"
131+
132+
108133def test_context_manager_and_base_url_join (server : MockServer ) -> None :
109134 server .enqueue (json_body = PROFILE )
110135 with Webshare (base_url = server .base_url + "/" , api_key = "k" ) as client :
@@ -126,57 +151,58 @@ def test_empty_api_key_falls_back_to_environment(
126151) -> None :
127152 monkeypatch .setenv ("WEBSHARE_API_KEY" , "env-key" )
128153 server .enqueue (json_body = PROFILE )
129- client = Webshare (base_url = server .base_url , api_key = "" )
130- client .profile .get ()
154+ with Webshare (base_url = server .base_url , api_key = "" ) as client :
155+ client .profile .get ()
131156 assert server .requests [0 ].headers ["Authorization" ] == "Token env-key"
132157
133158
134159def test_unauthenticated_operations_never_send_token (server : MockServer ) -> None :
135160 # Even on a credentialed client, `security: []` operations do not send
136161 # the Authorization header.
137162 server .enqueue (json_body = {"referral_code" : "abc" , "promo_type" : None , "promo_value" : None })
138- client = make_client (server )
139- info = client .referral .get_code_info (referral_code = "abc" )
163+ with make_client (server ) as client :
164+ info = client .referral .get_code_info (referral_code = "abc" )
140165 assert info .referral_code == "abc"
141166 assert "Authorization" not in server .requests [0 ].headers
142167
143168
144169def test_unauthenticated_client (server : MockServer , monkeypatch : pytest .MonkeyPatch ) -> None :
145170 monkeypatch .delenv ("WEBSHARE_API_KEY" , raising = False )
146- client = Webshare (base_url = server .base_url , unauthenticated = True )
147- server .enqueue (json_body = {"referral_code" : "abc" , "promo_type" : None , "promo_value" : None })
148- client .referral .get_code_info (referral_code = "abc" )
149- assert "Authorization" not in server .requests [0 ].headers
150- # Authenticated operations fail client-side with a clear message.
151- with pytest .raises (webshare .WebshareError , match = "unauthenticated=True" ):
152- client .profile .get ()
171+ with Webshare (base_url = server .base_url , unauthenticated = True ) as client :
172+ server .enqueue (json_body = {"referral_code" : "abc" , "promo_type" : None , "promo_value" : None })
173+ client .referral .get_code_info (referral_code = "abc" )
174+ assert "Authorization" not in server .requests [0 ].headers
175+ # Authenticated operations fail client-side with a clear message.
176+ with pytest .raises (webshare .WebshareError , match = "unauthenticated=True" ):
177+ client .profile .get ()
153178 assert len (server .requests ) == 1
154179
155180
156181def test_source_header_default_format (server : MockServer ) -> None :
157182 import re
158183
159184 server .enqueue (json_body = PROFILE )
160- make_client (server ).profile .get ()
185+ with make_client (server ) as client :
186+ client .profile .get ()
161187 source = server .requests [0 ].headers ["X-Webshare-Source" ]
162188 assert re .fullmatch (r"WebshareSDK/\d+\.\d+\.\d+ \(Python; \d+\.\d+\.\d+[^)]*\)" , source ), source
163189
164190
165191def test_source_header_override (server : MockServer ) -> None :
166192 server .enqueue (json_body = PROFILE )
167193 server .enqueue (json_body = PROFILE )
168- client = make_client (server , source = "WebshareCLI/1.2.3" )
169- client .profile .get ()
170- assert server .requests [0 ].headers ["X-Webshare-Source" ] == "WebshareCLI/1.2.3"
171- # Per-request headers still win over everything.
172- client .profile .get (headers = {"X-Webshare-Source" : "custom/0" })
173- assert server .requests [1 ].headers ["X-Webshare-Source" ] == "custom/0"
194+ with make_client (server , source = "WebshareCLI/1.2.3" ) as client :
195+ client .profile .get ()
196+ assert server .requests [0 ].headers ["X-Webshare-Source" ] == "WebshareCLI/1.2.3"
197+ # Per-request headers still win over everything.
198+ client .profile .get (headers = {"X-Webshare-Source" : "custom/0" })
199+ assert server .requests [1 ].headers ["X-Webshare-Source" ] == "custom/0"
174200
175201
176202def test_default_headers_merge_case_insensitively (server : MockServer ) -> None :
177203 server .enqueue (json_body = PROFILE )
178- client = make_client (server , default_headers = {"accept" : "text/plain" })
179- client .profile .get ()
204+ with make_client (server , default_headers = {"accept" : "text/plain" }) as client :
205+ client .profile .get ()
180206 request = server .requests [0 ]
181207 accept_headers = [(k , v ) for k , v in request .raw_headers if k .lower () == "accept" ]
182208 assert accept_headers == [("accept" , "text/plain" )]
@@ -187,9 +213,9 @@ def test_injected_http_client_timeout_is_respected(server: MockServer) -> None:
187213
188214 server .enqueue (json_body = PROFILE , delay = 1.0 )
189215 http_client = httpx .Client (timeout = 0.1 )
190- client = Webshare (base_url = server .base_url , api_key = "k" , http_client = http_client )
191- with pytest .raises (webshare .APITimeoutError ):
192- client .profile .get (max_retries = 0 )
216+ with Webshare (base_url = server .base_url , api_key = "k" , http_client = http_client ) as client :
217+ with pytest .raises (webshare .APITimeoutError ):
218+ client .profile .get (max_retries = 0 )
193219 http_client .close ()
194220
195221
@@ -198,6 +224,8 @@ def test_explicit_timeout_overrides_injected_http_client(server: MockServer) ->
198224
199225 server .enqueue (json_body = PROFILE , delay = 0.3 )
200226 http_client = httpx .Client (timeout = 0.05 )
201- client = Webshare (base_url = server .base_url , api_key = "k" , http_client = http_client , timeout = 5.0 )
202- assert client .profile .get (max_retries = 0 ).id == 1
227+ with Webshare (
228+ base_url = server .base_url , api_key = "k" , http_client = http_client , timeout = 5.0
229+ ) as client :
230+ assert client .profile .get (max_retries = 0 ).id == 1
203231 http_client .close ()
0 commit comments