|
| 1 | +"""Tests for extension.yml manifest validation.""" |
| 2 | + |
| 3 | +import re |
| 4 | + |
| 5 | + |
| 6 | +def test_schema_version(manifest): |
| 7 | + assert manifest["schema_version"] == "1.0" |
| 8 | + |
| 9 | + |
| 10 | +def test_required_fields(extension): |
| 11 | + required = ["id", "name", "version", "description", "author", "repository", "license"] |
| 12 | + for field in required: |
| 13 | + assert field in extension, f"Missing required field: extension.{field}" |
| 14 | + |
| 15 | + |
| 16 | +def test_id_format(extension): |
| 17 | + assert re.match(r"^[a-z0-9-]+$", extension["id"]), ( |
| 18 | + f"Extension ID must be lowercase with hyphens only, got: {extension['id']}" |
| 19 | + ) |
| 20 | + |
| 21 | + |
| 22 | +def test_version_semver(extension): |
| 23 | + assert re.match(r"^\d+\.\d+\.\d+$", extension["version"]), ( |
| 24 | + f"Version must follow semver (X.Y.Z), got: {extension['version']}" |
| 25 | + ) |
| 26 | + |
| 27 | + |
| 28 | +def test_description_length(extension): |
| 29 | + assert len(extension["description"]) < 200, ( |
| 30 | + f"Description too long: {len(extension['description'])} chars (max 200)" |
| 31 | + ) |
| 32 | + |
| 33 | + |
| 34 | +def test_repository_url(extension): |
| 35 | + assert extension["repository"].startswith("https://"), ( |
| 36 | + f"Repository must be an HTTPS URL, got: {extension['repository']}" |
| 37 | + ) |
| 38 | + |
| 39 | + |
| 40 | +def test_tags_present(manifest): |
| 41 | + tags = manifest.get("tags", []) |
| 42 | + assert 2 <= len(tags) <= 10, f"Expected 2-10 tags, got {len(tags)}" |
| 43 | + |
| 44 | + |
| 45 | +def test_tags_lowercase(manifest): |
| 46 | + for tag in manifest.get("tags", []): |
| 47 | + assert tag == tag.lower(), f"Tag must be lowercase: {tag}" |
| 48 | + |
| 49 | + |
| 50 | +def test_command_naming(manifest): |
| 51 | + ext_id = manifest["extension"]["id"] |
| 52 | + for cmd in manifest["provides"]["commands"]: |
| 53 | + name = cmd["name"] |
| 54 | + assert name.startswith(f"speckit.{ext_id}."), ( |
| 55 | + f"Command name must match speckit.{ext_id}.<cmd>, got: {name}" |
| 56 | + ) |
| 57 | + |
| 58 | + |
| 59 | +def test_command_has_description(manifest): |
| 60 | + for cmd in manifest["provides"]["commands"]: |
| 61 | + assert "description" in cmd, f"Command {cmd['name']} missing description" |
| 62 | + assert len(cmd["description"]) > 0, f"Command {cmd['name']} has empty description" |
| 63 | + |
| 64 | + |
| 65 | +def test_command_has_file(manifest): |
| 66 | + for cmd in manifest["provides"]["commands"]: |
| 67 | + assert "file" in cmd, f"Command {cmd['name']} missing file reference" |
| 68 | + |
| 69 | + |
| 70 | +def test_requires_speckit_version(manifest): |
| 71 | + assert "requires" in manifest |
| 72 | + assert "speckit_version" in manifest["requires"], "Missing requires.speckit_version" |
0 commit comments