From ac08a8d02480858d5a331e478691bf65a7474ca3 Mon Sep 17 00:00:00 2001 From: phernandez Date: Tue, 3 Jun 2025 09:08:17 -0500 Subject: [PATCH] fix: update FastMCP initialization for API changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove deprecated auth_server_provider parameter - Use auth parameter correctly with OAuthProvider instead of AuthSettings - Fixes type error after dependency updates 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- ...cc7172b46608_update_search_index_schema.py | 1 - .../api/routers/knowledge_router.py | 2 +- .../api/routers/project_router.py | 8 +++- src/basic_memory/config.py | 2 +- src/basic_memory/mcp/server.py | 3 +- src/basic_memory/mcp/tools/move_note.py | 2 +- src/basic_memory/services/project_service.py | 3 +- src/basic_memory/services/search_service.py | 2 +- src/basic_memory/sync/sync_service.py | 6 ++- test-int/conftest.py | 22 ++++++--- tests/api/conftest.py | 2 +- tests/api/test_knowledge_router.py | 2 +- tests/api/test_project_router.py | 18 ++++---- tests/cli/conftest.py | 2 +- tests/cli/test_cli_tools.py | 2 +- tests/cli/test_project_info.py | 2 - tests/conftest.py | 46 ++++++++++++------- tests/mcp/test_tool_move_note.py | 19 ++++++-- tests/services/test_entity_service.py | 9 ++-- tests/services/test_project_service.py | 39 +++++++++------- tests/services/test_search_service.py | 20 ++++---- tests/sync/test_sync_service.py | 13 ++++-- 22 files changed, 135 insertions(+), 90 deletions(-) diff --git a/src/basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py b/src/basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py index c4e8f6d0..f39a13a5 100644 --- a/src/basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py +++ b/src/basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py @@ -57,7 +57,6 @@ def upgrade() -> None: """) - def downgrade() -> None: """Downgrade database schema to use old search index.""" # Drop the updated search_index table diff --git a/src/basic_memory/api/routers/knowledge_router.py b/src/basic_memory/api/routers/knowledge_router.py index a3b12ebc..23be35fa 100644 --- a/src/basic_memory/api/routers/knowledge_router.py +++ b/src/basic_memory/api/routers/knowledge_router.py @@ -274,4 +274,4 @@ async def delete_entities( background_tasks.add_task(search_service.delete_by_permalink, permalink) result = DeleteEntitiesResponse(deleted=deleted) - return result \ No newline at end of file + return result diff --git a/src/basic_memory/api/routers/project_router.py b/src/basic_memory/api/routers/project_router.py index dea5f412..f8f84624 100644 --- a/src/basic_memory/api/routers/project_router.py +++ b/src/basic_memory/api/routers/project_router.py @@ -145,7 +145,9 @@ async def remove_project( try: old_project = await project_service.get_project(name) if not old_project: # pragma: no cover - raise HTTPException(status_code=404, detail=f"Project: '{name}' does not exist") # pragma: no cover + raise HTTPException( + status_code=404, detail=f"Project: '{name}' does not exist" + ) # pragma: no cover await project_service.remove_project(name) @@ -186,7 +188,9 @@ async def set_default_project( # get the new project new_default_project = await project_service.get_project(name) if not new_default_project: # pragma: no cover - raise HTTPException(status_code=404, detail=f"Project: '{name}' does not exist") # pragma: no cover + raise HTTPException( + status_code=404, detail=f"Project: '{name}' does not exist" + ) # pragma: no cover await project_service.set_default_project(name) diff --git a/src/basic_memory/config.py b/src/basic_memory/config.py index f19bc707..0c1ef0b6 100644 --- a/src/basic_memory/config.py +++ b/src/basic_memory/config.py @@ -179,7 +179,7 @@ class ConfigManager: def save_config(self, config: BasicMemoryConfig) -> None: """Save configuration to file.""" - try: + try: self.config_file.write_text(json.dumps(config.model_dump(), indent=2)) except Exception as e: # pragma: no cover logger.error(f"Failed to save config: {e}") diff --git a/src/basic_memory/mcp/server.py b/src/basic_memory/mcp/server.py index 43a28412..90fbb855 100644 --- a/src/basic_memory/mcp/server.py +++ b/src/basic_memory/mcp/server.py @@ -106,6 +106,5 @@ auth_settings, auth_provider = create_auth_config() mcp = FastMCP( name="Basic Memory", log_level="DEBUG", - auth_server_provider=auth_provider, - auth=auth_settings, + auth=auth_provider, ) diff --git a/src/basic_memory/mcp/tools/move_note.py b/src/basic_memory/mcp/tools/move_note.py index 77ab7a4a..c58ee62b 100644 --- a/src/basic_memory/mcp/tools/move_note.py +++ b/src/basic_memory/mcp/tools/move_note.py @@ -31,7 +31,7 @@ async def move_note( Examples: - Move to new folder: move_note("My Note", "work/notes/my-note.md") - - Move by permalink: move_note("my-note-permalink", "archive/old-notes/my-note.md") + - Move by permalink: move_note("my-note-permalink", "archive/old-notes/my-note.md") - Specify project: move_note("My Note", "archive/my-note.md", project="work-project") Note: This operation moves notes within the specified project only. Moving notes diff --git a/src/basic_memory/services/project_service.py b/src/basic_memory/services/project_service.py index d9d63183..88105db9 100644 --- a/src/basic_memory/services/project_service.py +++ b/src/basic_memory/services/project_service.py @@ -22,6 +22,7 @@ from basic_memory.config import WATCH_STATUS_JSON from basic_memory.utils import generate_permalink from basic_memory.config import config_manager + class ProjectService: """Service for managing Basic Memory projects.""" @@ -545,4 +546,4 @@ class ProjectService: database_size=db_size_readable, watch_status=watch_status, timestamp=datetime.now(), - ) \ No newline at end of file + ) diff --git a/src/basic_memory/services/search_service.py b/src/basic_memory/services/search_service.py index b988522b..60587db5 100644 --- a/src/basic_memory/services/search_service.py +++ b/src/basic_memory/services/search_service.py @@ -148,7 +148,7 @@ class SearchService: # If parsing fails, treat as single tag return [tags] if tags.strip() else [] - return [] # pragma: no cover + return [] # pragma: no cover async def index_entity( self, diff --git a/src/basic_memory/sync/sync_service.py b/src/basic_memory/sync/sync_service.py index 797d0fbc..18d0ff10 100644 --- a/src/basic_memory/sync/sync_service.py +++ b/src/basic_memory/sync/sync_service.py @@ -379,7 +379,9 @@ class SyncService: updates = {"file_path": new_path} # If configured, also update permalink to match new path - if self.app_config.update_permalinks_on_move and self.file_service.is_markdown(new_path): + if self.app_config.update_permalinks_on_move and self.file_service.is_markdown( + new_path + ): # generate new permalink value new_permalink = await self.entity_service.resolve_permalink(new_path) @@ -505,4 +507,4 @@ class SyncService: f"duration_ms={duration_ms}" ) - return result \ No newline at end of file + return result diff --git a/test-int/conftest.py b/test-int/conftest.py index 1b7ff447..6694d292 100644 --- a/test-int/conftest.py +++ b/test-int/conftest.py @@ -49,10 +49,8 @@ async def test_my_mcp_tool(mcp_server, app): The `app` fixture ensures FastAPI dependency overrides are active, and `mcp_server` provides the MCP server with proper project session initialization. """ -import os + from typing import AsyncGenerator -from unittest import mock -from unittest.mock import patch import pytest import pytest_asyncio @@ -110,21 +108,29 @@ async def test_project(tmp_path, engine_factory) -> Project: project = await project_repository.create(project_data) return project + @pytest.fixture def config_home(tmp_path, monkeypatch) -> Path: monkeypatch.setenv("HOME", str(tmp_path)) return tmp_path + @pytest.fixture(scope="function") def app_config(config_home, test_project, tmp_path, monkeypatch) -> BasicMemoryConfig: """Create test app configuration.""" projects = {test_project.name: str(test_project.path)} - app_config = BasicMemoryConfig(env="test", projects=projects, default_project=test_project.name, update_permalinks_on_move=True) + app_config = BasicMemoryConfig( + env="test", + projects=projects, + default_project=test_project.name, + update_permalinks_on_move=True, + ) # Set the module app_config instance project list (like regular tests) monkeypatch.setattr("basic_memory.config.app_config", app_config) return app_config + @pytest.fixture def config_manager(app_config: BasicMemoryConfig, config_home, monkeypatch) -> ConfigManager: config_manager = ConfigManager() @@ -145,6 +151,7 @@ def config_manager(app_config: BasicMemoryConfig, config_home, monkeypatch) -> C return config_manager + @pytest.fixture def project_session(test_project: Project): # initialize the project session with the test project @@ -166,9 +173,10 @@ def project_config(test_project, monkeypatch): return project_config - @pytest.fixture(scope="function") -def app(app_config, project_config, engine_factory, test_project, project_session, config_manager) -> FastAPI: +def app( + app_config, project_config, engine_factory, test_project, project_session, config_manager +) -> FastAPI: """Create test FastAPI application with single project.""" app = fastapi_app @@ -228,4 +236,4 @@ def mcp_server(app_config, search_service): async def client(app: FastAPI) -> AsyncGenerator[AsyncClient, None]: """Create test client that both MCP and tests will use.""" async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: - yield client \ No newline at end of file + yield client diff --git a/tests/api/conftest.py b/tests/api/conftest.py index 0b39b877..87e5d933 100644 --- a/tests/api/conftest.py +++ b/tests/api/conftest.py @@ -37,4 +37,4 @@ def project_url(test_project: Project) -> str: """ # Make sure this matches what's in tests/conftest.py for test_project creation # The permalink should be generated from "Test Project Context" - return f"/{test_project.permalink}" \ No newline at end of file + return f"/{test_project.permalink}" diff --git a/tests/api/test_knowledge_router.py b/tests/api/test_knowledge_router.py index 8877d43c..8a6acdfd 100644 --- a/tests/api/test_knowledge_router.py +++ b/tests/api/test_knowledge_router.py @@ -930,7 +930,7 @@ async def test_move_entity_success(client: AsyncClient, project_url): assert response.status_code == 200 response_model = EntityResponse.model_validate(response.json()) assert response_model.file_path == "target/MovedNote.md" - + # Verify original entity no longer exists response = await client.get(f"{project_url}/knowledge/entities/{original_permalink}") assert response.status_code == 404 diff --git a/tests/api/test_project_router.py b/tests/api/test_project_router.py index 37bb8f36..7e01f4f4 100644 --- a/tests/api/test_project_router.py +++ b/tests/api/test_project_router.py @@ -149,25 +149,25 @@ async def test_remove_project_endpoint(test_config, client, project_service): # First create a test project to remove test_project_name = "test-remove-project" await project_service.add_project(test_project_name, "/tmp/test-remove-project") - + # Verify it exists project = await project_service.get_project(test_project_name) assert project is not None - + # Remove the project response = await client.delete(f"/projects/{test_project_name}") - + # Verify response assert response.status_code == 200 data = response.json() - + # Check response structure assert "message" in data assert "status" in data assert data["status"] == "success" assert "old_project" in data assert data["old_project"]["name"] == test_project_name - + # Verify project is actually removed removed_project = await project_service.get_project(test_project_name) assert removed_project is None @@ -179,20 +179,20 @@ async def test_set_default_project_endpoint(test_config, client, project_service # Create a test project to set as default test_project_name = "test-default-project" await project_service.add_project(test_project_name, "/tmp/test-default-project") - + # Set it as default response = await client.put(f"/projects/{test_project_name}/default") - + # Verify response assert response.status_code == 200 data = response.json() - + # Check response structure assert "message" in data assert "status" in data assert data["status"] == "success" assert "new_project" in data assert data["new_project"]["name"] == test_project_name - + # Verify it's actually set as default assert project_service.default_project == test_project_name diff --git a/tests/cli/conftest.py b/tests/cli/conftest.py index fbe8ec98..90183d01 100644 --- a/tests/cli/conftest.py +++ b/tests/cli/conftest.py @@ -29,4 +29,4 @@ async def client(app: FastAPI) -> AsyncGenerator[AsyncClient, None]: @pytest.fixture def cli_env(project_config, client, test_config): """Set up CLI environment with correct project session.""" - return {"project_config": project_config, "client": client} \ No newline at end of file + return {"project_config": project_config, "client": client} diff --git a/tests/cli/test_cli_tools.py b/tests/cli/test_cli_tools.py index e00d7335..460ab0c3 100644 --- a/tests/cli/test_cli_tools.py +++ b/tests/cli/test_cli_tools.py @@ -439,4 +439,4 @@ def test_ensure_migrations_handles_errors(mock_initialize_database, project_conf # Call the function - should not raise exception ensure_initialization(project_config) - # We're just making sure it doesn't crash by calling it \ No newline at end of file + # We're just making sure it doesn't crash by calling it diff --git a/tests/cli/test_project_info.py b/tests/cli/test_project_info.py index 5a94fd52..5f3ffa02 100644 --- a/tests/cli/test_project_info.py +++ b/tests/cli/test_project_info.py @@ -5,7 +5,6 @@ import json from typer.testing import CliRunner from basic_memory.cli.main import app as cli_app -from basic_memory.config import config def test_info_stats_command(cli_env, test_graph, project_session): @@ -15,7 +14,6 @@ def test_info_stats_command(cli_env, test_graph, project_session): # Run the command result = runner.invoke(cli_app, ["project", "info"]) - # Verify exit code assert result.exit_code == 0 diff --git a/tests/conftest.py b/tests/conftest.py index cfe819fa..a989a707 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,16 +1,13 @@ """Common test fixtures.""" -import os + from dataclasses import dataclass from datetime import datetime, timezone from pathlib import Path from textwrap import dedent from typing import AsyncGenerator -from unittest import mock -from unittest.mock import patch import pytest import pytest_asyncio -from loguru import logger from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker import basic_memory.mcp.project_session @@ -49,42 +46,52 @@ def anyio_backend(): def project_root() -> Path: return Path(__file__).parent.parent + @pytest.fixture def config_home(tmp_path, monkeypatch) -> Path: # Patch HOME environment variable for the duration of the test monkeypatch.setenv("HOME", str(tmp_path)) return tmp_path + @pytest.fixture(scope="function", autouse=True) def app_config(config_home, tmp_path, monkeypatch) -> BasicMemoryConfig: """Create test app configuration.""" # Create a basic config without depending on test_project to avoid circular dependency projects = {"test-project": str(config_home)} - app_config = BasicMemoryConfig(env="test", projects=projects, default_project="test-project", update_permalinks_on_move=True) - + app_config = BasicMemoryConfig( + env="test", + projects=projects, + default_project="test-project", + update_permalinks_on_move=True, + ) # Patch the module app_config instance for the duration of the test monkeypatch.setattr("basic_memory.config.app_config", app_config) return app_config + @pytest.fixture(autouse=True) -def config_manager(app_config: BasicMemoryConfig, project_config: ProjectConfig, config_home: Path, monkeypatch) -> ConfigManager: +def config_manager( + app_config: BasicMemoryConfig, project_config: ProjectConfig, config_home: Path, monkeypatch +) -> ConfigManager: # Create a new ConfigManager that uses the test home directory config_manager = ConfigManager() # Update its paths to use the test directory config_manager.config_dir = config_home / ".basic-memory" config_manager.config_file = config_manager.config_dir / "config.json" config_manager.config_dir.mkdir(parents=True, exist_ok=True) - + # Override the config directly instead of relying on disk load config_manager.config = app_config - + # Ensure the config file is written to disk config_manager.save_config(app_config) - + # Patch the config_manager in all locations where it's imported monkeypatch.setattr("basic_memory.config.config_manager", config_manager) monkeypatch.setattr("basic_memory.services.project_service.config_manager", config_manager) + # Mock get_project_config to return test project config for test-project, fallback for others def mock_get_project_config(project_name=None): if project_name == "test-project" or project_name is None: @@ -92,18 +99,24 @@ def config_manager(app_config: BasicMemoryConfig, project_config: ProjectConfig, # For any other project name, return a default config pointing to test location fallback_config = ProjectConfig(name=project_name or "main", home=Path(config_home)) return fallback_config - monkeypatch.setattr("basic_memory.mcp.project_session.get_project_config", mock_get_project_config) - + + monkeypatch.setattr( + "basic_memory.mcp.project_session.get_project_config", mock_get_project_config + ) + # Patch the project config that CLI commands import (only modules that actually import config) monkeypatch.setattr("basic_memory.cli.commands.project.config", project_config) monkeypatch.setattr("basic_memory.cli.commands.sync.config", project_config) monkeypatch.setattr("basic_memory.cli.commands.status.config", project_config) monkeypatch.setattr("basic_memory.cli.commands.import_memory_json.config", project_config) monkeypatch.setattr("basic_memory.cli.commands.import_claude_projects.config", project_config) - monkeypatch.setattr("basic_memory.cli.commands.import_claude_conversations.config", project_config) + monkeypatch.setattr( + "basic_memory.cli.commands.import_claude_conversations.config", project_config + ) monkeypatch.setattr("basic_memory.cli.commands.import_chatgpt.config", project_config) return config_manager + @pytest.fixture(autouse=True) def project_session(test_project: Project): # initialize the project session with the test project @@ -134,17 +147,18 @@ class TestConfig: app_config: BasicMemoryConfig config_manager: ConfigManager + @pytest.fixture def test_config(config_home, project_config, app_config, config_manager) -> TestConfig: """All test configuration fixtures""" - + @dataclass class TestConfig: config_home: Path project_config: ProjectConfig app_config: BasicMemoryConfig config_manager: ConfigManager - + return TestConfig(config_home, project_config, app_config, config_manager) @@ -501,4 +515,4 @@ def test_files(project_config, project_root) -> dict[str, Path]: async def synced_files(sync_service, project_config, test_files): # Initial sync - should create forward reference await sync_service.sync(project_config.home) - return test_files \ No newline at end of file + return test_files diff --git a/tests/mcp/test_tool_move_note.py b/tests/mcp/test_tool_move_note.py index e8c36fdb..46ad4c73 100644 --- a/tests/mcp/test_tool_move_note.py +++ b/tests/mcp/test_tool_move_note.py @@ -167,7 +167,11 @@ async def test_move_note_nonexistent_note(client): # Should raise an exception from the API with friendly error message error_msg = str(exc_info.value) - assert "Entity not found" in error_msg or "Invalid request" in error_msg or "malformed" in error_msg + assert ( + "Entity not found" in error_msg + or "Invalid request" in error_msg + or "malformed" in error_msg + ) @pytest.mark.asyncio @@ -222,7 +226,11 @@ async def test_move_note_destination_exists(client): # Should raise an exception (400 gets wrapped as malformed request) error_msg = str(exc_info.value) - assert "Destination already exists" in error_msg or "Invalid request" in error_msg or "malformed" in error_msg + assert ( + "Destination already exists" in error_msg + or "Invalid request" in error_msg + or "malformed" in error_msg + ) @pytest.mark.asyncio @@ -244,7 +252,12 @@ async def test_move_note_same_location(client): # Should raise an exception (400 gets wrapped as malformed request) error_msg = str(exc_info.value) - assert "Destination already exists" in error_msg or "same location" in error_msg or "Invalid request" in error_msg or "malformed" in error_msg + assert ( + "Destination already exists" in error_msg + or "same location" in error_msg + or "Invalid request" in error_msg + or "malformed" in error_msg + ) @pytest.mark.asyncio diff --git a/tests/services/test_entity_service.py b/tests/services/test_entity_service.py index cd9ada67..8c88642b 100644 --- a/tests/services/test_entity_service.py +++ b/tests/services/test_entity_service.py @@ -1230,7 +1230,7 @@ async def test_move_entity_success( # Move entity assert entity.permalink == "original/test-note" - result = await entity_service.move_entity( + await entity_service.move_entity( identifier=entity.permalink, destination_path="moved/test-note.md", project_config=project_config, @@ -1276,14 +1276,13 @@ async def test_move_entity_with_permalink_update( app_config = BasicMemoryConfig(update_permalinks_on_move=True) # Move entity - result = await entity_service.move_entity( + await entity_service.move_entity( identifier=entity.permalink, destination_path="moved/test-note.md", project_config=project_config, app_config=app_config, ) - # Verify entity was found by new path (since permalink changed) moved_entity = await entity_service.link_resolver.resolve_link("moved/test-note.md") assert moved_entity is not None @@ -1473,7 +1472,7 @@ async def test_move_entity_by_title( app_config = BasicMemoryConfig(update_permalinks_on_move=False) # Move by title - result = await entity_service.move_entity( + await entity_service.move_entity( identifier="Test Note", # Use title instead of permalink destination_path="moved/test-note.md", project_config=project_config, @@ -1657,4 +1656,4 @@ async def test_move_entity_with_complex_observations( relation_targets = {rel.to_name for rel in moved_entity.relations} assert "Branch Strategy" in relation_targets assert "Multiple" in relation_targets - assert "Links" in relation_targets \ No newline at end of file + assert "Links" in relation_targets diff --git a/tests/services/test_project_service.py b/tests/services/test_project_service.py index c17065a4..a11a65af 100644 --- a/tests/services/test_project_service.py +++ b/tests/services/test_project_service.py @@ -13,6 +13,7 @@ from basic_memory.schemas import ( from basic_memory.services.project_service import ProjectService from basic_memory.config import ConfigManager + def test_projects_property(project_service: ProjectService): """Test the projects property.""" # Get the projects @@ -63,7 +64,9 @@ def test_current_project_property(project_service: ProjectService): @pytest.mark.asyncio -async def test_project_operations_sync_methods(app_config, project_service: ProjectService, config_manager: ConfigManager, tmp_path): +async def test_project_operations_sync_methods( + app_config, project_service: ProjectService, config_manager: ConfigManager, tmp_path +): """Test adding, switching, and removing a project using ConfigManager directly. This test uses the ConfigManager directly instead of the async methods. @@ -241,24 +244,24 @@ async def test_get_project_method(project_service: ProjectService, tmp_path): """Test the get_project method directly.""" test_project_name = f"test-get-project-{os.urandom(4).hex()}" test_project_path = str(tmp_path / "test-get-project") - + # Make sure the test directory exists os.makedirs(test_project_path, exist_ok=True) - + try: # Test getting a non-existent project result = await project_service.get_project("non-existent-project") assert result is None - + # Add a project await project_service.add_project(test_project_name, test_project_path) - + # Test getting an existing project result = await project_service.get_project(test_project_name) assert result is not None assert result.name == test_project_name assert result.path == test_project_path - + finally: # Clean up if test_project_name in project_service.projects: @@ -266,36 +269,38 @@ async def test_get_project_method(project_service: ProjectService, tmp_path): @pytest.mark.asyncio -async def test_set_default_project_config_db_mismatch(project_service: ProjectService, config_manager: ConfigManager, tmp_path): +async def test_set_default_project_config_db_mismatch( + project_service: ProjectService, config_manager: ConfigManager, tmp_path +): """Test set_default_project when project exists in config but not in database.""" test_project_name = f"test-mismatch-project-{os.urandom(4).hex()}" test_project_path = str(tmp_path / "test-mismatch-project") - - # Make sure the test directory exists + + # Make sure the test directory exists os.makedirs(test_project_path, exist_ok=True) - + original_default = project_service.default_project - + try: # Add project to config only (not to database) config_manager.add_project(test_project_name, test_project_path) - + # Verify it's in config but not in database assert test_project_name in project_service.projects db_project = await project_service.repository.get_by_name(test_project_name) assert db_project is None - + # Try to set as default - this should trigger the error log on line 142 await project_service.set_default_project(test_project_name) - + # Should still update config despite database mismatch assert project_service.default_project == test_project_name - + finally: # Restore original default if original_default: config_manager.set_default_project(original_default) - + # Clean up if test_project_name in project_service.projects: - config_manager.remove_project(test_project_name) \ No newline at end of file + config_manager.remove_project(test_project_name) diff --git a/tests/services/test_search_service.py b/tests/services/test_search_service.py index 91e732be..ecbd78fa 100644 --- a/tests/services/test_search_service.py +++ b/tests/services/test_search_service.py @@ -202,7 +202,7 @@ async def test_search_entity_type(search_service, test_graph): async def test_extract_entity_tags_exception_handling(search_service): """Test the _extract_entity_tags method exception handling (lines 147-151).""" from basic_memory.models.knowledge import Entity - + # Create entity with string tags that will cause parsing to fail and fall back to single tag entity_with_invalid_tags = Entity( title="Test Entity", @@ -210,23 +210,23 @@ async def test_extract_entity_tags_exception_handling(search_service): entity_metadata={"tags": "just a string"}, # This will fail ast.literal_eval content_type="text/markdown", file_path="test/test-entity.md", - project_id=1 + project_id=1, ) - + # This should trigger the except block on lines 147-149 result = search_service._extract_entity_tags(entity_with_invalid_tags) - assert result == ['just a string'] - + assert result == ["just a string"] + # Test with empty string (should return empty list) - covers line 149 entity_with_empty_tags = Entity( title="Test Entity Empty", - entity_type="test", + entity_type="test", entity_metadata={"tags": ""}, content_type="text/markdown", file_path="test/test-entity-empty.md", - project_id=1 + project_id=1, ) - + result = search_service._extract_entity_tags(entity_with_empty_tags) assert result == [] @@ -234,10 +234,10 @@ async def test_extract_entity_tags_exception_handling(search_service): @pytest.mark.asyncio async def test_delete_entity_without_permalink(search_service, sample_entity): """Test deleting an entity that has no permalink (edge case).""" - + # Set the entity permalink to None to trigger the else branch on line 355 sample_entity.permalink = None - + # This should trigger the delete_by_entity_id path (line 355) in handle_delete await search_service.handle_delete(sample_entity) diff --git a/tests/sync/test_sync_service.py b/tests/sync/test_sync_service.py index f490c26b..cb6a6a57 100644 --- a/tests/sync/test_sync_service.py +++ b/tests/sync/test_sync_service.py @@ -439,8 +439,12 @@ modified: 2024-01-01 # Verify outgoing relations by checking actual targets a_outgoing_targets = {rel.to_id for rel in entity_a.outgoing_relations} - assert entity_b.id in a_outgoing_targets, f"A should depend on B. A's targets: {a_outgoing_targets}, B's ID: {entity_b.id}" - assert entity_c.id in a_outgoing_targets, f"A should depend on C. A's targets: {a_outgoing_targets}, C's ID: {entity_c.id}" + assert entity_b.id in a_outgoing_targets, ( + f"A should depend on B. A's targets: {a_outgoing_targets}, B's ID: {entity_b.id}" + ) + assert entity_c.id in a_outgoing_targets, ( + f"A should depend on C. A's targets: {a_outgoing_targets}, C's ID: {entity_c.id}" + ) assert len(entity_a.outgoing_relations) == 2, "A should have exactly 2 outgoing relations" b_outgoing_targets = {rel.to_id for rel in entity_b.outgoing_relations} @@ -454,14 +458,13 @@ modified: 2024-01-01 # Verify incoming relations by checking actual sources a_incoming_sources = {rel.from_id for rel in entity_a.incoming_relations} assert entity_c.id in a_incoming_sources, "A should have incoming relation from C" - + b_incoming_sources = {rel.from_id for rel in entity_b.incoming_relations} assert entity_a.id in b_incoming_sources, "B should have incoming relation from A" - + c_incoming_sources = {rel.from_id for rel in entity_c.incoming_relations} assert entity_a.id in c_incoming_sources, "C should have incoming relation from A" assert entity_b.id in c_incoming_sources, "C should have incoming relation from B" - @pytest.mark.asyncio