"""Integration tests for full analysis workflow.""" import json import pytest from codesnap.core.analyzer import CodeAnalyzer from codesnap.core.language_detector import detect_language from codesnap.output.json_exporter import export_json from codesnap.output.llm_exporter import export_llm_optimized from codesnap.output.markdown_exporter import export_markdown @pytest.fixture def sample_python_project(tmp_path): """Create a sample Python project for testing.""" main_py = tmp_path / "main.py" main_py.write_text(''' """Main module for the application.""" import os from utils import helper def main(): """Main entry point.""" print("Hello, World!") helper.process() class Application: """Main application class.""" def __init__(self, config): self.config = config def run(self): """Run the application.""" if self.config.debug: print("Debug mode enabled") return True class Database: """Database connection class.""" def __init__(self, host, port): self.host = host self.port = port def connect(self): """Establish database connection.""" return "Connected" def query(self, sql): """Execute a query.""" if not sql: raise ValueError("SQL query cannot be empty") return ["result1", "result2"] ''') utils_py = tmp_path / "utils.py" utils_py.write_text(''' """Utility functions module.""" import sys from typing import List def process(): """Process data.""" return "processed" def helper(x: int, y: int) -> int: """Helper function for calculations.""" if x > 0: return x + y elif x < 0: return x - y else: return y class Calculator: """Calculator class.""" def add(self, a, b): return a + b def multiply(self, a, b): return a * b ''') return tmp_path @pytest.fixture def sample_multilang_project(tmp_path): """Create a multi-language project for testing.""" python_file = tmp_path / "processor.py" python_file.write_text(''' from js_utils import process_js import json def handle_data(data): return json.dumps(process_js(data)) ''') js_file = tmp_path / "js_utils.js" js_file.write_text(''' function process_js(data) { if (data && data.length > 0) { return data.map(x => x * 2); } return []; } module.exports = { process_js }; ''') go_file = tmp_path / "main.go" go_file.write_text(''' package main import "fmt" func main() { fmt.Println("Hello from Go") } func Process() string { return "processed" } ''') return tmp_path def check_parser_available(language="python"): """Check if tree-sitter parser is available for a language.""" try: from codesnap.core.parser import TreeSitterParser _ = TreeSitterParser() return True except Exception: return False class TestFullAnalysis: """Integration tests for full analysis workflow.""" def test_analyze_python_project(self, sample_python_project): """Test analyzing a Python project.""" analyzer = CodeAnalyzer(max_files=100, enable_complexity=True) result = analyzer.analyze(sample_python_project) assert result.summary["total_files"] == 2 if result.error_count == 0: assert result.summary["total_functions"] >= 4 assert result.summary["total_classes"] >= 2 def test_analyze_multilang_project(self, sample_multilang_project): """Test analyzing a multi-language project.""" analyzer = CodeAnalyzer(max_files=100, enable_complexity=False) result = analyzer.analyze(sample_multilang_project) assert result.summary["total_files"] == 3 languages = result.summary.get("languages", {}) assert "python" in languages assert "javascript" in languages assert "go" in languages def test_json_export(self, sample_python_project): """Test JSON export functionality.""" analyzer = CodeAnalyzer(max_files=100) result = analyzer.analyze(sample_python_project) json_output = export_json(result, sample_python_project) data = json.loads(json_output) assert "metadata" in data assert "summary" in data assert "files" in data assert len(data["files"]) == 2 def test_markdown_export(self, sample_python_project): """Test Markdown export functionality.""" analyzer = CodeAnalyzer(max_files=100) result = analyzer.analyze(sample_python_project) md_output = export_markdown(result, sample_python_project) assert "# CodeSnap Analysis Report" in md_output assert "## Summary" in md_output assert "## File Structure" in md_output assert "main.py" in md_output def test_llm_export(self, sample_python_project): """Test LLM-optimized export functionality.""" analyzer = CodeAnalyzer(max_files=100) result = analyzer.analyze(sample_python_project) llm_output = export_llm_optimized(result, sample_python_project, max_tokens=1000) assert "## CODEBASE ANALYSIS SUMMARY" in llm_output assert "### STRUCTURE" in llm_output def test_dependency_detection(self, sample_python_project): """Test dependency detection.""" analyzer = CodeAnalyzer(max_files=100, enable_complexity=False) result = analyzer.analyze(sample_python_project) if result.error_count == 0: assert len(result.dependencies) >= 0 dep_sources = [d["source"] for d in result.dependencies] assert any("main.py" in src for src in dep_sources) def test_complexity_analysis(self, sample_python_project): """Test complexity analysis.""" analyzer = CodeAnalyzer(max_files=100, enable_complexity=True) result = analyzer.analyze(sample_python_project) files_with_complexity = [f for f in result.files if f.complexity] if result.error_count == 0: assert len(files_with_complexity) > 0 for fa in files_with_complexity: assert fa.complexity.cyclomatic_complexity >= 1 assert fa.complexity.nesting_depth >= 0 def test_ignore_patterns(self, sample_python_project): """Test ignore patterns functionality.""" ignore_analyzer = CodeAnalyzer( max_files=100, ignore_patterns=["utils.py"], enable_complexity=False ) result = ignore_analyzer.analyze(sample_python_project) file_names = [f.path.name for f in result.files] assert "utils.py" not in file_names assert "main.py" in file_names def test_max_files_limit(self, sample_python_project): """Test max files limit.""" limited_analyzer = CodeAnalyzer(max_files=1) result = limited_analyzer.analyze(sample_python_project) assert len(result.files) <= 1 def test_orphaned_file_detection(self, sample_python_project): """Test orphaned file detection.""" analyzer = CodeAnalyzer(max_files=100, enable_complexity=False) result = analyzer.analyze(sample_python_project) orphaned = result.metrics.get("orphaned_files", []) if result.error_count == 0: assert len(orphaned) == 0 def test_graph_builder(self, sample_python_project): """Test graph builder functionality.""" analyzer = CodeAnalyzer(max_files=100, enable_complexity=False) result = analyzer.analyze(sample_python_project) assert analyzer.graph_builder.graph.number_of_nodes() >= 1 if result.error_count == 0: assert analyzer.graph_builder.graph.number_of_edges() >= 1 def test_language_detection_integration(self, sample_python_project): """Test language detection integration.""" python_file = sample_python_project / "main.py" content = python_file.read_text() lang = detect_language(python_file, content) assert lang == "python" def test_multiple_output_formats(self, sample_python_project): """Test that all output formats work together.""" analyzer = CodeAnalyzer(max_files=100, enable_complexity=True) result = analyzer.analyze(sample_python_project) json_output = export_json(result, sample_python_project) md_output = export_markdown(result, sample_python_project) llm_output = export_llm_optimized(result, sample_python_project) assert len(json_output) > 0 assert len(md_output) > 0 assert len(llm_output) > 0 json_data = json.loads(json_output) assert json_data["summary"]["total_files"] == result.summary["total_files"] class TestEdgeCases: """Test edge cases in analysis.""" def test_empty_directory(self, tmp_path): """Test analyzing an empty directory.""" analyzer = CodeAnalyzer(max_files=100) result = analyzer.analyze(tmp_path) assert result.summary["total_files"] == 0 assert result.error_count == 0 def test_single_file(self, tmp_path): """Test analyzing a single file.""" test_file = tmp_path / "single.py" test_file.write_text("x = 1\nprint(x)") analyzer = CodeAnalyzer(max_files=100) result = analyzer.analyze(tmp_path) assert result.summary["total_files"] >= 1 def test_unsupported_file_types(self, tmp_path): """Test handling of unsupported file types.""" text_file = tmp_path / "readme.txt" text_file.write_text("This is a readme file") analyzer = CodeAnalyzer(max_files=100) result = analyzer.analyze(tmp_path) assert len(result.files) == 0 or all( f.language == "unknown" for f in result.files )