Mechanical only, and separated from the judgment calls that follow so the reviewable changes are not buried in a 98-file whitespace diff. 227 automatic fixes: 60 blank lines carrying whitespace, 60 unsorted import blocks, 34 Optional[X] to X | None, 28 unused imports, 16 deprecated typing imports, 12 datetime.timezone.utc to datetime.UTC, and assorted smaller modernisations. Then `ruff format` over src and tests: 98 files reformatted, 35 already conforming. No file among the unused-import findings defines __all__ or is an __init__.py, so nothing here removes a re-export. `make test`: 658 passed, unchanged from HEAD. Two things observed while verifying, neither addressed here: `pytest tests/` cannot collect — tests/e2e/test_orchestration_e2e.py uses an `e2e` marker that is not registered, and the config is strict about markers. This fails identically at HEAD, so it predates this change; `make test` passes because it ignores tests/e2e, tests/integration and tests/contracts. test_tatlock_tool_call_logging_calculator is flaky. It failed once in a full run with these changes and passed on the next, passes in isolation with them, and fails in isolation at HEAD. It is order- or timing-dependent, not a regression from this commit — established by running the full suite both ways rather than by reasoning about which change could have caused it. Co-Authored-By: Claude <noreply@anthropic.com>
281 lines
10 KiB
Python
281 lines
10 KiB
Python
"""
|
|
Tests for the memory service (direct access layer).
|
|
"""
|
|
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
import pytest
|
|
|
|
from src.core.memory_service import (
|
|
MemoryRecord,
|
|
MemoryService,
|
|
MemoryType,
|
|
memory_service,
|
|
)
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestMemoryType:
|
|
"""Tests for MemoryType enum."""
|
|
|
|
def test_user_profile_type(self):
|
|
"""Test user_profile type exists."""
|
|
assert MemoryType.USER_PROFILE.value == "user_profile"
|
|
|
|
def test_preference_type(self):
|
|
"""Test preference type exists."""
|
|
assert MemoryType.PREFERENCE.value == "preference"
|
|
|
|
def test_learned_fact_type(self):
|
|
"""Test learned_fact type exists."""
|
|
assert MemoryType.LEARNED_FACT.value == "learned_fact"
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestMemoryRecord:
|
|
"""Tests for MemoryRecord model."""
|
|
|
|
def test_create_minimal_record(self):
|
|
"""Test creating record with minimal fields."""
|
|
record = MemoryRecord(
|
|
id="test_1",
|
|
type=MemoryType.USER_PROFILE,
|
|
key="location",
|
|
value="Amsterdam",
|
|
)
|
|
|
|
assert record.id == "test_1"
|
|
assert record.type == MemoryType.USER_PROFILE
|
|
assert record.key == "location"
|
|
assert record.value == "Amsterdam"
|
|
assert record.importance == 0.5 # Default
|
|
assert record.source == "explicit" # Default
|
|
|
|
def test_create_full_record(self):
|
|
"""Test creating record with all fields."""
|
|
record = MemoryRecord(
|
|
id="test_2",
|
|
type=MemoryType.LEARNED_FACT,
|
|
key="car",
|
|
value="Tesla Model 3",
|
|
keywords=["car", "vehicle", "tesla"],
|
|
importance=0.8,
|
|
source="conversation",
|
|
)
|
|
|
|
assert record.keywords == ["car", "vehicle", "tesla"]
|
|
assert record.importance == 0.8
|
|
assert record.source == "conversation"
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestMemoryServiceInit:
|
|
"""Tests for MemoryService initialization."""
|
|
|
|
def test_service_has_lazy_clients(self):
|
|
"""Test service initializes with lazy client loading."""
|
|
service = MemoryService()
|
|
|
|
assert service._qdrant is None
|
|
assert service._embedding is None
|
|
assert service._cache is None
|
|
|
|
def test_global_instance_exists(self):
|
|
"""Test global memory_service instance exists."""
|
|
assert memory_service is not None
|
|
assert isinstance(memory_service, MemoryService)
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestMemoryServiceProfileMethods:
|
|
"""Tests for profile-related methods."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_profile_uses_context(self):
|
|
"""Test get_profile uses request context for user."""
|
|
service = MemoryService()
|
|
|
|
with patch.object(service, "_get_memory", new_callable=AsyncMock) as mock_get:
|
|
mock_get.return_value = "Amsterdam"
|
|
|
|
with patch("src.core.memory_service.get_user", return_value="testuser"):
|
|
result = await service.get_profile("location")
|
|
|
|
mock_get.assert_called_once_with("testuser", MemoryType.USER_PROFILE, "location")
|
|
assert result == "Amsterdam"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_profile_explicit_user(self):
|
|
"""Test get_profile with explicit user parameter."""
|
|
service = MemoryService()
|
|
|
|
with patch.object(service, "_get_memory", new_callable=AsyncMock) as mock_get:
|
|
mock_get.return_value = "Berlin"
|
|
|
|
result = await service.get_profile("location", user="otheruser")
|
|
|
|
mock_get.assert_called_once_with("otheruser", MemoryType.USER_PROFILE, "location")
|
|
assert result == "Berlin"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_set_profile_high_importance(self):
|
|
"""Test set_profile uses high importance (0.9)."""
|
|
service = MemoryService()
|
|
|
|
with patch.object(service, "_set_memory", new_callable=AsyncMock) as mock_set:
|
|
mock_set.return_value = True
|
|
|
|
with patch("src.core.memory_service.get_user", return_value="testuser"):
|
|
result = await service.set_profile("timezone", "Europe/Amsterdam")
|
|
|
|
call_kwargs = mock_set.call_args[1]
|
|
assert call_kwargs["importance"] == 0.9
|
|
assert result is True
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestMemoryServicePreferenceMethods:
|
|
"""Tests for preference-related methods."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_preference(self):
|
|
"""Test get_preference retrieves correctly."""
|
|
service = MemoryService()
|
|
|
|
with patch.object(service, "_get_memory", new_callable=AsyncMock) as mock_get:
|
|
mock_get.return_value = "celsius"
|
|
|
|
with patch("src.core.memory_service.get_user", return_value="testuser"):
|
|
result = await service.get_preference("temperature_unit")
|
|
|
|
mock_get.assert_called_once_with("testuser", MemoryType.PREFERENCE, "temperature_unit")
|
|
assert result == "celsius"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_set_preference_medium_importance(self):
|
|
"""Test set_preference uses medium importance (0.7)."""
|
|
service = MemoryService()
|
|
|
|
with patch.object(service, "_set_memory", new_callable=AsyncMock) as mock_set:
|
|
mock_set.return_value = True
|
|
|
|
with patch("src.core.memory_service.get_user", return_value="testuser"):
|
|
result = await service.set_preference("theme", "dark")
|
|
|
|
call_kwargs = mock_set.call_args[1]
|
|
assert call_kwargs["importance"] == 0.7
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestMemoryServiceFactMethods:
|
|
"""Tests for fact-related methods."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_store_fact_default_importance(self):
|
|
"""Test store_fact uses default importance (0.5)."""
|
|
service = MemoryService()
|
|
|
|
with patch.object(service, "_set_memory", new_callable=AsyncMock) as mock_set:
|
|
mock_set.return_value = True
|
|
|
|
with patch("src.core.memory_service.get_user", return_value="testuser"):
|
|
result = await service.store_fact("car", "Tesla Model 3")
|
|
|
|
call_kwargs = mock_set.call_args[1]
|
|
assert call_kwargs["importance"] == 0.5
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_store_fact_custom_importance(self):
|
|
"""Test store_fact with custom importance."""
|
|
service = MemoryService()
|
|
|
|
with patch.object(service, "_set_memory", new_callable=AsyncMock) as mock_set:
|
|
mock_set.return_value = True
|
|
|
|
with patch("src.core.memory_service.get_user", return_value="testuser"):
|
|
result = await service.store_fact(
|
|
"employer",
|
|
"Acme Corp",
|
|
importance=0.8,
|
|
)
|
|
|
|
call_kwargs = mock_set.call_args[1]
|
|
assert call_kwargs["importance"] == 0.8
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_fact(self):
|
|
"""Test get_fact retrieves correctly."""
|
|
service = MemoryService()
|
|
|
|
with patch.object(service, "_get_memory", new_callable=AsyncMock) as mock_get:
|
|
mock_get.return_value = "Tesla Model 3"
|
|
|
|
with patch("src.core.memory_service.get_user", return_value="testuser"):
|
|
result = await service.get_fact("car")
|
|
|
|
mock_get.assert_called_once_with("testuser", MemoryType.LEARNED_FACT, "car")
|
|
assert result == "Tesla Model 3"
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestMemoryServicePrefetch:
|
|
"""Tests for prefetch_context method."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_prefetch_default_keys(self):
|
|
"""Test prefetch with default profile keys."""
|
|
service = MemoryService()
|
|
|
|
with patch.object(service, "get_profile", new_callable=AsyncMock) as mock_profile:
|
|
with patch.object(service, "get_all_preferences", new_callable=AsyncMock) as mock_prefs:
|
|
mock_profile.side_effect = [
|
|
"Amsterdam", # location
|
|
"Europe/Amsterdam", # timezone
|
|
"John", # name
|
|
]
|
|
mock_prefs.return_value = {"temperature_unit": "celsius"}
|
|
|
|
with patch("src.core.memory_service.get_user", return_value="testuser"):
|
|
result = await service.prefetch_context()
|
|
|
|
assert result["profile"]["location"] == "Amsterdam"
|
|
assert result["profile"]["timezone"] == "Europe/Amsterdam"
|
|
assert result["profile"]["name"] == "John"
|
|
assert result["preferences"]["temperature_unit"] == "celsius"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_prefetch_specific_keys(self):
|
|
"""Test prefetch with specific profile keys."""
|
|
service = MemoryService()
|
|
|
|
with patch.object(service, "get_profile", new_callable=AsyncMock) as mock_profile:
|
|
with patch.object(service, "get_all_preferences", new_callable=AsyncMock) as mock_prefs:
|
|
mock_profile.return_value = "Amsterdam"
|
|
mock_prefs.return_value = {}
|
|
|
|
with patch("src.core.memory_service.get_user", return_value="testuser"):
|
|
result = await service.prefetch_context(
|
|
profile_keys=["location"],
|
|
include_preferences=False,
|
|
)
|
|
|
|
# Should only fetch location
|
|
mock_profile.assert_called_once()
|
|
mock_prefs.assert_not_called()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_prefetch_no_profile(self):
|
|
"""Test prefetch without profile data."""
|
|
service = MemoryService()
|
|
|
|
with patch.object(service, "get_profile", new_callable=AsyncMock) as mock_profile:
|
|
with patch.object(service, "get_all_preferences", new_callable=AsyncMock) as mock_prefs:
|
|
mock_prefs.return_value = {"theme": "dark"}
|
|
|
|
with patch("src.core.memory_service.get_user", return_value="testuser"):
|
|
result = await service.prefetch_context(include_profile=False)
|
|
|
|
mock_profile.assert_not_called()
|
|
assert "profile" not in result
|
|
assert result["preferences"]["theme"] == "dark"
|