nanobot/tests/cli/test_interactive_retry_wait.py

199 lines
6.9 KiB
Python

from types import SimpleNamespace
from unittest.mock import patch
import pytest
from nanobot.cli import commands
@pytest.mark.asyncio
async def test_interactive_retry_wait_is_rendered_as_progress_even_when_progress_disabled():
"""Provider retry waits should not fall through as assistant responses."""
calls: list[tuple[str, object | None]] = []
thinking = None
channels_config = SimpleNamespace(send_progress=False, send_tool_hints=False)
msg = SimpleNamespace(
content="Model request failed, retry in 2s (attempt 1).",
metadata={"_retry_wait": True},
)
async def fake_print(text: str, active_thinking: object | None, renderer=None) -> None:
calls.append((text, active_thinking))
with patch("nanobot.cli.commands._print_interactive_progress_line", side_effect=fake_print):
handled = await commands._maybe_print_interactive_progress(
msg,
thinking,
channels_config,
)
assert handled is True
assert calls == [("Model request failed, retry in 2s (attempt 1).", thinking)]
@pytest.mark.asyncio
async def test_reasoning_displayed_when_show_reasoning_enabled():
"""Reasoning content should be displayed when show_reasoning is True."""
calls: list[str] = []
channels_config = SimpleNamespace(
send_progress=True, send_tool_hints=False, show_reasoning=True,
)
msg = SimpleNamespace(
content="Let me think about this...",
metadata={"_progress": True, "_reasoning": True},
)
with patch("nanobot.cli.commands._print_cli_reasoning", side_effect=lambda t, th, r=None: calls.append(t)):
handled = await commands._maybe_print_interactive_progress(msg, None, channels_config)
assert handled is True
assert calls == ["Let me think about this..."]
@pytest.mark.asyncio
async def test_reasoning_delta_displayed_when_show_reasoning_enabled():
"""Streamed reasoning delta frames should use the reasoning renderer."""
calls: list[str] = []
channels_config = SimpleNamespace(
send_progress=True, send_tool_hints=False, show_reasoning=True,
)
msg = SimpleNamespace(
content="I should search first.",
metadata={"_progress": True, "_reasoning_delta": True},
)
with patch("nanobot.cli.commands._print_cli_reasoning", side_effect=lambda t, th, r=None: calls.append(t)):
handled = await commands._maybe_print_interactive_progress(msg, None, channels_config)
assert handled is True
assert calls == ["I should search first."]
@pytest.mark.asyncio
async def test_reasoning_delta_buffers_until_sentence_boundary():
calls: list[str] = []
channels_config = SimpleNamespace(
send_progress=True, send_tool_hints=False, show_reasoning=True,
)
reasoning_buffer = commands._ReasoningBuffer()
with patch("nanobot.cli.commands._print_cli_reasoning", side_effect=lambda t, th, r=None: calls.append(t)):
first = await commands._maybe_print_interactive_progress(
SimpleNamespace(
content="The",
metadata={"_progress": True, "_reasoning_delta": True},
),
None,
channels_config,
reasoning_buffer=reasoning_buffer,
)
second = await commands._maybe_print_interactive_progress(
SimpleNamespace(
content=" user asked.",
metadata={"_progress": True, "_reasoning_delta": True},
),
None,
channels_config,
reasoning_buffer=reasoning_buffer,
)
assert first is True
assert second is True
assert calls == ["The user asked."]
@pytest.mark.asyncio
async def test_reasoning_end_flushes_buffered_delta():
calls: list[str] = []
channels_config = SimpleNamespace(
send_progress=True, send_tool_hints=False, show_reasoning=True,
)
reasoning_buffer = commands._ReasoningBuffer()
with patch("nanobot.cli.commands._print_cli_reasoning", side_effect=lambda t, th, r=None: calls.append(t)):
delta = await commands._maybe_print_interactive_progress(
SimpleNamespace(
content="The user asked",
metadata={"_progress": True, "_reasoning_delta": True},
),
None,
channels_config,
reasoning_buffer=reasoning_buffer,
)
end = await commands._maybe_print_interactive_progress(
SimpleNamespace(
content="",
metadata={"_progress": True, "_reasoning_end": True},
),
None,
channels_config,
reasoning_buffer=reasoning_buffer,
)
assert delta is True
assert end is True
assert calls == ["The user asked"]
@pytest.mark.asyncio
async def test_reasoning_hidden_when_show_reasoning_disabled():
"""Reasoning content should be suppressed when show_reasoning is False."""
channels_config = SimpleNamespace(
send_progress=True, send_tool_hints=False, show_reasoning=False,
)
msg = SimpleNamespace(
content="Let me think about this...",
metadata={"_progress": True, "_reasoning": True},
)
with patch("nanobot.cli.commands._print_cli_reasoning") as mock_reasoning:
handled = await commands._maybe_print_interactive_progress(msg, None, channels_config)
assert handled is True
mock_reasoning.assert_not_called()
@pytest.mark.asyncio
async def test_non_reasoning_progress_not_affected_by_show_reasoning():
"""Regular progress lines should display regardless of show_reasoning."""
calls: list[str] = []
channels_config = SimpleNamespace(
send_progress=True, send_tool_hints=False, show_reasoning=False,
)
msg = SimpleNamespace(
content="working on it...",
metadata={"_progress": True},
)
async def fake_print(text: str, thinking=None, renderer=None):
calls.append(text)
with patch("nanobot.cli.commands._print_interactive_progress_line", side_effect=fake_print):
handled = await commands._maybe_print_interactive_progress(msg, None, channels_config)
assert handled is True
assert calls == ["working on it..."]
@pytest.mark.asyncio
async def test_reasoning_shown_when_send_progress_disabled():
"""Reasoning display is governed by `show_reasoning` alone, independent
of `send_progress` — the two knobs are orthogonal."""
calls: list[str] = []
channels_config = SimpleNamespace(
send_progress=False, send_tool_hints=False, show_reasoning=True,
)
msg = SimpleNamespace(
content="Let me think about this...",
metadata={"_progress": True, "_reasoning": True},
)
with patch(
"nanobot.cli.commands._print_cli_reasoning",
side_effect=lambda t, th, r=None: calls.append(t),
):
handled = await commands._maybe_print_interactive_progress(msg, None, channels_config)
assert handled is True
assert calls == ["Let me think about this..."]