Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions src/basic_memory/services/project_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,15 +283,17 @@ async def set_default_project(self, name: str) -> None:
if not self.repository: # pragma: no cover
raise ValueError("Repository is required for set_default_project")

# First update config file (this will validate the project exists)
self.config_manager.set_default_project(name)

# Then update database using the same lookup logic as get_project
# Look up project in database first to validate it exists
project = await self.get_project(name)
if project:
await self.repository.set_as_default(project.id)
else:
logger.error(f"Project '{name}' exists in config but not in database")
if not project:
raise ValueError(f"Project '{name}' not found")

# Update database
await self.repository.set_as_default(project.id)

# Update config file only in local mode (cloud mode uses database only)
if not self.config_manager.config.cloud_mode:
self.config_manager.set_default_project(name)

logger.info(f"Project '{name}' set as default in configuration and database")

Expand Down
30 changes: 8 additions & 22 deletions tests/mcp/test_project_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ async def test_cloud_mode_requires_project_by_default(self):
mock_config = MagicMock()
mock_config.cloud_mode = True

with patch(
"basic_memory.mcp.project_context.ConfigManager"
) as mock_config_manager:
with patch("basic_memory.mcp.project_context.ConfigManager") as mock_config_manager:
mock_config_manager.return_value.config = mock_config

with pytest.raises(ValueError) as exc_info:
Expand All @@ -36,9 +34,7 @@ async def test_cloud_mode_allows_discovery_when_enabled(self):
mock_config = MagicMock()
mock_config.cloud_mode = True

with patch(
"basic_memory.mcp.project_context.ConfigManager"
) as mock_config_manager:
with patch("basic_memory.mcp.project_context.ConfigManager") as mock_config_manager:
mock_config_manager.return_value.config = mock_config

result = await resolve_project_parameter(project=None, allow_discovery=True)
Expand All @@ -53,9 +49,7 @@ async def test_cloud_mode_returns_project_when_specified(self):
mock_config = MagicMock()
mock_config.cloud_mode = True

with patch(
"basic_memory.mcp.project_context.ConfigManager"
) as mock_config_manager:
with patch("basic_memory.mcp.project_context.ConfigManager") as mock_config_manager:
mock_config_manager.return_value.config = mock_config

result = await resolve_project_parameter(project="my-project")
Expand All @@ -70,9 +64,7 @@ async def test_local_mode_uses_env_var_priority(self):
mock_config = MagicMock()
mock_config.cloud_mode = False

with patch(
"basic_memory.mcp.project_context.ConfigManager"
) as mock_config_manager:
with patch("basic_memory.mcp.project_context.ConfigManager") as mock_config_manager:
mock_config_manager.return_value.config = mock_config

with patch.dict(os.environ, {"BASIC_MEMORY_MCP_PROJECT": "env-project"}):
Expand All @@ -90,9 +82,7 @@ async def test_local_mode_uses_explicit_project(self):
mock_config.cloud_mode = False
mock_config.default_project_mode = False

with patch(
"basic_memory.mcp.project_context.ConfigManager"
) as mock_config_manager:
with patch("basic_memory.mcp.project_context.ConfigManager") as mock_config_manager:
mock_config_manager.return_value.config = mock_config

with patch.dict(os.environ, {}, clear=True):
Expand All @@ -112,9 +102,7 @@ async def test_local_mode_uses_default_project(self):
mock_config.default_project_mode = True
mock_config.default_project = "default-project"

with patch(
"basic_memory.mcp.project_context.ConfigManager"
) as mock_config_manager:
with patch("basic_memory.mcp.project_context.ConfigManager") as mock_config_manager:
mock_config_manager.return_value.config = mock_config

with patch.dict(os.environ, {}, clear=True):
Expand All @@ -132,13 +120,11 @@ async def test_local_mode_returns_none_when_no_resolution(self):
mock_config.cloud_mode = False
mock_config.default_project_mode = False

with patch(
"basic_memory.mcp.project_context.ConfigManager"
) as mock_config_manager:
with patch("basic_memory.mcp.project_context.ConfigManager") as mock_config_manager:
mock_config_manager.return_value.config = mock_config

with patch.dict(os.environ, {}, clear=True):
os.environ.pop("BASIC_MEMORY_MCP_PROJECT", None)
result = await resolve_project_parameter(project=None)

assert result is None
assert result is None
6 changes: 5 additions & 1 deletion tests/schemas/test_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,11 @@ def test_relation_response_with_null_permalink():
"permalink": "test/relation/123",
"relation_type": "relates_to",
"from_entity": {"permalink": None, "file_path": "notes/source-note.md"},
"to_entity": {"permalink": None, "file_path": "notes/target-note.md", "title": "Target Note"},
"to_entity": {
"permalink": None,
"file_path": "notes/target-note.md",
"title": "Target Note",
},
}
relation = RelationResponse.model_validate(data)
# Falls back to file_path directly (not converted to permalink)
Expand Down
16 changes: 4 additions & 12 deletions tests/services/test_project_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ async def test_get_project_method(project_service: ProjectService):
async def test_set_default_project_config_db_mismatch(
project_service: ProjectService, config_manager: ConfigManager
):
"""Test set_default_project when project exists in config but not in database."""
"""Test set_default_project raises error when project exists in config but not in database."""
test_project_name = f"test-mismatch-project-{os.urandom(4).hex()}"
with tempfile.TemporaryDirectory() as temp_dir:
test_root = Path(temp_dir)
Expand All @@ -293,8 +293,6 @@ async def test_set_default_project_config_db_mismatch(
# 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)
Expand All @@ -304,17 +302,11 @@ async def test_set_default_project_config_db_mismatch(
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
# Try to set as default - should raise ValueError since project not in database
with pytest.raises(ValueError, match=f"Project '{test_project_name}' not found"):
await project_service.set_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)
Expand Down
Loading