test_dependency_analyzer.py•9.27 kB
"""
Tests for dependency analyzer
"""
import pytest
from mcpindexer.dependency_analyzer import (
DependencyAnalyzer,
CrossRepoAnalyzer,
Dependency,
DependencyGraph
)
from mcpindexer.parser import CodeParser
# Sample code files for testing
SAMPLE_AUTH_CODE = '''
import bcrypt
from typing import Optional
from .database import get_user
from .models.user import User
def authenticate(username: str, password: str) -> Optional[User]:
user = get_user(username)
return user if bcrypt.checkpw(password, user.password) else None
'''
SAMPLE_DATABASE_CODE = '''
from sqlalchemy import create_engine
from .models.user import User
def get_user(username: str):
return User.query.filter_by(username=username).first()
'''
SAMPLE_USER_MODEL_CODE = '''
from dataclasses import dataclass
@dataclass
class User:
username: str
password: str
'''
SAMPLE_JS_CODE = '''
const express = require('express');
const { authenticate } = require('./auth');
const User = require('./models/user');
function login(req, res) {
const result = authenticate(req.body.username, req.body.password);
res.json(result);
}
'''
class TestDependencyAnalyzer:
"""Tests for DependencyAnalyzer class"""
def setup_method(self):
"""Set up test fixtures"""
self.parser = CodeParser()
self.analyzer = DependencyAnalyzer(repo_name="test-repo")
def test_analyzer_initialization(self):
"""Test analyzer initializes correctly"""
assert self.analyzer.repo_name == "test-repo"
assert len(self.analyzer.file_map) == 0
def test_add_file(self):
"""Test adding files to analyzer"""
parsed = self.parser.parse_file("auth.py", SAMPLE_AUTH_CODE)
self.analyzer.add_file(parsed)
assert len(self.analyzer.file_map) == 1
assert "auth.py" in self.analyzer.file_map
def test_analyze_external_dependencies(self):
"""Test detection of external dependencies"""
parsed = self.parser.parse_file("auth.py", SAMPLE_AUTH_CODE)
self.analyzer.add_file(parsed)
graph = self.analyzer.analyze()
# Should detect external packages (bcrypt, typing)
assert len(graph.external_packages) > 0
assert any("bcrypt" in pkg for pkg in graph.external_packages)
def test_analyze_internal_dependencies(self):
"""Test detection of internal dependencies"""
parsed = self.parser.parse_file("auth.py", SAMPLE_AUTH_CODE)
self.analyzer.add_file(parsed)
graph = self.analyzer.analyze()
# Should detect internal imports (.database, .models.user)
internal_deps = [d for d in graph.dependencies if not d.is_external]
assert len(internal_deps) > 0
def test_dependency_graph_structure(self):
"""Test that dependency graph is properly structured"""
parsed = self.parser.parse_file("auth.py", SAMPLE_AUTH_CODE)
self.analyzer.add_file(parsed)
graph = self.analyzer.analyze()
assert isinstance(graph, DependencyGraph)
assert isinstance(graph.dependencies, list)
assert isinstance(graph.internal_deps, dict)
assert isinstance(graph.external_deps, dict)
assert isinstance(graph.external_packages, set)
def test_multiple_files_analysis(self):
"""Test analyzing multiple files"""
auth_parsed = self.parser.parse_file("auth.py", SAMPLE_AUTH_CODE)
db_parsed = self.parser.parse_file("database.py", SAMPLE_DATABASE_CODE)
user_parsed = self.parser.parse_file("models/user.py", SAMPLE_USER_MODEL_CODE)
self.analyzer.add_file(auth_parsed)
self.analyzer.add_file(db_parsed)
self.analyzer.add_file(user_parsed)
graph = self.analyzer.analyze()
assert len(self.analyzer.file_map) == 3
assert len(graph.dependencies) > 0
def test_get_dependencies(self):
"""Test getting dependencies for a file"""
auth_parsed = self.parser.parse_file("auth.py", SAMPLE_AUTH_CODE)
self.analyzer.add_file(auth_parsed)
graph = self.analyzer.analyze()
deps = graph.get_dependencies("auth.py")
# auth.py has internal dependencies
assert isinstance(deps, list)
def test_find_external_calls(self):
"""Test finding calls to external packages"""
parsed = self.parser.parse_file("auth.py", SAMPLE_AUTH_CODE)
self.analyzer.add_file(parsed)
# Find uses of bcrypt
results = self.analyzer.find_external_calls("bcrypt")
assert len(results) > 0
assert all(isinstance(r, Dependency) for r in results)
def test_get_dependency_stats(self):
"""Test getting dependency statistics"""
auth_parsed = self.parser.parse_file("auth.py", SAMPLE_AUTH_CODE)
db_parsed = self.parser.parse_file("database.py", SAMPLE_DATABASE_CODE)
self.analyzer.add_file(auth_parsed)
self.analyzer.add_file(db_parsed)
stats = self.analyzer.get_dependency_stats()
assert "total_files" in stats
assert "total_dependencies" in stats
assert "external_packages" in stats
assert stats["total_files"] == 2
def test_javascript_dependencies(self):
"""Test analyzing JavaScript dependencies"""
parsed = self.parser.parse_file("app.js", SAMPLE_JS_CODE)
self.analyzer.add_file(parsed)
graph = self.analyzer.analyze()
# JS require statements may not be parsed yet - just check structure is valid
assert isinstance(graph.external_packages, set)
assert isinstance(graph.dependencies, list)
def test_extract_package_name(self):
"""Test package name extraction"""
# Regular package
assert self.analyzer._extract_package_name("express") == "express"
# Scoped package
assert self.analyzer._extract_package_name("@company/auth") == "@company/auth"
# Package with path
assert self.analyzer._extract_package_name("lodash/map") == "lodash"
# Scoped package with path
assert self.analyzer._extract_package_name("@company/auth/lib/utils") == "@company/auth"
def test_dependency_object_attributes(self):
"""Test Dependency object has required attributes"""
parsed = self.parser.parse_file("auth.py", SAMPLE_AUTH_CODE)
self.analyzer.add_file(parsed)
graph = self.analyzer.analyze()
if graph.dependencies:
dep = graph.dependencies[0]
assert hasattr(dep, 'source_file')
assert hasattr(dep, 'target_module')
assert hasattr(dep, 'is_external')
assert hasattr(dep, 'import_type')
assert hasattr(dep, 'symbols')
class TestCrossRepoAnalyzer:
"""Tests for CrossRepoAnalyzer class"""
def setup_method(self):
"""Set up test fixtures"""
self.parser = CodeParser()
self.cross_analyzer = CrossRepoAnalyzer()
def test_cross_analyzer_initialization(self):
"""Test cross-repo analyzer initializes"""
assert len(self.cross_analyzer.repo_analyzers) == 0
def test_add_repo(self):
"""Test adding repository analyzers"""
analyzer = DependencyAnalyzer(repo_name="repo1")
self.cross_analyzer.add_repo("repo1", analyzer)
assert len(self.cross_analyzer.repo_analyzers) == 1
assert "repo1" in self.cross_analyzer.repo_analyzers
def test_multiple_repos(self):
"""Test managing multiple repositories"""
analyzer1 = DependencyAnalyzer(repo_name="repo1")
analyzer2 = DependencyAnalyzer(repo_name="repo2")
self.cross_analyzer.add_repo("repo1", analyzer1)
self.cross_analyzer.add_repo("repo2", analyzer2)
assert len(self.cross_analyzer.repo_analyzers) == 2
def test_suggest_missing_repos(self):
"""Test suggesting missing repositories"""
# Create analyzer with scoped packages
analyzer = DependencyAnalyzer(repo_name="app")
code_with_scoped = '''
import { auth } from '@company/auth-service';
import { db } from '@company/database';
'''
parsed = self.parser.parse_file("app.js", code_with_scoped)
analyzer.add_file(parsed)
self.cross_analyzer.add_repo("app", analyzer)
# Suggest missing repos (none indexed yet)
suggestions = self.cross_analyzer.suggest_missing_repos(set())
# Should suggest the scoped packages
assert len(suggestions) >= 0 # May or may not detect depending on parsing
def test_find_cross_repo_dependencies(self):
"""Test finding dependencies between repos"""
analyzer1 = DependencyAnalyzer(repo_name="app")
analyzer2 = DependencyAnalyzer(repo_name="auth-service")
# Add some files
code = 'import express from "express";'
parsed = self.parser.parse_file("app.js", code)
analyzer1.add_file(parsed)
self.cross_analyzer.add_repo("app", analyzer1)
self.cross_analyzer.add_repo("auth-service", analyzer2)
cross_deps = self.cross_analyzer.find_cross_repo_dependencies()
# Should return a list (may be empty for simple test)
assert isinstance(cross_deps, list)
if __name__ == "__main__":
pytest.main([__file__, "-v"])