diff --git a/src/specify_cli/integrations/catalog.py b/src/specify_cli/integrations/catalog.py index aba5877d8f..c9797934b5 100644 --- a/src/specify_cli/integrations/catalog.py +++ b/src/specify_cli/integrations/catalog.py @@ -429,7 +429,8 @@ def add_catalog(self, url: str, name: Optional[str] = None) -> None: ) try: normalized_priority = int(raw_priority) - except (TypeError, ValueError): + except (TypeError, ValueError, OverflowError): + # OverflowError: int(float("inf")) — a ``priority: .inf``. raise IntegrationValidationError( f"Invalid catalog entry at index {idx} in {config_path}: " f"'priority' must be an integer, got " @@ -537,7 +538,8 @@ def _is_removable_catalog_entry(item: Any) -> bool: else: try: priority = int(raw_priority) - except (TypeError, ValueError): + except (TypeError, ValueError, OverflowError): + # OverflowError: int(float("inf")) — a ``priority: .inf``. priority = yaml_idx + 1 priority_pairs.append((priority, yaml_idx)) if not priority_pairs: diff --git a/tests/integrations/test_integration_catalog.py b/tests/integrations/test_integration_catalog.py index a0323b3ea6..3ab9c3612e 100644 --- a/tests/integrations/test_integration_catalog.py +++ b/tests/integrations/test_integration_catalog.py @@ -922,6 +922,57 @@ def test_add_catalog_rejects_non_mapping_entry_with_config_path( assert str(cfg_path) in message assert "expected a mapping" in message + def test_add_catalog_rejects_inf_priority_in_existing_entry( + self, tmp_path, monkeypatch + ): + # ``priority: .inf`` loads as float('inf'); int() on it raises + # OverflowError, which used to escape the IntegrationValidationError + # contract as a raw traceback (github/spec-kit#3526 fixed the sibling + # workflow/step loaders the same way). + self._isolate(tmp_path, monkeypatch) + cfg_path = tmp_path / ".specify" / "integration-catalogs.yml" + cfg_path.write_text( + yaml.dump( + { + "catalogs": [ + { + "url": "https://a.example.com/catalog.json", + "priority": float("inf"), + } + ] + } + ), + encoding="utf-8", + ) + cat = IntegrationCatalog(tmp_path) + with pytest.raises( + IntegrationValidationError, match="must be an integer" + ): + cat.add_catalog("https://new.example.com/catalog.json") + + def test_remove_catalog_tolerates_inf_priority(self, tmp_path, monkeypatch): + # Building the remove display order must not crash on a ``priority: + # .inf`` entry; it falls back to positional order like the other + # non-integer priorities do. + self._isolate(tmp_path, monkeypatch) + cfg_path = tmp_path / ".specify" / "integration-catalogs.yml" + cfg_path.write_text( + yaml.dump( + { + "catalogs": [ + { + "url": "https://a.example.com/catalog.json", + "priority": float("inf"), + }, + {"url": "https://b.example.com/catalog.json", "priority": 2}, + ] + } + ), + encoding="utf-8", + ) + cat = IntegrationCatalog(tmp_path) + cat.remove_catalog(0) # must not raise OverflowError + def test_add_catalog_skips_blank_url_entries(self, tmp_path, monkeypatch): self._isolate(tmp_path, monkeypatch) cfg_path = tmp_path / ".specify" / "integration-catalogs.yml"