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
4 changes: 4 additions & 0 deletions src/basic_memory/cli/commands/cloud/cloud_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ async def fetch_cloud_projects(
) -> CloudProjectList:
"""Fetch list of projects from cloud API.

Args:
project_name: Optional project name for workspace resolution
workspace: Cloud workspace tenant_id to list projects from

Returns:
CloudProjectList with projects from cloud
"""
Expand Down
2 changes: 2 additions & 0 deletions src/basic_memory/cli/commands/cloud/upload_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ async def _upload():
raise typer.Exit(1)

# Perform upload (or dry run)
if resolved_workspace:
console.print(f"[dim]Using workspace: {resolved_workspace}[/dim]")
if dry_run:
console.print(
f"[yellow]DRY RUN: Showing what would be uploaded to '{project}'[/yellow]"
Expand Down
2 changes: 2 additions & 0 deletions tests/cli/cloud/test_cloud_api_client_and_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,8 @@ async def api_request(**_kwargs):
await project_exists("alpha", api_request=api_request)




@pytest.mark.asyncio
async def test_make_api_request_prefers_api_key_over_oauth(config_home, config_manager):
"""API key in config should be used without needing an OAuth token on disk."""
Expand Down
2 changes: 1 addition & 1 deletion tests/cli/cloud/test_upload_command_routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
runner = CliRunner()


def test_cloud_upload_uses_control_plane_client(monkeypatch, tmp_path):
def test_cloud_upload_uses_control_plane_client(monkeypatch, tmp_path, config_manager):
"""Upload command should use control-plane cloud client for WebDAV PUT operations."""
import basic_memory.cli.commands.cloud.upload_command as upload_command

Expand Down
58 changes: 58 additions & 0 deletions tests/mcp/test_async_client_modes.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,64 @@ async def test_get_cloud_control_plane_client_uses_oauth_token(config_manager):
assert client.headers.get("Authorization") == "Bearer oauth-control-123"


@pytest.mark.asyncio
async def test_get_cloud_control_plane_client_with_workspace(config_manager):
"""Control plane client passes X-Workspace-ID header when workspace is provided."""
cfg = config_manager.load_config()
cfg.cloud_host = "https://cloud.example.test"
cfg.cloud_api_key = "bmc_test_key_123"
config_manager.save_config(cfg)

async with get_cloud_control_plane_client(workspace="tenant-abc") as client:
assert client.headers.get("X-Workspace-ID") == "tenant-abc"

# Without workspace, header should not be present
async with get_cloud_control_plane_client() as client:
assert "X-Workspace-ID" not in client.headers


@pytest.mark.asyncio
async def test_get_client_auto_resolves_workspace_from_project_config(config_manager):
"""get_client resolves workspace from project entry when not explicitly passed."""
cfg = config_manager.load_config()
cfg.cloud_host = "https://cloud.example.test"
cfg.cloud_api_key = "bmc_test_key_123"
cfg.set_project_mode("research", ProjectMode.CLOUD)
cfg.projects["research"].workspace_id = "tenant-from-config"
config_manager.save_config(cfg)

async with get_client(project_name="research") as client:
assert client.headers.get("X-Workspace-ID") == "tenant-from-config"


@pytest.mark.asyncio
async def test_get_client_auto_resolves_workspace_from_default(config_manager):
"""get_client falls back to default_workspace when project has no workspace_id."""
cfg = config_manager.load_config()
cfg.cloud_host = "https://cloud.example.test"
cfg.cloud_api_key = "bmc_test_key_123"
cfg.set_project_mode("research", ProjectMode.CLOUD)
cfg.default_workspace = "default-tenant-456"
config_manager.save_config(cfg)

async with get_client(project_name="research") as client:
assert client.headers.get("X-Workspace-ID") == "default-tenant-456"


@pytest.mark.asyncio
async def test_get_client_explicit_workspace_overrides_config(config_manager):
"""Explicit workspace param takes priority over project config."""
cfg = config_manager.load_config()
cfg.cloud_host = "https://cloud.example.test"
cfg.cloud_api_key = "bmc_test_key_123"
cfg.set_project_mode("research", ProjectMode.CLOUD)
cfg.projects["research"].workspace_id = "tenant-from-config"
config_manager.save_config(cfg)

async with get_client(project_name="research", workspace="explicit-tenant") as client:
assert client.headers.get("X-Workspace-ID") == "explicit-tenant"


@pytest.mark.asyncio
async def test_get_cloud_control_plane_client_raises_without_credentials(config_manager):
cfg = config_manager.load_config()
Expand Down
Loading