2323 _normalize_tag ,
2424 app ,
2525)
26+ from specify_cli .authentication .config import _default_config_path
2627
2728from tests .conftest import strip_ansi
2829
3132SENTINEL_GH_TOKEN = "SENTINEL-GH-TOKEN-VALUE"
3233SENTINEL_GITHUB_TOKEN = "SENTINEL-GITHUB-TOKEN-VALUE"
3334
35+ _RATE_LIMITED_REASON = (
36+ f"rate limited (configure { _default_config_path ()} with a GitHub token)"
37+ )
38+
3439
3540def _mock_urlopen_response (payload : dict ) -> MagicMock :
3641 body = json .dumps (payload ).encode ("utf-8" )
@@ -66,11 +71,20 @@ def test_prints_exactly_three_lines_and_exits_zero(self):
6671 ]
6772
6873 def test_stub_makes_no_network_call (self ):
69- # If the stub ever starts calling urllib, this patch's side_effect
70- # would fire and the assertion below would fail.
71- with patch (
72- "specify_cli.authentication.http.urllib.request.urlopen" ,
73- side_effect = AssertionError ("stub must not hit the network" ),
74+ # The stub must not hit the network via either urllib path:
75+ # unauthenticated requests use urlopen() directly; authenticated ones
76+ # go through build_opener(...).open(). Both are patched so that any
77+ # accidental network call raises immediately.
78+ network_error = AssertionError ("stub must not hit the network" )
79+ with (
80+ patch (
81+ "specify_cli.authentication.http.urllib.request.urlopen" ,
82+ side_effect = network_error ,
83+ ),
84+ patch (
85+ "specify_cli.authentication.http.urllib.request.build_opener" ,
86+ side_effect = network_error ,
87+ ),
7488 ):
7589 result = runner .invoke (app , ["self" , "upgrade" ])
7690 assert result .exit_code == 0
@@ -223,7 +237,7 @@ def test_403_maps_to_rate_limited(self):
223237 ):
224238 tag , reason = _fetch_latest_release_tag ()
225239 assert tag is None
226- assert reason == "rate limited (try setting GH_TOKEN and configuring ~/.specify/auth.json)"
240+ assert reason == _RATE_LIMITED_REASON
227241
228242 @pytest .mark .parametrize ("code" , [404 , 500 , 502 ])
229243 def test_other_http_uses_code_string (self , code ):
@@ -247,7 +261,7 @@ def test_generic_exception_propagates(self):
247261
248262_FAILURE_CASES = [
249263 ("offline or timeout" , urllib .error .URLError ("down" )),
250- ("rate limited (try setting GH_TOKEN and configuring ~/.specify/auth.json)" , _http_error (403 )),
264+ (_RATE_LIMITED_REASON , _http_error (403 )),
251265 ("HTTP 500" , _http_error (500 )),
252266]
253267
@@ -263,9 +277,8 @@ def test_failure_prints_installed_plus_one_line_reason(
263277 result = runner .invoke (app , ["self" , "check" ])
264278 output = strip_ansi (result .output )
265279 assert "Installed: 0.7.4" in output
266- if expected_reason == "rate limited (try setting GH_TOKEN and configuring ~/.specify/auth.json)" :
280+ if expected_reason == _RATE_LIMITED_REASON :
267281 assert "Could not check latest release: rate limited" in output
268- assert "GH_TOKEN" in output
269282 assert "auth.json" in output
270283 else :
271284 assert f"Could not check latest release: { expected_reason } " in output
0 commit comments