44from types import SimpleNamespace
55
66import pytest
7+ import typer
78from typer .testing import CliRunner
89
910from basic_memory .cli .app import app
@@ -161,6 +162,124 @@ def test_require_personal_workspace_allows_personal_workspace(monkeypatch, confi
161162 assert workspace .tenant_id == "personal-tenant"
162163
163164
165+ def test_require_personal_workspace_uses_default_workspace (monkeypatch , config_manager ):
166+ """When no project workspace is set, the single cloud default is used."""
167+ project_sync_command = importlib .import_module ("basic_memory.cli.commands.cloud.project_sync" )
168+
169+ config = config_manager .load_config ()
170+ config .default_workspace = None
171+ config .projects ["research" ] = ProjectEntry (path = "/tmp/research" , mode = ProjectMode .CLOUD )
172+ config_manager .save_config (config )
173+
174+ monkeypatch .setattr (
175+ project_sync_command ,
176+ "get_available_workspaces" ,
177+ lambda : _async_value (
178+ [
179+ _workspace ("team-tenant" , "organization" , "team" ),
180+ _workspace ("personal-tenant" , "personal" , "personal" , is_default = True ),
181+ ]
182+ ),
183+ )
184+
185+ workspace = project_sync_command ._require_personal_workspace ("research" , config )
186+
187+ assert workspace .tenant_id == "personal-tenant"
188+
189+
190+ def test_require_personal_workspace_uses_single_workspace (monkeypatch , config_manager ):
191+ """A single accessible workspace is unambiguous even when none is marked default."""
192+ project_sync_command = importlib .import_module ("basic_memory.cli.commands.cloud.project_sync" )
193+
194+ config = config_manager .load_config ()
195+ config .default_workspace = None
196+ config .projects ["research" ] = ProjectEntry (path = "/tmp/research" , mode = ProjectMode .CLOUD )
197+ config_manager .save_config (config )
198+
199+ monkeypatch .setattr (
200+ project_sync_command ,
201+ "get_available_workspaces" ,
202+ lambda : _async_value ([_workspace ("personal-tenant" , "personal" , "personal" )]),
203+ )
204+
205+ workspace = project_sync_command ._require_personal_workspace ("research" , config )
206+
207+ assert workspace .tenant_id == "personal-tenant"
208+
209+
210+ def test_require_personal_workspace_reports_no_accessible_workspaces (monkeypatch , config_manager ):
211+ """Workspace resolution exits with a clear error when the account has no workspaces."""
212+ project_sync_command = importlib .import_module ("basic_memory.cli.commands.cloud.project_sync" )
213+
214+ config = config_manager .load_config ()
215+ config .default_workspace = None
216+ config .projects ["research" ] = ProjectEntry (path = "/tmp/research" , mode = ProjectMode .CLOUD )
217+ config_manager .save_config (config )
218+
219+ monkeypatch .setattr (
220+ project_sync_command ,
221+ "get_available_workspaces" ,
222+ lambda : _async_value ([]),
223+ )
224+
225+ with pytest .raises (typer .Exit ) as exc_info :
226+ project_sync_command ._require_personal_workspace ("research" , config )
227+
228+ assert exc_info .value .exit_code == 1
229+
230+
231+ def test_require_personal_workspace_reports_inaccessible_configured_workspace (
232+ monkeypatch , config_manager
233+ ):
234+ """A configured workspace id must be present in the accessible workspace list."""
235+ project_sync_command = importlib .import_module ("basic_memory.cli.commands.cloud.project_sync" )
236+
237+ config = config_manager .load_config ()
238+ config .projects ["research" ] = ProjectEntry (
239+ path = "/tmp/research" ,
240+ mode = ProjectMode .CLOUD ,
241+ workspace_id = "missing-tenant" ,
242+ )
243+ config_manager .save_config (config )
244+
245+ monkeypatch .setattr (
246+ project_sync_command ,
247+ "get_available_workspaces" ,
248+ lambda : _async_value ([_workspace ("personal-tenant" , "personal" , "personal" )]),
249+ )
250+
251+ with pytest .raises (typer .Exit ) as exc_info :
252+ project_sync_command ._require_personal_workspace ("research" , config )
253+
254+ assert exc_info .value .exit_code == 1
255+
256+
257+ def test_require_personal_workspace_reports_ambiguous_workspace (monkeypatch , config_manager ):
258+ """Multiple accessible workspaces need an explicit project or account default."""
259+ project_sync_command = importlib .import_module ("basic_memory.cli.commands.cloud.project_sync" )
260+
261+ config = config_manager .load_config ()
262+ config .default_workspace = None
263+ config .projects ["research" ] = ProjectEntry (path = "/tmp/research" , mode = ProjectMode .CLOUD )
264+ config_manager .save_config (config )
265+
266+ monkeypatch .setattr (
267+ project_sync_command ,
268+ "get_available_workspaces" ,
269+ lambda : _async_value (
270+ [
271+ _workspace ("personal-tenant-a" , "personal" , "personal-a" ),
272+ _workspace ("personal-tenant-b" , "personal" , "personal-b" ),
273+ ]
274+ ),
275+ )
276+
277+ with pytest .raises (typer .Exit ) as exc_info :
278+ project_sync_command ._require_personal_workspace ("research" , config )
279+
280+ assert exc_info .value .exit_code == 1
281+
282+
164283def test_bisync_reset_skips_workspace_check_without_credentials (monkeypatch , tmp_path ):
165284 """Resetting local bisync state stays harmless when no cloud credentials exist."""
166285 project_sync_command = importlib .import_module ("basic_memory.cli.commands.cloud.project_sync" )
@@ -186,13 +305,15 @@ async def _async_value(value):
186305 return value
187306
188307
189- def _workspace (tenant_id : str , workspace_type : str , slug : str ) -> WorkspaceInfo :
308+ def _workspace (
309+ tenant_id : str , workspace_type : str , slug : str , * , is_default : bool = False
310+ ) -> WorkspaceInfo :
190311 return WorkspaceInfo (
191312 tenant_id = tenant_id ,
192313 workspace_type = workspace_type ,
193314 slug = slug ,
194315 name = slug .title (),
195316 role = "owner" ,
196- is_default = False ,
317+ is_default = is_default ,
197318 has_active_subscription = True ,
198319 )
0 commit comments