Skip to main content
Glama
test_dependency_resolver.py9.75 kB
"""Tests for dependency resolver functionality.""" import pytest from autodoc_mcp.core.dependency_resolver import ( CORE_FRAMEWORKS, LOW_PRIORITY_DEPS, WELL_DOCUMENTED, DependencyResolver, DependencySpec, PackageMetadata, ) from autodoc_mcp.core.network_client import BasicNetworkClient class TestDependencySpec: """Test dependency specification model.""" def test_from_requirement(self): """Test creating DependencySpec from packaging.requirements.Requirement.""" from packaging.requirements import Requirement req = Requirement("requests>=2.28.0") spec = DependencySpec.from_requirement(req) assert spec.name == "requests" assert spec.version_constraint == ">=2.28.0" assert spec.extras == [] assert not spec.is_optional assert spec.environment_markers == "" def test_from_requirement_with_extras(self): """Test requirement with extras.""" from packaging.requirements import Requirement req = Requirement("fastapi[all]>=0.100.0") spec = DependencySpec.from_requirement(req) assert spec.name == "fastapi" assert "all" in spec.extras assert ">=0.100.0" in spec.version_constraint class TestPackageMetadata: """Test package metadata model.""" def test_package_metadata_creation(self): """Test creating package metadata.""" metadata = PackageMetadata( name="test-package", version="1.0.0", summary="Test package", description="A test package for testing", ) assert metadata.name == "test-package" assert metadata.version == "1.0.0" assert metadata.runtime_requires == [] assert metadata.keywords == [] class TestDependencyResolver: """Test dependency resolver functionality.""" @pytest.fixture(autouse=True) def setup_method(self, mocker): """Set up test fixtures.""" # Mock network client self.mock_client = mocker.AsyncMock(spec=BasicNetworkClient) self.resolver = DependencyResolver(self.mock_client) async def test_resolve_context_dependencies_no_deps(self, mocker): """Test resolving dependencies when package has none.""" # Mock response for package with no dependencies mock_response = mocker.MagicMock() mock_response.json.return_value = { "info": { "name": "simple-package", "version": "1.0.0", "summary": "Simple package", "description": "No dependencies", "requires_dist": None, } } self.mock_client.get_with_retry.return_value = mock_response deps = await self.resolver.resolve_context_dependencies( "simple-package", "1.0.0" ) assert deps == [] async def test_resolve_context_dependencies_with_deps(self, mocker): """Test resolving dependencies with actual dependencies.""" # Mock response for fastapi-like package mock_response = mocker.MagicMock() mock_response.json.return_value = { "info": { "name": "fastapi", "version": "0.104.1", "summary": "FastAPI framework", "description": "Modern web framework", "requires_dist": [ "pydantic>=2.0.0", "starlette>=0.27.0", "typing-extensions>=4.0.0", ], } } self.mock_client.get_with_retry.return_value = mock_response deps = await self.resolver.resolve_context_dependencies( "fastapi", "0.104.1", max_dependencies=3 ) # Should prioritize pydantic (core framework) and starlette assert len(deps) > 0 assert "pydantic" in deps assert "starlette" in deps # typing-extensions is low priority, might be excluded async def test_resolve_context_dependencies_with_token_limit(self, mocker): """Test token-aware dependency selection.""" mock_response = mocker.MagicMock() mock_response.json.return_value = { "info": { "name": "test-package", "version": "1.0.0", "summary": "Test", "description": "Test", "requires_dist": [ "requests>=2.28.0", "pydantic>=2.0.0", "click>=8.0.0", ], } } self.mock_client.get_with_retry.return_value = mock_response # Very low token limit - should limit dependencies deps = await self.resolver.resolve_context_dependencies( "test-package", "1.0.0", max_tokens=1000 ) # Should only include highest priority dep due to token limit assert len(deps) <= 2 # Limited by token budget def test_calculate_dependency_relevance_core_framework(self): """Test relevance scoring for core frameworks.""" spec = DependencySpec(name="fastapi", version_constraint=">=0.100.0") score = self.resolver._calculate_dependency_relevance(spec, "myapp") # Core frameworks get 10.0 base score assert score >= 10.0 def test_calculate_dependency_relevance_relationship_boost(self): """Test package-specific relationship boosts.""" spec = DependencySpec(name="pydantic", version_constraint=">=2.0.0") # Should get relationship boost when used with fastapi score = self.resolver._calculate_dependency_relevance(spec, "fastapi") # Should get core framework (10.0) + relationship boost (8.0) assert score >= 18.0 def test_calculate_dependency_relevance_low_priority(self): """Test penalty for low-priority dependencies.""" spec = DependencySpec(name="setuptools", version_constraint=">=60.0.0") score = self.resolver._calculate_dependency_relevance(spec, "myapp") # Low priority deps get negative score, but minimum is 0 assert score == 0.0 def test_calculate_dependency_relevance_well_documented(self): """Test boost for well-documented packages.""" spec = DependencySpec(name="requests", version_constraint=">=2.28.0") score = self.resolver._calculate_dependency_relevance(spec, "myapp") # requests is both core framework (10.0) and well documented (+2.0) assert score >= 12.0 async def test_estimate_dependency_tokens(self): """Test token estimation for different types of packages.""" # Core framework - should have high token estimate core_tokens = await self.resolver._estimate_dependency_tokens("fastapi") assert core_tokens == 2500 # Package in CORE_FRAMEWORKS gets 2500 (first match wins) requests_tokens = await self.resolver._estimate_dependency_tokens("requests") assert requests_tokens == 2500 # Package in CORE_FRAMEWORKS gets 2500 (first match wins) click_tokens = await self.resolver._estimate_dependency_tokens("click") assert click_tokens == 2500 # Unknown package - default estimate default_tokens = await self.resolver._estimate_dependency_tokens("unknown-pkg") assert default_tokens == 1000 async def test_graceful_degradation_on_network_error(self): """Test graceful handling of network errors.""" # Mock network error self.mock_client.get_with_retry.side_effect = Exception("Network error") deps = await self.resolver.resolve_context_dependencies("test-package", "1.0.0") # Should return empty list gracefully assert deps == [] async def test_get_dependency_metadata_caching(self, mocker): """Test that metadata is cached properly.""" mock_response = mocker.MagicMock() mock_response.json.return_value = { "info": { "name": "cached-package", "version": "1.0.0", "summary": "Cached", "description": "Test caching", } } self.mock_client.get_with_retry.return_value = mock_response # First call should hit network metadata1 = await self.resolver._get_package_metadata("cached-package", "1.0.0") assert metadata1.name == "cached-package" # Second call should use cache metadata2 = await self.resolver._get_package_metadata("cached-package", "1.0.0") assert metadata2.name == "cached-package" # Should only have called network once assert self.mock_client.get_with_retry.call_count == 1 class TestDependencyConstants: """Test dependency categorization constants.""" def test_core_frameworks_contains_expected(self): """Test that core frameworks includes expected packages.""" assert "fastapi" in CORE_FRAMEWORKS assert "django" in CORE_FRAMEWORKS assert "flask" in CORE_FRAMEWORKS assert "requests" in CORE_FRAMEWORKS assert "pydantic" in CORE_FRAMEWORKS def test_well_documented_contains_expected(self): """Test that well documented includes expected packages.""" assert "requests" in WELL_DOCUMENTED assert "fastapi" in WELL_DOCUMENTED assert "pydantic" in WELL_DOCUMENTED assert "click" in WELL_DOCUMENTED def test_low_priority_contains_build_tools(self): """Test that low priority includes build tools.""" assert "setuptools" in LOW_PRIORITY_DEPS assert "wheel" in LOW_PRIORITY_DEPS assert "pip" in LOW_PRIORITY_DEPS assert "hatchling" in LOW_PRIORITY_DEPS

Latest Blog Posts

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/bradleyfay/autodoc-mcp'

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