mirror of
https://github.com/alirezarezvani/claude-skills.git
synced 2026-10-11 03:37:58 +00:00
PR #593 moved every skill from <plugin>/<name>/ to <plugin>/skills/<name>/ to satisfy Claude Code's runtime loader. That broke two CI jobs that hardcoded the old paths: 1. **9 test files** had `sys.path.insert(0, ".../<domain>/<skill>/scripts")` pointing at the pre-restructure location. Inserted `"skills"` between the domain and skill segments so imports resolve again. 2. **scripts/sync-codex-skills.py** scanned `<domain>/<skill>/SKILL.md` and emitted "No skills found in repository". Updated `find_skills` to prefer `<domain>/skills/<name>/` and fall back to `<domain>/<name>/` so it works with both layouts (in case any domain hasn't been restructured yet). Verified locally: - `python3 scripts/sync-codex-skills.py --dry-run` finds 178 skills - `pytest tests/` collects all modules; 3216 tests pass - 4 pre-existing failures remain (3 argparse duplicates in an `integrations/` script, 1 strict "must have .py" check on a JS-only skill) — out of scope for this fix.
118 lines
3.7 KiB
Python
118 lines
3.7 KiB
Python
"""Unit tests for the Commit Linter (Conventional Commits)."""
|
|
|
|
import sys
|
|
import os
|
|
import tempfile
|
|
|
|
import pytest
|
|
|
|
sys.path.insert(0, os.path.join(
|
|
os.path.dirname(__file__), "..", "engineering", "skills", "changelog-generator", "scripts"
|
|
))
|
|
from commit_linter import lint, CONVENTIONAL_RE, lines_from_file, CLIError
|
|
|
|
|
|
class TestConventionalCommitRegex:
|
|
"""Test the regex pattern against various commit message formats."""
|
|
|
|
@pytest.mark.parametrize("msg", [
|
|
"feat: add user authentication",
|
|
"fix: resolve null pointer in parser",
|
|
"docs: update API documentation",
|
|
"refactor: simplify login flow",
|
|
"test: add integration tests for auth",
|
|
"build: upgrade webpack to v5",
|
|
"ci: add GitHub Actions workflow",
|
|
"chore: update dependencies",
|
|
"perf: optimize database queries",
|
|
"security: patch XSS vulnerability",
|
|
"deprecated: mark v1 API as deprecated",
|
|
"remove: drop legacy payment module",
|
|
])
|
|
def test_valid_types(self, msg):
|
|
assert CONVENTIONAL_RE.match(msg) is not None
|
|
|
|
@pytest.mark.parametrize("msg", [
|
|
"feat(auth): add OAuth2 support",
|
|
"fix(parser/html): handle malformed tags",
|
|
"docs(api.v2): update endpoint docs",
|
|
])
|
|
def test_valid_scopes(self, msg):
|
|
assert CONVENTIONAL_RE.match(msg) is not None
|
|
|
|
def test_breaking_change_marker(self):
|
|
assert CONVENTIONAL_RE.match("feat!: redesign API") is not None
|
|
assert CONVENTIONAL_RE.match("feat(api)!: breaking change") is not None
|
|
|
|
@pytest.mark.parametrize("msg", [
|
|
"Update readme",
|
|
"Fixed the bug",
|
|
"WIP: something",
|
|
"FEAT: uppercase type",
|
|
"feat:missing space",
|
|
"feat : extra space before colon",
|
|
"",
|
|
"merge: not a valid type",
|
|
])
|
|
def test_invalid_messages(self, msg):
|
|
assert CONVENTIONAL_RE.match(msg) is None
|
|
|
|
|
|
class TestLint:
|
|
def test_all_valid(self):
|
|
lines = [
|
|
"feat: add login",
|
|
"fix: resolve crash",
|
|
"docs: update README",
|
|
]
|
|
report = lint(lines)
|
|
assert report.total == 3
|
|
assert report.valid == 3
|
|
assert report.invalid == 0
|
|
assert report.violations == []
|
|
|
|
def test_mixed_valid_invalid(self):
|
|
lines = [
|
|
"feat: add login",
|
|
"Updated the readme",
|
|
"fix: resolve crash",
|
|
]
|
|
report = lint(lines)
|
|
assert report.total == 3
|
|
assert report.valid == 2
|
|
assert report.invalid == 1
|
|
assert "line 2" in report.violations[0]
|
|
|
|
def test_all_invalid(self):
|
|
lines = ["bad commit", "another bad one"]
|
|
report = lint(lines)
|
|
assert report.valid == 0
|
|
assert report.invalid == 2
|
|
|
|
def test_empty_input(self):
|
|
report = lint([])
|
|
assert report.total == 0
|
|
assert report.valid == 0
|
|
assert report.invalid == 0
|
|
|
|
|
|
class TestLinesFromFile:
|
|
def test_reads_file(self):
|
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
|
|
f.write("feat: add feature\nfix: fix bug\n")
|
|
f.flush()
|
|
lines = lines_from_file(f.name)
|
|
os.unlink(f.name)
|
|
assert lines == ["feat: add feature", "fix: fix bug"]
|
|
|
|
def test_skips_blank_lines(self):
|
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
|
|
f.write("feat: add feature\n\n\nfix: fix bug\n")
|
|
f.flush()
|
|
lines = lines_from_file(f.name)
|
|
os.unlink(f.name)
|
|
assert len(lines) == 2
|
|
|
|
def test_nonexistent_file_raises(self):
|
|
with pytest.raises(CLIError, match="Failed reading"):
|
|
lines_from_file("/nonexistent/path.txt")
|