tests/test_cli_broker_errors.py
Rama main · réplica de lectura del repositorio original.
"""The `job` console entry point (`cli.main`) wraps the Typer app in a broker-
error boundary: a down / erroring / refusing broker yields a one-line stderr
diagnostic and a clean exit code, never a raw traceback (audit 2026-07-01, LOW —
most CLI commands previously tracebacked on BrokerUnreachable/ServerError/
Refusal; only a handful handled them). The boundary lives in `main()`, not
`app()`, so these tests drive `main()` directly (CliRunner invokes `app`).
"""
from __future__ import annotations
import subprocess
import sys
import pytest
import job_cli.cli as cli_mod
from jobd.client import BrokerRefusal, BrokerServerError, BrokerUnreachable
def _run_main_raising(exc, monkeypatch):
"""Replace the Typer `app` with a stub that raises `exc`, run `main()`, and
return the SystemExit code the boundary produced."""
def _boom():
raise exc
monkeypatch.setattr(cli_mod, "app", _boom)
with pytest.raises(SystemExit) as ei:
cli_mod.main()
return ei.value.code
def test_main_unreachable_exits_2_clean(monkeypatch, capsys):
code = _run_main_raising(BrokerUnreachable("ConnectError: connection refused"), monkeypatch)
assert code == 2
err = capsys.readouterr().err
assert "unreachable" in err.lower()
assert "job ping" in err # points the user at the diagnostic command
assert "Traceback" not in err
def test_main_server_error_exits_2_clean(monkeypatch, capsys):
code = _run_main_raising(BrokerServerError("boom", status_code=503), monkeypatch)
assert code == 2
err = capsys.readouterr().err
assert "503" in err
assert "Traceback" not in err
def test_main_refusal_exits_1_clean(monkeypatch, capsys):
code = _run_main_raising(
BrokerRefusal("no", status_code=409, detail="worker still online"),
monkeypatch,
)
assert code == 1
err = capsys.readouterr().err
assert "409" in err
assert "worker still online" in err
assert "Traceback" not in err
def test_main_passes_normal_exit_through(monkeypatch):
"""A normal command completion (SystemExit(0)) and a typer.Exit are NOT
intercepted by the broker boundary — only broker exceptions are."""
def _ok():
raise SystemExit(0)
monkeypatch.setattr(cli_mod, "app", _ok)
with pytest.raises(SystemExit) as ei:
cli_mod.main()
assert ei.value.code == 0
def test_real_entrypoint_unreachable_broker_no_traceback():
"""Real-execution check: the actual `job` entry point (`cli.main`, the
console-script target) run against a dead broker port prints a clean
diagnostic and exits 2 — no traceback. Drives the real argparse/httpx/
client-wrapping chain the unit tests stub out. Port 45789 is unbound on
loopback, so the connect is refused immediately."""
r = subprocess.run(
[sys.executable, "-c", "from job_cli.cli import main; main()", "workers"],
capture_output=True,
text=True,
timeout=30,
env={"PATH": "/usr/bin:/bin", "JOBD_URL": "http://127.0.0.1:45789"},
)
assert r.returncode == 2, f"exit={r.returncode} stderr={r.stderr}"
assert "unreachable" in r.stderr.lower()
assert "Traceback" not in r.stderr