From 772b8489e4cbe4422a20f7c25da778347a5c1858 Mon Sep 17 00:00:00 2001 From: Mouse Date: Sat, 2 May 2026 14:24:05 -0700 Subject: [PATCH] fix: harden CLI config top-level shape validation --- promptlens/cli.py | 13 ++++++++++++- tests/test_cli_config_shape_validation.py | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 tests/test_cli_config_shape_validation.py diff --git a/promptlens/cli.py b/promptlens/cli.py index 235963a..9348304 100644 --- a/promptlens/cli.py +++ b/promptlens/cli.py @@ -27,6 +27,17 @@ console = Console() +def _normalize_config_data(config_data: object, config_path: str) -> dict: + """Validate and normalize loaded YAML config payload.""" + if config_data is None: + raise ValueError(f"Configuration file is empty: {config_path}") + if not isinstance(config_data, dict): + raise ValueError( + f"Invalid configuration format in {config_path}: expected a YAML mapping at top level" + ) + return config_data + + def _remove_path_if_exists(path: Path) -> None: """Remove a file/symlink/directory path if it exists.""" if path.is_symlink() or path.is_file(): @@ -100,7 +111,7 @@ def run( # Load config console.print(f"\n[cyan]Loading configuration from {config}...[/cyan]") with open(config, "r") as f: - config_data = yaml.safe_load(f) + config_data = _normalize_config_data(yaml.safe_load(f), config) # Override with CLI options if golden_set: diff --git a/tests/test_cli_config_shape_validation.py b/tests/test_cli_config_shape_validation.py new file mode 100644 index 0000000..384e8d4 --- /dev/null +++ b/tests/test_cli_config_shape_validation.py @@ -0,0 +1,18 @@ +import pytest + +from promptlens.cli import _normalize_config_data + + +def test_normalize_config_data_rejects_empty_config() -> None: + with pytest.raises(ValueError, match="Configuration file is empty"): + _normalize_config_data(None, "config.yaml") + + +def test_normalize_config_data_rejects_non_mapping() -> None: + with pytest.raises(ValueError, match="expected a YAML mapping at top level"): + _normalize_config_data([{"golden_set": "tests.yaml"}], "config.yaml") + + +def test_normalize_config_data_accepts_mapping() -> None: + data = {"golden_set": "tests.yaml", "models": []} + assert _normalize_config_data(data, "config.yaml") == data