from personal_agent.database.database import Database from personal_agent.memory.models import MemoryCategory from personal_agent.memory.store import LongTermMemoryStore from personal_agent.tools.memory import ( LongTermMemoryDeleteTool, LongTermMemorySaveTool, LongTermMemorySearchTool, ToolError, ) def create_store(tmp_path): database = Database(tmp_path / "test.db") database.migrate() return LongTermMemoryStore(database) def test_save_tool_persists_memory(tmp_path): store = create_store(tmp_path) tool = LongTermMemorySaveTool(store) result = tool.execute( { "content": "User prefers Rust for systems programming.", "category": "semantic", "importance": 2, } ) memories = store.list() assert len(memories) == 1 assert memories[0].content == "User prefers Rust for systems programming." assert memories[0].category is MemoryCategory.SEMANTIC assert memories[0].importance == 2 assert str(memories[0].id) in result.content def test_save_tool_rejects_empty_content(tmp_path): store = create_store(tmp_path) tool = LongTermMemorySaveTool(store) try: tool.execute({"content": " ", "category": "semantic"}) except ToolError as error: assert str(error) == "content must not be empty" else: raise AssertionError("Expected ToolError") def test_save_tool_rejects_invalid_category(tmp_path): store = create_store(tmp_path) tool = LongTermMemorySaveTool(store) try: tool.execute( { "content": "Something", "category": "invalid", "importance": 2, } ) except ToolError as error: assert str(error) == "category must be 'semantic' or 'episodic'" else: raise AssertionError("Expected ToolError") def test_search_tool_returns_matching_memories(tmp_path): store = create_store(tmp_path) save_tool = LongTermMemorySaveTool(store) search_tool = LongTermMemorySearchTool(store) save_tool.execute( { "content": "User prefers Rust for systems programming.", "category": "semantic", "importance": 2, } ) save_tool.execute( { "content": "User visited Tokyo last year.", "category": "episodic", "importance": 2, } ) result = search_tool.execute({"query": "Rust"}) assert "User prefers Rust for systems programming." in result.content assert "User visited Tokyo last year." not in result.content def test_search_tool_can_filter_category(tmp_path): store = create_store(tmp_path) save_tool = LongTermMemorySaveTool(store) search_tool = LongTermMemorySearchTool(store) save_tool.execute( { "content": "User likes Rust.", "category": "semantic", "importance": 2, } ) save_tool.execute( { "content": "User worked on a Rust project last summer.", "category": "episodic", "importance": 2, } ) result = search_tool.execute( { "query": "Rust", "category": "episodic", "importance": 2, } ) assert "worked on a Rust project" in result.content assert "User likes Rust." not in result.content def test_search_tool_returns_no_matches(tmp_path): store = create_store(tmp_path) search_tool = LongTermMemorySearchTool(store) result = search_tool.execute({"query": "nonexistent"}) assert result.content == "No matching memories found." def test_search_tool_rejects_empty_query(tmp_path): store = create_store(tmp_path) search_tool = LongTermMemorySearchTool(store) try: search_tool.execute({"query": " "}) except ToolError as error: assert str(error) == "query must not be empty" else: raise AssertionError("Expected ToolError") def test_delete_tool_removes_memory(tmp_path): store = create_store(tmp_path) save_tool = LongTermMemorySaveTool(store) delete_tool = LongTermMemoryDeleteTool(store) result = save_tool.execute( { "content": "User likes Rust.", "category": "semantic", "importance": 2, } ) memory_id = result.content.removeprefix("Saved memory: ") delete_result = delete_tool.execute({"id": memory_id}) assert delete_result.content == f"Deleted memory: {memory_id}" assert store.list() == [] def test_delete_tool_rejects_invalid_id(tmp_path): store = create_store(tmp_path) delete_tool = LongTermMemoryDeleteTool(store) try: delete_tool.execute({"id": "not-a-uuid"}) except ToolError as error: assert str(error) == "id must be a valid UUID" else: raise AssertionError("Expected ToolError") def test_save_tool_persists_importance(tmp_path): store = create_store(tmp_path) tool = LongTermMemorySaveTool(store) tool.execute( { "content": "Especially important user preference.", "category": "semantic", "importance": 3, } ) memories = store.list() assert len(memories) == 1 assert memories[0].importance == 3 def test_save_tool_rejects_invalid_importance(tmp_path): store = create_store(tmp_path) tool = LongTermMemorySaveTool(store) for importance in (0, 4): try: tool.execute( { "content": "Invalid importance.", "category": "semantic", "importance": importance, } ) except ToolError as error: assert str(error) == "importance must be between 1 and 3" else: raise AssertionError("Expected ToolError") def test_save_tool_rejects_non_integer_importance(tmp_path): store = create_store(tmp_path) tool = LongTermMemorySaveTool(store) try: tool.execute( { "content": "Invalid importance.", "category": "semantic", "importance": "3", } ) except ToolError as error: assert str(error) == "importance must be an integer" else: raise AssertionError("Expected ToolError") def test_save_tool_rejects_boolean_importance(tmp_path): store = create_store(tmp_path) tool = LongTermMemorySaveTool(store) try: tool.execute( { "content": "Invalid importance.", "category": "semantic", "importance": True, } ) except ToolError as error: assert str(error) == "importance must be an integer" else: raise AssertionError("Expected ToolError")