Files
phernandez 2e5813d31e feat: CLI refactoring + workspace-aware cloud project listing
Refactor CLI commands to use typed ProjectClient instead of raw HTTP calls,
and add workspace metadata to cloud project listings so users can distinguish
personal vs organization projects.

Key changes:
- 🔧 CLI commands now use ProjectClient typed API clients instead of
  call_get/call_post with manual URL construction
- 🏢 Cloud project listings include workspace_name, workspace_type, and
  workspace_tenant_id for each cloud-sourced project
- Pass config.default_workspace when fetching cloud projects via
  _fetch_cloud_projects() and CLI list_projects
- Add --workspace flag to `bm project list` for explicit workspace override
- Add "Workspace" column to CLI project list table
- Add `bm tool list-projects` and `bm tool list-workspaces` JSON commands
- Comprehensive tests for workspace passthrough, merge behavior, and CLI routing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2026-02-21 20:04:03 -06:00

60 lines
2.1 KiB
Python

"""Regression tests for project info error handling."""
from contextlib import asynccontextmanager
from types import SimpleNamespace
import pytest
import typer
from mcp.server.fastmcp.exceptions import ToolError
from typer.testing import CliRunner
from basic_memory.cli.app import app
from basic_memory.mcp.clients.project import ProjectClient
import basic_memory.cli.commands.command_utils as command_utils
import basic_memory.cli.commands.project as project_cmd # noqa: F401
runner = CliRunner()
@pytest.mark.asyncio
async def test_get_project_info_cloud_config_error_has_clear_message(monkeypatch, capsys):
"""Cloud internal proxy config failures should surface actionable guidance."""
@asynccontextmanager
async def fake_get_client(project_name=None):
yield object()
async def fake_get_active_project(client, project, context):
return SimpleNamespace(external_id="proj-123")
async def fake_get_info(self, project_external_id):
raise ToolError("Internal proxy error: Project 'demo' not found in configuration")
monkeypatch.setattr(command_utils, "get_client", fake_get_client)
monkeypatch.setattr(command_utils, "get_active_project", fake_get_active_project)
monkeypatch.setattr(ProjectClient, "get_info", fake_get_info)
with pytest.raises(typer.Exit) as exc:
await command_utils.get_project_info("demo")
assert exc.value.exit_code == 1
captured = capsys.readouterr()
combined_output = captured.out + captured.err
assert "Project info failed: cloud returned an internal configuration error" in combined_output
assert "bm project list" in combined_output
assert "--cloud" in combined_output
def test_project_info_does_not_print_wrapper_exit_code(monkeypatch):
"""project info should not append a secondary 'Error getting project info: 1' line."""
async def fake_get_project_info(_project_name: str):
raise typer.Exit(1)
monkeypatch.setattr(project_cmd, "get_project_info", fake_get_project_info)
result = runner.invoke(app, ["project", "info", "demo"])
assert result.exit_code == 1
assert "Error getting project info" not in result.output