Skip to content

Commit 3b0fc62

Browse files
Copilotmnriem
andauthored
fix: use resolved config path in warning/error messages and patch build_opener in no-network test
Agent-Logs-Url: https://github.com/github/spec-kit/sessions/86df9557-54f1-4fe4-a25f-9501cb2356cf Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
1 parent 105f03b commit 3b0fc62

3 files changed

Lines changed: 29 additions & 12 deletions

File tree

src/specify_cli/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1721,6 +1721,7 @@ def _fetch_latest_release_tag() -> tuple[str | None, str | None]:
17211721
On anything else — including a malformed response body — the exception
17221722
propagates; there is no catch-all (research D-006).
17231723
"""
1724+
from .authentication.config import _default_config_path as _auth_config_path
17241725
from .authentication.http import open_url
17251726

17261727
try:
@@ -1737,7 +1738,9 @@ def _fetch_latest_release_tag() -> tuple[str | None, str | None]:
17371738
except urllib.error.HTTPError as e:
17381739
# Order matters: HTTPError is a subclass of URLError.
17391740
if e.code == 403:
1740-
return None, "rate limited (try setting GH_TOKEN and configuring ~/.specify/auth.json)"
1741+
return None, (
1742+
f"rate limited (configure {_auth_config_path()} with a GitHub token)"
1743+
)
17411744
return None, f"HTTP {e.code}"
17421745
except (urllib.error.URLError, OSError):
17431746
return None, "offline or timeout"

src/specify_cli/authentication/http.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from urllib.parse import urlparse
1818

1919
from . import get_provider
20-
from .config import AuthConfigEntry, find_entries_for_url, load_auth_config
20+
from .config import AuthConfigEntry, _default_config_path, find_entries_for_url, load_auth_config
2121

2222

2323
_config_override: list[AuthConfigEntry] | None = None
@@ -31,8 +31,9 @@ def _load_config() -> list[AuthConfigEntry]:
3131
return load_auth_config()
3232
except (ValueError, OSError) as exc:
3333
import warnings
34+
config_path = _default_config_path()
3435
warnings.warn(
35-
f"Failed to load ~/.specify/auth.json: {exc}. "
36+
f"Failed to load {config_path}: {exc}. "
3637
"All requests will be unauthenticated.",
3738
UserWarning,
3839
stacklevel=2,

tests/test_upgrade.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
_normalize_tag,
2424
app,
2525
)
26+
from specify_cli.authentication.config import _default_config_path
2627

2728
from tests.conftest import strip_ansi
2829

@@ -31,6 +32,10 @@
3132
SENTINEL_GH_TOKEN = "SENTINEL-GH-TOKEN-VALUE"
3233
SENTINEL_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

3540
def _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

Comments
 (0)