"""Tests for the Navi Code terminal client."""
from __future__ import annotations
import json
from pathlib import Path
import pytest
from click.testing import CliRunner
from clients.terminal.cli import main
from clients.terminal.config import Settings
from clients.terminal.render import Renderer
from clients.terminal.state import StateManager
class TestStateManager:
def test_load_missing_returns_empty(self, tmp_path: Path) -> None:
mgr = StateManager(tmp_path)
assert mgr.load() == {}
assert mgr.get_session_id() is None
def test_roundtrip_session_id(self, tmp_path: Path) -> None:
mgr = StateManager(tmp_path)
mgr.set_session_id("sess-123")
assert mgr.get_session_id() == "sess-123"
assert (tmp_path / "state.json").exists()
data = json.loads((tmp_path / "state.json").read_text())
assert data == {"session_id": "sess-123"}
def test_clear_session_id(self, tmp_path: Path) -> None:
mgr = StateManager(tmp_path)
mgr.set_session_id("sess-123")
mgr.clear_session_id()
assert mgr.get_session_id() is None
class TestRenderer:
def test_stream_delta_prints_inline(self, capsys) -> None:
renderer = Renderer()
renderer.render({"type": "stream_delta", "delta": "hello"})
captured = capsys.readouterr()
assert "hello" in captured.out
def test_error_prints_red(self, capsys) -> None:
renderer = Renderer()
renderer.render({"type": "error", "message": "boom"})
captured = capsys.readouterr()
assert "boom" in captured.out
def test_tool_started_shown_when_events_enabled(self, capsys) -> None:
renderer = Renderer(show_events=True)
renderer.render({"type": "tool_started", "tool": "terminal", "args": {"cmd": "ls"}})
captured = capsys.readouterr()
assert "terminal" in captured.out
def test_tool_started_hidden_when_events_disabled(self, capsys) -> None:
renderer = Renderer(show_events=False)
renderer.render({"type": "tool_started", "tool": "terminal", "args": {"cmd": "ls"}})
captured = capsys.readouterr()
assert captured.out == ""
class TestSettings:
def test_websocket_url_converts_http_to_ws(self) -> None:
s = Settings(base_url="http://localhost:8000")
assert s.websocket_url("abc") == "ws://localhost:8000/ws/sessions/abc"
def test_websocket_url_converts_https_to_wss(self) -> None:
s = Settings(base_url="https://navi.example.com")
assert s.websocket_url("abc") == "wss://navi.example.com/ws/sessions/abc"
def test_websocket_url_uses_explicit_ws_url(self) -> None:
s = Settings(ws_url="ws://custom:9000")
assert s.websocket_url("abc") == "ws://custom:9000/ws/sessions/abc"
class TestCliRunner:
def test_help_shows_usage(self) -> None:
runner = CliRunner()
result = runner.invoke(main, ["--help"])
assert result.exit_code == 0
assert "Navi Code" in result.output
def test_version_shows_version(self) -> None:
runner = CliRunner()
result = runner.invoke(main, ["--version"])
assert result.exit_code == 0
assert "0.1.0" in result.output
def test_raw_mode_uses_session_id_and_name_fields(
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
"""Raw CLI must read session_id/name/preview from the server API."""
class FakeWsClient:
def __init__(self, session_id: str, renderer=None, cwd=None) -> None:
self.session_id = session_id
self.cwd = cwd
async def run_one_shot(self, prompt: str) -> None:
pass
monkeypatch.setattr("clients.terminal.cli.NaviWebSocketClient", FakeWsClient)
from clients.terminal import config
original_state_dir = config.settings.state_dir
config.settings.state_dir = tmp_path
def fake_create_session(profile_id: str | None = None) -> dict:
return {
"session_id": "sess-raw-1234",
"profile_id": profile_id or "navi_code",
}
def fake_get_session(session_id: str) -> dict:
return {
"session_id": session_id,
"profile_id": "navi_code",
"name": "Raw session",
}
monkeypatch.setattr("clients.terminal.api.create_session", fake_create_session)
monkeypatch.setattr("clients.terminal.api.get_session", fake_get_session)
state = StateManager(tmp_path)
state.set_session_id("sess-raw-1234")
runner = CliRunner()
try:
result = runner.invoke(main, ["--raw", "--base-url", "http://localhost:8000", "hello"])
assert result.exit_code == 0
assert "Resumed session sess-raw" in result.output
finally:
config.settings.state_dir = original_state_dir