← Files Plugin AutopilotARCHIVED FILE

tests/test_skill_frontmatter_yaml.py

6.29 KB · Oct 4, 2026 · 12:33 UTC

↓ Download file

import json
import subprocess
import tempfile
import unittest
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
VALIDATOR = ROOT / "skills/chatgpt-codex-plugin-autopilot/scripts/validate_plugin.py"


def square_svg() -> str:
    return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64"/></svg>\n'


def base_manifest() -> dict:
    return {
        "name": "fixture-plugin",
        "version": "1.0.0",
        "description": "Fixture plugin for Skill frontmatter tests.",
        "author": {"name": "Test Publisher", "url": "https://example.com"},
        "skills": "./skills/",
        "interface": {
            "displayName": "Fixture",
            "shortDescription": "Fixture plugin",
            "longDescription": "Fixture used by Plugin Autopilot regression tests.",
            "developerName": "Test Publisher",
            "category": "Developer Tools",
            "logo": "./assets/icon.svg",
            "composerIcon": "./assets/icon.svg",
            "websiteURL": "https://example.com",
            "privacyPolicyURL": "https://example.com/privacy",
            "termsOfServiceURL": "https://example.com/terms",
            "supportURL": "https://example.com/support",
        },
    }


def write_fixture(root: Path, frontmatter: str) -> None:
    (root / ".codex-plugin").mkdir(parents=True)
    (root / "assets").mkdir()
    (root / "skills" / "worker").mkdir(parents=True)
    (root / ".codex-plugin" / "plugin.json").write_text(json.dumps(base_manifest()), encoding="utf-8")
    (root / "assets" / "icon.svg").write_text(square_svg(), encoding="utf-8")
    (root / "skills" / "worker" / "SKILL.md").write_text(
        f"---\n{frontmatter}\n---\n\nValidate the fixture.\n",
        encoding="utf-8",
    )


def validate(root: Path) -> tuple[subprocess.CompletedProcess[str], dict]:
    proc = subprocess.run(
        ["python3", str(VALIDATOR), str(root), "--json"],
        cwd=ROOT,
        text=True,
        capture_output=True,
    )
    return proc, json.loads(proc.stdout)


class SkillFrontmatterYamlTests(unittest.TestCase):
    def test_rejects_malformed_yaml_that_regex_metadata_would_accept(self):
        with tempfile.TemporaryDirectory() as temp:
            root = Path(temp) / "plugin"
            write_fixture(
                root,
                "name: worker\n"
                "description: Use when validating malformed frontmatter.\n"
                "metadata: [unterminated",
            )

            proc, report = validate(root)

            self.assertNotEqual(proc.returncode, 0, report)
            self.assertFalse(report["ok"], report)
            self.assertTrue(
                any("frontmatter" in error.lower() and "malformed" in error.lower() for error in report["errors"]),
                report,
            )

    def test_rejects_non_mapping_top_level_frontmatter(self):
        with tempfile.TemporaryDirectory() as temp:
            root = Path(temp) / "plugin"
            write_fixture(root, "- worker\n- metadata")

            proc, report = validate(root)

            self.assertNotEqual(proc.returncode, 0, report)
            self.assertTrue(
                any("frontmatter" in error.lower() and "mapping" in error.lower() for error in report["errors"]),
                report,
            )

    def test_rejects_non_string_name_and_description(self):
        with tempfile.TemporaryDirectory() as temp:
            root = Path(temp) / "plugin"
            write_fixture(root, "name: [worker]\ndescription: 123")

            proc, report = validate(root)

            self.assertNotEqual(proc.returncode, 0, report)
            joined = "\n".join(report["errors"]).lower()
            self.assertIn("skill name", joined)
            self.assertIn("string", joined)
            self.assertIn("skill description", joined)
            self.assertIn("string", joined)

    def test_accepts_quoted_scalars_with_colons_hashes_and_unicode(self):
        with tempfile.TemporaryDirectory() as temp:
            root = Path(temp) / "plugin"
            write_fixture(
                root,
                'name: "quoted-worker"\n'
                'description: "Use when metadata includes: colons, # marks, and Unicode ✓."',
            )

            proc, report = validate(root)

            self.assertEqual(proc.returncode, 0, report)
            self.assertEqual(report["skills"], ["quoted-worker"])

    def test_accepts_block_scalar_description(self):
        with tempfile.TemporaryDirectory() as temp:
            root = Path(temp) / "plugin"
            write_fixture(
                root,
                "name: multiline-worker\n"
                "description: >-\n"
                "  Use when validating multiline\n"
                "  Skill metadata safely.",
            )

            proc, report = validate(root)

            self.assertEqual(proc.returncode, 0, report)
            self.assertEqual(report["skills"], ["multiline-worker"])

    def test_rejects_leading_comma_in_flow_collections(self):
        cases = (
            "metadata: [ , extra]",
            "metadata: {, extra: 1}",
        )
        for extra in cases:
            with self.subTest(extra=extra), tempfile.TemporaryDirectory() as temp:
                root = Path(temp) / "plugin"
                write_fixture(
                    root,
                    "name: worker\n"
                    "description: Use when validating malformed flow collections.\n"
                    f"{extra}",
                )

                proc, report = validate(root)

                self.assertNotEqual(proc.returncode, 0, report)
                self.assertTrue(
                    any("frontmatter" in error.lower() and "malformed" in error.lower() for error in report["errors"]),
                    report,
                )

    def test_rejects_explicit_yaml_tags(self):
        with tempfile.TemporaryDirectory() as temp:
            root = Path(temp) / "plugin"
            write_fixture(
                root,
                "name: worker\n"
                "description: !!python/object/apply:os.system ['echo unsafe']",
            )

            proc, report = validate(root)

            self.assertNotEqual(proc.returncode, 0, report)
            self.assertTrue(any("frontmatter" in error.lower() for error in report["errors"]), report)
            self.assertNotIn("unsafe", json.dumps(report))

SHA-256: 959c088e715fc16a5ca636e7d4f274cdb7b189f1b79e6fed17826df692ae2d07