Skip to main content
Glama
by fkesheh
test_skill_files_crud.py13.1 kB
"""Tests for unified skill files CRUD tool.""" import pytest from skill_mcp.core.config import SKILLS_DIR from skill_mcp.models_crud import FileSpec, SkillFilesCrudInput from skill_mcp.tools.skill_files_crud import SkillFilesCrud @pytest.fixture def test_skill_name(): """Provide a test skill name.""" return "test-files-skill" @pytest.fixture def setup_test_skill(test_skill_name): """Create a test skill directory.""" skill_dir = SKILLS_DIR / test_skill_name skill_dir.mkdir(parents=True, exist_ok=True) # Create SKILL.md skill_md = skill_dir / "SKILL.md" skill_md.write_text("---\nname: Test Skill\ndescription: Test\n---\n# Test") yield test_skill_name # Cleanup if skill_dir.exists(): import shutil shutil.rmtree(skill_dir) class TestSkillFilesCrudCreate: """Tests for create operation.""" @pytest.mark.asyncio async def test_create_file_in_nonexistent_skill(self): """Test that creating file in non-existent skill raises error.""" input_data = SkillFilesCrudInput( operation="create", skill_name="nonexistent-skill-xyz", file_path="test.py", content="print('hello')", ) result = await SkillFilesCrud.skill_files_crud(input_data) # Should raise error, not create invalid skill assert "Error" in result[0].text assert "does not exist" in result[0].text or "not found" in result[0].text.lower() # Verify skill was NOT created skill_dir = SKILLS_DIR / "nonexistent-skill-xyz" assert not skill_dir.exists(), "Bug: Skill directory should not be created" @pytest.mark.asyncio async def test_create_single_file(self, setup_test_skill): """Test creating a single file.""" input_data = SkillFilesCrudInput( operation="create", skill_name=setup_test_skill, file_path="test.py", content="print('hello')", ) result = await SkillFilesCrud.skill_files_crud(input_data) assert len(result) == 1 assert f"Successfully created file '{setup_test_skill}:test.py'" in result[0].text assert (SKILLS_DIR / setup_test_skill / "test.py").exists() @pytest.mark.asyncio async def test_create_multiple_files(self, setup_test_skill): """Test creating multiple files in bulk.""" files = [ FileSpec(path="file1.py", content="# File 1"), FileSpec(path="file2.py", content="# File 2"), FileSpec(path="file3.py", content="# File 3"), ] input_data = SkillFilesCrudInput( operation="create", skill_name=setup_test_skill, files=files, atomic=True ) result = await SkillFilesCrud.skill_files_crud(input_data) assert len(result) == 1 assert "Successfully created 3 files" in result[0].text assert (SKILLS_DIR / setup_test_skill / "file1.py").exists() assert (SKILLS_DIR / setup_test_skill / "file2.py").exists() assert (SKILLS_DIR / setup_test_skill / "file3.py").exists() @pytest.mark.asyncio async def test_create_with_nested_path(self, setup_test_skill): """Test creating a file in a nested directory.""" input_data = SkillFilesCrudInput( operation="create", skill_name=setup_test_skill, file_path="src/utils.py", content="# Utils", ) result = await SkillFilesCrud.skill_files_crud(input_data) assert len(result) == 1 assert f"Successfully created file '{setup_test_skill}:src/utils.py'" in result[0].text assert (SKILLS_DIR / setup_test_skill / "src" / "utils.py").exists() @pytest.mark.asyncio async def test_create_cannot_mix_single_and_bulk(self, setup_test_skill): """Test that cannot specify both single and bulk parameters.""" files = [FileSpec(path="file1.py", content="# File 1")] input_data = SkillFilesCrudInput( operation="create", skill_name=setup_test_skill, file_path="test.py", # Single content="test", # Single files=files, # Bulk ) result = await SkillFilesCrud.skill_files_crud(input_data) assert len(result) == 1 assert "Error" in result[0].text assert "Cannot specify both" in result[0].text @pytest.mark.asyncio async def test_create_without_content(self, setup_test_skill): """Test create fails without content.""" input_data = SkillFilesCrudInput( operation="create", skill_name=setup_test_skill, file_path="test.py" ) result = await SkillFilesCrud.skill_files_crud(input_data) assert len(result) == 1 assert "Error" in result[0].text class TestSkillFilesCrudRead: """Tests for read operation.""" @pytest.mark.asyncio async def test_read_existing_file(self, setup_test_skill): """Test reading an existing file.""" # Create a file first test_content = "# Test content" test_file = SKILLS_DIR / setup_test_skill / "test.py" test_file.write_text(test_content) # Read it input_data = SkillFilesCrudInput( operation="read", skill_name=setup_test_skill, file_path="test.py" ) result = await SkillFilesCrud.skill_files_crud(input_data) assert len(result) == 1 assert f"=== {setup_test_skill}:test.py ===" in result[0].text assert test_content in result[0].text @pytest.mark.asyncio async def test_read_without_file_path(self, setup_test_skill): """Test read fails without file_path.""" input_data = SkillFilesCrudInput(operation="read", skill_name=setup_test_skill) result = await SkillFilesCrud.skill_files_crud(input_data) assert len(result) == 1 assert "Error" in result[0].text assert "file_path is required" in result[0].text @pytest.mark.asyncio async def test_read_multiple_files(self, setup_test_skill): """Test reading multiple files in bulk.""" # Create test files file1 = SKILLS_DIR / setup_test_skill / "file1.py" file2 = SKILLS_DIR / setup_test_skill / "file2.py" file3 = SKILLS_DIR / setup_test_skill / "file3.py" file1.write_text("# File 1 content") file2.write_text("# File 2 content") file3.write_text("# File 3 content") # Read multiple files input_data = SkillFilesCrudInput( operation="read", skill_name=setup_test_skill, file_paths=["file1.py", "file2.py", "file3.py"], ) result = await SkillFilesCrud.skill_files_crud(input_data) assert len(result) == 1 output = result[0].text # Should contain all three files with namespaced paths assert f"=== {setup_test_skill}:file1.py ===" in output assert "# File 1 content" in output assert f"=== {setup_test_skill}:file2.py ===" in output assert "# File 2 content" in output assert f"=== {setup_test_skill}:file3.py ===" in output assert "# File 3 content" in output @pytest.mark.asyncio async def test_read_multiple_files_some_missing(self, setup_test_skill): """Test reading multiple files where some don't exist.""" # Create only one file file1 = SKILLS_DIR / setup_test_skill / "exists.py" file1.write_text("# Exists") # Try to read multiple files (one exists, one doesn't) input_data = SkillFilesCrudInput( operation="read", skill_name=setup_test_skill, file_paths=["exists.py", "missing.py"] ) result = await SkillFilesCrud.skill_files_crud(input_data) assert len(result) == 1 output = result[0].text # Should show error for missing file assert "Error" in output or "does not exist" in output @pytest.mark.asyncio async def test_read_nonexistent_file(self, setup_test_skill): """Test reading a nonexistent file.""" input_data = SkillFilesCrudInput( operation="read", skill_name=setup_test_skill, file_path="nonexistent.py" ) result = await SkillFilesCrud.skill_files_crud(input_data) assert len(result) == 1 assert "Error" in result[0].text class TestSkillFilesCrudUpdate: """Tests for update operation.""" @pytest.mark.asyncio async def test_update_single_file(self, setup_test_skill): """Test updating a single file.""" # Create a file first test_file = SKILLS_DIR / setup_test_skill / "test.py" test_file.write_text("# Original") # Update it new_content = "# Updated content" input_data = SkillFilesCrudInput( operation="update", skill_name=setup_test_skill, file_path="test.py", content=new_content, ) result = await SkillFilesCrud.skill_files_crud(input_data) assert len(result) == 1 assert f"Successfully updated file '{setup_test_skill}:test.py'" in result[0].text assert test_file.read_text() == new_content @pytest.mark.asyncio async def test_update_multiple_files(self, setup_test_skill): """Test updating multiple files in bulk.""" # Create files first (SKILLS_DIR / setup_test_skill / "file1.py").write_text("# Original 1") (SKILLS_DIR / setup_test_skill / "file2.py").write_text("# Original 2") # Update them files = [ FileSpec(path="file1.py", content="# Updated 1"), FileSpec(path="file2.py", content="# Updated 2"), ] input_data = SkillFilesCrudInput( operation="update", skill_name=setup_test_skill, files=files ) result = await SkillFilesCrud.skill_files_crud(input_data) assert len(result) == 1 assert "Successfully updated 2 files" in result[0].text assert (SKILLS_DIR / setup_test_skill / "file1.py").read_text() == "# Updated 1" assert (SKILLS_DIR / setup_test_skill / "file2.py").read_text() == "# Updated 2" @pytest.mark.asyncio async def test_update_without_content(self, setup_test_skill): """Test update fails without content.""" input_data = SkillFilesCrudInput( operation="update", skill_name=setup_test_skill, file_path="test.py" ) result = await SkillFilesCrud.skill_files_crud(input_data) assert len(result) == 1 assert "Error" in result[0].text class TestSkillFilesCrudDelete: """Tests for delete operation.""" @pytest.mark.asyncio async def test_delete_existing_file(self, setup_test_skill): """Test deleting an existing file.""" # Create a file first test_file = SKILLS_DIR / setup_test_skill / "test.py" test_file.write_text("# Test") # Delete it input_data = SkillFilesCrudInput( operation="delete", skill_name=setup_test_skill, file_path="test.py" ) result = await SkillFilesCrud.skill_files_crud(input_data) assert len(result) == 1 assert f"Successfully deleted file '{setup_test_skill}:test.py'" in result[0].text assert not test_file.exists() @pytest.mark.asyncio async def test_delete_without_file_path(self, setup_test_skill): """Test delete fails without file_path.""" input_data = SkillFilesCrudInput(operation="delete", skill_name=setup_test_skill) result = await SkillFilesCrud.skill_files_crud(input_data) assert len(result) == 1 assert "Error" in result[0].text assert "file_path is required" in result[0].text @pytest.mark.asyncio async def test_delete_protected_skill_md(self, setup_test_skill): """Test that SKILL.md cannot be deleted through CRUD.""" # Verify SKILL.md exists skill_md = SKILLS_DIR / setup_test_skill / "SKILL.md" assert skill_md.exists() # Try to delete it input_data = SkillFilesCrudInput( operation="delete", skill_name=setup_test_skill, file_path="SKILL.md" ) result = await SkillFilesCrud.skill_files_crud(input_data) # Should fail with error assert len(result) == 1 assert "Error" in result[0].text assert "protected" in result[0].text.lower() or "cannot delete" in result[0].text.lower() # Verify SKILL.md still exists assert skill_md.exists() class TestSkillFilesCrudInvalidOperation: """Tests for invalid operations.""" @pytest.mark.asyncio async def test_unknown_operation(self, setup_test_skill): """Test unknown operation.""" input_data = SkillFilesCrudInput(operation="invalid_op", skill_name=setup_test_skill) result = await SkillFilesCrud.skill_files_crud(input_data) assert len(result) == 1 assert "Unknown operation" in result[0].text assert "invalid_op" in result[0].text

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/fkesheh/skill-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server