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
2 changes: 1 addition & 1 deletion src/basic_memory/api/v2/routers/project_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ async def add_project(
return ProjectStatusResponse( # pyright: ignore [reportCallIssue]
message=f"Project '{new_project.name}' added successfully",
status="success",
default=project_data.set_default,
default=new_project.is_default or False,
new_project=ProjectItem(
id=new_project.id,
external_id=new_project.external_id,
Expand Down
13 changes: 13 additions & 0 deletions src/basic_memory/services/project_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,19 @@ async def add_project(self, name: str, path: str, set_default: bool = False) ->
await self.repository.set_as_default(created_project.id)
self.config_manager.set_default_project(name)
logger.info(f"Project '{name}' set as default")
else:
config_default = self.config_manager.default_project
if config_default is not None:
db_default_project = await self.repository.get_by_name(config_default)
if db_default_project is None:
await self.repository.set_as_default(created_project.id)
self.config_manager.set_default_project(name)
Comment thread
phernandez marked this conversation as resolved.
logger.info(
"Promoted project '%s' to default because configured default '%s' "
"is missing from database",
name,
config_default,
)

logger.info(f"Project '{name}' added at {resolved_path}")

Expand Down
41 changes: 41 additions & 0 deletions tests/api/v2/test_project_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import pytest
from httpx import AsyncClient

from basic_memory.config import ProjectEntry
from basic_memory.models import Project
from basic_memory.schemas.project_info import ProjectItem, ProjectStatusResponse
from basic_memory.schemas.v2 import ProjectResolveResponse
Expand Down Expand Up @@ -54,6 +55,46 @@ async def test_get_project_by_id_not_found(client: AsyncClient, v2_projects_url)
assert "not found" in response.json()["detail"].lower()


@pytest.mark.asyncio
async def test_add_project_response_reflects_promoted_default(
client: AsyncClient,
v2_projects_url,
app_config,
config_manager,
config_home,
project_repository,
):
"""Regression #974/#985: POST response should echo persisted default promotion."""
main_home = config_home / "basic-memory"
main_home.mkdir(parents=True, exist_ok=True)
qa_path = config_home / "qa-notes"
qa_path.mkdir(parents=True, exist_ok=True)

fresh_config = app_config.model_copy(
update={
"projects": {"main": ProjectEntry(path=str(main_home))},
"default_project": "main",
}
)
config_manager.save_config(fresh_config)

for project in await project_repository.find_all():
await project_repository.delete(project.id)

response = await client.post(
f"{v2_projects_url}/",
json={"name": "qa", "path": str(qa_path), "set_default": False},
)

assert response.status_code == 201
status_response = ProjectStatusResponse.model_validate(response.json())
assert status_response.status == "success"
assert status_response.default is True
new_project = _project_item(status_response.new_project)
assert new_project.name == "qa"
assert new_project.is_default is True


@pytest.mark.asyncio
async def test_update_project_path_by_id(
client: AsyncClient, test_project: Project, v2_projects_url
Expand Down
46 changes: 46 additions & 0 deletions tests/services/test_project_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,52 @@ async def test_add_project_with_set_default_false(project_service: ProjectServic
await project_service.remove_project(test_project_name)


@pytest.mark.asyncio
async def test_add_project_promotes_when_config_default_missing_from_db(
config_home, app_config, config_manager, engine_factory
):
"""Regression #974: config default exists only in config, not DB — promote on add."""
from basic_memory import config as config_module
from basic_memory.config import ProjectConfig, ProjectEntry
from basic_memory.markdown.entity_parser import EntityParser
from basic_memory.markdown.markdown_processor import MarkdownProcessor
from basic_memory.repository.project_repository import ProjectRepository
from basic_memory.services.file_service import FileService

config_module._CONFIG_CACHE = None
config_module._CONFIG_MTIME = None
config_module._CONFIG_SIZE = None

main_home = config_home / "basic-memory"
main_home.mkdir(parents=True, exist_ok=True)
qa_path = config_home / "qa-notes"
qa_path.mkdir(parents=True, exist_ok=True)

fresh_config = app_config.model_copy(
update={
"projects": {"main": ProjectEntry(path=str(main_home))},
"default_project": "main",
}
)
config_manager.save_config(fresh_config)

_, session_maker = engine_factory
repo = ProjectRepository(session_maker)
for project in await repo.find_all():
await repo.delete(project.id)

file_service = FileService(qa_path, MarkdownProcessor(EntityParser(qa_path)))
service = ProjectService(repository=repo, file_service=file_service)

await service.add_project("qa", str(qa_path), set_default=False)

assert service.default_project == "qa"
qa_project = await repo.get_by_name("qa")
assert qa_project is not None
assert qa_project.is_default is True
assert await repo.get_by_name("main") is None


@pytest.mark.asyncio
async def test_add_project_default_parameter_omitted(project_service: ProjectService):
"""Test adding a project without set_default parameter defaults to False behavior."""
Expand Down
Loading