144 lines
4.9 KiB
Python
144 lines
4.9 KiB
Python
"""Tests for the optional-skills/web-development/publish-site skill.
|
|
|
|
Structural + internal-consistency checks only (stdlib + pytest, no network).
|
|
"""
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
REPO = Path(__file__).resolve().parents[2]
|
|
SKILL_DIR = REPO / "optional-skills" / "web-development" / "publish-site"
|
|
SKILL_MD = SKILL_DIR / "SKILL.md"
|
|
|
|
VALID_PLATFORMS = {"linux", "macos", "windows"}
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def skill_text() -> str:
|
|
return SKILL_MD.read_text(encoding="utf-8")
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def frontmatter(skill_text: str) -> dict:
|
|
"""Parse the YAML frontmatter with stdlib only (flat key: value fields)."""
|
|
m = re.match(r"^---\n(.*?)\n---\n", skill_text, re.DOTALL)
|
|
assert m, "SKILL.md must open with '---' delimited YAML frontmatter"
|
|
fm: dict[str, str] = {}
|
|
for line in m.group(1).splitlines():
|
|
if line.startswith((" ", "\t")) or ":" not in line:
|
|
continue # nested metadata keys — not needed here
|
|
key, _, value = line.partition(":")
|
|
fm[key.strip()] = value.strip()
|
|
return fm
|
|
|
|
|
|
def test_skill_file_exists():
|
|
assert SKILL_MD.is_file(), f"missing {SKILL_MD}"
|
|
|
|
|
|
def test_frontmatter_parses(frontmatter: dict):
|
|
assert frontmatter.get("name") == "publish-site"
|
|
assert frontmatter.get("version"), "version field required"
|
|
assert frontmatter.get("license") == "MIT"
|
|
assert "Hermes Agent" in frontmatter.get("author", "")
|
|
|
|
|
|
def test_description_length_and_period(frontmatter: dict):
|
|
desc = frontmatter.get("description", "").strip().strip('"')
|
|
assert desc, "no description field"
|
|
assert len(desc) <= 60, f"description is {len(desc)} chars (>60): {desc!r}"
|
|
assert desc.endswith("."), "description must end with a period"
|
|
|
|
|
|
def test_platforms_list_valid(frontmatter: dict):
|
|
raw = frontmatter.get("platforms", "")
|
|
platforms = [p.strip() for p in raw.strip("[]").split(",") if p.strip()]
|
|
assert platforms, "platforms list must be non-empty"
|
|
assert set(platforms) <= VALID_PLATFORMS, f"invalid platforms: {platforms}"
|
|
# gh/wrangler/netlify are all cross-platform — the skill keeps all three.
|
|
assert set(platforms) == VALID_PLATFORMS
|
|
|
|
|
|
def test_required_sections_present(skill_text: str):
|
|
for heading in (
|
|
"## When to Use",
|
|
"## Prerequisites",
|
|
"## How to Run",
|
|
"## Quick Reference",
|
|
"## Procedure",
|
|
"## Pitfalls",
|
|
"## Verification",
|
|
):
|
|
assert heading in skill_text, f"missing section: {heading}"
|
|
|
|
|
|
def test_no_dangling_skill_view_references(skill_text: str):
|
|
"""Every skill_view(name='...') reference must target a shipped skill."""
|
|
targets = re.findall(r"skill_view\(\s*name\s*=\s*['\"]([^'\"]+)['\"]", skill_text)
|
|
for name in targets:
|
|
hits = list((REPO / "skills").glob(f"**/{name}/SKILL.md")) + list(
|
|
(REPO / "optional-skills").glob(f"**/{name}/SKILL.md")
|
|
)
|
|
assert hits, f"skill_view reference to non-shipped skill: {name}"
|
|
|
|
|
|
def test_referenced_sibling_skills_ship(skill_text: str):
|
|
"""Prose-referenced companion skills must exist in the shipped trees."""
|
|
for name in ("cloudflare-temporary-deploy",):
|
|
if name in skill_text:
|
|
hits = list((REPO / "skills").glob(f"**/{name}/SKILL.md")) + list(
|
|
(REPO / "optional-skills").glob(f"**/{name}/SKILL.md")
|
|
)
|
|
assert hits, f"referenced skill does not ship: {name}"
|
|
|
|
|
|
def test_provider_ladder_documented(skill_text: str):
|
|
"""All three rungs of the provider ladder with their deploy commands."""
|
|
assert "gh repo create" in skill_text
|
|
assert "gh-pages" in skill_text
|
|
assert "wrangler@latest pages deploy" in skill_text
|
|
assert "netlify deploy --prod" in skill_text
|
|
|
|
|
|
def test_version_before_deploy_discipline(skill_text: str):
|
|
assert "git tag" in skill_text, "deploys must be tagged"
|
|
assert "Never deploy uncommitted files" in skill_text
|
|
|
|
|
|
def test_rollback_documented(skill_text: str):
|
|
assert "Rollback" in skill_text
|
|
assert re.search(r"git checkout deploy-", skill_text), "rollback must redeploy a previous tag"
|
|
|
|
|
|
def test_secrets_never_in_repo(skill_text: str):
|
|
assert "NEVER commit secrets" in skill_text
|
|
assert ".gitignore" in skill_text
|
|
|
|
|
|
def test_spa_404_pitfall_documented(skill_text: str):
|
|
assert "404.html" in skill_text
|
|
assert "_redirects" in skill_text
|
|
|
|
|
|
def test_verification_uses_real_http_check(skill_text: str):
|
|
assert "%{http_code}" in skill_text
|
|
assert "200" in skill_text
|
|
assert "deploy log alone" in skill_text
|
|
|
|
|
|
def test_preview_before_deploy(skill_text: str):
|
|
assert "http.server" in skill_text
|
|
assert "cloudflared tunnel --url" in skill_text
|
|
assert "trycloudflare.com" in skill_text
|
|
|
|
|
|
def test_category_description_exists():
|
|
desc = REPO / "optional-skills" / "web-development" / "DESCRIPTION.md"
|
|
assert desc.is_file(), "optional web-development category needs a DESCRIPTION.md"
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(pytest.main([__file__, "-q"]))
|