Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions tests/arch/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ AST enforcement, sub-package layer contracts, and architectural invariant tests.
| `test_python_no_hardcoded_temp.py` | Architectural invariant: no literal `.autoskillit/temp` outside the whitelist |
| `test_quota_capability_isolation.py` | AST guard: quota modules must not reference BackendCapabilities fields |
| `test_recipe_diagram_freshness.py` | Parametrized diagram freshness enforcement: bundled recipes must have non-stale diagrams; missing diagrams are xfail(strict=True) with shrink-enforcement meta-test |
| `test_recipe_contract_freshness.py` | Parametrized JSON card freshness enforcement: contract cards must have non-stale .json companions with content parity |
| `test_recipe_rule_registration.py` | REQ-RECIPE-001: every recipe/rules_*.py file must be imported by recipe/__init__.py |
| `test_rule_severity_consistency.py` | AST guards: rule functions must use make_finding()/make_block_finding(); RuleFinding severity must match @semantic_rule decorator severity; _KNOWN_NON_CONFORMING_RULES entries must carry tracking comments |
| `test_regex_guards.py` | Arch guard: keyword regexes in cmd-scanning rules must use path-safe lookbehind guards |
Expand Down
74 changes: 74 additions & 0 deletions tests/arch/test_recipe_contract_freshness.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""Parametrized JSON contract card freshness enforcement: contract cards must have
non-stale .json companions with content parity, and the contract collection must
not silently shrink.
"""

from __future__ import annotations

import json

import pytest

from autoskillit.core.io import load_yaml
from autoskillit.recipe.io import builtin_recipes_dir

pytestmark = [pytest.mark.layer("arch"), pytest.mark.small]

_RECIPES_DIR = builtin_recipes_dir()
_CONTRACTS_DIR = _RECIPES_DIR / "contracts"
_CONTRACT_STEMS = (
sorted(p.stem for p in _CONTRACTS_DIR.glob("*.yaml")) if _CONTRACTS_DIR.is_dir() else []
)

MINIMUM_CONTRACT_COUNT = 15 # floor below current count (17 at creation); catches silent deletion


def test_contracts_dir_exists() -> None:
assert _CONTRACTS_DIR.is_dir(), (
f"Contracts directory not found: {_CONTRACTS_DIR}. "
"Is builtin_recipes_dir() resolving correctly?"
)


@pytest.mark.parametrize("stem", _CONTRACT_STEMS, ids=_CONTRACT_STEMS)
def test_json_card_exists(stem: str) -> None:
json_path = _CONTRACTS_DIR / f"{stem}.json"
assert json_path.is_file(), (
f"Contract '{stem}' has a .yaml card but no .json companion. "
f"Run 'task regen-contracts' to regenerate."
)


@pytest.mark.parametrize("stem", _CONTRACT_STEMS, ids=_CONTRACT_STEMS)
def test_json_card_content_parity(stem: str) -> None:
yaml_path = _CONTRACTS_DIR / f"{stem}.yaml"
json_path = _CONTRACTS_DIR / f"{stem}.json"
if not json_path.is_file():
pytest.skip(f"JSON card missing for '{stem}' (covered by test_json_card_exists)")
yaml_data = load_yaml(yaml_path)
json_data = json.loads(json_path.read_text(encoding="utf-8"))
assert yaml_data == json_data, (
f"Content mismatch between '{stem}.yaml' and '{stem}.json'. "
f"Run 'task regen-contracts' to regenerate."
)


def test_collection_count_rot_guard() -> None:
assert len(_CONTRACT_STEMS) >= MINIMUM_CONTRACT_COUNT, (
f"Expected at least {MINIMUM_CONTRACT_COUNT} contract card YAMLs in "
f"{_CONTRACTS_DIR}, found {len(_CONTRACT_STEMS)}. "
"Is builtin_recipes_dir() resolving correctly? "
"Run 'task regen-contracts' to regenerate."
)


def test_no_orphan_json_cards() -> None:
if not _CONTRACTS_DIR.is_dir():
pytest.skip("Contracts directory does not exist")
json_stems = {p.stem for p in _CONTRACTS_DIR.glob("*.json")}
yaml_stems = set(_CONTRACT_STEMS)
orphans = sorted(json_stems - yaml_stems)
assert not orphans, (
f"Found orphan JSON cards with no matching .yaml: {orphans}. "
f"Remove stale .json files or run 'task regen-contracts'."
)
Loading