Skip to main content
Glama

Adversary MCP Server

by brettbergin
test_project_context_builder.py14.8 kB
"""Tests for ProjectContextBuilder.""" import tempfile from pathlib import Path from unittest.mock import Mock, patch import pytest from adversary_mcp_server.session.project_context import ( ProjectContext, ProjectContextBuilder, ProjectFile, ) class TestProjectContextBuilder: """Test ProjectContextBuilder functionality.""" def setup_method(self): """Set up test fixtures.""" self.builder = ProjectContextBuilder(max_context_tokens=30000) def test_initialization(self): """Test builder initialization.""" assert self.builder.max_context_tokens == 30000 assert len(self.builder.security_keywords) > 0 assert len(self.builder.entry_point_patterns) > 0 assert len(self.builder.config_patterns) > 0 def test_calculate_priority_score(self): """Test priority score calculation.""" # Test entry point file entry_file = Path("main.py") score = self.builder._calculate_priority_score(entry_file, Path("main.py")) assert score > 0.8 # Test nested file nested_file = Path("src/deep/nested/file.py") nested_path = Path("src/deep/nested/file.py") score = self.builder._calculate_priority_score(nested_file, nested_path) assert score < 0.5 # Test file in important directory src_file = Path("src/important.py") src_path = Path("src/important.py") score = self.builder._calculate_priority_score(src_file, src_path) assert score > 0.3 # Adjusted expectation based on actual implementation def test_calculate_security_relevance(self): """Test security relevance calculation.""" # Test security keyword in filename auth_file = Path("authentication.py") score = self.builder._calculate_security_relevance(auth_file, "") assert score > 0.0 # Test security keywords in content content = "password validation authentication" score = self.builder._calculate_security_relevance(Path("test.py"), content) assert score > 0.0 # Test non-security file score = self.builder._calculate_security_relevance( Path("utils.py"), "helper functions" ) assert score >= 0.0 def test_detect_project_type(self): """Test project type detection.""" with tempfile.TemporaryDirectory() as temp_dir: project_root = Path(temp_dir) # Test Node.js project (project_root / "package.json").write_text('{"name": "test"}') project_type = self.builder._detect_project_type(project_root, []) assert "Node.js" in project_type # Test Python project (project_root / "package.json").unlink() (project_root / "requirements.txt").write_text("flask==2.0.0") project_type = self.builder._detect_project_type(project_root, []) assert "Python" in project_type def test_build_structure_overview(self): """Test structure overview generation.""" with tempfile.TemporaryDirectory() as temp_dir: project_root = Path(temp_dir) # Create test structure (project_root / "src").mkdir() (project_root / "src" / "main.py").write_text("# main file") (project_root / "tests").mkdir() (project_root / "tests" / "test_main.py").write_text("# test file") files = [ project_root / "src" / "main.py", project_root / "tests" / "test_main.py", ] overview = self.builder._build_structure_overview(project_root, files) assert "src/" in overview assert "tests/" in overview assert "main.py" in overview @patch("adversary_mcp_server.session.project_context.FileFilter") def test_discover_files(self, mock_file_filter): """Test file discovery.""" with tempfile.TemporaryDirectory() as temp_dir: project_root = Path(temp_dir) # Create test files (project_root / "main.py").write_text("# main") (project_root / "config.json").write_text("{}") (project_root / ".git").mkdir() # Mock file filter mock_filter_instance = Mock() mock_filter_instance.filter_files.return_value = [ project_root / "main.py", project_root / "config.json", ] mock_file_filter.return_value = mock_filter_instance files = self.builder._discover_files(project_root) assert len(files) >= 1 mock_file_filter.assert_called_once() def test_create_project_file(self): """Test ProjectFile creation.""" with tempfile.TemporaryDirectory() as temp_dir: project_root = Path(temp_dir) test_file = project_root / "auth.py" test_file.write_text("def authenticate(password): pass") project_file = self.builder._create_project_file(test_file, project_root) assert project_file.path == Path("auth.py") assert project_file.language == "python" assert project_file.size_bytes > 0 assert project_file.security_relevance > 0.0 assert "authenticate" in project_file.content_preview def test_select_key_files(self): """Test key file selection within token budget.""" # Create mock project files files = [ ProjectFile( path=Path("critical.py"), language="python", size_bytes=1000, security_relevance=1.0, is_security_critical=True, content_preview="a" * 100, ), ProjectFile( path=Path("normal.py"), language="python", size_bytes=500, security_relevance=0.3, content_preview="b" * 50, ), ProjectFile( path=Path("large.py"), language="python", size_bytes=10000, security_relevance=0.8, content_preview="c" * 8000, # Large content ), ] context = ProjectContext(project_root=Path("/test")) selected = self.builder._select_key_files(files, context) # Should select critical files first assert any(f.is_security_critical for f in selected) # Should not exceed token budget significantly total_chars = sum(len(f.content_preview) for f in selected) assert total_chars < self.builder.max_context_tokens * 4 # Rough token estimate def test_extract_dependencies(self): """Test dependency extraction.""" with tempfile.TemporaryDirectory() as temp_dir: project_root = Path(temp_dir) # Test Python requirements requirements = project_root / "requirements.txt" requirements.write_text("flask==2.0.0\nrequests>=2.25.0\n# comment\npytest") deps = self.builder._extract_dependencies(project_root) assert "flask" in deps assert "requests" in deps assert "pytest" in deps assert "# comment" not in deps def test_build_context_integration(self): """Test full context building integration.""" with tempfile.TemporaryDirectory() as temp_dir: project_root = Path(temp_dir) # Create realistic project structure (project_root / "src").mkdir() (project_root / "src" / "app.py").write_text( """ from flask import Flask app = Flask(__name__) @app.route('/login') def login(): return 'login page' """ ) (project_root / "requirements.txt").write_text("flask==2.0.0") (project_root / "config.json").write_text('{"debug": true}') # Build context context = self.builder.build_context(project_root) # Verify context properties assert context.project_root == project_root assert context.total_files > 0 assert len(context.languages_used) > 0 assert context.estimated_tokens > 0 assert context.project_type # Verify context prompt generation prompt = context.to_context_prompt() assert str(project_root) in prompt assert "Flask" in prompt or "Python" in prompt def test_build_context_with_target_files(self): """Test context building with specific target files.""" with tempfile.TemporaryDirectory() as temp_dir: project_root = Path(temp_dir) # Create files main_file = project_root / "main.py" main_file.write_text("print('main')") other_file = project_root / "other.py" other_file.write_text("print('other')") # Build context focusing on specific file context = self.builder.build_context(project_root, target_files=[main_file]) # Should still build overall context but may prioritize target files assert context.total_files >= 1 assert any("main.py" in str(f.path) for f in context.key_files) def test_is_analyzable_file(self): """Test file analyzability check.""" # Analyzable files assert self.builder._is_analyzable_file(Path("test.py")) assert self.builder._is_analyzable_file(Path("app.js")) assert self.builder._is_analyzable_file(Path("config.json")) # Non-analyzable files assert not self.builder._is_analyzable_file(Path("image.png")) assert not self.builder._is_analyzable_file(Path("data.bin")) def test_estimate_tokens(self): """Test token estimation.""" context = ProjectContext( project_root=Path("/test"), key_files=[ ProjectFile( path=Path("test.py"), language="python", size_bytes=1000, content_preview="a" * 100, ) ], ) estimated = self.builder._estimate_tokens(context) assert estimated > 0 # Should be roughly chars / 4 assert estimated < 1000 # Should be reasonable estimate @pytest.fixture def temp_project(): """Create a temporary project for testing.""" with tempfile.TemporaryDirectory() as temp_dir: project_root = Path(temp_dir) # Create a realistic project structure (project_root / "src").mkdir() (project_root / "src" / "__init__.py").write_text("") (project_root / "src" / "main.py").write_text( """ import os from flask import Flask, request app = Flask(__name__) @app.route('/login', methods=['POST']) def login(): username = request.form['username'] password = request.form['password'] # Vulnerable SQL injection query = f"SELECT * FROM users WHERE username='{username}'" return authenticate(query, password) def authenticate(query, password): # More vulnerable code return True """ ) (project_root / "src" / "config.py").write_text( """ DATABASE_URL = "sqlite:///app.db" SECRET_KEY = "hardcoded-secret-key" DEBUG = True """ ) (project_root / "requirements.txt").write_text( """ flask==2.0.0 requests==2.25.0 sqlalchemy==1.4.0 """ ) (project_root / "tests").mkdir() (project_root / "tests" / "test_main.py").write_text( """ import unittest from src.main import app class TestApp(unittest.TestCase): def test_login(self): pass """ ) yield project_root class TestProjectContextBuilderIntegration: """Integration tests for ProjectContextBuilder.""" def test_realistic_project_analysis(self, temp_project): """Test analysis of a realistic project structure.""" builder = ProjectContextBuilder(max_context_tokens=50000) context = builder.build_context(temp_project) # Verify project analysis assert context.project_type == "Flask Web Application" assert "python" in context.languages_used assert context.total_files >= 4 assert len(context.key_files) >= 2 # Verify security analysis assert len(context.security_modules) > 0 security_files = [f for f in context.key_files if f.is_security_critical] assert len(security_files) > 0 # Verify dependencies assert "flask" in context.dependencies assert "requests" in context.dependencies # Verify structure assert "src/" in context.structure_overview assert "tests/" in context.structure_overview # Verify context prompt prompt = context.to_context_prompt() assert "Flask" in prompt or "flask" in prompt assert "main.py" in prompt assert "Security-Relevant Files" in prompt def test_context_token_budget_management(self, temp_project): """Test that context building respects token budgets.""" # Test with small budget small_builder = ProjectContextBuilder(max_context_tokens=1000) small_context = small_builder.build_context(temp_project) # Test with large budget large_builder = ProjectContextBuilder(max_context_tokens=100000) large_context = large_builder.build_context(temp_project) # Small budget should have fewer files assert len(small_context.key_files) <= len(large_context.key_files) assert small_context.estimated_tokens <= large_context.estimated_tokens def test_security_file_prioritization(self, temp_project): """Test that security-relevant files are prioritized.""" builder = ProjectContextBuilder(max_context_tokens=20000) context = builder.build_context(temp_project) # Security files should be in key files key_file_names = [str(f.path) for f in context.key_files] # main.py contains authentication code assert any("main.py" in name for name in key_file_names) # config.py contains security settings - may not always be in top files due to prioritization # Check if security-critical files are present security_files = [f for f in context.key_files if f.is_security_critical] assert len(security_files) > 0 # Security files should have high relevance scores security_files = [f for f in context.key_files if f.is_security_critical] for file in security_files: assert file.security_relevance > 0.5

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/brettbergin/adversary-mcp-server'

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