import logging from datetime import datetime, timezone from unittest.mock import MagicMock import pytest from mem0.memory.main import AsyncMemory, Memory, _normalize_iso_timestamp_to_utc def _setup_mocks(mocker): """Helper to setup common mocks for both sync and async fixtures""" mock_embedder = mocker.MagicMock() mock_embedder.return_value.embed.return_value = [0.1, 0.2, 0.3] mocker.patch("mem0.utils.factory.EmbedderFactory.create", mock_embedder) mock_vector_store = mocker.MagicMock() mock_vector_store.return_value.search.return_value = [] mocker.patch( "mem0.utils.factory.VectorStoreFactory.create", side_effect=[mock_vector_store.return_value, mocker.MagicMock()] ) mock_llm = mocker.MagicMock() mocker.patch("mem0.utils.factory.LlmFactory.create", mock_llm) mocker.patch("mem0.memory.storage.SQLiteManager", mocker.MagicMock()) return mock_llm, mock_vector_store class TestAddToVectorStoreErrors: @pytest.fixture def mock_memory(self, mocker): """Fixture that returns a Memory instance with mocker-based mocks""" mock_llm, _ = _setup_mocks(mocker) memory = Memory() memory.config = mocker.MagicMock() memory.config.custom_fact_extraction_prompt = None memory.config.custom_update_memory_prompt = None memory.api_version = "v1.1" return memory def test_empty_llm_response_fact_extraction(self, mocker, mock_memory, caplog): """Test empty response from LLM during fact extraction""" # Setup mock_memory.llm.generate_response.return_value = "invalid json" # This will trigger a JSON decode error mock_capture_event = mocker.MagicMock() mocker.patch("mem0.memory.main.capture_event", mock_capture_event) # Execute with caplog.at_level(logging.ERROR): result = mock_memory._add_to_vector_store( messages=[{"role": "user", "content": "test"}], metadata={}, filters={}, infer=True ) # Verify assert mock_memory.llm.generate_response.call_count == 1 assert result == [] # Should return empty list when no memories processed # Check for error message in any of the log records assert any("Error in new_retrieved_facts" in record.msg for record in caplog.records), "Expected error message not found in logs" assert mock_capture_event.call_count == 1 def test_empty_llm_response_memory_actions(self, mock_memory, caplog): """Test empty response from LLM during memory actions""" # Setup # First call returns valid JSON, second call returns empty string mock_memory.llm.generate_response.side_effect = ['{"facts": ["test fact"]}', ""] # Execute with caplog.at_level(logging.WARNING): result = mock_memory._add_to_vector_store( messages=[{"role": "user", "content": "test"}], metadata={}, filters={}, infer=True ) # Verify assert mock_memory.llm.generate_response.call_count == 2 assert result == [] # Should return empty list when no memories processed assert "Empty response from LLM, no memories to extract" in caplog.text @pytest.mark.asyncio class TestAsyncAddToVectorStoreErrors: @pytest.fixture def mock_async_memory(self, mocker): """Fixture for AsyncMemory with mocker-based mocks""" mock_llm, _ = _setup_mocks(mocker) memory = AsyncMemory() memory.config = mocker.MagicMock() memory.config.custom_fact_extraction_prompt = None memory.config.custom_update_memory_prompt = None memory.api_version = "v1.1" return memory @pytest.mark.asyncio async def test_async_empty_llm_response_fact_extraction(self, mock_async_memory, caplog, mocker): """Test empty response in AsyncMemory._add_to_vector_store""" mocker.patch("mem0.utils.factory.EmbedderFactory.create", return_value=MagicMock()) mock_async_memory.llm.generate_response.return_value = "invalid json" # This will trigger a JSON decode error mock_capture_event = mocker.MagicMock() mocker.patch("mem0.memory.main.capture_event", mock_capture_event) with caplog.at_level(logging.ERROR): result = await mock_async_memory._add_to_vector_store( messages=[{"role": "user", "content": "test"}], metadata={}, effective_filters={}, infer=True ) assert mock_async_memory.llm.generate_response.call_count == 1 assert result == [] # Check for error message in any of the log records assert any("Error in new_retrieved_facts" in record.msg for record in caplog.records), "Expected error message not found in logs" assert mock_capture_event.call_count == 1 @pytest.mark.asyncio async def test_async_empty_llm_response_memory_actions(self, mock_async_memory, caplog, mocker): """Test empty response in AsyncMemory._add_to_vector_store""" mocker.patch("mem0.utils.factory.EmbedderFactory.create", return_value=MagicMock()) mock_async_memory.llm.generate_response.side_effect = ['{"facts": ["test fact"]}', ""] mock_capture_event = mocker.MagicMock() mocker.patch("mem0.memory.main.capture_event", mock_capture_event) with caplog.at_level(logging.WARNING): result = await mock_async_memory._add_to_vector_store( messages=[{"role": "user", "content": "test"}], metadata={}, effective_filters={}, infer=True ) assert result == [] assert "Empty response from LLM, no memories to extract" in caplog.text assert mock_capture_event.call_count == 1 def _build_memory_instance(mocker, memory_cls): _setup_mocks(mocker) mocker.patch("mem0.memory.main.SQLiteManager", mocker.MagicMock()) mocker.patch("mem0.memory.main.MEM0_TELEMETRY", False) memory = memory_cls() memory.config = mocker.MagicMock() memory.config.custom_fact_extraction_prompt = None memory.config.custom_update_memory_prompt = None memory.api_version = "v1.1" memory.vector_store = mocker.MagicMock() memory.db = mocker.MagicMock() return memory def _assert_utc_timestamp(timestamp: str): parsed = datetime.fromisoformat(timestamp) assert parsed.tzinfo == timezone.utc assert parsed.utcoffset().total_seconds() == 0 def test_create_memory_uses_utc_timestamps(mocker): memory = _build_memory_instance(mocker, Memory) memory._create_memory("new memory", {"new memory": [0.1, 0.2, 0.3]}, metadata={}) payload = memory.vector_store.insert.call_args.kwargs["payloads"][0] _assert_utc_timestamp(payload["created_at"]) def test_update_memory_uses_utc_timestamps(mocker): memory = _build_memory_instance(mocker, Memory) memory.vector_store.get.return_value = MagicMock( payload={"data": "old memory", "created_at": "2026-03-17T17:00:00-07:00"} ) memory._update_memory("memory-id", "new memory", {"new memory": [0.1, 0.2, 0.3]}, metadata={}) payload = memory.vector_store.update.call_args.kwargs["payload"] assert payload["created_at"] == "2026-03-18T00:00:00+00:00" _assert_utc_timestamp(payload["updated_at"]) @pytest.mark.asyncio async def test_async_create_memory_uses_utc_timestamps(mocker): memory = _build_memory_instance(mocker, AsyncMemory) await memory._create_memory("new memory", {"new memory": [0.1, 0.2, 0.3]}, metadata={}) payload = memory.vector_store.insert.call_args.kwargs["payloads"][0] _assert_utc_timestamp(payload["created_at"]) @pytest.mark.asyncio async def test_async_update_memory_uses_utc_timestamps(mocker): memory = _build_memory_instance(mocker, AsyncMemory) memory.vector_store.get.return_value = MagicMock( payload={"data": "old memory", "created_at": "2026-03-17T17:00:00-07:00"} ) await memory._update_memory("memory-id", "new memory", {"new memory": [0.1, 0.2, 0.3]}, metadata={}) payload = memory.vector_store.update.call_args.kwargs["payload"] assert payload["created_at"] == "2026-03-18T00:00:00+00:00" _assert_utc_timestamp(payload["updated_at"]) def test_normalize_iso_timestamp_to_utc_preserves_naive_values(): assert _normalize_iso_timestamp_to_utc("2026-03-18T00:00:00") == "2026-03-18T00:00:00" def test_normalize_iso_timestamp_to_utc_converts_pacific(): result = _normalize_iso_timestamp_to_utc("2026-03-17T17:00:00-07:00") assert result == "2026-03-18T00:00:00+00:00" def test_normalize_iso_timestamp_to_utc_handles_none(): assert _normalize_iso_timestamp_to_utc(None) is None def test_normalize_iso_timestamp_to_utc_handles_empty(): assert _normalize_iso_timestamp_to_utc("") == ""