diff --git a/src/specify_cli/presets/__init__.py b/src/specify_cli/presets/__init__.py index de4116228e..f011e1d918 100644 --- a/src/specify_cli/presets/__init__.py +++ b/src/specify_cli/presets/__init__.py @@ -296,6 +296,12 @@ def _validate(self): f"(expected {self.SCHEMA_VERSION})" ) + for section in ("preset", "requires", "provides"): + if not isinstance(self.data[section], dict): + raise PresetValidationError( + f"Invalid {section}: expected a mapping" + ) + # Validate preset metadata pack = self.data["preset"] for field in ["id", "name", "version", "description"]: diff --git a/tests/test_presets.py b/tests/test_presets.py index dbf6ac4ccb..e7df7f7d3e 100644 --- a/tests/test_presets.py +++ b/tests/test_presets.py @@ -197,6 +197,25 @@ def test_non_mapping_yaml_raises_validation_error(self, temp_dir): with pytest.raises(PresetValidationError, match="YAML mapping"): PresetManifest(manifest_path) + @pytest.mark.parametrize("section", ["preset", "requires", "provides"]) + @pytest.mark.parametrize("bad_value", [None, [], "text"]) + def test_required_section_not_mapping_raises_validation_error( + self, temp_dir, valid_pack_data, section, bad_value + ): + """Required manifest sections reject null, list, and scalar values.""" + valid_pack_data[section] = bad_value + manifest_path = temp_dir / "preset.yml" + manifest_path.write_text( + yaml.safe_dump(valid_pack_data), + encoding="utf-8", + ) + + with pytest.raises( + PresetValidationError, + match=rf"Invalid {section}: expected a mapping", + ): + PresetManifest(manifest_path) + @pytest.mark.parametrize( "bad", [