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