@@ -625,42 +625,96 @@ def test_get_workspace_for_project_override_no_match_raises(monkeypatch, config_
625625 assert "No accessible workspace matches 'acme'" in str (exc_info .value )
626626
627627
628- def test_cloud_setup_workspace_configures_named_remote (monkeypatch ):
629- """`bm cloud setup --workspace acme` provisions the acme tenant's own remote."""
630- core = importlib .import_module ("basic_memory.cli.commands.cloud.core_commands" )
631- recorder : dict = {}
632-
628+ def _stub_setup_env (monkeypatch , core , * , remote_exists = False , recorder = None ):
629+ """Stub the `bm cloud setup` dependency chain for the acme workspace."""
633630 monkeypatch .setattr (core , "install_rclone" , lambda : None )
634631 monkeypatch .setattr (
635632 core ,
636633 "get_available_workspaces" ,
637634 lambda : _async_value ([_workspace ("team-tenant" , "organization" , "acme" )]),
638635 )
636+ monkeypatch .setattr (core , "rclone_remote_exists" , lambda _remote : remote_exists )
637+
638+ def _mint (_tenant_id ):
639+ if recorder is not None :
640+ recorder ["minted" ] = True
641+ return _async_value (SimpleNamespace (access_key = "ak" , secret_key = "sk" ))
642+
639643 monkeypatch .setattr (
640644 core ,
641645 "get_mount_info" ,
642646 lambda ** _kwargs : _async_value (
643647 SimpleNamespace (tenant_id = "team-tenant" , bucket_name = "acme-bucket" )
644648 ),
645649 )
646- monkeypatch .setattr (
647- core ,
648- "generate_mount_credentials" ,
649- lambda _tenant_id : _async_value (SimpleNamespace (access_key = "ak" , secret_key = "sk" )),
650- )
650+ monkeypatch .setattr (core , "generate_mount_credentials" , _mint )
651651
652652 def _fake_configure (** kwargs ):
653- recorder .update (kwargs )
653+ if recorder is not None :
654+ recorder .update (kwargs )
654655 return kwargs .get ("remote_name" )
655656
656657 monkeypatch .setattr (core , "configure_rclone_remote" , _fake_configure )
657658
659+
660+ def test_cloud_setup_workspace_configures_named_remote (monkeypatch ):
661+ """`bm cloud setup --workspace acme` provisions the acme tenant's own remote."""
662+ core = importlib .import_module ("basic_memory.cli.commands.cloud.core_commands" )
663+ recorder : dict = {}
664+ _stub_setup_env (monkeypatch , core , remote_exists = False , recorder = recorder )
665+
658666 result = runner .invoke (app , ["cloud" , "setup" , "--workspace" , "acme" ])
659667
660668 assert result .exit_code == 0 , result .output
661669 assert recorder ["remote_name" ] == "basic-memory-cloud-acme"
662670
663671
672+ def test_cloud_setup_aborts_when_remote_exists_without_force (monkeypatch ):
673+ """Setup refuses to overwrite an existing remote, and mints nothing (the #922 footgun)."""
674+ core = importlib .import_module ("basic_memory.cli.commands.cloud.core_commands" )
675+ recorder : dict = {}
676+ _stub_setup_env (monkeypatch , core , remote_exists = True , recorder = recorder )
677+
678+ result = runner .invoke (app , ["cloud" , "setup" , "--workspace" , "acme" ])
679+
680+ assert result .exit_code == 1 , result .output
681+ output = " " .join (result .output .split ())
682+ assert "basic-memory-cloud-acme' is already configured" in output
683+ assert "--force" in output
684+ # Aborted before minting credentials or touching the remote.
685+ assert "minted" not in recorder
686+ assert "remote_name" not in recorder
687+
688+
689+ def test_cloud_setup_force_overwrites_existing_remote (monkeypatch ):
690+ """--force reconfigures an existing remote."""
691+ core = importlib .import_module ("basic_memory.cli.commands.cloud.core_commands" )
692+ recorder : dict = {}
693+ _stub_setup_env (monkeypatch , core , remote_exists = True , recorder = recorder )
694+
695+ result = runner .invoke (app , ["cloud" , "setup" , "--workspace" , "acme" , "--force" ])
696+
697+ assert result .exit_code == 0 , result .output
698+ assert recorder ["remote_name" ] == "basic-memory-cloud-acme"
699+ assert recorder .get ("minted" ) is True
700+
701+
702+ def test_cloud_setup_default_workspace_aborts_when_remote_exists (monkeypatch ):
703+ """The original footgun: `bm cloud setup` (no --workspace) must not clobber
704+ the shared basic-memory-cloud remote without --force."""
705+ core = importlib .import_module ("basic_memory.cli.commands.cloud.core_commands" )
706+ recorder : dict = {}
707+ _stub_setup_env (monkeypatch , core , remote_exists = True , recorder = recorder )
708+
709+ result = runner .invoke (app , ["cloud" , "setup" ]) # no --workspace → basic-memory-cloud
710+
711+ assert result .exit_code == 1 , result .output
712+ output = " " .join (result .output .split ())
713+ assert "'basic-memory-cloud' is already configured" in output
714+ assert "minted" not in recorder # nothing minted on abort
715+ assert "remote_name" not in recorder
716+
717+
664718async def _async_value (value ):
665719 return value
666720
0 commit comments