fleet-memory/hindsight-embed/tests/test_daemon_client.py
Nicolò Boschi d3302c95b9
feat: HindsightEmbedded python SDK (#293)
* feat: HindsightEmbedded python SDK

* feat: HindsightEmbedded python SDK

* fixes

* improve

* ci

* improvemnts

* fix test

* fix test

* fix: update tests to use Pydantic model attributes instead of dict access

- Fixed test_server_integration.py to access Pydantic model attributes directly
- Changed dict-style access (response["field"]) to attribute access (response.field)
- Fixed .get() calls on Pydantic models
- Updated recall() calls to access .results attribute
- Updated reflect() calls to access .text attribute
- Fixed test_list_banks to use namespace API instead of deleted default_api
- Fixed attribute shadowing in HindsightClient wrapper (renamed _*_api to _*_namespace)

* fix: add list() method to BanksAPI namespace

* fix: remove leftover async cleanup code from test_list_banks

* docs: remove Advanced Configuration section from embed.md
2026-02-04 14:41:19 +01:00

180 lines
7.2 KiB
Python

"""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