mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
fix: update FastMCP initialization for API changes
- 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 <noreply@anthropic.com>
This commit is contained in:
@@ -57,7 +57,6 @@ def upgrade() -> None:
|
||||
""")
|
||||
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade database schema to use old search index."""
|
||||
# Drop the updated search_index table
|
||||
|
||||
@@ -274,4 +274,4 @@ async def delete_entities(
|
||||
background_tasks.add_task(search_service.delete_by_permalink, permalink)
|
||||
|
||||
result = DeleteEntitiesResponse(deleted=deleted)
|
||||
return result
|
||||
return result
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
return result
|
||||
|
||||
+15
-7
@@ -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
|
||||
yield client
|
||||
|
||||
@@ -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}"
|
||||
return f"/{test_project.permalink}"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
return {"project_config": project_config, "client": client}
|
||||
|
||||
@@ -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
|
||||
# We're just making sure it doesn't crash by calling it
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+30
-16
@@ -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
|
||||
return test_files
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
assert "Links" in relation_targets
|
||||
|
||||
@@ -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)
|
||||
config_manager.remove_project(test_project_name)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user