Spaces:
Paused
Paused
| """Tests for CLI background command TUI refresh behavior. | |
| Ensures the TUI is properly refreshed before printing background task output | |
| to prevent spinner/status bar overlap (#2718). | |
| """ | |
| import threading | |
| from types import SimpleNamespace | |
| from unittest.mock import MagicMock, patch | |
| import pytest | |
| from cli import HermesCLI | |
| def _make_cli(): | |
| """Create a minimal HermesCLI instance for testing.""" | |
| cli_obj = HermesCLI.__new__(HermesCLI) | |
| cli_obj.model = "test-model" | |
| cli_obj._background_tasks = {} | |
| cli_obj._background_task_counter = 0 | |
| cli_obj.conversation_history = [] | |
| cli_obj.agent = None | |
| cli_obj._app = None | |
| return cli_obj | |
| class TestBackgroundCommandTuiRefresh: | |
| """Tests for TUI refresh in background command output.""" | |
| def test_invalidate_called_before_success_output(self): | |
| """App.invalidate() is called before printing background success output.""" | |
| cli_obj = _make_cli() | |
| mock_app = MagicMock() | |
| cli_obj._app = mock_app | |
| # Track call order | |
| call_order = [] | |
| original_invalidate = mock_app.invalidate | |
| def track_invalidate(): | |
| call_order.append("invalidate") | |
| return original_invalidate() | |
| mock_app.invalidate = track_invalidate | |
| # Patch print to track when it's called | |
| with patch("builtins.print") as mock_print: | |
| mock_print.side_effect = lambda *args, **kwargs: call_order.append("print") | |
| # Simulate the background task output code path | |
| if cli_obj._app: | |
| cli_obj._app.invalidate() | |
| import time | |
| time.sleep(0.01) # reduced for test | |
| print() | |
| # Verify invalidate was called before print | |
| assert call_order[0] == "invalidate" | |
| assert "print" in call_order | |
| def test_invalidate_called_before_error_output(self): | |
| """App.invalidate() is called before printing background error output.""" | |
| cli_obj = _make_cli() | |
| mock_app = MagicMock() | |
| cli_obj._app = mock_app | |
| call_order = [] | |
| mock_app.invalidate.side_effect = lambda: call_order.append("invalidate") | |
| with patch("builtins.print") as mock_print: | |
| mock_print.side_effect = lambda *args, **kwargs: call_order.append("print") | |
| # Simulate error path | |
| if cli_obj._app: | |
| cli_obj._app.invalidate() | |
| import time | |
| time.sleep(0.01) | |
| print() | |
| assert call_order[0] == "invalidate" | |
| assert "print" in call_order | |
| def test_no_crash_when_app_is_none(self): | |
| """No crash when _app is None (non-TUI mode).""" | |
| cli_obj = _make_cli() | |
| cli_obj._app = None | |
| # This should not raise | |
| if cli_obj._app: | |
| cli_obj._app.invalidate() | |
| # If we get here without exception, test passes | |
| def test_background_task_thread_safety(self): | |
| """Background task tracking is thread-safe.""" | |
| cli_obj = _make_cli() | |
| # Simulate adding and removing background tasks | |
| task_id = "test_task_1" | |
| cli_obj._background_tasks[task_id] = MagicMock() | |
| assert task_id in cli_obj._background_tasks | |
| # Clean up | |
| cli_obj._background_tasks.pop(task_id, None) | |
| assert task_id not in cli_obj._background_tasks | |