Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions src/specify_cli/extensions/catalog/command_remove.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
74 changes: 74 additions & 0 deletions tests/specify_cli/extensions/catalog/test_command_remove.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
}