""" Parse and validate e2e.yml — the per-repo test contract. """ from __future__ import annotations import yaml from dataclasses import dataclass, field from pathlib import Path from typing import Literal @dataclass class HealthCheck: name: str url: str timeout: int = 120 # seconds @dataclass class E2EConfig: name: str compose_file: str # relative to repo root test_command: str health_checks: list[HealthCheck] = field(default_factory=list) timeout: int = 300 # hard limit for test_command cleanup: Literal["always", "on_success", "never"] = "always" env: dict[str, str] = field(default_factory=dict) @classmethod def load(cls, repo_root: Path) -> "E2EConfig": config_path = repo_root / "e2e" / "e2e.yml" if not config_path.exists(): raise FileNotFoundError(f"No e2e.yml found at {config_path}") raw = yaml.safe_load(config_path.read_text()) health_checks = [ HealthCheck( name=hc.get("name", hc["url"]), url=hc["url"], timeout=int(hc.get("timeout", 120)), ) for hc in raw.get("health_checks", []) ] env = {} for item in raw.get("env", []): if isinstance(item, dict) and "key" in item: env[item["key"]] = str(item.get("value", "")) elif isinstance(item, dict): env.update(item) return cls( name=raw["name"], compose_file=raw["compose_file"], test_command=raw["test_command"], health_checks=health_checks, timeout=int(raw.get("timeout", 300)), cleanup=raw.get("cleanup", "always"), env=env, )