diff --git a/src/specify_cli/extensions/catalog/command_remove.py b/src/specify_cli/extensions/catalog/command_remove.py index e99bc7faa9..4d42de937b 100644 --- a/src/specify_cli/extensions/catalog/command_remove.py +++ b/src/specify_cli/extensions/catalog/command_remove.py @@ -48,16 +48,21 @@ def catalog_remove( _commands.console.print(f"[red]Error:[/red] Catalog '{safe_name}' not found.") raise typer.Exit(1) - config["catalogs"] = catalogs - config_path.write_text( - _commands.yaml.safe_dump( - config, - default_flow_style=False, - sort_keys=False, - allow_unicode=True, - ), - encoding="utf-8", - ) + if catalogs: + config["catalogs"] = catalogs + config_path.write_text( + _commands.yaml.safe_dump( + config, + default_flow_style=False, + sort_keys=False, + allow_unicode=True, + ), + encoding="utf-8", + ) + else: + # The catalog loader rejects an existing config with no entries, so + # delete the file instead of writing ``catalogs: []``. + config_path.unlink(missing_ok=True) _commands.console.print(f"[green]✓[/green] Removed catalog '{safe_name}'") if not catalogs: diff --git a/tests/specify_cli/extensions/catalog/test_command_remove.py b/tests/specify_cli/extensions/catalog/test_command_remove.py index a66891a121..c4e7f93342 100644 --- a/tests/specify_cli/extensions/catalog/test_command_remove.py +++ b/tests/specify_cli/extensions/catalog/test_command_remove.py @@ -73,3 +73,77 @@ def test_extension_catalog_remove_escapes_catalog_name_markup(self, tmp_path): assert result.exit_code == 0, result.output output = _normalize_cli_output(result.output) assert f"Removed catalog '{catalog_name}'" in output + + def test_extension_catalog_remove_final_entry_restores_defaults( + self, tmp_path, monkeypatch + ): + """The loader rejects an empty ``catalogs`` list, so removing the last + entry must not leave one behind for follow-up commands to trip on.""" + project = self._make_project(tmp_path) + monkeypatch.setenv("HOME", str(tmp_path)) + monkeypatch.setenv("USERPROFILE", str(tmp_path)) + monkeypatch.delenv("SPECKIT_CATALOG_URL", raising=False) + + add = self._invoke( + [ + "extension", + "catalog", + "add", + "https://only.example.com/catalog.json", + "--name", + "only", + ], + project, + ) + assert add.exit_code == 0, add.output + + remove = self._invoke(["extension", "catalog", "remove", "only"], project) + assert remove.exit_code == 0, remove.output + assert "Built-in defaults will be used" in _normalize_cli_output(remove.output) + + listing = self._invoke(["extension", "catalog", "list"], project) + assert listing.exit_code == 0, listing.output + output = _normalize_cli_output(listing.output) + assert "default (priority 1)" in output + assert "community (priority 2)" in output + assert "Using built-in default catalog stack" in output + assert not (project / ".specify" / "extension-catalogs.yml").exists() + + def test_extension_catalog_remove_keeps_config_with_remaining_entries( + self, tmp_path + ): + project = self._make_project(tmp_path) + keep = { + "name": "keep", + "url": "https://keep.example.com/catalog.json", + "priority": 20, + "install_allowed": False, + "description": "", + } + cfg_path = project / ".specify" / "extension-catalogs.yml" + cfg_path.write_text( + yaml.safe_dump( + { + "catalogs": [ + { + "name": "drop", + "url": "https://drop.example.com/catalog.json", + "priority": 10, + "install_allowed": False, + "description": "", + }, + keep, + ] + }, + sort_keys=False, + ), + encoding="utf-8", + ) + + result = self._invoke(["extension", "catalog", "remove", "drop"], project) + + assert result.exit_code == 0, result.output + assert "No catalogs remain" not in _normalize_cli_output(result.output) + assert yaml.safe_load(cfg_path.read_text(encoding="utf-8")) == { + "catalogs": [keep] + }