"""Fixture discovery and materialization helpers for release fixtures. Each fixture is a directory under ``tests/fixtures/releases///`` containing one ``expected.yaml`` file. See ``releases/README.md`` for the schema. """ from __future__ import annotations from dataclasses import dataclass from pathlib import Path import yaml FIXTURES_ROOT = Path(__file__).parent @dataclass(frozen=True) class ReleaseFixture: """A loaded fixture, ready to be materialized into a temp dir.""" name: str # "/", e.g. "easy/back_in_action" path: Path # directory containing expected.yaml data: dict # parsed YAML contents @property def release_name(self) -> str: return self.data["release_name"] @property def expected_parsed(self) -> dict: return self.data.get("parsed", {}) @property def tree(self) -> list[str]: return self.data.get("tree", []) @property def routing(self) -> dict: return self.data.get("routing", {}) @property def xfail_reason(self) -> str | None: """If set, the fixture is expected to fail — wrapped with ``pytest.mark.xfail`` by the test runner. Used for known not-supported pathological cases (typically PATH OF PAIN bucket). """ return self.data.get("xfail_reason") def materialize(self, root: Path) -> None: """Create the fixture's ``tree`` as empty files/dirs under ``root``.""" for entry in self.tree: target = root / entry if entry.endswith("/"): target.mkdir(parents=True, exist_ok=True) else: target.parent.mkdir(parents=True, exist_ok=True) target.touch() def discover_fixtures() -> list[ReleaseFixture]: """Find all ``expected.yaml`` files under FIXTURES_ROOT.""" fixtures = [] for yaml_path in sorted(FIXTURES_ROOT.glob("*/*/expected.yaml")): data = yaml.safe_load(yaml_path.read_text()) name = f"{yaml_path.parent.parent.name}/{yaml_path.parent.name}" fixtures.append(ReleaseFixture(name=name, path=yaml_path.parent, data=data)) return fixtures