From 0ce4ca0ee521b1081cbc242ee338be12a1c31910 Mon Sep 17 00:00:00 2001 From: Antoine Date: Thu, 28 May 2026 13:30:01 +0200 Subject: [PATCH] fix: validate canonical yaml root --- src/job_research/storage.py | 7 ++++++- tests/test_storage.py | 10 ++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/job_research/storage.py b/src/job_research/storage.py index 5f33c7a..9e06d9a 100644 --- a/src/job_research/storage.py +++ b/src/job_research/storage.py @@ -1,5 +1,6 @@ from __future__ import annotations +from collections.abc import Mapping from pathlib import Path from typing import Any @@ -15,4 +16,8 @@ def save_candidate_profile_yaml(path: Path, profile: CandidateProfileOutput) -> def load_yaml(path: Path) -> dict[str, Any]: - return yaml.safe_load(path.read_text(encoding="utf-8")) or {} + payload = yaml.safe_load(path.read_text(encoding="utf-8")) + if not isinstance(payload, Mapping): + raise ValueError("candidate-profile YAML root must be a mapping") + + return dict(payload) diff --git a/tests/test_storage.py b/tests/test_storage.py index e1c49c0..8b11b8e 100644 --- a/tests/test_storage.py +++ b/tests/test_storage.py @@ -1,3 +1,5 @@ +import pytest + from job_research.models import CandidateProfileOutput, ExperienceEntry, WarningItem from job_research.storage import save_candidate_profile_yaml, load_yaml @@ -27,3 +29,11 @@ def test_save_candidate_profile_yaml_round_trips_readable_output(tmp_path) -> No assert payload["name"] == "Tonio" assert payload["constraints"] == ["CDI only", "France only"] assert payload["warnings"][0]["field"] == "years_of_experience" + + +def test_load_yaml_rejects_non_mapping_root(tmp_path) -> None: + path = tmp_path / "candidate-profile.yaml" + path.write_text("[]", encoding="utf-8") + + with pytest.raises(ValueError, match="mapping"): + load_yaml(path)