Skip to content

Commit 9d663ab

Browse files
Added 100% test coverage
1 parent 39503fa commit 9d663ab

File tree

3 files changed

+90
-7
lines changed

3 files changed

+90
-7
lines changed

docs/examples/mem0-toolset.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,3 @@ await agent.run('My favorite color is red', deps='user_bob')
6565
## Learn More
6666

6767
For detailed documentation on the Mem0 integration, see the [Mem0 Memory Integration](../mem0.md) guide.
68-

pydantic_ai_slim/pydantic_ai/toolsets/mem0.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@
1818

1919
try:
2020
from mem0 import AsyncMemoryClient, MemoryClient
21-
except ImportError as _e:
22-
_import_error = _e
23-
AsyncMemoryClient = None
24-
MemoryClient = None
21+
except ImportError as _e: # pragma: no cover
22+
_import_error = _e # pragma: no cover
23+
AsyncMemoryClient = None # pragma: no cover
24+
MemoryClient = None # pragma: no cover
2525
else:
2626
_import_error = None
2727

tests/test_mem0_toolset.py

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,18 @@ def test_mem0_import_error():
3838
if _import_error is None:
3939
pytest.skip('mem0 is installed, skipping import error test')
4040

41-
with pytest.raises(ImportError, match='mem0 is not installed'):
42-
Mem0Toolset(api_key='test-key')
41+
with pytest.raises(ImportError, match='mem0 is not installed'): # pragma: no cover
42+
Mem0Toolset(api_key='test-key') # pragma: no cover
43+
44+
45+
def test_mem0_import_error_mocked():
46+
"""Test ImportError handling by mocking the import error."""
47+
from unittest.mock import patch
48+
49+
# Mock the import error scenario
50+
with patch('pydantic_ai.toolsets.mem0._import_error', new=ImportError('Mocked import error')):
51+
with pytest.raises(ImportError, match='mem0 is not installed'):
52+
Mem0Toolset(api_key='test-key')
4353

4454

4555
@pytest.mark.skipif(_import_error is not None, reason='mem0 is not installed')
@@ -56,6 +66,36 @@ async def test_mem0_toolset_initialization():
5666
assert toolset._is_async is True
5767

5868

69+
@pytest.mark.skipif(_import_error is not None, reason='mem0 is not installed')
70+
async def test_mem0_toolset_initialization_without_client():
71+
"""Test Mem0Toolset initialization without providing a client."""
72+
from unittest.mock import patch
73+
74+
# Mock the AsyncMemoryClient to avoid needing real API key
75+
with patch('pydantic_ai.toolsets.mem0.AsyncMemoryClient') as mock_client_class:
76+
mock_client = AsyncMock()
77+
mock_client_class.return_value = mock_client
78+
79+
# Initialize toolset without client (will create AsyncMemoryClient)
80+
toolset = Mem0Toolset(
81+
api_key='test-key',
82+
host='https://test.mem0.ai',
83+
org_id='test-org',
84+
project_id='test-project',
85+
)
86+
87+
# Verify AsyncMemoryClient was called with correct params
88+
mock_client_class.assert_called_once_with(
89+
api_key='test-key',
90+
host='https://test.mem0.ai',
91+
org_id='test-org',
92+
project_id='test-project',
93+
)
94+
95+
assert toolset.client is mock_client
96+
assert toolset._is_async is True
97+
98+
5999
@pytest.mark.skipif(_import_error is not None, reason='mem0 is not installed')
60100
async def test_mem0_toolset_tool_registration():
61101
"""Test that Mem0Toolset registers the expected tools."""
@@ -249,6 +289,34 @@ async def test_mem0_toolset_search_memory_unexpected_response():
249289
assert 'Error: Unexpected response format from Mem0' in result
250290

251291

292+
@pytest.mark.skipif(_import_error is not None, reason='mem0 is not installed')
293+
async def test_mem0_toolset_search_memory_with_non_dict_items():
294+
"""Test search_memory with non-dict items in results."""
295+
mock_client = AsyncMock()
296+
# Mix of dict and non-dict items
297+
mock_client.search = AsyncMock(
298+
return_value={
299+
'results': [
300+
{'memory': 'Valid memory', 'score': 0.9},
301+
'invalid_string_item', # This should be skipped
302+
{'memory': 'Another valid memory', 'score': 0.8},
303+
None, # This should also be skipped
304+
]
305+
}
306+
)
307+
308+
toolset = Mem0Toolset(client=mock_client)
309+
context = build_run_context('user_123')
310+
311+
result = await toolset._search_memory_impl(context, 'test')
312+
313+
# Only dict items should be included in the output
314+
assert 'Found relevant memories:' in result
315+
assert 'Valid memory (relevance: 0.90)' in result
316+
assert 'Another valid memory (relevance: 0.80)' in result
317+
assert 'invalid_string_item' not in result
318+
319+
252320
@pytest.mark.skipif(_import_error is not None, reason='mem0 is not installed')
253321
async def test_mem0_toolset_save_memory():
254322
"""Test the save_memory tool."""
@@ -336,6 +404,22 @@ async def test_mem0_toolset_sync_client():
336404
assert 'Successfully saved to memory' in result
337405

338406

407+
@pytest.mark.skipif(_import_error is not None, reason='mem0 is not installed')
408+
async def test_mem0_toolset_client_without_search():
409+
"""Test Mem0Toolset with a client that doesn't have search attribute."""
410+
411+
# Create a client without search attribute to test the _is_async=False path
412+
class MinimalClient:
413+
def add(self, messages: Any, user_id: Any) -> None:
414+
pass
415+
416+
mock_client = MinimalClient()
417+
toolset = Mem0Toolset(client=mock_client)
418+
419+
# Should detect it's not async because it doesn't have search method
420+
assert toolset._is_async is False
421+
422+
339423
@pytest.mark.skipif(_import_error is not None, reason='mem0 is not installed')
340424
async def test_mem0_toolset_with_dataclass_deps():
341425
"""Test Mem0Toolset with dataclass deps containing user_id."""

0 commit comments

Comments
 (0)