from pathlib import Path import textwrap import pytest from automation.config import RulesConfig def write_yaml(path: Path, content: str) -> None: path.write_text(textwrap.dedent(content).strip() + "\n", encoding="utf-8") @pytest.fixture() def sample_repo(tmp_path: Path) -> Path: root = tmp_path / "repo" (root / "Docs" / "features" / "FR_123" / "discussions").mkdir(parents=True, exist_ok=True) write_yaml( root / ".ai-rules.yml", """ file_associations: feature_request.md: feature_request rules: feature_request: outputs: summary: path: "{feature_id}/summary.md" """, ) write_yaml( root / "Docs" / "features" / ".ai-rules.yml", """ file_associations: design.md: design_rule rules: design_rule: outputs: diagram: path: "diagrams/{stage}.puml" """, ) write_yaml( root / "Docs" / "features" / "FR_123" / ".ai-rules.yml", """ rules: design_rule: outputs: diagram: instruction: "Draw updated design diagram" """, ) return root def test_get_rule_name_cascades(sample_repo: Path) -> None: cfg = RulesConfig.load(sample_repo) assert cfg.get_rule_name(Path("feature_request.md")) == "feature_request" assert cfg.get_rule_name(Path("Docs/features/design.md")) == "design_rule" assert cfg.get_rule_name(Path("unknown.md")) is None def test_cascade_for_merges_overrides(sample_repo: Path) -> None: cfg = RulesConfig.load(sample_repo) rel = Path("Docs/features/FR_123/discussions/design.discussion.md") merged = cfg.cascade_for(rel, "design_rule") outputs = merged["outputs"] assert "diagram" in outputs diagram_cfg = outputs["diagram"] assert diagram_cfg["path"] == "diagrams/{stage}.puml" assert diagram_cfg["instruction"] == "Draw updated design diagram" def test_template_rendering(sample_repo: Path) -> None: cfg = RulesConfig.load(sample_repo) rel = Path("Docs/features/FR_123/discussions/design.discussion.md") rendered = cfg.resolve_template("{feature_id}/{stage}.sum.md", rel) assert rendered == "FR_123/design.sum.md" rendered_dir = cfg.resolve_template("{dir}/generated.md", Path("Docs/features/FR_123/request.md")) assert rendered_dir == "Docs/features/FR_123/generated.md" rendered_repo = cfg.resolve_template("{repo}/README.md", Path("Docs/features/FR_123/request.md")) assert rendered_repo == "./README.md" rendered_path = cfg.resolve_template("copy-of-{path}", Path("Docs/features/FR_123/request.md")) assert rendered_path == "copy-of-Docs/features/FR_123/request.md" def test_normalize_repo_rel_blocks_escape(sample_repo: Path) -> None: cfg = RulesConfig.load(sample_repo) with pytest.raises(ValueError): cfg.normalize_repo_rel("../outside.md") assert cfg.normalize_repo_rel("Docs/features/file.md").as_posix() == "Docs/features/file.md"