diff --git a/src/specify_cli/bundler/models/catalog.py b/src/specify_cli/bundler/models/catalog.py index 293d31c69b..848ec789da 100644 --- a/src/specify_cli/bundler/models/catalog.py +++ b/src/specify_cli/bundler/models/catalog.py @@ -152,14 +152,21 @@ def from_dict(cls, data: Any) -> "CatalogEntry": if not isinstance(data, dict): raise BundlerError("Each catalog entry must be a mapping.") entry_id = str(data.get("id", "")).strip() - requires = data.get("requires") or {} - if not isinstance(requires, dict): + # `or {}` would coerce a FALSY non-mapping (0, '', False, []) to {} before + # the isinstance guard, silently accepting a corrupt catalog entry; only + # an absent/None value means "not present". + requires = data.get("requires") + if requires is None: + requires = {} + elif not isinstance(requires, dict): raise BundlerError( f"Catalog entry '{entry_id or ''}': 'requires' must be a " "mapping when present." ) - provides_raw = data.get("provides") or {} - if not isinstance(provides_raw, dict): + provides_raw = data.get("provides") + if provides_raw is None: + provides_raw = {} + elif not isinstance(provides_raw, dict): raise BundlerError( f"Catalog entry '{entry_id or ''}': 'provides' must be a " "mapping when present." diff --git a/tests/contract/test_catalog_schema.py b/tests/contract/test_catalog_schema.py index bfa289d497..64cd47db6e 100644 --- a/tests/contract/test_catalog_schema.py +++ b/tests/contract/test_catalog_schema.py @@ -207,3 +207,17 @@ def test_catalog_entry_rejects_non_mapping_provides(): data["provides"] = "extensions" with pytest.raises(BundlerError, match="'provides' must be a mapping"): CatalogEntry.from_dict(data) + + +@pytest.mark.parametrize("field", ["requires", "provides"]) +@pytest.mark.parametrize("bad", [[], "", 0, False]) +def test_catalog_entry_rejects_falsy_non_mapping(field, bad): + # `or {}` coerced a FALSY non-mapping ([], '', 0, False) to {} before the + # isinstance guard, silently accepting a corrupt entry; only absent/None + # means "not present". Mirrors the manifest requires/provides guard. + from specify_cli.bundler.models.catalog import CatalogEntry + + data = catalog_entry_dict("demo") + data[field] = bad + with pytest.raises(BundlerError, match=f"'{field}' must be a mapping"): + CatalogEntry.from_dict(data)