"""Unit tests for config wiring - verifying config options are actually used.
These tests follow TDD and verify that config options defined in RecallSettings
are actually wired up and used by the MCP tool handlers.
"""
import uuid
from unittest.mock import AsyncMock, patch
import pytest
from recall.config import RecallSettings
from recall.embedding.ollama import OllamaClient
from recall.storage.chromadb import ChromaStore
from recall.storage.hybrid import HybridStore
from recall.storage.sqlite import SQLiteStore
def unique_collection_name() -> str:
"""Generate a unique collection name for test isolation."""
return f"test_{uuid.uuid4().hex[:8]}"
@pytest.fixture
def mock_embedding_client():
"""Create mock OllamaClient for testing."""
client = AsyncMock(spec=OllamaClient)
client.embed.return_value = [0.1] * 1024
return client
@pytest.fixture
def ephemeral_store(mock_embedding_client):
"""Create HybridStore with ephemeral stores for testing."""
sqlite = SQLiteStore(ephemeral=True)
chroma = ChromaStore(ephemeral=True, collection_name=unique_collection_name())
store = HybridStore(
sqlite_store=sqlite,
chroma_store=chroma,
embedding_client=mock_embedding_client,
)
yield store
sqlite.close()
class TestDefaultNamespaceWiring:
"""Tests for default_namespace config wiring."""
@pytest.mark.asyncio
async def test_store_uses_config_default_namespace(self, ephemeral_store, monkeypatch):
"""Test that memory_store_tool uses default_namespace from config when namespace not specified."""
from recall.__main__ import memory_store_tool
# Set custom default namespace via env var
monkeypatch.setenv("RECALL_DEFAULT_NAMESPACE", "project:mydefault")
# Patch both the store and the default_namespace global
with (
patch("recall.__main__.hybrid_store", ephemeral_store),
patch("recall.__main__.default_namespace", "project:mydefault"),
):
result = await memory_store_tool(
content="Test memory without explicit namespace",
memory_type="preference",
# No namespace provided - should use config default
)
assert result["success"] is True
# Verify the memory was stored with the config default namespace
memory_id = result["id"]
memory = await ephemeral_store.get_memory(memory_id)
assert memory is not None
assert memory["namespace"] == "project:mydefault"
@pytest.mark.asyncio
async def test_store_explicit_namespace_overrides_default(self, ephemeral_store):
"""Test that explicit namespace parameter overrides config default."""
from recall.__main__ import memory_store_tool
with (
patch("recall.__main__.hybrid_store", ephemeral_store),
patch("recall.__main__.default_namespace", "project:default"),
):
result = await memory_store_tool(
content="Test memory with explicit namespace",
memory_type="preference",
namespace="project:explicit", # Explicit namespace
)
assert result["success"] is True
memory = await ephemeral_store.get_memory(result["id"])
assert memory["namespace"] == "project:explicit" # Should use explicit, not default
class TestDefaultImportanceWiring:
"""Tests for default_importance config wiring."""
@pytest.mark.asyncio
async def test_store_uses_config_default_importance(self, ephemeral_store):
"""Test that memory_store_tool uses default_importance from config when importance not specified."""
from recall.__main__ import memory_store_tool
# Patch with custom default importance
with (
patch("recall.__main__.hybrid_store", ephemeral_store),
patch("recall.__main__.default_importance", 0.75),
):
result = await memory_store_tool(
content="Test memory without explicit importance",
memory_type="decision",
# No importance provided - should use config default
)
assert result["success"] is True
memory = await ephemeral_store.get_memory(result["id"])
assert memory is not None
assert memory["importance"] == 0.75
@pytest.mark.asyncio
async def test_store_explicit_importance_overrides_default(self, ephemeral_store):
"""Test that explicit importance parameter overrides config default."""
from recall.__main__ import memory_store_tool
with (
patch("recall.__main__.hybrid_store", ephemeral_store),
patch("recall.__main__.default_importance", 0.75),
):
result = await memory_store_tool(
content="Test memory with explicit importance",
memory_type="decision",
importance=0.9, # Explicit importance
)
assert result["success"] is True
memory = await ephemeral_store.get_memory(result["id"])
assert memory["importance"] == 0.9 # Should use explicit, not default
class TestDefaultTokenBudgetWiring:
"""Tests for default_token_budget config wiring."""
@pytest.mark.asyncio
async def test_context_uses_config_default_token_budget(self, ephemeral_store):
"""Test that memory_context_tool uses default_token_budget from config when not specified."""
from recall.__main__ import memory_context_tool, memory_store_tool
with (
patch("recall.__main__.hybrid_store", ephemeral_store),
patch("recall.__main__.default_token_budget", 8000),
):
# Store some test content
await memory_store_tool(
content="Test memory for context",
memory_type="preference",
)
# Get context without explicit token_budget
result = await memory_context_tool(
query="test",
# No token_budget provided - should use config default
)
assert result["success"] is True
# The token_estimate should respect the larger budget
# We can't directly verify the budget was used, but we verify no error
@pytest.mark.asyncio
async def test_context_explicit_token_budget_overrides_default(self, ephemeral_store):
"""Test that explicit token_budget parameter overrides config default."""
from recall.__main__ import memory_context_tool, memory_store_tool
with (
patch("recall.__main__.hybrid_store", ephemeral_store),
patch("recall.__main__.default_token_budget", 8000),
):
await memory_store_tool(
content="Test memory for context with explicit budget",
memory_type="preference",
)
# Get context with explicit small token_budget
result = await memory_context_tool(
query="test",
token_budget=1000, # Explicit budget
)
assert result["success"] is True
# Token estimate should respect explicit budget
assert result["token_estimate"] <= 1000
class TestConfigWiringIntegration:
"""Integration tests verifying complete config wiring flow."""
def test_settings_have_correct_defaults(self):
"""Verify RecallSettings has correct default values."""
settings = RecallSettings()
assert settings.default_namespace == "global"
assert settings.default_importance == 0.5
assert settings.default_token_budget == 4000
def test_settings_env_override_defaults(self, monkeypatch):
"""Verify environment variables override defaults."""
monkeypatch.setenv("RECALL_DEFAULT_NAMESPACE", "project:custom")
monkeypatch.setenv("RECALL_DEFAULT_IMPORTANCE", "0.8")
monkeypatch.setenv("RECALL_DEFAULT_TOKEN_BUDGET", "6000")
settings = RecallSettings()
assert settings.default_namespace == "project:custom"
assert settings.default_importance == 0.8
assert settings.default_token_budget == 6000
@pytest.mark.asyncio
async def test_full_config_wiring_e2e(self, ephemeral_store, monkeypatch):
"""End-to-end test that config flows from env vars to tool behavior."""
from recall.__main__ import memory_store_tool
# Set custom config via environment
monkeypatch.setenv("RECALL_DEFAULT_NAMESPACE", "project:e2e_test")
monkeypatch.setenv("RECALL_DEFAULT_IMPORTANCE", "0.65")
# These patches simulate what main() would do after reading config
with (
patch("recall.__main__.hybrid_store", ephemeral_store),
patch("recall.__main__.default_namespace", "project:e2e_test"),
patch("recall.__main__.default_importance", 0.65),
):
result = await memory_store_tool(
content="E2E test memory using config defaults",
memory_type="pattern",
# No namespace or importance specified
)
assert result["success"] is True
memory = await ephemeral_store.get_memory(result["id"])
assert memory["namespace"] == "project:e2e_test"
assert memory["importance"] == 0.65