"""Unit tests for the dimension IR + ``admits`` scoping gate (Fase 1, Step 1, SC1). ``admits`` must accept an in-dimension candidate (``measure_type`` in the allowed set) and reject an out-of-dimension one, exercising BOTH branches of the code-prefix logic — empty prefixes (measure_type-only) and non-empty prefixes where a matching vs non-matching code decides the outcome. A ``Dimension`` with an empty ``label`` must fail construction (Pydantic ``ValidationError``). Model test: ``tests/test_validator.py`` (rejection assertions). """ from __future__ import annotations import pytest from pydantic import ValidationError from portfolio_optimiser.dimension import Dimension, admits, load_dimension def _energy_dim(*, prefixes: frozenset[str] = frozenset()) -> Dimension: return Dimension( id="energi", label="Energi", allowed_measure_types=frozenset({"energy_efficiency"}), allowed_code_prefixes=prefixes, ) def test_admits_in_dimension_measure_type_only() -> None: """Empty ``allowed_code_prefixes``: only the measure_type gate applies.""" dim = _energy_dim() assert ( admits(measure_type="energy_efficiency", codes=frozenset({"07.1"}), dimension=dim) is True ) def test_admits_rejects_out_of_dimension_measure_type() -> None: """Out-of-dimension measure_type is rejected regardless of codes.""" dim = _energy_dim() assert admits(measure_type="paving", codes=frozenset({"07.1"}), dimension=dim) is False def test_admits_prefix_branch_matching_code() -> None: """Non-empty prefixes: a code matching a prefix is admitted.""" dim = _energy_dim(prefixes=frozenset({"07"})) assert ( admits( measure_type="energy_efficiency", codes=frozenset({"07.1", "99.9"}), dimension=dim, ) is True ) def test_admits_prefix_branch_no_matching_code() -> None: """Non-empty prefixes: no code matches any prefix -> rejected even when measure_type is allowed.""" dim = _energy_dim(prefixes=frozenset({"07"})) assert ( admits( measure_type="energy_efficiency", codes=frozenset({"99.9"}), dimension=dim, ) is False ) def test_dimension_empty_label_raises() -> None: """An empty ``label`` violates ``Field(min_length=1)`` -> ValidationError at construction.""" with pytest.raises(ValidationError): Dimension( id="energi", label="", allowed_measure_types=frozenset({"energy_efficiency"}), ) # --- Step 1 (S5.3): fail-fast load_dimension loader (mirrors load_goal_config) ------------------- def test_load_dimension_round_trip(tmp_path) -> None: """A valid dimension JSON round-trips through ``load_dimension`` (accepts str | Path).""" dim = Dimension( id="energi", label="Energi", allowed_measure_types=frozenset({"energy_efficiency"}), allowed_code_prefixes=frozenset({"07"}), ) p = tmp_path / "dim.json" p.write_text(dim.model_dump_json(), encoding="utf-8") assert load_dimension(p) == dim assert load_dimension(str(p)) == dim # str path also accepted def test_load_dimension_missing_file_raises(tmp_path) -> None: """A missing file fails fast with ``FileNotFoundError`` — authoritative startup config, NOT a tolerant RAW inbox layer (contrast the verdict inbox).""" with pytest.raises(FileNotFoundError): load_dimension(tmp_path / "does-not-exist.json") def test_load_dimension_malformed_shape_raises(tmp_path) -> None: """Malformed content (missing required fields) fails fast with ``ValidationError``.""" bad = tmp_path / "dim.json" bad.write_text('{"id": "energi"}', encoding="utf-8") # missing label + allowed_measure_types with pytest.raises(ValidationError): load_dimension(bad)