77from typer .testing import CliRunner
88
99from basic_memory .cli .app import app
10- from basic_memory .config import ProjectMode
10+ from basic_memory .config import ProjectEntry , ProjectMode
11+ from basic_memory .schemas .cloud import WorkspaceInfo
1112
1213runner = CliRunner ()
1314
@@ -28,6 +29,9 @@ def test_cloud_sync_commands_skip_explicit_cloud_project_sync(monkeypatch, argv,
2829 config_manager .save_config (config )
2930
3031 monkeypatch .setattr (project_sync_command , "_require_cloud_credentials" , lambda _config : None )
32+ monkeypatch .setattr (
33+ project_sync_command , "_require_personal_workspace" , lambda _name , _config : None
34+ )
3135 monkeypatch .setattr (
3236 project_sync_command ,
3337 "get_mount_info" ,
@@ -63,6 +67,9 @@ def test_cloud_bisync_fails_fast_when_sync_entry_disappears(monkeypatch, config_
6367 config_manager .save_config (config )
6468
6569 monkeypatch .setattr (project_sync_command , "_require_cloud_credentials" , lambda _config : None )
70+ monkeypatch .setattr (
71+ project_sync_command , "_require_personal_workspace" , lambda _name , _config : None
72+ )
6673 monkeypatch .setattr (
6774 project_sync_command ,
6875 "get_mount_info" ,
@@ -88,5 +95,104 @@ def test_cloud_bisync_fails_fast_when_sync_entry_disappears(monkeypatch, config_
8895 assert "unexpectedly missing after validation" in result .output
8996
9097
98+ @pytest .mark .parametrize (
99+ "argv" ,
100+ [
101+ ["cloud" , "sync" , "--name" , "research" ],
102+ ["cloud" , "bisync" , "--name" , "research" ],
103+ ["cloud" , "check" , "--name" , "research" ],
104+ ["cloud" , "bisync-reset" , "research" ],
105+ ["cloud" , "sync-setup" , "research" , "/tmp/research" ],
106+ ],
107+ )
108+ def test_cloud_sync_commands_block_organization_workspace (monkeypatch , argv , config_manager ):
109+ """Rclone sync commands should fail before setup/execution for Team workspaces."""
110+ project_sync_command = importlib .import_module ("basic_memory.cli.commands.cloud.project_sync" )
111+
112+ config = config_manager .load_config ()
113+ config .cloud_api_key = "bmc_test"
114+ config .projects ["research" ] = ProjectEntry (
115+ path = "/tmp/research" ,
116+ mode = ProjectMode .CLOUD ,
117+ workspace_id = "team-tenant" ,
118+ local_sync_path = "/tmp/research" ,
119+ )
120+ config_manager .save_config (config )
121+
122+ monkeypatch .setattr (
123+ project_sync_command ,
124+ "get_available_workspaces" ,
125+ lambda : _async_value ([_workspace ("team-tenant" , "organization" , "team" )]),
126+ )
127+ monkeypatch .setattr (
128+ project_sync_command ,
129+ "get_mount_info" ,
130+ lambda : pytest .fail ("workspace guard should run before mount lookup" ),
131+ )
132+
133+ result = runner .invoke (app , argv )
134+
135+ assert result .exit_code == 1 , result .output
136+ assert "Local rclone sync/bisync is supported only for Personal workspaces" in result .output
137+ assert "Team workspaces are accessed through the cloud API/MCP" in result .output
138+
139+
140+ def test_require_personal_workspace_allows_personal_workspace (monkeypatch , config_manager ):
141+ """Personal workspaces keep the existing rclone sync path available."""
142+ project_sync_command = importlib .import_module ("basic_memory.cli.commands.cloud.project_sync" )
143+
144+ config = config_manager .load_config ()
145+ config .projects ["research" ] = ProjectEntry (
146+ path = "/tmp/research" ,
147+ mode = ProjectMode .CLOUD ,
148+ workspace_id = "personal-tenant" ,
149+ local_sync_path = "/tmp/research" ,
150+ )
151+ config_manager .save_config (config )
152+
153+ monkeypatch .setattr (
154+ project_sync_command ,
155+ "get_available_workspaces" ,
156+ lambda : _async_value ([_workspace ("personal-tenant" , "personal" , "personal" )]),
157+ )
158+
159+ workspace = project_sync_command ._require_personal_workspace ("research" , config )
160+
161+ assert workspace .tenant_id == "personal-tenant"
162+
163+
164+ def test_bisync_reset_skips_workspace_check_without_credentials (monkeypatch , tmp_path ):
165+ """Resetting local bisync state stays harmless when no cloud credentials exist."""
166+ project_sync_command = importlib .import_module ("basic_memory.cli.commands.cloud.project_sync" )
167+
168+ monkeypatch .setattr (
169+ project_sync_command ,
170+ "get_available_workspaces" ,
171+ lambda : pytest .fail ("workspace lookup requires cloud credentials" ),
172+ )
173+ monkeypatch .setattr (
174+ project_sync_command ,
175+ "get_project_bisync_state" ,
176+ lambda _name : tmp_path / "missing-state" ,
177+ )
178+
179+ result = runner .invoke (app , ["cloud" , "bisync-reset" , "research" ])
180+
181+ assert result .exit_code == 0 , result .output
182+ assert "No bisync state found for project 'research'" in result .output
183+
184+
91185async def _async_value (value ):
92186 return value
187+
188+
189+ def _workspace (tenant_id : str , workspace_type : str , slug : str ) -> WorkspaceInfo :
190+ return WorkspaceInfo (
191+ tenant_id = tenant_id ,
192+ workspace_type = workspace_type ,
193+ slug = slug ,
194+ name = slug .title (),
195+ role = "owner" ,
196+ is_default = False ,
197+ has_active_subscription = True ,
198+ )
0 commit comments