mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
fced804438
The test previously asserted that write_note fails when ConfigManager has no default_project. With the API fallback, it now correctly resolves to the database is_default project. Updated the test to verify this fallback behavior instead of expecting an error. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: phernandez <paul@basicmachines.co>
336 lines
12 KiB
Python
336 lines
12 KiB
Python
"""
|
|
Integration tests for default project resolution.
|
|
|
|
Tests the default_project configuration that allows tools to automatically
|
|
use the default_project when no project parameter is specified, covering
|
|
parameter resolution hierarchy and fallback behavior.
|
|
"""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from fastmcp import Client
|
|
from unittest.mock import patch
|
|
|
|
from basic_memory.config import ConfigManager, BasicMemoryConfig
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_default_project_write_note(mcp_server, app, test_project):
|
|
"""Test that write_note uses default project when no project specified."""
|
|
|
|
mock_config = BasicMemoryConfig(
|
|
default_project=test_project.name,
|
|
projects={test_project.name: test_project.path},
|
|
)
|
|
|
|
with patch.object(ConfigManager, "config", mock_config):
|
|
async with Client(mcp_server) as client:
|
|
result = await client.call_tool(
|
|
"write_note",
|
|
{
|
|
"title": "Default Mode Test",
|
|
"directory": "test",
|
|
"content": "# Default Mode Test\n\nThis should use the default project automatically.",
|
|
"tags": "default,mode,test",
|
|
},
|
|
)
|
|
|
|
assert len(result.content) == 1
|
|
response_text = result.content[0].text # pyright: ignore [reportAttributeAccessIssue]
|
|
|
|
assert f"project: {test_project.name}" in response_text
|
|
assert "# Created note" in response_text
|
|
assert "file_path: test/Default Mode Test.md" in response_text
|
|
assert f"[Session: Using project '{test_project.name}']" in response_text
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_explicit_project_overrides_default(
|
|
mcp_server, app, test_project, config_home, engine_factory
|
|
):
|
|
"""Test that explicit project parameter overrides default_project."""
|
|
|
|
engine, session_maker = engine_factory
|
|
from basic_memory.repository.project_repository import ProjectRepository
|
|
|
|
project_repository = ProjectRepository(session_maker)
|
|
|
|
other_project = await project_repository.create(
|
|
{
|
|
"name": "other-project",
|
|
"description": "Second project for testing",
|
|
"path": str(config_home / "other-project"),
|
|
"is_active": True,
|
|
"is_default": False,
|
|
}
|
|
)
|
|
|
|
mock_config = BasicMemoryConfig(
|
|
default_project=test_project.name,
|
|
projects={test_project.name: test_project.path, other_project.name: other_project.path},
|
|
)
|
|
|
|
with patch.object(ConfigManager, "config", mock_config):
|
|
async with Client(mcp_server) as client:
|
|
result = await client.call_tool(
|
|
"write_note",
|
|
{
|
|
"title": "Override Test",
|
|
"directory": "test",
|
|
"content": "# Override Test\n\nThis should go to the explicitly specified project.",
|
|
"project": other_project.name, # Explicit override
|
|
},
|
|
)
|
|
|
|
assert len(result.content) == 1
|
|
response_text = result.content[0].text # pyright: ignore [reportAttributeAccessIssue]
|
|
|
|
assert f"project: {other_project.name}" in response_text
|
|
assert "# Created note" in response_text
|
|
assert f"[Session: Using project '{other_project.name}']" in response_text
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_no_config_default_falls_back_to_db(mcp_server, app, test_project):
|
|
"""When ConfigManager has no default_project, tools fall back to the database is_default flag."""
|
|
|
|
mock_config = BasicMemoryConfig(
|
|
default_project=None, # No config default
|
|
projects={test_project.name: test_project.path},
|
|
)
|
|
|
|
# test_project has is_default=True in the database, so write_note should
|
|
# resolve to it via the API fallback in resolve_project_parameter.
|
|
with patch.object(ConfigManager, "config", mock_config):
|
|
async with Client(mcp_server) as client:
|
|
result = await client.call_tool(
|
|
"write_note",
|
|
{
|
|
"title": "DB Fallback Test",
|
|
"directory": "test",
|
|
"content": "# DB Fallback Test\n\nShould resolve to the database default project.",
|
|
},
|
|
)
|
|
|
|
assert len(result.content) == 1
|
|
response_text = result.content[0].text # pyright: ignore [reportAttributeAccessIssue]
|
|
|
|
assert f"project: {test_project.name}" in response_text
|
|
assert "# Created note" in response_text
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_cli_constraint_overrides_default_project(
|
|
mcp_server, app, test_project, config_home, engine_factory
|
|
):
|
|
"""Test that CLI --project constraint overrides default_project."""
|
|
|
|
engine, session_maker = engine_factory
|
|
from basic_memory.repository.project_repository import ProjectRepository
|
|
|
|
project_repository = ProjectRepository(session_maker)
|
|
|
|
other_project = await project_repository.create(
|
|
{
|
|
"name": "cli-project",
|
|
"description": "Project for CLI constraint testing",
|
|
"path": str(config_home / "cli-project"),
|
|
"is_active": True,
|
|
"is_default": False,
|
|
}
|
|
)
|
|
|
|
os.environ["BASIC_MEMORY_MCP_PROJECT"] = other_project.name
|
|
|
|
mock_config = BasicMemoryConfig(
|
|
default_project=test_project.name,
|
|
projects={test_project.name: test_project.path, other_project.name: other_project.path},
|
|
)
|
|
|
|
try:
|
|
with patch.object(ConfigManager, "config", mock_config):
|
|
async with Client(mcp_server) as client:
|
|
result = await client.call_tool(
|
|
"write_note",
|
|
{
|
|
"title": "CLI Constraint Test",
|
|
"directory": "test",
|
|
"content": "# CLI Constraint Test\n\nThis should use CLI constrained project.",
|
|
},
|
|
)
|
|
|
|
assert len(result.content) == 1
|
|
response_text = result.content[0].text # pyright: ignore [reportAttributeAccessIssue]
|
|
|
|
assert f"project: {other_project.name}" in response_text
|
|
assert "# Created note" in response_text
|
|
assert f"[Session: Using project '{other_project.name}']" in response_text
|
|
|
|
finally:
|
|
if "BASIC_MEMORY_MCP_PROJECT" in os.environ:
|
|
del os.environ["BASIC_MEMORY_MCP_PROJECT"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_default_project_read_note(mcp_server, app, test_project):
|
|
"""Test that read_note works with default_project."""
|
|
|
|
mock_config = BasicMemoryConfig(
|
|
default_project=test_project.name,
|
|
projects={test_project.name: test_project.path},
|
|
)
|
|
|
|
with patch.object(ConfigManager, "config", mock_config):
|
|
async with Client(mcp_server) as client:
|
|
await client.call_tool(
|
|
"write_note",
|
|
{
|
|
"title": "Read Test Note",
|
|
"directory": "test",
|
|
"content": "# Read Test Note\n\nThis note will be read back.",
|
|
},
|
|
)
|
|
|
|
result = await client.call_tool(
|
|
"read_note",
|
|
{
|
|
"identifier": "Read Test Note",
|
|
},
|
|
)
|
|
|
|
assert len(result.content) == 1
|
|
response_text = result.content[0].text # pyright: ignore [reportAttributeAccessIssue]
|
|
|
|
assert "# Read Test Note" in response_text
|
|
assert "This note will be read back." in response_text
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_default_project_edit_note(mcp_server, app, test_project):
|
|
"""Test that edit_note works with default_project."""
|
|
|
|
mock_config = BasicMemoryConfig(
|
|
default_project=test_project.name,
|
|
projects={test_project.name: test_project.path},
|
|
)
|
|
|
|
with patch.object(ConfigManager, "config", mock_config):
|
|
async with Client(mcp_server) as client:
|
|
await client.call_tool(
|
|
"write_note",
|
|
{
|
|
"title": "Edit Test Note",
|
|
"directory": "test",
|
|
"content": "# Edit Test Note\n\nOriginal content.",
|
|
},
|
|
)
|
|
|
|
result = await client.call_tool(
|
|
"edit_note",
|
|
{
|
|
"identifier": "Edit Test Note",
|
|
"operation": "append",
|
|
"content": "\n\n## Added Content\n\nThis was added via edit_note.",
|
|
},
|
|
)
|
|
|
|
assert len(result.content) == 1
|
|
response_text = result.content[0].text # pyright: ignore [reportAttributeAccessIssue]
|
|
|
|
assert "# Edited note" in response_text
|
|
assert "operation: Added" in response_text
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_project_resolution_hierarchy(
|
|
mcp_server, app, test_project, config_home, engine_factory
|
|
):
|
|
"""Test the complete three-tier project resolution hierarchy."""
|
|
|
|
engine, session_maker = engine_factory
|
|
from basic_memory.repository.project_repository import ProjectRepository
|
|
|
|
project_repository = ProjectRepository(session_maker)
|
|
|
|
default_project = test_project
|
|
cli_project = await project_repository.create(
|
|
{
|
|
"name": "cli-hierarchy-project",
|
|
"description": "Project for CLI hierarchy testing",
|
|
"path": str(config_home / "cli-hierarchy-project"),
|
|
"is_active": True,
|
|
"is_default": False,
|
|
}
|
|
)
|
|
explicit_project = await project_repository.create(
|
|
{
|
|
"name": "explicit-hierarchy-project",
|
|
"description": "Project for explicit hierarchy testing",
|
|
"path": str(config_home / "explicit-hierarchy-project"),
|
|
"is_active": True,
|
|
"is_default": False,
|
|
}
|
|
)
|
|
|
|
mock_config = BasicMemoryConfig(
|
|
default_project=default_project.name,
|
|
projects={
|
|
default_project.name: Path(default_project.path).as_posix(),
|
|
cli_project.name: Path(cli_project.path).as_posix(),
|
|
explicit_project.name: Path(explicit_project.path).as_posix(),
|
|
},
|
|
)
|
|
|
|
# Test 1: CLI constraint (highest priority)
|
|
os.environ["BASIC_MEMORY_MCP_PROJECT"] = cli_project.name
|
|
|
|
try:
|
|
with patch.object(ConfigManager, "config", mock_config):
|
|
async with Client(mcp_server) as client:
|
|
result = await client.call_tool(
|
|
"write_note",
|
|
{
|
|
"title": "CLI Priority Test",
|
|
"directory": "test",
|
|
"content": "# CLI Priority Test",
|
|
"project": explicit_project.name, # Should be ignored
|
|
},
|
|
)
|
|
response_text = result.content[0].text # pyright: ignore [reportAttributeAccessIssue]
|
|
assert f"project: {cli_project.name}" in response_text
|
|
|
|
finally:
|
|
del os.environ["BASIC_MEMORY_MCP_PROJECT"]
|
|
|
|
# Test 2: Explicit project (medium priority)
|
|
with patch.object(ConfigManager, "config", mock_config):
|
|
async with Client(mcp_server) as client:
|
|
result = await client.call_tool(
|
|
"write_note",
|
|
{
|
|
"title": "Explicit Priority Test",
|
|
"directory": "test",
|
|
"content": "# Explicit Priority Test",
|
|
"project": explicit_project.name,
|
|
},
|
|
)
|
|
response_text = result.content[0].text # pyright: ignore [reportAttributeAccessIssue]
|
|
assert f"project: {explicit_project.name}" in response_text
|
|
|
|
# Test 3: Default project (lowest priority)
|
|
with patch.object(ConfigManager, "config", mock_config):
|
|
async with Client(mcp_server) as client:
|
|
result = await client.call_tool(
|
|
"write_note",
|
|
{
|
|
"title": "Default Priority Test",
|
|
"directory": "test",
|
|
"content": "# Default Priority Test",
|
|
# No project specified
|
|
},
|
|
)
|
|
response_text = result.content[0].text # pyright: ignore [reportAttributeAccessIssue]
|
|
assert f"project: {default_project.name}" in response_text
|