Merge branch 'development' into ruff-and-mypy
This commit is contained in:
83
tests/test_bootstrap_skill.py
Normal file
83
tests/test_bootstrap_skill.py
Normal file
@@ -0,0 +1,83 @@
|
||||
"""Tests for the bootstrap skill script."""
|
||||
|
||||
import subprocess
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def project_root():
|
||||
"""Get project root directory."""
|
||||
return Path(__file__).parent.parent
|
||||
|
||||
|
||||
class TestBootstrapSkillScript:
|
||||
"""Tests for scripts/bootstrap_skill.sh"""
|
||||
|
||||
def test_script_exists(self, project_root):
|
||||
"""Test that bootstrap script exists and is executable."""
|
||||
script = project_root / "scripts" / "bootstrap_skill.sh"
|
||||
assert script.exists(), "bootstrap_skill.sh should exist"
|
||||
assert script.stat().st_mode & 0o111, "bootstrap_skill.sh should be executable"
|
||||
|
||||
def test_header_template_exists(self, project_root):
|
||||
"""Test that skill header template exists."""
|
||||
header = project_root / "scripts" / "skill_header.md"
|
||||
assert header.exists(), "skill_header.md should exist"
|
||||
|
||||
def test_header_has_required_sections(self, project_root):
|
||||
"""Test that header template has required operational sections."""
|
||||
header = project_root / "scripts" / "skill_header.md"
|
||||
content = header.read_text()
|
||||
|
||||
# Must have prerequisites
|
||||
assert "## Prerequisites" in content, "Header must have Prerequisites section"
|
||||
assert "pip install skill-seekers" in content, "Header must have pip install instruction"
|
||||
|
||||
# Must have commands table
|
||||
assert "## Commands" in content, "Header must have Commands section"
|
||||
assert "skill-seekers-codebase" in content, "Header must mention codebase command"
|
||||
assert "skill-seekers scrape" in content, "Header must mention scrape command"
|
||||
assert "skill-seekers github" in content, "Header must mention github command"
|
||||
|
||||
def test_header_has_yaml_frontmatter(self, project_root):
|
||||
"""Test that header has valid YAML frontmatter."""
|
||||
header = project_root / "scripts" / "skill_header.md"
|
||||
content = header.read_text()
|
||||
|
||||
assert content.startswith("---"), "Header must start with YAML frontmatter"
|
||||
assert "name: skill-seekers" in content, "Header must have skill name"
|
||||
assert "description:" in content, "Header must have description"
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_bootstrap_script_runs(self, project_root, tmp_path):
|
||||
"""Test that bootstrap script runs successfully.
|
||||
|
||||
Note: This test is slow as it runs full codebase analysis.
|
||||
Run with: pytest -m slow
|
||||
"""
|
||||
script = project_root / "scripts" / "bootstrap_skill.sh"
|
||||
|
||||
# Run script (skip if uv not available)
|
||||
result = subprocess.run(
|
||||
["bash", str(script)],
|
||||
cwd=project_root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=600, # 10 minute timeout
|
||||
)
|
||||
|
||||
# Check script completed
|
||||
assert result.returncode == 0, f"Script failed: {result.stderr}"
|
||||
|
||||
# Check outputs exist (directory named 'skill-seekers' for Claude Code)
|
||||
output_dir = project_root / "output" / "skill-seekers"
|
||||
assert output_dir.exists(), "Output directory should be created"
|
||||
|
||||
skill_md = output_dir / "SKILL.md"
|
||||
assert skill_md.exists(), "SKILL.md should be created"
|
||||
|
||||
# Check SKILL.md has header prepended
|
||||
content = skill_md.read_text()
|
||||
assert "## Prerequisites" in content, "SKILL.md should have header prepended"
|
||||
assert "pip install skill-seekers" in content, "SKILL.md should have install instructions"
|
||||
171
tests/test_bootstrap_skill_e2e.py
Normal file
171
tests/test_bootstrap_skill_e2e.py
Normal file
@@ -0,0 +1,171 @@
|
||||
"""
|
||||
End-to-end tests for bootstrap skill feature (PR #249)
|
||||
|
||||
Tests verify:
|
||||
1. Bootstrap script creates proper skill structure
|
||||
2. Generated SKILL.md is valid and usable
|
||||
3. Skill is installable in isolated virtual environment
|
||||
4. Output works with all platform adaptors
|
||||
5. Error cases handled gracefully
|
||||
|
||||
Coverage: 8-12 tests
|
||||
Execution time: Fast tests ~2-3 min, Full tests ~5-10 min
|
||||
Requires: Python 3.10+, bash, uv
|
||||
|
||||
Run fast tests:
|
||||
pytest tests/test_bootstrap_skill_e2e.py -v -k "not venv"
|
||||
|
||||
Run full suite:
|
||||
pytest tests/test_bootstrap_skill_e2e.py -v -m "e2e"
|
||||
|
||||
Run with venv tests:
|
||||
pytest tests/test_bootstrap_skill_e2e.py -v -m "venv"
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def project_root():
|
||||
"""Get project root directory."""
|
||||
return Path(__file__).parent.parent
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def run_bootstrap(project_root):
|
||||
"""Execute bootstrap script and return result"""
|
||||
def _run(timeout=600):
|
||||
script = project_root / "scripts" / "bootstrap_skill.sh"
|
||||
|
||||
result = subprocess.run(
|
||||
["bash", str(script)],
|
||||
cwd=project_root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
return result
|
||||
return _run
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def output_skill_dir(project_root):
|
||||
"""Get path to bootstrap output directory"""
|
||||
return project_root / "output" / "skill-seekers"
|
||||
|
||||
|
||||
@pytest.mark.e2e
|
||||
class TestBootstrapSkillE2E:
|
||||
"""End-to-end tests for bootstrap skill"""
|
||||
|
||||
def test_bootstrap_creates_output_structure(self, run_bootstrap, output_skill_dir):
|
||||
"""Verify bootstrap creates correct directory structure"""
|
||||
result = run_bootstrap()
|
||||
|
||||
assert result.returncode == 0, f"Bootstrap failed: {result.stderr}"
|
||||
assert output_skill_dir.exists(), "Output directory not created"
|
||||
assert (output_skill_dir / "SKILL.md").exists(), "SKILL.md not created"
|
||||
assert (output_skill_dir / "SKILL.md").stat().st_size > 0, "SKILL.md is empty"
|
||||
|
||||
def test_bootstrap_prepends_header(self, run_bootstrap, output_skill_dir):
|
||||
"""Verify header template prepended to SKILL.md"""
|
||||
result = run_bootstrap()
|
||||
assert result.returncode == 0
|
||||
|
||||
content = (output_skill_dir / "SKILL.md").read_text()
|
||||
|
||||
# Check header sections present
|
||||
assert "## Prerequisites" in content, "Missing Prerequisites section"
|
||||
assert "pip install skill-seekers" in content, "Missing install instruction"
|
||||
assert "## Commands" in content, "Missing Commands section"
|
||||
|
||||
def test_bootstrap_validates_yaml_frontmatter(self, run_bootstrap, output_skill_dir):
|
||||
"""Verify generated SKILL.md has valid YAML frontmatter"""
|
||||
result = run_bootstrap()
|
||||
assert result.returncode == 0
|
||||
|
||||
content = (output_skill_dir / "SKILL.md").read_text()
|
||||
|
||||
# Check frontmatter structure
|
||||
assert content.startswith("---"), "Missing frontmatter start"
|
||||
|
||||
# Find closing delimiter
|
||||
lines = content.split('\n')
|
||||
closing_found = False
|
||||
for i, line in enumerate(lines[1:], 1):
|
||||
if line.strip() == "---":
|
||||
closing_found = True
|
||||
break
|
||||
|
||||
assert closing_found, "Missing frontmatter closing delimiter"
|
||||
|
||||
# Check required fields
|
||||
assert "name:" in content[:500], "Missing name field"
|
||||
assert "description:" in content[:500], "Missing description field"
|
||||
|
||||
def test_bootstrap_output_line_count(self, run_bootstrap, output_skill_dir):
|
||||
"""Verify output SKILL.md has reasonable line count"""
|
||||
result = run_bootstrap()
|
||||
assert result.returncode == 0
|
||||
|
||||
line_count = len((output_skill_dir / "SKILL.md").read_text().splitlines())
|
||||
|
||||
# Should be substantial (header ~44 + auto-generated ~200+)
|
||||
assert line_count > 100, f"SKILL.md too short: {line_count} lines"
|
||||
assert line_count < 2000, f"SKILL.md suspiciously long: {line_count} lines"
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.venv
|
||||
def test_skill_installable_in_venv(self, run_bootstrap, output_skill_dir, tmp_path):
|
||||
"""Test skill is installable in clean virtual environment"""
|
||||
# First run bootstrap
|
||||
result = run_bootstrap()
|
||||
assert result.returncode == 0
|
||||
|
||||
# Create venv
|
||||
venv_path = tmp_path / "test_venv"
|
||||
subprocess.run(
|
||||
[sys.executable, "-m", "venv", str(venv_path)],
|
||||
check=True,
|
||||
timeout=60
|
||||
)
|
||||
|
||||
# Install skill in venv
|
||||
pip_path = venv_path / "bin" / "pip"
|
||||
result = subprocess.run(
|
||||
[str(pip_path), "install", "-e", "."],
|
||||
cwd=output_skill_dir.parent.parent,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120
|
||||
)
|
||||
|
||||
# Should install successfully
|
||||
assert result.returncode == 0, f"Install failed: {result.stderr}"
|
||||
|
||||
def test_skill_packageable_with_adaptors(self, run_bootstrap, output_skill_dir, tmp_path):
|
||||
"""Verify bootstrap output works with all platform adaptors"""
|
||||
result = run_bootstrap()
|
||||
assert result.returncode == 0
|
||||
|
||||
# Try to package with claude adaptor (simplest)
|
||||
from skill_seekers.cli.adaptors import get_adaptor
|
||||
|
||||
adaptor = get_adaptor('claude')
|
||||
|
||||
# Should be able to package without errors
|
||||
try:
|
||||
package_path = adaptor.package(
|
||||
skill_dir=output_skill_dir, # Path object, not str
|
||||
output_path=tmp_path # Path object, not str
|
||||
)
|
||||
|
||||
assert Path(package_path).exists(), "Package not created"
|
||||
assert Path(package_path).stat().st_size > 0, "Package is empty"
|
||||
except Exception as e:
|
||||
pytest.fail(f"Packaging failed: {e}")
|
||||
@@ -114,7 +114,7 @@ class TestUnifiedCLIEntryPoints(unittest.TestCase):
|
||||
|
||||
# Should show version
|
||||
output = result.stdout + result.stderr
|
||||
self.assertIn("2.5.1", output)
|
||||
self.assertIn('2.7.0', output)
|
||||
|
||||
except FileNotFoundError:
|
||||
# If skill-seekers is not installed, skip this test
|
||||
|
||||
Reference in New Issue
Block a user