"""Tests for daemon_client module.""" import os import subprocess from pathlib import Path from unittest.mock import MagicMock, Mock, patch import pytest from hindsight_embed import daemon_client @pytest.fixture def config(): """Default config for tests.""" return { "llm_api_key": "test-key", "llm_provider": "openai", "llm_model": "gpt-4o-mini", "bank_id": "test-bank", } @pytest.fixture def mock_cli_binary(tmp_path): """Create a mock CLI binary.""" cli_path = tmp_path / "hindsight" cli_path.write_text("#!/bin/bash\nexit 0") cli_path.chmod(0o755) return cli_path class TestRunCli: """Tests for run_cli function.""" def test_run_cli_with_external_api_url(self, config, mock_cli_binary, monkeypatch): """Test that external HINDSIGHT_EMBED_API_URL skips daemon startup.""" # Set up environment with external API URL external_api_url = "http://external-api:8000" monkeypatch.setenv("HINDSIGHT_EMBED_API_URL", external_api_url) # Mock functions mock_ensure_cli = Mock(return_value=True) mock_find_cli = Mock(return_value=mock_cli_binary) mock_ensure_daemon = Mock(return_value=True) mock_subprocess_run = Mock(return_value=Mock(returncode=0)) with ( patch.object(daemon_client, "ensure_cli_installed", mock_ensure_cli), patch.object(daemon_client, "find_cli_binary", mock_find_cli), patch.object(daemon_client, "ensure_daemon_running", mock_ensure_daemon), patch("subprocess.run", mock_subprocess_run), ): # Run CLI exit_code = daemon_client.run_cli(["memory", "recall", "test", "query"], config) # Verify daemon was NOT started (since external API URL is set) assert mock_ensure_daemon.call_count == 0 # Verify CLI was called assert mock_subprocess_run.call_count == 1 call_args = mock_subprocess_run.call_args # Verify environment contains the external API URL assert call_args.kwargs["env"]["HINDSIGHT_API_URL"] == external_api_url # Verify exit code assert exit_code == 0 def test_run_cli_without_external_api_url(self, config, mock_cli_binary, monkeypatch): """Test that without external API URL, daemon is started.""" # Ensure HINDSIGHT_EMBED_API_URL is not set monkeypatch.delenv("HINDSIGHT_EMBED_API_URL", raising=False) # Mock functions mock_ensure_cli = Mock(return_value=True) mock_find_cli = Mock(return_value=mock_cli_binary) mock_ensure_daemon = Mock(return_value=True) mock_subprocess_run = Mock(return_value=Mock(returncode=0)) with ( patch.object(daemon_client, "ensure_cli_installed", mock_ensure_cli), patch.object(daemon_client, "find_cli_binary", mock_find_cli), patch.object(daemon_client, "ensure_daemon_running", mock_ensure_daemon), patch("subprocess.run", mock_subprocess_run), ): # Run CLI exit_code = daemon_client.run_cli(["memory", "recall", "test", "query"], config) # Verify daemon WAS started (since no external API URL) assert mock_ensure_daemon.call_count == 1 assert mock_ensure_daemon.call_args[0][0] == config # Verify CLI was called assert mock_subprocess_run.call_count == 1 call_args = mock_subprocess_run.call_args # Verify environment contains the local daemon URL assert call_args.kwargs["env"]["HINDSIGHT_API_URL"] == daemon_client.get_daemon_url() # Verify exit code assert exit_code == 0 def test_run_cli_daemon_startup_failure(self, config, mock_cli_binary, monkeypatch): """Test that daemon startup failure is handled properly.""" # Ensure HINDSIGHT_EMBED_API_URL is not set monkeypatch.delenv("HINDSIGHT_EMBED_API_URL", raising=False) # Mock functions - daemon startup fails mock_ensure_cli = Mock(return_value=True) mock_find_cli = Mock(return_value=mock_cli_binary) mock_ensure_daemon = Mock(return_value=False) # Daemon fails to start with ( patch.object(daemon_client, "ensure_cli_installed", mock_ensure_cli), patch.object(daemon_client, "find_cli_binary", mock_find_cli), patch.object(daemon_client, "ensure_daemon_running", mock_ensure_daemon), ): # Run CLI exit_code = daemon_client.run_cli(["memory", "recall", "test", "query"], config) # Verify daemon startup was attempted assert mock_ensure_daemon.call_count == 1 # Verify exit code indicates failure assert exit_code == 1 def test_run_cli_without_cli_binary(self, config, monkeypatch): """Test that missing CLI binary is handled properly.""" # Ensure HINDSIGHT_EMBED_API_URL is not set monkeypatch.delenv("HINDSIGHT_EMBED_API_URL", raising=False) # Mock functions - CLI not installed mock_ensure_cli = Mock(return_value=True) mock_find_cli = Mock(return_value=None) # CLI not found with ( patch.object(daemon_client, "ensure_cli_installed", mock_ensure_cli), patch.object(daemon_client, "find_cli_binary", mock_find_cli), ): # Run CLI exit_code = daemon_client.run_cli(["memory", "recall", "test", "query"], config) # Verify exit code indicates failure assert exit_code == 1 def test_run_cli_with_api_token(self, config, mock_cli_binary, monkeypatch): """Test that HINDSIGHT_EMBED_API_TOKEN is passed through to the CLI.""" # Set up environment with external API URL and token external_api_url = "http://external-api:8000" api_token = "test-bearer-token-12345" monkeypatch.setenv("HINDSIGHT_EMBED_API_URL", external_api_url) monkeypatch.setenv("HINDSIGHT_EMBED_API_TOKEN", api_token) # Mock functions mock_ensure_cli = Mock(return_value=True) mock_find_cli = Mock(return_value=mock_cli_binary) mock_ensure_daemon = Mock(return_value=True) mock_subprocess_run = Mock(return_value=Mock(returncode=0)) with ( patch.object(daemon_client, "ensure_cli_installed", mock_ensure_cli), patch.object(daemon_client, "find_cli_binary", mock_find_cli), patch.object(daemon_client, "ensure_daemon_running", mock_ensure_daemon), patch("subprocess.run", mock_subprocess_run), ): # Run CLI exit_code = daemon_client.run_cli(["memory", "recall", "test", "query"], config) # Verify daemon was NOT started (since external API URL is set) assert mock_ensure_daemon.call_count == 0 # Verify CLI was called assert mock_subprocess_run.call_count == 1 call_args = mock_subprocess_run.call_args # Verify environment contains both the API URL and the API key assert call_args.kwargs["env"]["HINDSIGHT_API_URL"] == external_api_url assert call_args.kwargs["env"]["HINDSIGHT_API_KEY"] == api_token # Verify exit code assert exit_code == 0