mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
fix(cli): limit team workspace guard to bisync
Signed-off-by: Drew Cain <groksrc@gmail.com>
This commit is contained in:
committed by
Paul Hernandez
parent
d0ae373f45
commit
8bf7bdbc0d
@@ -33,12 +33,9 @@ from basic_memory.utils import generate_permalink, normalize_project_path
|
||||
|
||||
console = Console()
|
||||
|
||||
TEAM_WORKSPACE_SYNC_UNSUPPORTED = (
|
||||
"Mirror-style rclone sync/bisync is supported only for Personal workspaces.\n"
|
||||
"Team workspaces should not use local mirror workflows because they can "
|
||||
"overwrite or delete shared cloud files.\n"
|
||||
"Use cloud API/MCP routing for Team workspace edits, or inspect projects with "
|
||||
"`bm project list --workspace <workspace>`."
|
||||
TEAM_WORKSPACE_BISYNC_UNSUPPORTED = (
|
||||
"The bisync operation is only supported on Personal workspaces.\n"
|
||||
"Use `bm cloud sync --name {name}` instead."
|
||||
)
|
||||
|
||||
|
||||
@@ -96,7 +93,7 @@ async def _get_workspace_for_project(name: str, config: BasicMemoryConfig) -> Wo
|
||||
|
||||
|
||||
def _require_personal_workspace(name: str, config: BasicMemoryConfig) -> WorkspaceInfo:
|
||||
"""Exit before rclone work when the target workspace is not personal."""
|
||||
"""Exit before bisync work when the target workspace is not personal."""
|
||||
try:
|
||||
workspace = run_with_cleanup(_get_workspace_for_project(name, config))
|
||||
except Exception as exc:
|
||||
@@ -104,7 +101,7 @@ def _require_personal_workspace(name: str, config: BasicMemoryConfig) -> Workspa
|
||||
raise typer.Exit(1)
|
||||
|
||||
if workspace.workspace_type != "personal":
|
||||
console.print(f"[red]{TEAM_WORKSPACE_SYNC_UNSUPPORTED}[/red]")
|
||||
console.print(f"[red]{TEAM_WORKSPACE_BISYNC_UNSUPPORTED.format(name=name)}[/red]")
|
||||
raise typer.Exit(1)
|
||||
|
||||
return workspace
|
||||
@@ -161,7 +158,6 @@ def sync_project_command(
|
||||
"""
|
||||
config = ConfigManager().config
|
||||
_require_cloud_credentials(config)
|
||||
_require_personal_workspace(name, config)
|
||||
|
||||
try:
|
||||
# Get tenant info for bucket name
|
||||
@@ -270,7 +266,6 @@ def check_project_command(
|
||||
"""
|
||||
config = ConfigManager().config
|
||||
_require_cloud_credentials(config)
|
||||
_require_personal_workspace(name, config)
|
||||
|
||||
try:
|
||||
# Get tenant info for bucket name
|
||||
@@ -353,7 +348,6 @@ def setup_project_sync(
|
||||
config_manager = ConfigManager()
|
||||
config = config_manager.config
|
||||
_require_cloud_credentials(config)
|
||||
_require_personal_workspace(name, config)
|
||||
|
||||
async def _verify_project_exists():
|
||||
"""Verify the project exists on cloud by listing all projects."""
|
||||
@@ -402,8 +396,8 @@ def setup_project_sync(
|
||||
console.print(f"[green]Sync configured for project '{name}'[/green]")
|
||||
console.print(f"\nLocal sync path: {resolved_path}")
|
||||
console.print("\nNext steps:")
|
||||
console.print(f" 1. Preview: bm cloud bisync --name {name} --resync --dry-run")
|
||||
console.print(f" 2. Sync: bm cloud bisync --name {name} --resync")
|
||||
console.print(f" 1. Preview: bm cloud sync --name {name} --dry-run")
|
||||
console.print(f" 2. Sync: bm cloud sync --name {name}")
|
||||
except Exception as e:
|
||||
console.print(f"[red]Error configuring sync: {str(e)}[/red]")
|
||||
raise typer.Exit(1)
|
||||
|
||||
@@ -99,15 +99,12 @@ def test_cloud_bisync_fails_fast_when_sync_entry_disappears(monkeypatch, config_
|
||||
@pytest.mark.parametrize(
|
||||
"argv",
|
||||
[
|
||||
["cloud", "sync", "--name", "research"],
|
||||
["cloud", "bisync", "--name", "research"],
|
||||
["cloud", "check", "--name", "research"],
|
||||
["cloud", "bisync-reset", "research"],
|
||||
["cloud", "sync-setup", "research", "/tmp/research"],
|
||||
],
|
||||
)
|
||||
def test_cloud_sync_commands_block_organization_workspace(monkeypatch, argv, config_manager):
|
||||
"""Rclone sync commands should fail before setup/execution for Team workspaces."""
|
||||
def test_cloud_bisync_commands_block_organization_workspace(monkeypatch, argv, config_manager):
|
||||
"""Bisync commands should fail before setup/execution for Team workspaces."""
|
||||
project_sync_command = importlib.import_module("basic_memory.cli.commands.cloud.project_sync")
|
||||
|
||||
config = config_manager.load_config()
|
||||
@@ -130,13 +127,62 @@ def test_cloud_sync_commands_block_organization_workspace(monkeypatch, argv, con
|
||||
"get_mount_info",
|
||||
lambda: pytest.fail("workspace guard should run before mount lookup"),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
project_sync_command,
|
||||
"get_project_bisync_state",
|
||||
lambda _name: pytest.fail("workspace guard should run before bisync state lookup"),
|
||||
)
|
||||
|
||||
result = runner.invoke(app, argv)
|
||||
|
||||
assert result.exit_code == 1, result.output
|
||||
output = " ".join(result.output.split())
|
||||
assert "Mirror-style rclone sync/bisync is supported only for Personal workspaces" in output
|
||||
assert "overwrite or delete shared cloud files" in output
|
||||
assert "The bisync operation is only supported on Personal workspaces" in output
|
||||
assert "Use `bm cloud sync --name research` instead" in output
|
||||
|
||||
|
||||
def test_cloud_sync_allows_organization_workspace(monkeypatch, config_manager):
|
||||
"""Team workspaces can still use the one-way sync command."""
|
||||
project_sync_command = importlib.import_module("basic_memory.cli.commands.cloud.project_sync")
|
||||
|
||||
config = config_manager.load_config()
|
||||
config.cloud_api_key = "bmc_test"
|
||||
config.projects["research"] = ProjectEntry(
|
||||
path="/tmp/research",
|
||||
mode=ProjectMode.CLOUD,
|
||||
workspace_id="team-tenant",
|
||||
local_sync_path="/tmp/research",
|
||||
)
|
||||
config_manager.save_config(config)
|
||||
|
||||
monkeypatch.setattr(
|
||||
project_sync_command,
|
||||
"get_available_workspaces",
|
||||
lambda: pytest.fail("sync should not require a personal workspace"),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
project_sync_command,
|
||||
"get_mount_info",
|
||||
lambda: _async_value(SimpleNamespace(bucket_name="tenant-bucket")),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
project_sync_command,
|
||||
"_get_cloud_project",
|
||||
lambda _name: _async_value(
|
||||
SimpleNamespace(name="research", external_id="external-project-id", path="research")
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
project_sync_command,
|
||||
"_get_sync_project",
|
||||
lambda _name, _config, _project_data: (SimpleNamespace(name="research"), "/tmp/research"),
|
||||
)
|
||||
monkeypatch.setattr(project_sync_command, "project_sync", lambda *args, **kwargs: True)
|
||||
|
||||
result = runner.invoke(app, ["cloud", "sync", "--name", "research"])
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
assert "research synced successfully" in result.output
|
||||
|
||||
|
||||
def test_require_personal_workspace_allows_personal_workspace(monkeypatch, config_manager):
|
||||
|
||||
Reference in New Issue
Block a user