import json
from datetime import datetime
from pathlib import Path
from unittest.mock import MagicMock, patch, AsyncMock
import pytest
from skillhub.config import Settings
from skillhub.models.source import Source, SourceType
from skillhub.models.skill import SkillManifest
from skillhub.models.repository import RateLimit
from skillhub.services.credential_manager import CredentialManagerImpl
class TestCredentialManager:
"""Tests for CredentialManagerImpl."""
@pytest.fixture
def credential_manager(self, mock_settings: Settings, mock_keyring):
"""Create credential manager instance."""
return CredentialManagerImpl(mock_settings)
@pytest.mark.asyncio
async def test_store_token(self, credential_manager: CredentialManagerImpl, mock_keyring):
"""Test storing token."""
await credential_manager.store_token("github", "test_token")
mock_keyring.set_password.assert_called()
@pytest.mark.asyncio
async def test_get_token(self, credential_manager: CredentialManagerImpl, mock_keyring):
"""Test getting token."""
result = await credential_manager.get_token("github")
assert result == "test_token_123"
@pytest.mark.asyncio
async def test_get_token_non_existing(self, credential_manager: CredentialManagerImpl, mock_keyring):
"""Test getting non-existing token."""
mock_keyring.get_password.return_value = None
result = await credential_manager.get_token("non-existing")
assert result is None
@pytest.mark.asyncio
async def test_remove_token(self, credential_manager: CredentialManagerImpl, mock_keyring):
"""Test removing token."""
await credential_manager.remove_token("github")
mock_keyring.delete_password.assert_called()
@pytest.mark.asyncio
async def test_remove_token_non_existing(self, credential_manager: CredentialManagerImpl, mock_keyring):
"""Test removing non-existing token."""
mock_keyring.delete_password.side_effect = Exception("not found")
await credential_manager.remove_token("non-existing")
@pytest.mark.asyncio
async def test_list_tokens(self, credential_manager: CredentialManagerImpl, mock_keyring):
"""Test listing tokens."""
tokens = await credential_manager.list_tokens()
assert isinstance(tokens, list)
@pytest.mark.asyncio
async def test_list_tokens_empty(self, credential_manager: CredentialManagerImpl, mock_keyring):
"""Test listing empty tokens."""
credential_manager._metadata = {}
tokens = await credential_manager.list_tokens()
assert tokens == []
@pytest.mark.asyncio
async def test_store_token_with_expires(self, credential_manager: CredentialManagerImpl, mock_keyring):
"""Test storing token with expiry."""
expires = datetime(2025, 1, 1)
await credential_manager.store_token(
"github",
"token",
expires_at=expires,
scopes=["repo"],
)
assert "github" in credential_manager._metadata
@pytest.mark.asyncio
async def test_store_token_with_scopes(self, credential_manager: CredentialManagerImpl, mock_keyring):
"""Test storing token with scopes."""
await credential_manager.store_token(
"gitee",
"gitee_token",
scopes=["read", "write"],
)
assert "gitee" in credential_manager._metadata
@pytest.mark.asyncio
async def test_get_token_multiple_platforms(self, credential_manager: CredentialManagerImpl, mock_keyring):
"""Test getting tokens for multiple platforms."""
await credential_manager.store_token("github", "gh_token")
await credential_manager.store_token("gitee", "ge_token")
mock_keyring.get_password.return_value = "test_token"
gh_result = await credential_manager.get_token("github")
ge_result = await credential_manager.get_token("gitee")
assert gh_result is not None
assert ge_result is not None
def test_is_secure_storage_available(self, credential_manager: CredentialManagerImpl):
"""Test checking secure storage availability."""
result = credential_manager.is_secure_storage_available()
assert isinstance(result, bool)
def test_metadata_file_path(self, mock_settings: Settings):
"""Test metadata file path is set correctly."""
manager = CredentialManagerImpl(mock_settings)
assert manager.metadata_file.name == "tokens.json"
@pytest.mark.asyncio
async def test_token_metadata_persistence(
self, credential_manager: CredentialManagerImpl, mock_keyring, temp_dir: Path
):
"""Test token metadata persistence."""
expires = datetime(2025, 12, 31)
await credential_manager.store_token(
"test_platform",
"test_token_value",
expires_at=expires,
scopes=["admin"],
)
credential_manager._save_metadata()
@pytest.mark.asyncio
async def test_remove_token_removes_metadata(self, credential_manager: CredentialManagerImpl, mock_keyring):
"""Test that removing token also removes metadata."""
await credential_manager.store_token("test-remove", "token123", scopes=["read"])
assert "test-remove" in credential_manager._metadata
await credential_manager.remove_token("test-remove")
assert "test-remove" not in credential_manager._metadata
@pytest.mark.asyncio
async def test_list_tokens_with_metadata(self, credential_manager: CredentialManagerImpl, mock_keyring):
"""Test listing tokens with actual metadata."""
await credential_manager.store_token("github", "gh_token", scopes=["repo"])
await credential_manager.store_token("gitee", "ge_token", scopes=["read"])
tokens = await credential_manager.list_tokens()
assert len(tokens) == 2
platforms = [t.platform for t in tokens]
assert "github" in platforms
assert "gitee" in platforms
@pytest.mark.asyncio
async def test_validate_token_success(self, credential_manager: CredentialManagerImpl):
"""Test validating a valid token."""
with patch("skillhub.adapters.factory.AdapterFactory.create") as mock_factory:
mock_adapter = AsyncMock()
mock_adapter.get_rate_limit = AsyncMock(
return_value=RateLimit(limit=5000, remaining=5000, reset_at=datetime.utcnow(), used=0)
)
mock_adapter.close = AsyncMock()
mock_factory.return_value = mock_adapter
result = await credential_manager.validate_token("github", "valid_token")
assert result.valid is True
assert result.rate_limit is not None
assert "valid" in result.message.lower()
@pytest.mark.asyncio
async def test_validate_token_invalid_platform(self, credential_manager: CredentialManagerImpl):
"""Test validating token with invalid platform."""
result = await credential_manager.validate_token("invalid_platform", "token")
assert result.valid is False
@pytest.mark.asyncio
async def test_validate_token_adapter_error(self, credential_manager: CredentialManagerImpl):
"""Test validating token when adapter fails."""
with patch("skillhub.adapters.factory.AdapterFactory.create") as mock_factory:
mock_adapter = AsyncMock()
mock_adapter.get_rate_limit = AsyncMock(side_effect=Exception("API error"))
mock_adapter.close = AsyncMock()
mock_factory.return_value = mock_adapter
result = await credential_manager.validate_token("github", "bad_token")
assert result.valid is False
assert "API error" in result.message
def test_is_secure_storage_available_exception(self, mock_settings: Settings):
"""Test secure storage check with exception."""
with patch("skillhub.services.credential_manager.keyring.get_keyring") as mock_keyring:
mock_keyring.side_effect = Exception("Keyring error")
manager = CredentialManagerImpl(mock_settings)
result = manager.is_secure_storage_available()
assert result is False
def test_setup_keyring_fail_backend(self, mock_settings: Settings):
"""Test keyring setup when get_keyring returns fail backend."""
import keyring.backends.fail
with patch("skillhub.services.credential_manager.keyring") as mock_kr:
mock_kr.get_keyring.return_value = keyring.backends.fail.Keyring()
mock_kr.set_keyring = MagicMock()
manager = CredentialManagerImpl(mock_settings)
assert manager is not None
mock_kr.set_keyring.assert_called()
def test_setup_keyring_exception(self, mock_settings: Settings):
"""Test keyring setup when get_keyring raises exception."""
with patch("skillhub.services.credential_manager.keyring") as mock_kr:
mock_kr.get_keyring.side_effect = RuntimeError("Backend unavailable")
mock_kr.set_keyring = MagicMock()
manager = CredentialManagerImpl(mock_settings)
assert manager is not None
mock_kr.set_keyring.assert_called()
def test_load_metadata_from_file(self, mock_settings: Settings, temp_dir: Path):
"""Test loading metadata from existing file."""
metadata_file = temp_dir / "tokens.json"
metadata_file.write_text(
json.dumps(
{
"github": {
"stored_at": "2024-01-01T00:00:00",
"scopes": ["repo"],
}
}
)
)
mock_settings.data_dir = temp_dir
manager = CredentialManagerImpl(mock_settings)
manager.metadata_file = metadata_file
manager._load_metadata()
assert "github" in manager._metadata
@pytest.mark.asyncio
async def test_validate_token_mock(self, credential_manager: CredentialManagerImpl, mock_keyring):
"""Test validating token (if method exists)."""
await credential_manager.store_token("github", "valid_token")
if hasattr(credential_manager, "validate_token"):
try:
result = await credential_manager.validate_token("github", "valid_token")
if hasattr(result, 'valid'):
assert result is not None
except TypeError:
pass
class TestSkillResolver:
"""Tests for SkillResolverImpl."""
@pytest.fixture
def skill_resolver(self, sample_source):
"""Create skill resolver instance."""
from skillhub.services.skill_resolver import SkillResolverImpl
return SkillResolverImpl([sample_source])
def test_init_with_sources(self, sample_source):
"""Test resolver initialization with sources."""
from skillhub.services.skill_resolver import SkillResolverImpl
resolver = SkillResolverImpl([sample_source])
assert len(resolver.sources) == 1
def test_init_with_empty_sources(self):
"""Test resolver initialization with empty sources."""
from skillhub.services.skill_resolver import SkillResolverImpl
resolver = SkillResolverImpl([])
assert len(resolver.sources) == 0
@pytest.mark.asyncio
async def test_resolve_version_no_sources(self):
"""Test resolving with no sources."""
from skillhub.services.skill_resolver import SkillResolverImpl
resolver = SkillResolverImpl([])
result = await resolver.resolve_version("test-skill", "latest")
assert result is None
@pytest.mark.asyncio
async def test_resolve_version_disabled_source(self, sample_source):
"""Test resolving with disabled source."""
from skillhub.services.skill_resolver import SkillResolverImpl
sample_source.enabled = False
resolver = SkillResolverImpl([sample_source])
result = await resolver.resolve_version("test-skill", "latest")
assert result is None
def test_validate_manifest_valid(self, skill_resolver, sample_manifest):
"""Test validating valid manifest."""
result = skill_resolver.validate_manifest(sample_manifest)
assert result.valid is True
assert len(result.errors) == 0
@pytest.mark.asyncio
async def test_resolve_dependencies(self, skill_resolver, sample_resolved_skill):
"""Test resolving dependencies."""
sample_resolved_skill.manifest.dependencies = None
result = await skill_resolver.resolve_dependencies(sample_resolved_skill)
assert result is not None
assert result.root.name == sample_resolved_skill.name
def test_resolver_has_sources(self, skill_resolver):
"""Test resolver has sources attribute."""
assert hasattr(skill_resolver, 'sources')
assert len(skill_resolver.sources) > 0
@pytest.mark.asyncio
async def test_list_available_versions_empty(self):
"""Test listing available versions with no sources."""
from skillhub.services.skill_resolver import SkillResolverImpl
resolver = SkillResolverImpl([])
result = await resolver.list_available_versions("test-skill")
assert result == []
@pytest.mark.asyncio
async def test_resolve_dependencies_no_deps(self, skill_resolver, sample_resolved_skill):
"""Test resolving dependencies with no dependencies."""
sample_resolved_skill.manifest.dependencies = None
result = await skill_resolver.resolve_dependencies(sample_resolved_skill)
assert len(result.dependencies) == 0
assert len(result.conflicts) == 0
@pytest.mark.asyncio
async def test_fetch_manifest_mock(self):
"""Test fetching manifest with mock."""
from skillhub.services.skill_resolver import SkillResolverImpl
resolver = SkillResolverImpl([])
with patch("skillhub.services.skill_resolver.AdapterFactory.create") as mock_factory:
mock_adapter = AsyncMock()
mock_adapter.get_file_content = AsyncMock(return_value="---\nname: test\ndescription: test\n---")
mock_adapter.close = AsyncMock()
mock_factory.return_value = mock_adapter
result = await resolver.fetch_manifest("https://github.com/test/repo", "main", "github")
assert result.name == "test"
def test_parse_manifest_valid(self):
"""Test parsing valid manifest content."""
from skillhub.services.skill_resolver import SkillResolverImpl
resolver = SkillResolverImpl([])
content = "---\nname: test-skill\nversion: 1.0.0\ndescription: Test skill\n---"
result = resolver._parse_manifest(content)
assert result.name == "test-skill"
def test_parse_manifest_invalid(self):
"""Test parsing invalid manifest content."""
from skillhub.services.skill_resolver import SkillResolverImpl
resolver = SkillResolverImpl([])
content = "No frontmatter here"
result = resolver._parse_manifest(content)
assert result.name == "unknown"
def test_validate_manifest_missing_name(self):
"""Test validating manifest with missing name."""
from skillhub.services.skill_resolver import SkillResolverImpl
resolver = SkillResolverImpl([])
manifest = MagicMock()
manifest.name = None
manifest.description = "test"
result = resolver.validate_manifest(manifest)
assert result.valid is False
assert "name" in str(result.errors).lower()
def test_validate_manifest_missing_description(self):
"""Test validating manifest with missing description."""
from skillhub.services.skill_resolver import SkillResolverImpl
resolver = SkillResolverImpl([])
manifest = MagicMock()
manifest.name = "test"
manifest.description = None
result = resolver.validate_manifest(manifest)
assert result.valid is False
assert "description" in str(result.errors).lower()
def test_validate_manifest_complete(self):
"""Test validating complete manifest."""
from skillhub.services.skill_resolver import SkillResolverImpl
resolver = SkillResolverImpl([])
manifest = MagicMock()
manifest.name = "test"
manifest.description = "test description"
result = resolver.validate_manifest(manifest)
assert result.valid is True
@pytest.mark.asyncio
async def test_resolve_version_with_owner_repo(self, sample_source):
"""Test resolving with owner/repo format skill name."""
from skillhub.services.skill_resolver import SkillResolverImpl
sample_source.subpath = None
resolver = SkillResolverImpl([sample_source])
with patch("skillhub.services.skill_resolver.AdapterFactory.create") as mock_factory:
mock_adapter = AsyncMock()
mock_repo = MagicMock()
mock_repo.url = "https://github.com/owner/repo"
mock_repo.default_branch = "main"
mock_adapter.get_repository = AsyncMock(return_value=mock_repo)
mock_adapter.get_file_content = AsyncMock(return_value="---\nname: owner/skill\ndescription: test\n---")
mock_adapter.list_tags = AsyncMock(return_value=[])
mock_adapter.close = AsyncMock()
mock_factory.return_value = mock_adapter
await resolver.resolve_version("owner/skill", "latest")
mock_adapter.get_repository.assert_called_once()
@pytest.mark.asyncio
async def test_resolve_version_monorepo(self, sample_source):
"""Test resolving skill from monorepo."""
from skillhub.services.skill_resolver import SkillResolverImpl
sample_source.subpath = "skills"
sample_source.url = "https://github.com/owner/monorepo"
sample_source.type = SourceType.GITHUB
resolver = SkillResolverImpl([sample_source])
with patch("skillhub.services.skill_resolver.AdapterFactory.create") as mock_factory:
mock_adapter = AsyncMock()
mock_adapter.get_file_content = AsyncMock(return_value="---\nname: test-skill\ndescription: test\n---")
mock_repo = MagicMock()
mock_repo.default_branch = "main"
mock_adapter.get_repository = AsyncMock(return_value=mock_repo)
mock_adapter.close = AsyncMock()
mock_factory.return_value = mock_adapter
await resolver.resolve_version("test-skill", "latest")
mock_adapter.get_file_content.assert_called()
@pytest.mark.asyncio
async def test_resolve_version_with_config(self, sample_source):
"""Test resolving with config for credentials."""
from skillhub.services.skill_resolver import SkillResolverImpl
sample_source.subpath = None
sample_source.type = SourceType.GITHUB
mock_config = MagicMock()
resolver = SkillResolverImpl([sample_source], config=mock_config)
with patch("skillhub.services.skill_resolver.AdapterFactory.create") as mock_factory:
mock_adapter = AsyncMock()
mock_repo = MagicMock()
mock_repo.url = "https://github.com/owner/repo"
mock_repo.default_branch = "main"
mock_adapter.get_repository = AsyncMock(return_value=mock_repo)
mock_adapter.get_file_content = AsyncMock(return_value="---\nname: test\ndescription: test\n---")
mock_adapter.list_tags = AsyncMock(return_value=[])
mock_adapter.close = AsyncMock()
mock_factory.return_value = mock_adapter
with patch("skillhub.services.credential_manager.CredentialManagerImpl") as mock_cred:
mock_cred_instance = MagicMock()
mock_cred_instance.get_token = AsyncMock(return_value="test_token")
mock_cred.return_value = mock_cred_instance
await resolver.resolve_version("test-skill", "latest")
mock_cred_instance.get_token.assert_called()
@pytest.mark.asyncio
async def test_list_available_versions_with_source(self, sample_source):
"""Test listing versions from source."""
from skillhub.services.skill_resolver import SkillResolverImpl
sample_source.type = SourceType.GITHUB
resolver = SkillResolverImpl([sample_source])
with patch("skillhub.services.skill_resolver.AdapterFactory.create") as mock_factory:
mock_adapter = AsyncMock()
mock_tag = MagicMock()
mock_tag.name = "v1.0.0"
mock_adapter.list_tags = AsyncMock(return_value=[mock_tag])
mock_adapter.close = AsyncMock()
mock_factory.return_value = mock_adapter
result = await resolver.list_available_versions("test-skill")
assert result == ["v1.0.0"]
@pytest.mark.asyncio
async def test_resolve_dependencies_with_deps(self, skill_resolver, sample_resolved_skill):
"""Test resolving dependencies with actual dependencies."""
from skillhub.models.skill import ResolvedSkill
sample_resolved_skill.manifest.dependencies = {"dep-skill": ">=1.0.0"}
with patch.object(skill_resolver, "resolve_version", new_callable=AsyncMock) as mock_resolve:
dep_skill = ResolvedSkill(
name="dep-skill",
version="1.0.0",
repository="https://github.com/test/dep",
ref="v1.0.0",
manifest=SkillManifest(name="dep-skill", description="Dependency"),
source={"id": "test", "type": "github"},
download_url="https://example.com/dep.zip",
)
mock_resolve.return_value = dep_skill
result = await skill_resolver.resolve_dependencies(sample_resolved_skill)
assert "dep-skill" in result.dependencies
assert len(result.conflicts) == 0
@pytest.mark.asyncio
async def test_resolve_dependencies_conflict(self, skill_resolver, sample_resolved_skill):
"""Test resolving dependencies with conflict."""
sample_resolved_skill.manifest.dependencies = {"missing-skill": ">=1.0.0"}
with patch.object(skill_resolver, "resolve_version", new_callable=AsyncMock) as mock_resolve:
mock_resolve.return_value = None
result = await skill_resolver.resolve_dependencies(sample_resolved_skill)
assert len(result.conflicts) == 1
assert result.conflicts[0].skill == "missing-skill"
@pytest.mark.asyncio
async def test_fetch_manifest_invalid_url(self):
"""Test fetch_manifest with invalid URL."""
from skillhub.services.skill_resolver import SkillResolverImpl
resolver = SkillResolverImpl([])
with pytest.raises(ValueError):
await resolver.fetch_manifest("invalid-url", "main", "github")
@pytest.mark.asyncio
async def test_resolve_version_specific_version(self, sample_source):
"""Test resolving specific version (not 'latest')."""
from skillhub.services.skill_resolver import SkillResolverImpl
sample_source.subpath = None
sample_source.type = SourceType.GITHUB
sample_source.url = "https://github.com/owner/repo"
resolver = SkillResolverImpl([sample_source])
with patch("skillhub.services.skill_resolver.AdapterFactory.create") as mock_factory:
mock_adapter = AsyncMock()
mock_repo = MagicMock()
mock_repo.url = "https://github.com/owner/repo"
mock_repo.default_branch = "main"
mock_adapter.get_repository = AsyncMock(return_value=mock_repo)
mock_adapter.get_file_content = AsyncMock(
return_value="---\nname: test\ndescription: test\nversion: 1.0.0\n---"
)
mock_tag1 = MagicMock()
mock_tag1.name = "v1.0.0"
mock_adapter.list_tags = AsyncMock(return_value=[mock_tag1])
mock_adapter.close = AsyncMock()
mock_factory.return_value = mock_adapter
await resolver.resolve_version("owner/repo", ">=2.0.0")
mock_adapter.get_repository.assert_called()
@pytest.mark.asyncio
async def test_resolve_version_exception(self, sample_source):
"""Test resolve_version handles exceptions."""
from skillhub.services.skill_resolver import SkillResolverImpl
resolver = SkillResolverImpl([sample_source])
with patch("skillhub.services.skill_resolver.AdapterFactory.create") as mock_factory:
mock_factory.side_effect = Exception("Connection error")
result = await resolver.resolve_version("test-skill", "latest")
assert result is None
@pytest.mark.asyncio
async def test_list_available_versions_disabled_source(self, sample_source):
"""Test list_available_versions with disabled source."""
from skillhub.services.skill_resolver import SkillResolverImpl
sample_source.enabled = False
resolver = SkillResolverImpl([sample_source])
result = await resolver.list_available_versions("test-skill")
assert result == []
@pytest.mark.asyncio
async def test_monorepo_resolution_no_source_repo(self):
"""Test monorepo resolution when URL has no repo."""
from skillhub.services.skill_resolver import SkillResolverImpl
source = Source(
id="test",
name="Test",
type=SourceType.GITHUB,
url="https://github.com",
subpath="skills",
enabled=True,
)
resolver = SkillResolverImpl([source])
with patch("skillhub.services.skill_resolver.AdapterFactory.create") as mock_factory:
mock_adapter = AsyncMock()
mock_adapter.get_file_content = AsyncMock(return_value="---\nname: test\n---")
mock_adapter.close = AsyncMock()
mock_factory.return_value = mock_adapter
await resolver.resolve_version("test-skill", "latest")
@pytest.mark.asyncio
async def test_direct_repo_resolution_no_version(self, sample_source):
"""Test direct repo resolution when no matching version found."""
from skillhub.services.skill_resolver import SkillResolverImpl
source = Source(
id="direct-source",
name="Direct Source",
type=SourceType.GITHUB,
url="https://github.com/owner/repo",
subpath="",
priority=1,
enabled=True,
)
resolver = SkillResolverImpl([source])
with patch("skillhub.services.skill_resolver.AdapterFactory.create") as mock_factory:
mock_adapter = AsyncMock()
mock_repo = MagicMock()
mock_repo.url = "https://github.com/owner/repo"
mock_repo.default_branch = "main"
mock_adapter.get_repository = AsyncMock(return_value=mock_repo)
mock_adapter.get_file_content = AsyncMock(return_value="---\nname: test\ndescription: test\n---")
mock_adapter.get_file_content.side_effect = [
Exception("Not found"),
"---\nname: test\ndescription: test\n---",
]
mock_adapter.list_tags = AsyncMock(return_value=[])
mock_adapter.close = AsyncMock()
mock_factory.return_value = mock_adapter
await resolver.resolve_version("owner/repo", ">=2.0.0")
@pytest.mark.asyncio
async def test_list_available_versions_with_config(self, sample_source):
"""Test list_available_versions with config."""
from skillhub.services.skill_resolver import SkillResolverImpl
sample_source.type = SourceType.GITHUB
mock_config = MagicMock()
resolver = SkillResolverImpl([sample_source], config=mock_config)
with patch("skillhub.services.skill_resolver.AdapterFactory.create") as mock_factory:
mock_adapter = AsyncMock()
mock_tag = MagicMock()
mock_tag.name = "v1.0.0"
mock_adapter.list_tags = AsyncMock(return_value=[mock_tag])
mock_adapter.close = AsyncMock()
mock_factory.return_value = mock_adapter
with patch("skillhub.services.credential_manager.CredentialManagerImpl") as mock_cred:
mock_cred_instance = MagicMock()
mock_cred_instance.get_token = AsyncMock(return_value="test_token")
mock_cred.return_value = mock_cred_instance
await resolver.list_available_versions("test-skill")
mock_cred_instance.get_token.assert_called()
@pytest.mark.asyncio
async def test_list_available_versions_exception(self, sample_source):
"""Test list_available_versions handles exception."""
from skillhub.services.skill_resolver import SkillResolverImpl
sample_source.type = SourceType.GITHUB
resolver = SkillResolverImpl([sample_source])
with patch("skillhub.services.skill_resolver.AdapterFactory.create") as mock_factory:
mock_adapter = AsyncMock()
mock_adapter.list_tags = AsyncMock(side_effect=Exception("API error"))
mock_adapter.close = AsyncMock()
mock_factory.return_value = mock_adapter
result = await resolver.list_available_versions("test-skill")
assert result == []
@pytest.mark.asyncio
async def test_direct_repo_resolution_with_version(self):
"""Test direct repo resolution with matching version."""
from skillhub.services.skill_resolver import SkillResolverImpl
source = Source(
id="version-source",
name="Version Source",
type=SourceType.GITHUB,
url="https://github.com/owner/repo",
subpath="",
priority=1,
enabled=True,
)
resolver = SkillResolverImpl([source])
with patch("skillhub.services.skill_resolver.AdapterFactory.create") as mock_factory:
mock_adapter = AsyncMock()
mock_repo = MagicMock()
mock_repo.url = "https://github.com/owner/repo"
mock_repo.default_branch = "main"
mock_adapter.get_repository = AsyncMock(return_value=mock_repo)
mock_adapter.get_file_content = AsyncMock()
mock_adapter.get_file_content.side_effect = [
Exception("Not in monorepo"),
"---\nname: test\ndescription: test\nversion: 1.0.0\n---",
]
mock_tag = MagicMock()
mock_tag.name = "v2.0.0"
mock_adapter.list_tags = AsyncMock(return_value=[mock_tag])
mock_adapter.close = AsyncMock()
mock_factory.return_value = mock_adapter
with patch("skillhub.services.skill_resolver.satisfies_version", return_value=True):
await resolver.resolve_version("owner/repo", ">=2.0.0")
class TestInstallEngine:
"""Tests for InstallEngine."""
pass