diff --git a/src/wingfoot/cli.py b/src/wingfoot/cli.py index cd82178..9e83c55 100644 --- a/src/wingfoot/cli.py +++ b/src/wingfoot/cli.py @@ -3,6 +3,7 @@ import argparse import sys +import urllib.error from urllib.parse import urlsplit from . import DIRECTORY_PATH, __version__ @@ -102,7 +103,13 @@ def cmd_sign(args) -> int: for k, v in signed.headers.items(): print(f"{k}: {v}") return 0 - resp = _http.request(args.url, method=args.method, headers=signed.headers) + try: + resp = _http.request(args.url, method=args.method, headers=signed.headers) + except urllib.error.URLError as exc: + # A connection failure (DNS, refused, timeout) has no HTTP status, so + # `http.request` lets it through. Report it cleanly instead of a traceback. + print(f"{C.red}Could not reach {args.url}{C.reset}: {exc.reason}", file=sys.stderr) + return 1 color = C.green if 200 <= resp.status < 300 else C.red print(f"{color}HTTP {resp.status}{C.reset}") for k, v in signed.headers.items(): diff --git a/tests/test_cli.py b/tests/test_cli.py index af4c351..e373792 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,4 +1,6 @@ """Bare `wingfoot` greets with the full help instead of an argparse error.""" +import urllib.error + import pytest from wingfoot.cli import main @@ -16,3 +18,26 @@ def test_unknown_command_still_errors(): with pytest.raises(SystemExit) as exc: main(["not-a-command"]) assert exc.value.code == 2 + + +def test_sign_reports_connection_error_cleanly(monkeypatch, capsys): + """`wingfoot sign ` should print a clean error and exit 1, + not surface a urllib traceback to the user.""" + def boom(*args, **kwargs): + raise urllib.error.URLError("Connection refused") + + monkeypatch.setattr("wingfoot.cli._http.request", boom) + rc = main(["sign", "http://127.0.0.1:1/"]) + assert rc == 1 + err = capsys.readouterr().err + assert "Could not reach http://127.0.0.1:1/" in err + assert "Connection refused" in err + + +def test_sign_print_only_needs_no_network(monkeypatch): + """--print-only must never touch the network.""" + def boom(*args, **kwargs): + raise AssertionError("network should not be used with --print-only") + + monkeypatch.setattr("wingfoot.cli._http.request", boom) + assert main(["sign", "https://example.com/", "--print-only"]) == 0