Skip to content

Commit dec57ea

Browse files
committed
fix: align catalog remove with visible entries
1 parent af56f63 commit dec57ea

2 files changed

Lines changed: 96 additions & 7 deletions

File tree

src/specify_cli/integrations/catalog.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -604,16 +604,26 @@ def remove_catalog(self, index: int) -> str:
604604
# only to mirror the order shown by ``catalog list``; entries
605605
# that ``_load_catalog_config`` would have rejected outright
606606
# would have failed ``catalog list`` already.
607+
def _is_removable_catalog_entry(item: Any) -> bool:
608+
if not isinstance(item, dict):
609+
return False
610+
raw_url = item.get("url")
611+
return isinstance(raw_url, str) and bool(raw_url.strip())
612+
607613
priority_pairs: List[Tuple[int, int]] = []
608614
for yaml_idx, item in enumerate(catalogs):
609-
if isinstance(item, dict):
610-
try:
611-
priority = int(item.get("priority", yaml_idx + 1))
612-
except (TypeError, ValueError):
613-
priority = yaml_idx + 1
614-
else:
615+
if not _is_removable_catalog_entry(item):
616+
continue
617+
618+
try:
619+
priority = int(item.get("priority", yaml_idx + 1))
620+
except (TypeError, ValueError):
615621
priority = yaml_idx + 1
616622
priority_pairs.append((priority, yaml_idx))
623+
if not priority_pairs:
624+
raise IntegrationValidationError(
625+
"Catalog config contains no removable catalog entries."
626+
)
617627
# Stable sort: ties keep their YAML order, matching list-view ordering.
618628
priority_pairs.sort(key=lambda p: p[0])
619629
display_order: List[int] = [yaml_idx for _, yaml_idx in priority_pairs]
@@ -626,7 +636,7 @@ def remove_catalog(self, index: int) -> str:
626636
target_yaml_idx = display_order[index]
627637
removed = catalogs.pop(target_yaml_idx)
628638

629-
if catalogs:
639+
if any(_is_removable_catalog_entry(item) for item in catalogs):
630640
data["catalogs"] = catalogs
631641
with open(config_path, "w", encoding="utf-8") as f:
632642
yaml.dump(

tests/integrations/test_integration_catalog.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,6 +1082,85 @@ def test_remove_catalog_display_order_with_missing_priorities(
10821082
data = yaml.safe_load(cfg_path.read_text(encoding="utf-8"))
10831083
assert [c["name"] for c in data["catalogs"]] == ["two", "three"]
10841084

1085+
def test_remove_catalog_display_order_skips_blank_url_entries(
1086+
self, tmp_path, monkeypatch
1087+
):
1088+
"""Blank-url entries are not shown by catalog list, so remove skips them too."""
1089+
self._isolate(tmp_path, monkeypatch)
1090+
cfg_path = tmp_path / ".specify" / "integration-catalogs.yml"
1091+
cfg_path.parent.mkdir(parents=True, exist_ok=True)
1092+
cfg_path.write_text(
1093+
yaml.dump(
1094+
{
1095+
"catalogs": [
1096+
{"url": " ", "name": "blank", "priority": 0},
1097+
{"url": "https://one.example.com/c.json", "name": "one"},
1098+
{"url": "https://two.example.com/c.json", "name": "two"},
1099+
]
1100+
}
1101+
),
1102+
encoding="utf-8",
1103+
)
1104+
cat = IntegrationCatalog(tmp_path)
1105+
1106+
removed = cat.remove_catalog(0)
1107+
assert removed == "one"
1108+
1109+
data = yaml.safe_load(cfg_path.read_text(encoding="utf-8"))
1110+
assert [c["name"] for c in data["catalogs"]] == ["blank", "two"]
1111+
1112+
def test_remove_catalog_deletes_file_when_only_skipped_entries_remain(
1113+
self, tmp_path, monkeypatch
1114+
):
1115+
self._isolate(tmp_path, monkeypatch)
1116+
cfg_path = tmp_path / ".specify" / "integration-catalogs.yml"
1117+
cfg_path.parent.mkdir(parents=True, exist_ok=True)
1118+
cfg_path.write_text(
1119+
yaml.dump(
1120+
{
1121+
"catalogs": [
1122+
{"url": " ", "name": "blank", "priority": 0},
1123+
{"url": "https://one.example.com/c.json", "name": "one"},
1124+
]
1125+
}
1126+
),
1127+
encoding="utf-8",
1128+
)
1129+
cat = IntegrationCatalog(tmp_path)
1130+
1131+
removed = cat.remove_catalog(0)
1132+
assert removed == "one"
1133+
assert not cfg_path.exists()
1134+
1135+
active = cat.get_active_catalogs()
1136+
assert [e.name for e in active] == ["default", "community"]
1137+
1138+
def test_remove_catalog_errors_when_no_entries_are_removable(
1139+
self, tmp_path, monkeypatch
1140+
):
1141+
self._isolate(tmp_path, monkeypatch)
1142+
cfg_path = tmp_path / ".specify" / "integration-catalogs.yml"
1143+
cfg_path.parent.mkdir(parents=True, exist_ok=True)
1144+
cfg_path.write_text(
1145+
yaml.dump(
1146+
{
1147+
"catalogs": [
1148+
{"url": "", "name": "empty"},
1149+
{"name": "missing"},
1150+
"not-a-mapping",
1151+
]
1152+
}
1153+
),
1154+
encoding="utf-8",
1155+
)
1156+
cat = IntegrationCatalog(tmp_path)
1157+
1158+
with pytest.raises(
1159+
IntegrationValidationError,
1160+
match="no removable catalog entries",
1161+
):
1162+
cat.remove_catalog(0)
1163+
10851164
def test_remove_catalog_display_order_mixes_explicit_and_default(
10861165
self, tmp_path, monkeypatch
10871166
):

0 commit comments

Comments
 (0)