diff --git a/project_name/tests/{% if cli_framework == 'cyclopts' %}test_cli.py{% endif %}.jinja b/project_name/tests/{% if cli_framework == 'cyclopts' %}test_cli.py{% endif %}.jinja index 1876e85..81c2b4a 100644 --- a/project_name/tests/{% if cli_framework == 'cyclopts' %}test_cli.py{% endif %}.jinja +++ b/project_name/tests/{% if cli_framework == 'cyclopts' %}test_cli.py{% endif %}.jinja @@ -1,7 +1,14 @@ import pytest from {{ package_name }} import __version__, cli -from {{ package_name }}.cli import EX_SOFTWARE, app, main +from {{ package_name }}.cli import ( + EX_NOINPUT, + EX_NOPERM, + EX_SOFTWARE, + EX_UNAVAILABLE, + app, + main, +) def test_version(capsys: pytest.CaptureFixture[str]) -> None: @@ -26,6 +33,32 @@ def test_main_usage_error() -> None: assert exc_info.value.code == 2 +@pytest.mark.parametrize( + ("error", "code"), + [ + (FileNotFoundError("missing.txt"), EX_NOINPUT), + (PermissionError("locked.txt"), EX_NOPERM), + (ConnectionError("down"), EX_UNAVAILABLE), + # a subclass lands on its parent's code + (ConnectionRefusedError("refused"), EX_UNAVAILABLE), + ], +) +def test_main_reported_error( + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture[str], + error: Exception, + code: int, +) -> None: + def explode(*_args: object, **_kwargs: object) -> None: + raise error + + monkeypatch.setattr(cli, "app", explode) + with pytest.raises(SystemExit) as exc_info: + main() + assert exc_info.value.code == code + assert capsys.readouterr().err == f"error: {error}\n" + + def test_main_unhandled_error(monkeypatch: pytest.MonkeyPatch) -> None: # Accept the call that `main` makes, so that the RuntimeError below is what # reaches it, rather than a TypeError over the signature.