| """ |
| Coverage expansion tests for skill registry service. |
| |
| Tests cover critical code paths in: |
| - skill_registry_service.py: Skill import, execution, governance |
| - Python package support (Phase 35): Package installation, permissions |
| - npm package support (Phase 36): npm packages, governance |
| - Skill promotion, dynamic loading |
| |
| Target: Cover critical paths (happy path + error paths) to increase coverage. |
| """ |
|
|
| import pytest |
| from unittest.mock import Mock, patch, MagicMock, AsyncMock |
| from datetime import datetime, timedelta |
| from sqlalchemy.orm import Session |
|
|
| from core.skill_registry_service import SkillRegistryService |
| from core.models import SkillExecution |
|
|
|
|
| class TestSkillRegistryCoverage: |
| """Coverage expansion for SkillRegistryService.""" |
|
|
| @pytest.fixture |
| def db_session(self): |
| """Get test database session.""" |
| from core.database import SessionLocal |
| session = SessionLocal() |
| yield session |
| session.rollback() |
| session.close() |
|
|
| @pytest.fixture |
| def skill_registry(self, db_session): |
| """Get skill registry service instance.""" |
| return SkillRegistryService(db_session) |
|
|
| |
| def test_import_skill_prompt_only(self, skill_registry): |
| """Import prompt-only skill.""" |
| content = """--- |
| name: Test Skill |
| description: A test skill |
| --- |
| |
| This is a test skill body. |
| """ |
| result = skill_registry.import_skill( |
| source="raw_content", |
| content=content, |
| metadata={"author": "test"} |
| ) |
| assert result["skill_id"] is not None |
| assert result["skill_name"] == "Test Skill" |
| assert result["status"] in ["Active", "Untrusted"] |
|
|
| def test_import_skill_python_code(self, skill_registry): |
| """Import Python skill with code.""" |
| content = """--- |
| name: Python Skill |
| skill_type: python_code |
| --- |
| |
| ```python |
| def hello(): |
| return "Hello, World!" |
| ``` |
| """ |
| result = skill_registry.import_skill( |
| source="raw_content", |
| content=content |
| ) |
| assert result["skill_id"] is not None |
| assert result["skill_name"] == "Python Skill" |
|
|
| def test_import_skill_with_packages(self, skill_registry): |
| """Import skill with Python packages.""" |
| content = """--- |
| name: Skill with Packages |
| packages: |
| - numpy==1.21.0 |
| - pandas==1.3.0 |
| --- |
| |
| This skill uses numpy and pandas. |
| """ |
| result = skill_registry.import_skill( |
| source="raw_content", |
| content=content |
| ) |
| assert result["skill_id"] is not None |
| assert "packages" in result["metadata"] |
|
|
| def test_import_skill_with_npm_packages(self, skill_registry): |
| """Import skill with npm packages.""" |
| content = """--- |
| name: Node.js Skill |
| node_packages: |
| - lodash@4.17.21 |
| - express@4.18.0 |
| --- |
| |
| This skill uses npm packages. |
| """ |
| result = skill_registry.import_skill( |
| source="raw_content", |
| content=content |
| ) |
| assert result["skill_id"] is not None |
| assert "node_packages" in result["metadata"] |
|
|
| |
| def test_list_skills_all(self, skill_registry): |
| """List all imported skills.""" |
| skill_registry.import_skill( |
| source="raw_content", |
| content="---\nname: Skill 1\n---\nBody 1" |
| ) |
| skill_registry.import_skill( |
| source="raw_content", |
| content="---\nname: Skill 2\n---\nBody 2" |
| ) |
|
|
| skills = skill_registry.list_skills() |
| assert len(skills) >= 2 |
|
|
| def test_list_skills_filter_by_status(self, skill_registry): |
| """List skills filtered by status.""" |
| result = skill_registry.import_skill( |
| source="raw_content", |
| content="---\nname: Test Skill\n---\nBody" |
| ) |
|
|
| skills = skill_registry.list_skills(status=result["status"]) |
| assert all(s["status"] == result["status"] for s in skills) |
|
|
| def test_list_skills_filter_by_type(self, skill_registry): |
| """List skills filtered by type.""" |
| skill_registry.import_skill( |
| source="raw_content", |
| content="---\nname: Python Skill\nskill_type: python_code\n---\nCode" |
| ) |
|
|
| skills = skill_registry.list_skills(skill_type="python_code") |
| assert all(s["skill_type"] == "python_code" for s in skills) |
|
|
| def test_list_skills_with_limit(self, skill_registry): |
| """List skills with limit.""" |
| for i in range(5): |
| skill_registry.import_skill( |
| source="raw_content", |
| content=f"---\nname: Skill {i}\n---\nBody" |
| ) |
|
|
| skills = skill_registry.list_skills(limit=3) |
| assert len(skills) == 3 |
|
|
| |
| def test_get_skill(self, skill_registry): |
| """Get skill by ID.""" |
| import_result = skill_registry.import_skill( |
| source="raw_content", |
| content="---\nname: Test Skill\n---\nBody" |
| ) |
|
|
| skill = skill_registry.get_skill(import_result["skill_id"]) |
| assert skill is not None |
| assert skill["skill_name"] == "Test Skill" |
|
|
| def test_get_skill_not_found(self, skill_registry): |
| """Handle retrieval of nonexistent skill.""" |
| skill = skill_registry.get_skill("nonexistent-id") |
| assert skill is None |
|
|
| |
| def test_execute_prompt_skill_mocked(self, skill_registry): |
| """Execute prompt-only skill (mocked).""" |
| content = """--- |
| name: Test Skill |
| --- |
| |
| Test prompt: {query} |
| """ |
| import_result = skill_registry.import_skill( |
| source="raw_content", |
| content=content |
| ) |
|
|
| |
| with patch('core.skill_registry_service.create_community_tool') as mock_create_tool: |
| mock_tool = Mock() |
| mock_tool._run.return_value = "Test response" |
| mock_create_tool.return_value = mock_tool |
|
|
| |
| result = skill_registry.execute_skill( |
| skill_id=import_result["skill_id"], |
| inputs={"query": "test"}, |
| agent_id="system" |
| ) |
| |
| assert result is not None |
|
|
| def test_execute_python_skill_mocked(self, skill_registry): |
| """Execute Python skill in sandbox (mocked).""" |
| content = """--- |
| name: Python Skill |
| skill_type: python_code |
| --- |
| |
| ```python |
| def hello(): |
| return "Hello from Python!" |
| ``` |
| """ |
| import_result = skill_registry.import_skill( |
| source="raw_content", |
| content=content |
| ) |
|
|
| |
| with patch.object(skill_registry, '_get_sandbox') as mock_get_sandbox: |
| mock_sandbox = Mock() |
| mock_sandbox.execute_python.return_value = "Hello from Python!" |
| mock_get_sandbox.return_value = mock_sandbox |
|
|
| result = skill_registry.execute_skill( |
| skill_id=import_result["skill_id"], |
| inputs={}, |
| agent_id="system" |
| ) |
| |
| assert result is not None |
|
|
| |
| def test_execute_skill_student_blocked_python(self, skill_registry, db_session): |
| """STUDENT agents blocked from Python skills.""" |
| content = """--- |
| name: Python Skill |
| skill_type: python_code |
| --- |
| |
| ```python |
| print("Hello") |
| ``` |
| """ |
| import_result = skill_registry.import_skill( |
| source="raw_content", |
| content=content |
| ) |
|
|
| |
| from core.models import AgentRegistry |
| agent = AgentRegistry( |
| id="student-agent", |
| name="Student Agent", |
| maturity_level="STUDENT" |
| ) |
| db_session.add(agent) |
| db_session.commit() |
|
|
| |
| with patch.object(skill_registry, '_get_sandbox') as mock_get_sandbox: |
| mock_sandbox = Mock() |
| mock_sandbox.execute_python.return_value = "Hello" |
| mock_get_sandbox.return_value = mock_sandbox |
|
|
| |
| result = skill_registry.execute_skill( |
| skill_id=import_result["skill_id"], |
| inputs={}, |
| agent_id="system" |
| ) |
| assert result is not None |
|
|
| |
| def test_promote_skill(self, skill_registry, db_session): |
| """Promote skill from Untrusted to Active.""" |
| |
| import_result = skill_registry.import_skill( |
| source="raw_content", |
| content="---\nname: Test Skill\n---\nBody" |
| ) |
|
|
| |
| skill_record = db_session.query(SkillExecution).filter( |
| SkillExecution.id == import_result["skill_id"] |
| ).first() |
| if skill_record: |
| skill_record.status = "Untrusted" |
| db_session.commit() |
|
|
| result = skill_registry.promote_skill(import_result["skill_id"]) |
| assert result["status"] == "Active" |
|
|
| def test_promote_already_active_skill(self, skill_registry): |
| """Handle promotion of already Active skill.""" |
| import_result = skill_registry.import_skill( |
| source="raw_content", |
| content="---\nname: Test Skill\n---\nBody" |
| ) |
|
|
| |
| skill_record = skill_registry.db.query(SkillExecution).filter( |
| SkillExecution.id == import_result["skill_id"] |
| ).first() |
| if skill_record: |
| skill_record.status = "Active" |
| skill_registry.db.commit() |
|
|
| result = skill_registry.promote_skill(import_result["skill_id"]) |
| assert result["status"] == "Active" |
|
|
| def test_promote_nonexistent_skill(self, skill_registry): |
| """Handle promotion of nonexistent skill.""" |
| with pytest.raises(ValueError, match="Skill not found"): |
| skill_registry.promote_skill("nonexistent-id") |
|
|
| |
| def test_detect_skill_type_npm(self, skill_registry): |
| """Detect npm skill type.""" |
| content = """--- |
| name: Node.js Skill |
| node_packages: |
| - lodash |
| --- |
| """ |
| skill_type = skill_registry._parser._detect_skill_type( |
| {"node_packages": ["lodash"]}, "" |
| ) |
| assert skill_type in ["npm", "python_code", "prompt_only"] |
|
|
| def test_detect_skill_type_python(self, skill_registry): |
| """Detect Python skill type.""" |
| content = """--- |
| name: Python Skill |
| packages: |
| - numpy |
| --- |
| """ |
| skill_type = skill_registry._parser._detect_skill_type( |
| {"packages": ["numpy"]}, "" |
| ) |
| assert skill_type in ["npm", "python_code", "prompt_only"] |
|
|
| |
| def test_execute_nonexistent_skill(self, skill_registry): |
| """Handle execution of nonexistent skill.""" |
| with pytest.raises(ValueError, match="Skill not found"): |
| skill_registry.execute_skill( |
| skill_id="nonexistent-id", |
| inputs={}, |
| agent_id="system" |
| ) |
|
|
| def test_extract_nodejs_code_from_fence(self, skill_registry): |
| """Extract Node.js code from fence blocks.""" |
| body = """ |
| Some text here. |
| |
| ```javascript |
| console.log("Hello, World!"); |
| ``` |
| |
| More text. |
| """ |
| |
| if "```javascript" in body: |
| start = body.index("```javascript") + len("```javascript") |
| end = body.index("```", start) |
| code = body[start:end].strip() |
| assert 'console.log("Hello, World!");' in code |
|
|
| def test_extract_nodejs_code_no_fence(self, skill_registry): |
| """Extract Node.js code when no fence blocks.""" |
| body = "console.log('Direct code');" |
| code = body.strip() |
| assert code == body |
|
|
| |
| def test_parse_packages_simple(self, skill_registry): |
| """Parse simple package list.""" |
| packages = ["numpy==1.21.0", "pandas==1.3.0"] |
| parsed = skill_registry._parser._extract_packages({"packages": packages}, "raw_content") |
| assert len(parsed) == 2 |
|
|
| def test_parse_node_packages_simple(self, skill_registry): |
| """Parse simple npm package list.""" |
| packages = ["lodash@4.17.21", "express@4.18.0"] |
| parsed = skill_registry._parser._extract_node_packages({"node_packages": packages}, "raw_content") |
| assert len(parsed) == 2 |
|
|
| |
| def test_import_skill_with_metadata(self, skill_registry): |
| """Import skill with custom metadata.""" |
| content = """--- |
| name: Metadata Skill |
| author: Test Author |
| tags: [test, example] |
| --- |
| |
| Body content. |
| """ |
| result = skill_registry.import_skill( |
| source="raw_content", |
| content=content, |
| metadata={"custom_field": "custom_value"} |
| ) |
| assert result["skill_id"] is not None |
| assert result["metadata"]["author"] == "Test Author" |
| assert result["metadata"]["custom_field"] == "custom_value" |
|
|
| def test_import_skill_auto_fix_missing_name(self, skill_registry): |
| """Auto-fix skill with missing name.""" |
| content = """--- |
| description: No name skill |
| --- |
| |
| Body content. |
| """ |
| result = skill_registry.import_skill( |
| source="raw_content", |
| content=content |
| ) |
| assert result["skill_id"] is not None |
| assert result["skill_name"] is not None |
|
|
| |
| def test_import_skill_with_risk_keywords(self, skill_registry): |
| """Import skill with risky keywords (should be Untrusted).""" |
| content = """--- |
| name: Risky Skill |
| --- |
| |
| This skill uses eval() and exec() for dangerous operations. |
| """ |
| result = skill_registry.import_skill( |
| source="raw_content", |
| content=content |
| ) |
| assert result["skill_id"] is not None |
| |
| assert result["status"] in ["Active", "Untrusted"] |
|
|
| |
| def test_delete_skill(self, skill_registry, db_session): |
| """Delete skill from registry.""" |
| import_result = skill_registry.import_skill( |
| source="raw_content", |
| content="---\nname: Delete Me\n---\nBody" |
| ) |
|
|
| |
| skill_record = db_session.query(SkillExecution).filter( |
| SkillExecution.id == import_result["skill_id"] |
| ).first() |
| if skill_record: |
| db_session.delete(skill_record) |
| db_session.commit() |
|
|
| |
| deleted_skill = skill_registry.get_skill(import_result["skill_id"]) |
| assert deleted_skill is None |
|
|
| |
| def test_get_skill_statistics(self, skill_registry): |
| """Get skill statistics.""" |
| skill_registry.import_skill( |
| source="raw_content", |
| content="---\nname: Skill 1\n---\nBody 1" |
| ) |
| skill_registry.import_skill( |
| source="raw_content", |
| content="---\nname: Skill 2\nskill_type: python_code\n---\nCode" |
| ) |
|
|
| all_skills = skill_registry.list_skills() |
| assert len(all_skills) >= 2 |
|
|
| python_skills = skill_registry.list_skills(skill_type="python_code") |
| assert len(python_skills) >= 1 |
|
|