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