from __future__ import annotations import csv import sys import tomllib from pathlib import Path repo_root = Path(__file__).resolve().parents[2] sys.path.insert(0, str(repo_root / "src")) from ctx.monitor import routes as monitor_routes # noqa: E402 TRACKER = repo_root / "docs" / "qa" / "feature-user-story-status.csv" README = repo_root / "README.md" PASS_STATUSES = {"Tested Pass", "Retested Pass"} VALIDATION_STATUSES = {"Needs Validation"} FIX_STATUSES = {"Needs Fix"} ACTIONABLE_STATUSES = PASS_STATUSES | VALIDATION_STATUSES | FIX_STATUSES def _tracker_rows() -> list[dict[str, str]]: with TRACKER.open(newline="", encoding="utf-8") as f: return list(csv.DictReader(f)) def _tracker_text() -> str: return "\n".join(" ".join(row.values()) for row in _tracker_rows()) def test_feature_user_story_tracker_has_no_empty_core_fields() -> None: rows = _tracker_rows() assert rows required = ( "feature_id", "surface", "feature", "entrypoint_or_route", "user_story", "expected_behavior", "test_command_or_steps", "status", "first_test_result", "last_verified_at", ) for row in rows: for key in required: assert row[key].strip(), f"{row.get('feature_id', '')} missing {key}" assert row["status"] in ACTIONABLE_STATUSES if row["status"] in FIX_STATUSES: for key in ("error_id", "error_summary", "fix_status"): assert row[key].strip(), ( f"{row.get('feature_id', '')} has " f"{row['status']} without {key}" ) if row["status"] in VALIDATION_STATUSES: assert row["notes"].strip(), ( f"{row.get('feature_id', '')} needs validation " "without a validation note" ) def test_feature_user_story_tracker_covers_all_console_scripts() -> None: pyproject = tomllib.loads((repo_root / "pyproject.toml").read_text(encoding="utf-8")) scripts = sorted(pyproject["project"]["scripts"]) tracker = _tracker_text() assert scripts assert [script for script in scripts if script not in tracker] == [] def test_feature_user_story_tracker_covers_monitor_route_inventory() -> None: route_patterns: list[str] = [] route_patterns.extend(href for _key, _label, href in monitor_routes.NAV_ROUTES) route_patterns.extend(sorted(monitor_routes.PAGE_ROUTES)) route_patterns.extend(sorted(monitor_routes.GET_API_ROUTES)) route_patterns.extend(monitor_routes.GET_API_PATTERNS) route_patterns.extend(sorted(monitor_routes.POST_API_ROUTES)) route_patterns.extend(("/session/", "/skill/")) route_patterns = list(dict.fromkeys(route_patterns)) tracker = _tracker_text() assert route_patterns assert [route for route in route_patterns if route not in tracker] == [] def test_feature_user_story_tracker_covers_distribution_workflows() -> None: workflows = ( ".github/workflows/test.yml", ".github/workflows/docs.yml", ".github/workflows/huggingface-sync.yml", ".github/workflows/publish.yml", ".github/workflows/clean-host-contract.yml", ".github/workflows/xdist-experiment.yml", ) tracker = _tracker_text() assert [workflow for workflow in workflows if workflow not in tracker] == [] def test_feature_user_story_tracker_covers_maintainer_scripts() -> None: scripts = sorted((repo_root / "scripts").glob("*.py")) tracker = _tracker_text() script_paths = [script.relative_to(repo_root).as_posix() for script in scripts] assert scripts assert [path for path in script_paths if path not in tracker] == [] def test_feature_user_story_tracker_covers_public_docs_assets() -> None: assets = sorted((repo_root / "docs" / "assets" / "javascripts").glob("*.js")) tracker = _tracker_text() asset_paths = [asset.relative_to(repo_root).as_posix() for asset in assets] assert assets assert [path for path in asset_paths if path not in tracker] == [] def test_readme_shows_user_story_examples_from_tracker() -> None: readme = README.read_text(encoding="utf-8") assert "## Example user stories" in readme assert "docs/qa/feature-user-story-status.csv" in readme for feature_id in ("CLI-002", "CLI-026", "API-011"): assert feature_id in readme