返回 last30days-skill
test_plugin_contract.py
根目录 / tests / test_plugin_contract.py
1 import json
2 import tomllib
3 import unittest
4 from pathlib import Path
5
6 from lib.skill_meta import read_skill_version
7
8 ROOT = Path(__file__).resolve().parents[1]
9 SKILL_ROOT = ROOT / "skills" / "last30days"
10
11
12 def _json(path: Path) -> dict:
13 return json.loads(path.read_text(encoding="utf-8"))
14
15
16 def _skill_version() -> str:
17 version = read_skill_version(SKILL_ROOT / "SKILL.md")
18 if not version:
19 raise AssertionError("SKILL.md version frontmatter not found")
20 return version
21
22
23 class TestPluginContract(unittest.TestCase):
24 def test_codex_plugin_manifest_uses_repo_skill_root(self) -> None:
25 manifest = _json(ROOT / ".codex-plugin" / "plugin.json")
26
27 self.assertEqual("last30days", manifest["name"])
28 self.assertEqual("./skills/", manifest["skills"])
29 self.assertEqual("last30days", manifest["interface"]["displayName"])
30
31 def test_codex_marketplace_points_at_repo_root_plugin(self) -> None:
32 marketplace = _json(ROOT / ".agents" / "plugins" / "marketplace.json")
33 plugins = marketplace.get("plugins") or []
34 plugin_by_name = {plugin["name"]: plugin for plugin in plugins}
35
36 self.assertEqual("last30days-skill", marketplace["name"])
37 self.assertIn("last30days", plugin_by_name)
38 plugin = plugin_by_name["last30days"]
39 self.assertEqual(
40 {
41 "source": "url",
42 "url": "https://github.com/mvanhorn/last30days-skill.git",
43 },
44 plugin["source"],
45 )
46
47 def test_grok_plugin_manifest_uses_repo_skill_root(self) -> None:
48 manifest = _json(ROOT / ".grok-plugin" / "plugin.json")
49
50 self.assertEqual("last30days", manifest["name"])
51 self.assertEqual("./skills/", manifest["skills"])
52
53 def test_grok_marketplace_points_at_repo_root_plugin(self) -> None:
54 marketplace = _json(ROOT / ".grok-plugin" / "marketplace.json")
55 plugins = marketplace.get("plugins") or []
56 plugin_by_name = {plugin["name"]: plugin for plugin in plugins}
57
58 self.assertEqual("last30days-skill", marketplace["name"])
59 self.assertIn("last30days", plugin_by_name)
60 plugin = plugin_by_name["last30days"]
61 # Exact dict equality locks the bare Git URL source (anti-self-referential-local).
62 self.assertEqual(
63 {
64 "source": "url",
65 "url": "https://github.com/mvanhorn/last30days-skill.git",
66 },
67 plugin["source"],
68 )
69
70 def test_versions_match_across_manifests(self) -> None:
71 pyproject = tomllib.loads((ROOT / "pyproject.toml").read_text(encoding="utf-8"))
72 version = pyproject["project"]["version"]
73
74 self.assertEqual(version, _skill_version())
75 self.assertEqual(version, _json(ROOT / ".claude-plugin" / "plugin.json")["version"])
76 self.assertEqual(version, _json(ROOT / ".codex-plugin" / "plugin.json")["version"])
77 self.assertEqual(version, _json(ROOT / ".grok-plugin" / "plugin.json")["version"])
78 self.assertEqual(version, _json(ROOT / "gemini-extension.json")["version"])
79
80 marketplace = _json(ROOT / ".claude-plugin" / "marketplace.json")
81 plugins = marketplace.get("plugins") or []
82 self.assertEqual(1, len(plugins))
83 self.assertEqual(version, plugins[0]["version"])
84
85 grok_marketplace = _json(ROOT / ".grok-plugin" / "marketplace.json")
86 grok_plugins = grok_marketplace.get("plugins") or []
87 self.assertEqual(1, len(grok_plugins))
88 self.assertEqual(version, grok_plugins[0]["version"])
89
90 def test_claude_marketplace_has_current_schema_shape(self) -> None:
91 marketplace = _json(ROOT / ".claude-plugin" / "marketplace.json")
92
93 self.assertNotIn("$schema", marketplace)
94 self.assertNotIn("description", marketplace)
95 self.assertIn("metadata", marketplace)
96 self.assertIn("description", marketplace["metadata"])
97
98 def test_grok_marketplace_has_current_schema_shape(self) -> None:
99 marketplace = _json(ROOT / ".grok-plugin" / "marketplace.json")
100
101 self.assertNotIn("$schema", marketplace)
102 self.assertNotIn("metadata", marketplace)
103 self.assertIsInstance(marketplace["description"], str)
104 self.assertIn("name", marketplace)
105 self.assertIn("owner", marketplace)
106 self.assertIn("plugins", marketplace)
107
108 def test_workflows_do_not_reference_removed_root_scripts_dir(self) -> None:
109 # The historical root-level scripts/ directory was removed; workflows must not
110 # reference a bare `scripts/` path. Allowed replacements:
111 # skills/last30days/scripts/ (engine), mcp/scripts/ (.mcpb), .github/scripts/
112 # (release automation).
113 allowed_prefixes = (
114 "skills/last30days/scripts/",
115 "mcp/scripts/",
116 ".github/scripts/",
117 )
118 offenders = []
119 for path in sorted((ROOT / ".github" / "workflows").glob("*.yml")):
120 for line_number, line in enumerate(path.read_text(encoding="utf-8").splitlines(), start=1):
121 if "scripts/" not in line:
122 continue
123 if any(prefix in line for prefix in allowed_prefixes):
124 continue
125 offenders.append(f"{path.relative_to(ROOT)}:{line_number}: {line.strip()}")
126
127 self.assertEqual([], offenders)
128
129 if __name__ == "__main__":
130 unittest.main()
131
131 lines PYTHON