"""Tests for GrokBackend in src/timmy/backends.py and Grok dashboard routes.""" from unittest.mock import MagicMock, patch # ── grok_available ─────────────────────────────────────────────────────────── def test_grok_available_false_when_disabled(): """Grok not available when GROK_ENABLED is false.""" with patch("config.settings") as mock_settings: mock_settings.grok_enabled = False mock_settings.xai_api_key = "xai-test-key" from timmy.backends import grok_available assert grok_available() is False def test_grok_available_false_when_no_key(): """Grok not available when XAI_API_KEY is empty.""" with patch("config.settings") as mock_settings: mock_settings.grok_enabled = True mock_settings.xai_api_key = "" from timmy.backends import grok_available assert grok_available() is False def test_grok_available_true_when_enabled_and_key_set(): """Grok available when both enabled and key are set.""" with patch("config.settings") as mock_settings: mock_settings.grok_enabled = True mock_settings.xai_api_key = "xai-test-key" from timmy.backends import grok_available assert grok_available() is True # ── GrokBackend construction ──────────────────────────────────────────────── def test_grok_backend_init_with_explicit_params(): """GrokBackend can be created with explicit api_key and model.""" from timmy.backends import GrokBackend backend = GrokBackend(api_key="xai-test", model="grok-3-fast") assert backend._api_key == "xai-test" assert backend._model == "grok-3-fast" assert backend.stats.total_requests == 0 def test_grok_backend_init_from_settings(): """GrokBackend reads from config.settings when no params given.""" with patch("config.settings") as mock_settings: mock_settings.xai_api_key = "xai-from-env" mock_settings.grok_default_model = "grok-3" from timmy.backends import GrokBackend backend = GrokBackend() assert backend._api_key == "xai-from-env" assert backend._model == "grok-3" def test_grok_backend_run_no_key_returns_error(): """run() gracefully returns error message when no API key.""" from timmy.backends import GrokBackend backend = GrokBackend(api_key="", model="grok-3-fast") result = backend.run("hello") assert "not configured" in result.content def test_grok_backend_run_success(): """run() returns content from the API on success.""" from timmy.backends import GrokBackend backend = GrokBackend(api_key="xai-test", model="grok-3-fast") mock_response = MagicMock() mock_response.choices = [MagicMock()] mock_response.choices[0].message.content = "Grok says hello" mock_response.usage = MagicMock() mock_response.usage.prompt_tokens = 10 mock_response.usage.completion_tokens = 5 mock_response.model = "grok-3-fast" mock_client = MagicMock() mock_client.chat.completions.create.return_value = mock_response with patch.object(backend, "_get_client", return_value=mock_client): result = backend.run("hello") assert result.content == "Grok says hello" assert backend.stats.total_requests == 1 assert backend.stats.total_prompt_tokens == 10 assert backend.stats.total_completion_tokens == 5 def test_grok_backend_run_api_error(): """run() returns error message on API failure.""" from timmy.backends import GrokBackend backend = GrokBackend(api_key="xai-test", model="grok-3-fast") mock_client = MagicMock() mock_client.chat.completions.create.side_effect = Exception("API timeout") with patch.object(backend, "_get_client", return_value=mock_client): result = backend.run("hello") assert "unavailable" in result.content assert backend.stats.errors == 1 def test_grok_backend_history_management(): """GrokBackend maintains conversation history.""" from timmy.backends import GrokBackend backend = GrokBackend(api_key="xai-test", model="grok-3-fast") mock_response = MagicMock() mock_response.choices = [MagicMock()] mock_response.choices[0].message.content = "response" mock_response.usage = MagicMock() mock_response.usage.prompt_tokens = 10 mock_response.usage.completion_tokens = 5 mock_client = MagicMock() mock_client.chat.completions.create.return_value = mock_response with patch.object(backend, "_get_client", return_value=mock_client): backend.run("first message") backend.run("second message") assert len(backend._history) == 4 # 2 user + 2 assistant assert backend._history[0]["role"] == "user" assert backend._history[1]["role"] == "assistant" def test_grok_backend_health_check_no_key(): """health_check() returns not-ok when no API key.""" from timmy.backends import GrokBackend backend = GrokBackend(api_key="", model="grok-3-fast") health = backend.health_check() assert health["ok"] is False assert "not configured" in health["error"] def test_grok_backend_health_check_success(): """health_check() returns ok when API key is set and models endpoint works.""" from timmy.backends import GrokBackend backend = GrokBackend(api_key="xai-test", model="grok-3-fast") mock_client = MagicMock() mock_client.models.list.return_value = [] with patch.object(backend, "_get_client", return_value=mock_client): health = backend.health_check() assert health["ok"] is True assert health["backend"] == "grok" def test_grok_backend_estimated_cost(): """estimated_cost property calculates sats from token usage.""" from timmy.backends import GrokUsageStats stats = GrokUsageStats( total_prompt_tokens=1_000_000, total_completion_tokens=500_000, ) # Input: 1M tokens * $5/1M = $5 # Output: 500K tokens * $15/1M = $7.50 # Total: $12.50 / $0.001 = 12,500 sats assert stats.estimated_cost_sats == 12500 def test_grok_backend_build_messages(): """_build_messages includes system prompt and history.""" from timmy.backends import GrokBackend backend = GrokBackend(api_key="xai-test", model="grok-3-fast") backend._history = [ {"role": "user", "content": "previous"}, {"role": "assistant", "content": "yes"}, ] messages = backend._build_messages("new question") assert messages[0]["role"] == "system" assert messages[1]["role"] == "user" assert messages[1]["content"] == "previous" assert messages[-1]["role"] == "user" assert messages[-1]["content"] == "new question" def test_grok_prompt_contains_formatted_model_name(): """Grok prompt should have actual model name, not literal {model_name}.""" with patch("config.settings") as mock_settings: mock_settings.ollama_model = "llama3.2:3b" from timmy.backends import GrokBackend backend = GrokBackend(api_key="xai-test", model="grok-3-fast") messages = backend._build_messages("test message") system_prompt = messages[0]["content"] # Should contain the actual model name, not the placeholder assert "{model_name}" not in system_prompt assert "llama3.2:3b" in system_prompt def test_grok_prompt_gets_full_tier(): """Grok should get FULL tier prompt (tools_enabled=True).""" with patch("config.settings") as mock_settings: mock_settings.ollama_model = "test-model" from timmy.backends import GrokBackend backend = GrokBackend(api_key="xai-test", model="grok-3-fast") messages = backend._build_messages("test message") system_prompt = messages[0]["content"] # FULL tier should have TOOL USAGE section assert "TOOL USAGE" in system_prompt # FULL tier should have the full voice and brevity section assert "VOICE AND BREVITY" in system_prompt def test_grok_prompt_contains_session_id(): """Grok prompt should have session_id formatted, not placeholder.""" with patch("config.settings") as mock_settings: mock_settings.ollama_model = "test-model" from timmy.backends import GrokBackend backend = GrokBackend(api_key="xai-test", model="grok-3-fast") messages = backend._build_messages("test message") system_prompt = messages[0]["content"] # Should contain the session_id, not the placeholder assert '{session_id}"' not in system_prompt assert 'session "grok"' in system_prompt # ── get_grok_backend singleton ────────────────────────────────────────────── def test_get_grok_backend_returns_singleton(): """get_grok_backend returns the same instance on repeated calls.""" import timmy.backends as backends_mod # Reset singleton backends_mod._grok_backend = None b1 = backends_mod.get_grok_backend() b2 = backends_mod.get_grok_backend() assert b1 is b2 # Cleanup backends_mod._grok_backend = None # ── GROK_MODELS constant ─────────────────────────────────────────────────── def test_grok_models_dict_has_expected_entries(): from timmy.backends import GROK_MODELS assert "grok-3-fast" in GROK_MODELS assert "grok-3" in GROK_MODELS # ── consult_grok tool ────────────────────────────────────────────────────── def test_consult_grok_returns_unavailable_when_disabled(): """consult_grok tool returns error when Grok is not available.""" with patch("timmy.backends.grok_available", return_value=False): from timmy.tools import consult_grok result = consult_grok("test query") assert "not available" in result def test_consult_grok_calls_backend_when_available(): """consult_grok tool calls the Grok backend when available.""" from timmy.backends import RunResult mock_backend = MagicMock() mock_backend.run.return_value = RunResult(content="Grok answer") mock_backend.stats = MagicMock() mock_backend.stats.total_latency_ms = 100 with ( patch("timmy.backends.grok_available", return_value=True), patch("timmy.backends.get_grok_backend", return_value=mock_backend), patch("config.settings") as mock_settings, ): mock_settings.grok_free = True mock_settings.grok_enabled = True mock_settings.xai_api_key = "xai-test" from timmy.tools import consult_grok result = consult_grok("complex question") assert "Grok answer" in result mock_backend.run.assert_called_once_with("complex question") # ── Grok dashboard route tests ───────────────────────────────────────────── def test_grok_status_endpoint(client): """GET /grok/status returns HTML dashboard page.""" response = client.get("/grok/status") assert response.status_code == 200 assert "text/html" in response.headers.get("content-type", "") # Verify key status info is present in the rendered HTML text = response.text assert "Grok Status" in text assert "Status" in text def test_grok_toggle_returns_html(client): """POST /grok/toggle returns HTML response.""" response = client.post("/grok/toggle") assert response.status_code == 200 def test_grok_stats_endpoint(client): """GET /grok/stats returns usage statistics.""" response = client.get("/grok/stats") assert response.status_code == 200 data = response.json() assert "total_requests" in data or "error" in data def test_grok_chat_without_key(client): """POST /grok/chat returns error when Grok is not available.""" response = client.post( "/grok/chat", data={"message": "test query"}, ) assert response.status_code == 200 # Should contain error since GROK_ENABLED is false in test mode assert ( "not available" in response.text.lower() or "error" in response.text.lower() or "grok" in response.text.lower() )