test(cli): cover quiet-mode resume status lines routed to stderr

4 tests: session-not-found in quiet mode -> stderr; in full mode -> stdout
(unchanged); resumed banner in quiet mode -> stderr; has-no-messages in
quiet mode -> stderr.
This commit is contained in:
Teknium 2026-05-25 00:58:51 -07:00
parent 25295e7ac9
commit ffe11c14ec

View file

@ -0,0 +1,121 @@
"""Tests for /resume status lines going to stderr in quiet mode (#11793).
The fix in cli._init_agent routes three messages to stderr when
``tool_progress_mode == "off"`` (set by ``hermes chat --quiet``):
* "Session not found: ..."
* "↻ Resumed session ... (N user messages, M total messages)"
* "Session ... found but has no messages. Starting fresh."
Interactive mode (tool_progress_mode == "full") still uses ChatConsole.
"""
from datetime import datetime
from unittest.mock import MagicMock, patch
import pytest
from cli import HermesCLI
def _make_cli(quiet=False, session_id="20260524_111111_xyz", db=None):
"""Build a minimal HermesCLI bound to only what _init_agent needs for
the resume code path: _resumed, _session_db, conversation_history,
session_id, and tool_progress_mode."""
cli = HermesCLI.__new__(HermesCLI)
cli.session_id = session_id
cli._resumed = True
cli.conversation_history = []
cli._session_db = db
cli.tool_progress_mode = "off" if quiet else "full"
cli.session_start = datetime.now()
cli.agent = None
# We need _init_agent to reach the resume block (line ~4757) but not
# proceed into actual AIAgent construction. _ensure_runtime_credentials
# must return True (False returns early at line 4743). _install_tool_callbacks,
# _ensure_tirith_security are stubbed; the resume block will either return
# False (session-not-found) or reach the eventual AIAgent() call which
# we'll let raise — we only check stdout/stderr printed BEFORE that.
cli._install_tool_callbacks = lambda: None
cli._ensure_tirith_security = lambda: None
cli._ensure_runtime_credentials = lambda: True
return cli
class TestResumeQuietStderr:
def test_session_not_found_goes_to_stderr_in_quiet_mode(self, capsys):
db = MagicMock()
db.get_session.return_value = None
cli = _make_cli(quiet=True, db=db)
with patch("cli._prepare_deferred_agent_startup"):
result = cli._init_agent()
captured = capsys.readouterr()
assert result is False
# stdout must stay clean
assert "Session not found" not in captured.out
# the resume status goes to stderr
assert "Session not found" in captured.err
assert "hermes sessions list" in captured.err
def test_session_not_found_goes_to_stdout_in_full_mode(self, capsys):
db = MagicMock()
db.get_session.return_value = None
cli = _make_cli(quiet=False, db=db)
with patch("cli._prepare_deferred_agent_startup"):
result = cli._init_agent()
captured = capsys.readouterr()
assert result is False
# Interactive mode keeps the existing _cprint path → stdout.
assert "Session not found" in captured.out
def test_resumed_banner_goes_to_stderr_in_quiet_mode(self, capsys):
db = MagicMock()
db.get_session.return_value = {"id": "20260524_111111_xyz", "title": "demo"}
db.resolve_resume_session_id.return_value = "20260524_111111_xyz"
db.get_messages_as_conversation.return_value = [
{"role": "user", "content": "hi"},
{"role": "assistant", "content": "hey"},
]
db._conn = MagicMock() # for the reopen execute() call
cli = _make_cli(quiet=True, db=db)
# Stop _init_agent right after the resume banner: prevent it from
# constructing a real AIAgent (the next code path).
with patch("cli._prepare_deferred_agent_startup"):
try:
cli._init_agent()
except Exception:
# The post-resume agent-init machinery may fail in this
# stubbed context (no API key, no real config) — we only
# care about the printed banner that comes earlier.
pass
captured = capsys.readouterr()
# Banner on stderr — stdout stays clean for automation.
assert "↻ Resumed session" not in captured.out
assert "↻ Resumed session" in captured.err
assert "20260524_111111_xyz" in captured.err
assert "demo" in captured.err
def test_no_messages_goes_to_stderr_in_quiet_mode(self, capsys):
db = MagicMock()
db.get_session.return_value = {"id": "20260524_111111_xyz"}
db.resolve_resume_session_id.return_value = "20260524_111111_xyz"
db.get_messages_as_conversation.return_value = []
db._conn = MagicMock()
cli = _make_cli(quiet=True, db=db)
with patch("cli._prepare_deferred_agent_startup"):
try:
cli._init_agent()
except Exception:
pass
captured = capsys.readouterr()
assert "has no messages" not in captured.out
assert "has no messages" in captured.err
assert "Starting fresh" in captured.err