From 18d9c283913172ae65c84ac20876d4a962db9fd8 Mon Sep 17 00:00:00 2001 From: Andrew Chen <48723787+chuenchen309@users.noreply.github.com> Date: Sun, 19 Jul 2026 11:08:18 +0800 Subject: [PATCH] fix(integrations): catch OverflowError on a `priority: .inf` in add/remove IntegrationCatalog.add_catalog and remove_catalog re-validate the existing catalog entries' priorities inline, separately from the base loader. Both did `int(raw_priority)` under `except (TypeError, ValueError)`, so a `priority: .inf` (float('inf')) raised OverflowError: add_catalog leaked a raw traceback instead of IntegrationValidationError, and remove_catalog crashed while building the display order. Add OverflowError to both handlers, matching the base loader (#3525) and the workflow/step loaders (#3526). add_catalog now raises IntegrationValidationError; remove_catalog falls back to positional order like the other non-integer priorities. Co-Authored-By: Claude Opus 4.8 --- src/specify_cli/integrations/catalog.py | 6 ++- .../integrations/test_integration_catalog.py | 51 +++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) 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"