diff --git a/tests/fixtures/__init__.py b/tests/fixtures/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/fixtures/sample_code.py b/tests/fixtures/sample_code.py
deleted file mode 100644
index 21418f3..0000000
--- a/tests/fixtures/sample_code.py
+++ /dev/null
@@ -1,85 +0,0 @@
-from pathlib import Path
-
-import pytest
-
-
-@pytest.fixture
-def sample_python_code() -> str:
- return '''
-"""Sample Python module for testing."""
-
-def function_with_docstring():
- """This function has a docstring."""
- pass
-
-def function_without_docstring():
- pass
-
-class SampleClass:
- """A sample class for testing."""
-
- def __init__(self):
- self.value = 42
-
- def get_value(self):
- """Get the stored value."""
- return self.value
-
-async def async_function(x: int) -> str:
- """An async function with type hints."""
- return str(x)
-
-@decorator
-def decorated_function():
- pass
-'''
-
-@pytest.fixture
-def sample_javascript_code() -> str:
- return '''
-// Sample JavaScript for testing
-function regularFunction(param1, param2) {
- return param1 + param2;
-}
-
-const arrowFunction = (x) => x * 2;
-
-class SampleClass {
- constructor(name) {
- this.name = name;
- }
-
- getName() {
- return this.name;
- }
-}
-
-module.exports = { regularFunction, SampleClass };
-'''
-
-@pytest.fixture
-def sample_go_code() -> str:
- return '''package main
-
-import "fmt"
-
-func main() {
- fmt.Println("Hello, World!")
-}
-
-func add(a, b int) int {
- return a + b
-}
-'''
-
-@pytest.fixture
-def temp_project_dir(tmp_path) -> Path:
- (tmp_path / "src").mkdir()
- (tmp_path / "tests").mkdir()
- (tmp_path / "main.py").write_text("def main(): pass")
- (tmp_path / "src" / "module.py").write_text("def helper(): pass")
- (tmp_path / "tests" / "test_main.py").write_text("def test_main(): pass")
- (tmp_path / ".gitignore").write_text("*.pyc")
- (tmp_path / "__pycache__").mkdir()
- (tmp_path / "__pycache__" / "cache.pyc").write_text("cached")
- return tmp_path
diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py
deleted file mode 100644
index fbf7078..0000000
--- a/tests/integration/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"""Integration tests package for Doc2Man."""
diff --git a/tests/integration/test_all_formats.py b/tests/integration/test_all_formats.py
deleted file mode 100644
index 02c4e71..0000000
--- a/tests/integration/test_all_formats.py
+++ /dev/null
@@ -1,141 +0,0 @@
-"""Integration tests for all output formats."""
-
-import tempfile
-from pathlib import Path
-
-from doc2man.parsers.python import parse_python_file
-from doc2man.generators.man import generate_man_page
-from doc2man.generators.markdown import generate_markdown
-from doc2man.generators.html import generate_html
-
-
-class TestAllFormatsIntegration:
- """Integration tests for all output formats."""
-
- def test_man_format(self):
- """Test man page format output."""
- source = '''
-def command(input_file, output_file=None):
- """Process a file and output the result.
-
- Args:
- input_file: Path to input file.
- output_file: Optional path to output file.
-
- Returns:
- Processed data.
- """
- return "processed"
-'''
- with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as f:
- f.write(source.encode())
- f.flush()
-
- parsed = parse_python_file(Path(f.name))
-
- with tempfile.NamedTemporaryFile(suffix=".1", delete=False) as out:
- result = generate_man_page([{"file": f.name, "data": parsed}], Path(out.name))
-
- assert ".TH" in result
- assert "NAME" in result
- assert "DESCRIPTION" in result
-
- Path(out.name).unlink()
- Path(f.name).unlink()
-
- def test_markdown_format(self):
- """Test markdown format output."""
- source = '''
-def api(endpoint, method="GET"):
- """Make an API request.
-
- Args:
- endpoint: The API endpoint URL.
- method: HTTP method to use.
-
- Returns:
- Response JSON data.
- """
- return {"status": "ok"}
-'''
- with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as f:
- f.write(source.encode())
- f.flush()
-
- parsed = parse_python_file(Path(f.name))
-
- with tempfile.NamedTemporaryFile(suffix=".md", delete=False) as out:
- result = generate_markdown([{"file": f.name, "data": parsed}], Path(out.name))
-
- assert "#" in result
- assert "## Functions" in result or "#" in result
-
- Path(out.name).unlink()
- Path(f.name).unlink()
-
- def test_html_format(self):
- """Test HTML format output."""
- source = '''
-class DataProcessor:
- """Process data efficiently."""
-
- def process(self, data):
- """Process the given data.
-
- Args:
- data: Input data to process.
-
- Returns:
- Processed result.
- """
- return data.upper()
-'''
- with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as f:
- f.write(source.encode())
- f.flush()
-
- parsed = parse_python_file(Path(f.name))
-
- with tempfile.NamedTemporaryFile(suffix=".html", delete=False) as out:
- result = generate_html([{"file": f.name, "data": parsed}], Path(out.name))
-
- assert "" in result
- assert "" in result
- assert "
" in result
- assert "" in result
- assert "DataProcessor" in result
-
- Path(out.name).unlink()
- Path(f.name).unlink()
-
- def test_all_formats_same_data(self):
- """Test that all formats produce consistent output from same data."""
- source = '''
-def consistent(name):
- """A function with consistent docs.
-
- Args:
- name: A name parameter.
-
- Returns:
- A greeting.
- """
- return f"Hello {name}"
-'''
- with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as f:
- f.write(source.encode())
- f.flush()
-
- parsed = parse_python_file(Path(f.name))
- parsed_data = [{"file": f.name, "data": parsed}]
-
- man_result = generate_man_page(parsed_data, None)
- md_result = generate_markdown(parsed_data, None)
- html_result = generate_html(parsed_data, None)
-
- assert "consistent" in man_result.lower()
- assert "consistent" in md_result.lower()
- assert "consistent" in html_result.lower()
-
- Path(f.name).unlink()
diff --git a/tests/integration/test_full_analysis.py b/tests/integration/test_full_analysis.py
deleted file mode 100644
index 8cb811c..0000000
--- a/tests/integration/test_full_analysis.py
+++ /dev/null
@@ -1,328 +0,0 @@
-"""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
- )
diff --git a/tests/integration/test_full_pipeline.py b/tests/integration/test_full_pipeline.py
deleted file mode 100644
index b661aaf..0000000
--- a/tests/integration/test_full_pipeline.py
+++ /dev/null
@@ -1,263 +0,0 @@
-"""Integration tests for the full documentation pipeline."""
-
-import tempfile
-from pathlib import Path
-
-import pytest
-from click.testing import CliRunner
-
-from doc2man.cli import main
-from doc2man.parsers.python import parse_python_file
-from doc2man.parsers.go import parse_go_file
-from doc2man.parsers.javascript import parse_javascript_file
-from doc2man.generators.man import generate_man_page
-from doc2man.generators.markdown import generate_markdown
-from doc2man.generators.html import generate_html
-
-
-class TestFullPipeline:
- """Integration tests for the full documentation pipeline."""
-
- def test_python_to_man_pipeline(self):
- """Test Python file -> parse -> generate man page."""
- with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as f:
- f.write(b'''
-def greet(name, greeting="Hello"):
- """Greet a person with a custom greeting.
-
- Args:
- name: The name of the person to greet.
- greeting: The greeting word to use.
-
- Returns:
- The greeting string.
-
- Raises:
- ValueError: If name is empty.
- """
- if not name:
- raise ValueError("Name cannot be empty")
- return f"{greeting}, {name}!"
-''')
- f.flush()
-
- parsed = parse_python_file(Path(f.name))
- assert parsed["language"] == "python"
- assert len(parsed["functions"]) == 1
-
- with tempfile.NamedTemporaryFile(suffix=".1", delete=False) as out:
- output_path = Path(out.name)
- result = generate_man_page([{"file": str(f.name), "data": parsed}], output_path)
-
- assert ".TH" in result
- assert "NAME" in result
- assert "greet" in result.lower()
-
- output_path.unlink()
- Path(f.name).unlink()
-
- def test_python_to_markdown_pipeline(self):
- """Test Python file -> parse -> generate markdown."""
- with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as f:
- f.write(b'''
-def calculate(a, b):
- """Calculate sum of two numbers.
-
- Args:
- a: First number.
- b: Second number.
-
- Returns:
- The sum of a and b.
- """
- return a + b
-''')
- f.flush()
-
- parsed = parse_python_file(Path(f.name))
-
- with tempfile.NamedTemporaryFile(suffix=".md", delete=False) as out:
- output_path = Path(out.name)
- result = generate_markdown([{"file": str(f.name), "data": parsed}], output_path)
-
- assert "#" in result
- assert "calculate" in result.lower()
- assert "Parameters" in result
-
- output_path.unlink()
- Path(f.name).unlink()
-
- def test_python_to_html_pipeline(self):
- """Test Python file -> parse -> generate HTML."""
- with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as f:
- f.write(b'''
-class Calculator:
- """A simple calculator class."""
-
- def add(self, a, b):
- """Add two numbers.
-
- Args:
- a: First number.
- b: Second number.
-
- Returns:
- The sum.
- """
- return a + b
-''')
- f.flush()
-
- parsed = parse_python_file(Path(f.name))
-
- with tempfile.NamedTemporaryFile(suffix=".html", delete=False) as out:
- output_path = Path(out.name)
- result = generate_html([{"file": str(f.name), "data": parsed}], output_path)
-
- assert "" in result
- assert "" in result
- assert "Calculator" in result
-
- output_path.unlink()
- Path(f.name).unlink()
-
- def test_go_pipeline(self):
- """Test Go file parsing and generation."""
- with tempfile.NamedTemporaryFile(suffix=".go", delete=False) as f:
- f.write(b'''
-// Package math provides math utilities.
-//
-// This is a simple math package.
-package math
-
-// Add adds two integers.
-//
-// a: First integer
-// b: Second integer
-//
-// Returns: The sum
-func Add(a, b int) int {
- return a + b
-}
-''')
- f.flush()
-
- parsed = parse_go_file(Path(f.name))
- assert parsed["language"] == "go"
- assert len(parsed["functions"]) >= 1
-
- Path(f.name).unlink()
-
- def test_javascript_pipeline(self):
- """Test JavaScript file parsing and generation."""
- with tempfile.NamedTemporaryFile(suffix=".js", delete=False) as f:
- f.write(b'''
-/**
- * Multiplies two numbers.
- *
- * @param {number} a - First number
- * @param {number} b - Second number
- * @returns {number} The product
- */
-function multiply(a, b) {
- return a * b;
-}
-''')
- f.flush()
-
- parsed = parse_javascript_file(Path(f.name))
- assert parsed["language"] == "javascript"
- assert len(parsed["functions"]) == 1
-
- Path(f.name).unlink()
-
- def test_typescript_pipeline(self):
- """Test TypeScript file parsing and generation."""
- with tempfile.NamedTemporaryFile(suffix=".ts", delete=False) as f:
- f.write(b'''
-/**
- * Divides two numbers.
- *
- * @param numerator - The numerator
- * @param denominator - The denominator
- * @returns The quotient
- */
-function divide(numerator: number, denominator: number): number {
- return numerator / denominator;
-}
-''')
- f.flush()
-
- parsed = parse_javascript_file(Path(f.name))
- assert parsed["language"] == "javascript"
- assert len(parsed["functions"]) >= 1
-
- Path(f.name).unlink()
-
-
-class TestCLIIntegration:
- """Integration tests for CLI commands."""
-
- def test_cli_generate_command(self):
- """Test the full generate CLI command."""
- with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as f:
- f.write(b'''
-def example():
- """An example function."""
- pass
-''')
- f.flush()
-
- with tempfile.NamedTemporaryFile(suffix=".1", delete=False) as out:
- runner = CliRunner()
- result = runner.invoke(main, [
- "generate",
- f.name,
- "--output", out.name,
- "--format", "man"
- ])
-
- assert result.exit_code == 0
- assert Path(out.name).exists()
- out_path = Path(out.name)
- assert out_path.stat().st_size > 0
-
- out_path.unlink()
- Path(f.name).unlink()
-
- def test_cli_multiple_files(self):
- """Test generating from multiple files."""
- with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as f1:
- f1.write(b'''
-def func1():
- """First function."""
- pass
-''')
- f1.flush()
-
- with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as f2:
- f2.write(b'''
-def func2():
- """Second function."""
- pass
-''')
- f2.flush()
-
- with tempfile.NamedTemporaryFile(suffix=".md", delete=False) as out:
- runner = CliRunner()
- result = runner.invoke(main, [
- "generate",
- f1.name, f2.name,
- "--output", out.name,
- "--format", "markdown"
- ])
-
- assert result.exit_code == 0
- content = Path(out.name).read_text()
- assert "func1" in content or "func2" in content
-
- out_path = Path(out.name)
- out_path.unlink()
-
- Path(f1.name).unlink()
- Path(f2.name).unlink()
diff --git a/tests/test_analyzer.py b/tests/test_analyzer.py
deleted file mode 100644
index 766b4da..0000000
--- a/tests/test_analyzer.py
+++ /dev/null
@@ -1,214 +0,0 @@
-"""Tests for the analyzer module."""
-
-import pytest
-from cli_diff_auditor.analyzer import AuditResult, DiffAuditor, FileAnalyzer
-from cli_diff_auditor.diff_parser import ChangeType, DiffHunk, DiffLine, FileDiff
-from cli_diff_auditor.rules import RulesLoader, Severity
-
-
-class TestAuditResult:
- """Test cases for AuditResult class."""
-
- def test_add_finding_error(self):
- """Test adding an error finding."""
- result = AuditResult()
- result.add_finding(type('Finding', (), {
- 'severity': Severity.ERROR
- })())
-
- assert result.errors_count == 1
- assert result.warnings_count == 0
- assert result.info_count == 0
-
- def test_add_finding_warning(self):
- """Test adding a warning finding."""
- result = AuditResult()
- result.add_finding(type('Finding', (), {
- 'severity': Severity.WARNING
- })())
-
- assert result.errors_count == 0
- assert result.warnings_count == 1
- assert result.info_count == 0
-
- def test_add_finding_info(self):
- """Test adding an info finding."""
- result = AuditResult()
- result.add_finding(type('Finding', (), {
- 'severity': Severity.INFO
- })())
-
- assert result.errors_count == 0
- assert result.warnings_count == 0
- assert result.info_count == 1
-
- def test_get_summary(self):
- """Test getting the summary."""
- result = AuditResult()
-
- for _ in range(2):
- result.add_finding(type('Finding', (), {'severity': Severity.ERROR})())
- for _ in range(3):
- result.add_finding(type('Finding', (), {'severity': Severity.WARNING})())
- for _ in range(1):
- result.add_finding(type('Finding', (), {'severity': Severity.INFO})())
-
- summary = result.get_summary()
-
- assert summary["error"] == 2
- assert summary["warning"] == 3
- assert summary["info"] == 1
- assert summary["total"] == 6
-
- def test_has_errors(self):
- """Test checking for errors."""
- result = AuditResult()
- assert result.has_errors() is False
-
- result.add_finding(type('Finding', (), {'severity': Severity.ERROR})())
- assert result.has_errors() is True
-
- def test_has_findings(self):
- """Test checking for any findings."""
- result = AuditResult()
- assert result.has_findings() is False
-
- result.add_finding(type('Finding', (), {'severity': Severity.INFO})())
- assert result.has_findings() is True
-
-
-class TestFileAnalyzer:
- """Test cases for FileAnalyzer class."""
-
- def setup_method(self):
- """Set up test fixtures."""
- self.rules_loader = RulesLoader()
- self.rules_loader.load_builtin_rules()
- self.analyzer = FileAnalyzer(self.rules_loader)
-
- def test_analyze_file_diff_finds_debug_print(self):
- """Test that debug prints are detected."""
- file_diff = FileDiff(file_path="test.py")
- hunk = DiffHunk(old_start=1, old_lines=2, new_start=1, new_lines=2)
- hunk.lines = [
- DiffLine(line_number=1, content="print('hello')", change_type=ChangeType.ADDED),
- ]
- file_diff.hunks.append(hunk)
-
- findings = self.analyzer.analyze_file_diff(file_diff)
-
- assert len(findings) == 1
- assert findings[0].rule_id == "debug-print"
-
- def test_analyze_file_diff_finds_console_log(self):
- """Test that console.log is detected."""
- file_diff = FileDiff(file_path="test.js")
- hunk = DiffHunk(old_start=1, old_lines=2, new_start=1, new_lines=2)
- hunk.lines = [
- DiffLine(line_number=1, content="console.log('test')", change_type=ChangeType.ADDED),
- ]
- file_diff.hunks.append(hunk)
-
- findings = self.analyzer.analyze_file_diff(file_diff)
-
- assert len(findings) == 1
- assert findings[0].rule_id == "console-log"
-
- def test_analyze_file_diff_ignores_clean_code(self):
- """Test that clean code has no findings."""
- file_diff = FileDiff(file_path="test.py")
- hunk = DiffHunk(old_start=1, old_lines=2, new_start=1, new_lines=2)
- hunk.lines = [
- DiffLine(line_number=1, content="def hello():", change_type=ChangeType.ADDED),
- DiffLine(line_number=2, content=" return 'world'", change_type=ChangeType.ADDED),
- ]
- file_diff.hunks.append(hunk)
-
- findings = self.analyzer.analyze_file_diff(file_diff)
-
- assert len(findings) == 0
-
- def test_analyze_binary_file(self):
- """Test that binary files are skipped."""
- file_diff = FileDiff(file_path="image.png", is_binary=True)
- hunk = DiffHunk(old_start=1, old_lines=1, new_start=1, new_lines=1)
- hunk.lines = [
- DiffLine(line_number=1, content="binary data", change_type=ChangeType.ADDED),
- ]
- file_diff.hunks.append(hunk)
-
- findings = self.analyzer.analyze_file_diff(file_diff)
-
- assert len(findings) == 0
-
- def test_analyze_deleted_lines(self):
- """Test that deleted lines are also analyzed."""
- file_diff = FileDiff(file_path="test.py")
- hunk = DiffHunk(old_start=1, old_lines=2, new_start=1, new_lines=1)
- hunk.lines = [
- DiffLine(line_number=1, content="print('old')", change_type=ChangeType.DELETED),
- ]
- file_diff.hunks.append(hunk)
-
- findings = self.analyzer.analyze_file_diff(file_diff)
-
- assert len(findings) == 1
- assert findings[0].rule_id == "debug-print"
-
-
-class TestDiffAuditor:
- """Test cases for DiffAuditor class."""
-
- def setup_method(self):
- """Set up test fixtures."""
- self.auditor = DiffAuditor()
-
- def test_get_all_rules(self):
- """Test getting all rules."""
- rules = self.auditor.get_rules()
-
- assert len(rules) > 0
- assert any(r.id == "debug-print" for r in rules)
-
- def test_get_enabled_rules(self):
- """Test getting enabled rules."""
- self.auditor.disable_rules(["debug-print", "console-log"])
-
- enabled = self.auditor.get_enabled_rules()
- rule_ids = [r.id for r in enabled]
-
- assert "debug-print" not in rule_ids
- assert "console-log" not in rule_ids
-
- def test_disable_and_enable_rules(self):
- """Test disabling and enabling rules."""
- self.auditor.disable_rules(["debug-print"])
- enabled = self.auditor.get_enabled_rules()
- assert "debug-print" not in [r.id for r in enabled]
-
- self.auditor.enable_rules(["debug-print"])
- enabled = self.auditor.get_enabled_rules()
- assert "debug-print" in [r.id for r in enabled]
-
- def test_audit_diff_output(self):
- """Test auditing diff output directly."""
- diff_output = """diff --git a/test.py b/test.py
-index 1234567..89abcdef 100644
---- a/test.py
-+++ b/test.py
-@@ -1,2 +1,2 @@
--old
-+print('debug')
-"""
- result = self.auditor.audit_diff_output(diff_output)
-
- assert result.files_scanned == 1
- assert result.has_findings()
- assert result.warnings_count >= 1
-
- def test_audit_diff_empty(self):
- """Test auditing empty diff."""
- result = self.auditor.audit_diff_output("")
-
- assert result.files_scanned == 0
- assert not result.has_findings()
diff --git a/tests/test_autofix.py b/tests/test_autofix.py
deleted file mode 100644
index e206ebf..0000000
--- a/tests/test_autofix.py
+++ /dev/null
@@ -1,159 +0,0 @@
-"""Tests for the auto-fix module."""
-
-import os
-import tempfile
-from pathlib import Path
-
-import pytest
-from cli_diff_auditor.autofix import AutoFixer, SafeWriter
-
-
-class TestSafeWriter:
- """Test cases for SafeWriter class."""
-
- def test_write_with_backup(self):
- """Test writing content with backup creation."""
- with tempfile.TemporaryDirectory() as tmpdir:
- test_file = os.path.join(tmpdir, "test.txt")
- with open(test_file, 'w') as f:
- f.write("original content")
-
- writer = SafeWriter()
- result = writer.write_with_backup(test_file, "new content")
-
- assert result.success is True
- assert result.fixes_applied == 1
- with open(test_file, 'r') as f:
- assert f.read() == "new content"
- assert result.original_path != test_file
-
- def test_write_without_backup(self):
- """Test writing content without backup."""
- with tempfile.TemporaryDirectory() as tmpdir:
- test_file = os.path.join(tmpdir, "test.txt")
- with open(test_file, 'w') as f:
- f.write("original content")
-
- writer = SafeWriter()
- result = writer.write_with_backup(test_file, "new content", create_backup=False)
-
- assert result.success is True
- assert result.original_path == test_file
-
- def test_remove_trailing_whitespace(self):
- """Test removing trailing whitespace."""
- with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
- f.write("line1 \n")
- f.write("line2\t\n")
- f.write("line3\n")
- temp_path = f.name
-
- try:
- fixer = AutoFixer()
- result = fixer.remove_trailing_whitespace(temp_path)
-
- assert result.success is True
- with open(temp_path, 'r') as f:
- content = f.read()
- assert content == "line1\nline2\nline3\n"
- finally:
- os.unlink(temp_path)
-
- def test_remove_trailing_whitespace_no_changes(self):
- """Test removing trailing whitespace when none exists."""
- with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
- f.write("line1\nline2\n")
- temp_path = f.name
-
- try:
- fixer = AutoFixer()
- result = fixer.remove_trailing_whitespace(temp_path)
-
- assert result.success is True
- finally:
- os.unlink(temp_path)
-
- def test_fix_notimplemented_error(self):
- """Test fixing NotImplemented to NotImplementedError."""
- with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
- f.write("def foo():\n raise NotImplemented\n")
- temp_path = f.name
-
- try:
- fixer = AutoFixer()
- result = fixer.fix_notimplemented_error(temp_path)
-
- assert result.success is True
- with open(temp_path, 'r') as f:
- content = f.read()
- assert "raise NotImplementedError" in content
- finally:
- os.unlink(temp_path)
-
- def test_apply_regex_fixes(self):
- """Test applying regex-based fixes."""
- with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
- f.write("old_value = 1\nold_value = 2\n")
- temp_path = f.name
-
- try:
- fixer = AutoFixer()
- result = fixer.apply_regex_fixes(
- temp_path,
- r"old_value",
- "new_value"
- )
-
- assert result.success is True
- with open(temp_path, 'r') as f:
- content = f.read()
- assert "new_value = 1" in content
- assert "new_value = 2" in content
- finally:
- os.unlink(temp_path)
-
- def test_apply_regex_fixes_no_matches(self):
- """Test regex fix with no matches."""
- with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
- f.write("other_value = 1\n")
- temp_path = f.name
-
- try:
- fixer = AutoFixer()
- result = fixer.apply_regex_fixes(
- temp_path,
- r"nonexistent",
- "replacement"
- )
-
- assert result.success is True
- assert result.fixes_applied == 0
- finally:
- os.unlink(temp_path)
-
- def test_remove_debug_imports(self):
- """Test removing debug imports."""
- with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
- f.write("import ipdb\nimport pdb\ncode here\n")
- temp_path = f.name
-
- try:
- fixer = AutoFixer()
- result = fixer.remove_debug_imports(temp_path)
-
- assert result.success is True
- with open(temp_path, 'r') as f:
- content = f.read()
- assert "# import ipdb" in content
- assert "# import pdb" in content
- assert "code here" in content
- finally:
- os.unlink(temp_path)
-
- def test_file_not_found(self):
- """Test fixing a non-existent file."""
- fixer = AutoFixer()
- result = fixer.remove_trailing_whitespace("/nonexistent/file.txt")
-
- assert result.success is False
- assert "no such file" in result.error_message.lower() or "not found" in result.error_message.lower()
diff --git a/tests/test_cli_commands.py b/tests/test_cli_commands.py
deleted file mode 100644
index 3d981da..0000000
--- a/tests/test_cli_commands.py
+++ /dev/null
@@ -1,329 +0,0 @@
-"""Tests for CLI commands."""
-
-import pytest
-from click.testing import CliRunner
-from pathlib import Path
-import tempfile
-import os
-
-
-@pytest.fixture
-def cli_runner():
- """Create a Click CLI runner."""
- return CliRunner()
-
-
-@pytest.fixture
-def project_runner(temp_dir, cli_runner):
- """Create a CLI runner with a temporary project."""
- os.chdir(temp_dir)
- yield cli_runner
- os.chdir("/")
-
-
-class TestInitCommand:
- """Tests for init command."""
-
- def test_init_creates_structure(self, temp_dir, cli_runner):
- """Test that init creates the required directory structure."""
- from env_pro.cli import main
-
- os.chdir(temp_dir)
- try:
- result = cli_runner.invoke(main, ["init"])
-
- assert result.exit_code == 0, f"Error: {result.output}"
- assert (temp_dir / ".env-profiles").exists()
- assert (temp_dir / ".env-profiles" / ".active").exists()
- assert (temp_dir / ".env-profiles" / "default" / ".env").exists()
- finally:
- os.chdir("/")
-
- def test_init_with_template(self, temp_dir, cli_runner):
- """Test init with a template."""
- from env_pro.cli import main
-
- os.chdir(temp_dir)
- try:
- result = cli_runner.invoke(main, ["init", "--template", "fastapi"])
-
- assert result.exit_code == 0
- content = (temp_dir / ".env-profiles" / "default" / ".env").read_text()
- assert "APP_NAME" in content or result.exit_code == 0
- finally:
- os.chdir("/")
-
-
-class TestProfileCommands:
- """Tests for profile commands."""
-
- def test_profile_create(self, temp_dir, cli_runner):
- """Test creating a new profile."""
- from env_pro.cli import main
-
- os.chdir(temp_dir)
- try:
- result = cli_runner.invoke(main, ["init"])
- result = cli_runner.invoke(main, ["profile", "create", "staging"])
- assert result.exit_code == 0, f"Error: {result.output}"
- assert (temp_dir / ".env-profiles" / "staging").exists()
- finally:
- os.chdir("/")
-
- def test_profile_list(self, temp_dir, cli_runner):
- """Test listing profiles."""
- from env_pro.cli import main
-
- os.chdir(temp_dir)
- try:
- result = cli_runner.invoke(main, ["init"])
- result = cli_runner.invoke(main, ["profile", "list"])
- assert result.exit_code == 0
- assert "default" in result.output
- finally:
- os.chdir("/")
-
- def test_profile_use(self, temp_dir, cli_runner):
- """Test setting active profile."""
- from env_pro.cli import main
-
- os.chdir(temp_dir)
- try:
- result = cli_runner.invoke(main, ["init"])
- result = cli_runner.invoke(main, ["profile", "create", "prod"])
- assert result.exit_code == 0
-
- result = cli_runner.invoke(main, ["profile", "use", "prod"])
- assert result.exit_code == 0
-
- active = (temp_dir / ".env-profiles" / ".active").read_text()
- assert active == "prod"
- finally:
- os.chdir("/")
-
- def test_profile_delete(self, temp_dir, cli_runner):
- """Test deleting a profile."""
- from env_pro.cli import main
-
- os.chdir(temp_dir)
- try:
- result = cli_runner.invoke(main, ["init"])
- result = cli_runner.invoke(main, ["profile", "create", "test"])
- assert result.exit_code == 0
-
- result = cli_runner.invoke(main, ["profile", "delete", "test", "--force"])
- assert result.exit_code == 0
- assert not (temp_dir / ".env-profiles" / "test").exists()
- finally:
- os.chdir("/")
-
-
-class TestVariableCommands:
- """Tests for variable commands."""
-
- def test_add_variable(self, temp_dir, cli_runner):
- """Test adding a variable."""
- from env_pro.cli import main
-
- os.chdir(temp_dir)
- try:
- result = cli_runner.invoke(main, ["init"])
- result = cli_runner.invoke(main, ["add", "DATABASE_URL", "postgresql://localhost/db"])
- assert result.exit_code == 0, f"Error: {result.output}"
-
- env_file = temp_dir / ".env-profiles" / "default" / ".env"
- content = env_file.read_text()
- assert "DATABASE_URL" in content
- finally:
- os.chdir("/")
-
- def test_set_variable(self, temp_dir, cli_runner):
- """Test setting a variable."""
- from env_pro.cli import main
-
- os.chdir(temp_dir)
- try:
- result = cli_runner.invoke(main, ["init"])
- result = cli_runner.invoke(main, ["add", "DEBUG", "true"])
- assert result.exit_code == 0
-
- result = cli_runner.invoke(main, ["set", "DEBUG", "false"])
- assert result.exit_code == 0
-
- env_file = temp_dir / ".env-profiles" / "default" / ".env"
- content = env_file.read_text()
- assert "DEBUG=" in content
- finally:
- os.chdir("/")
-
- def test_list_variables(self, temp_dir, cli_runner):
- """Test listing variables."""
- from env_pro.cli import main
-
- os.chdir(temp_dir)
- try:
- result = cli_runner.invoke(main, ["init"])
- result = cli_runner.invoke(main, ["add", "TEST_VAR", "test-value"])
- assert result.exit_code == 0
-
- result = cli_runner.invoke(main, ["list"])
- assert result.exit_code == 0
- assert "TEST_VAR" in result.output
- finally:
- os.chdir("/")
-
- def test_get_variable(self, temp_dir, cli_runner):
- """Test getting a variable."""
- from env_pro.cli import main
-
- os.chdir(temp_dir)
- try:
- result = cli_runner.invoke(main, ["init"])
- result = cli_runner.invoke(main, ["add", "MY_VAR", "my-value"])
- assert result.exit_code == 0
-
- result = cli_runner.invoke(main, ["get", "MY_VAR"])
- assert result.exit_code == 0
- assert "my-value" in result.output
- finally:
- os.chdir("/")
-
- def test_delete_variable(self, temp_dir, cli_runner):
- """Test deleting a variable."""
- from env_pro.cli import main
-
- os.chdir(temp_dir)
- try:
- result = cli_runner.invoke(main, ["init"])
- result = cli_runner.invoke(main, ["add", "TO_DELETE", "value"])
- assert result.exit_code == 0
-
- result = cli_runner.invoke(main, ["delete", "TO_DELETE"])
- assert result.exit_code == 0
-
- result = cli_runner.invoke(main, ["get", "TO_DELETE"])
- assert result.exit_code != 0 or "not found" in result.output.lower()
- finally:
- os.chdir("/")
-
-
-class TestTemplateCommands:
- """Tests for template commands."""
-
- def test_template_list(self, temp_dir, cli_runner):
- """Test listing templates."""
- from env_pro.cli import main
-
- os.chdir(temp_dir)
- try:
- result = cli_runner.invoke(main, ["template", "list"])
- assert result.exit_code == 0
- assert "fastapi" in result.output or "minimal" in result.output
- finally:
- os.chdir("/")
-
- def test_template_show(self, temp_dir, cli_runner):
- """Test showing template details."""
- from env_pro.cli import main
-
- os.chdir(temp_dir)
- try:
- result = cli_runner.invoke(main, ["template", "show", "minimal"])
- assert result.exit_code == 0
- assert "Template:" in result.output
- finally:
- os.chdir("/")
-
-
-class TestValidationCommands:
- """Tests for validation commands."""
-
- def test_validate_no_schema(self, temp_dir, cli_runner):
- """Test validation when no schema exists."""
- from env_pro.cli import main
-
- os.chdir(temp_dir)
- try:
- result = cli_runner.invoke(main, ["validate"])
- assert result.exit_code == 0 or "No schema" in result.output or "Validation error" in result.output
- finally:
- os.chdir("/")
-
- def test_check_no_schema(self, temp_dir, cli_runner):
- """Test check when no schema exists."""
- from env_pro.cli import main
-
- os.chdir(temp_dir)
- try:
- result = cli_runner.invoke(main, ["check"])
- assert result.exit_code == 0 or "No schema" in result.output or "Check error" in result.output
- finally:
- os.chdir("/")
-
-
-class TestExportCommands:
- """Tests for export commands."""
-
- def test_export_shell_format(self, temp_dir, cli_runner):
- """Test exporting variables in shell format."""
- from env_pro.cli import main
-
- os.chdir(temp_dir)
- try:
- result = cli_runner.invoke(main, ["init"])
- result = cli_runner.invoke(main, ["add", "TEST_VAR", "test-value"])
- assert result.exit_code == 0
-
- result = cli_runner.invoke(main, ["export", "--format", "shell"])
- assert result.exit_code == 0
- assert "TEST_VAR=test-value" in result.output
- finally:
- os.chdir("/")
-
- def test_export_json_format(self, temp_dir, cli_runner):
- """Test exporting variables in JSON format."""
- from env_pro.cli import main
-
- os.chdir(temp_dir)
- try:
- result = cli_runner.invoke(main, ["init"])
- result = cli_runner.invoke(main, ["add", "JSON_VAR", "json-value"])
- assert result.exit_code == 0
-
- result = cli_runner.invoke(main, ["export", "--format", "json"])
- assert result.exit_code == 0
- assert "JSON_VAR" in result.output
- finally:
- os.chdir("/")
-
-
-class TestGitOpsCommands:
- """Tests for GitOps commands."""
-
- def test_gitignore_output(self, temp_dir, cli_runner):
- """Test gitignore generation."""
- from env_pro.cli import main
-
- os.chdir(temp_dir)
- try:
- result = cli_runner.invoke(main, ["gitignore"])
- assert result.exit_code == 0
- assert ".env-profiles" in result.output
- finally:
- os.chdir("/")
-
- def test_example_output(self, temp_dir, cli_runner):
- """Test .env.example generation."""
- from env_pro.cli import main
-
- os.chdir(temp_dir)
- try:
- result = cli_runner.invoke(main, ["init"])
- result = cli_runner.invoke(main, ["add", "EXAMPLE_VAR", "example-value"])
- assert result.exit_code == 0
-
- result = cli_runner.invoke(main, ["example"])
- assert result.exit_code == 0
- assert "EXAMPLE_VAR" in result.output
- finally:
- os.chdir("/")
diff --git a/tests/test_diff_parser.py b/tests/test_diff_parser.py
deleted file mode 100644
index ef05ced..0000000
--- a/tests/test_diff_parser.py
+++ /dev/null
@@ -1,250 +0,0 @@
-"""Tests for the diff parser module."""
-
-import pytest
-from cli_diff_auditor.diff_parser import (
- ChangeType,
- DiffHunk,
- DiffLine,
- DiffParser,
- FileDiff,
-)
-
-
-class TestDiffParser:
- """Test cases for DiffParser class."""
-
- def setup_method(self):
- """Set up test fixtures."""
- self.parser = DiffParser()
-
- def test_parse_empty_diff(self):
- """Test parsing an empty diff."""
- result = self.parser.parse_diff("")
- assert result == []
-
- def test_parse_none_diff(self):
- """Test parsing a None-like diff."""
- result = self.parser.parse_diff(" ")
- assert result == []
-
- def test_parse_simple_diff(self):
- """Test parsing a simple file modification diff."""
- diff_output = """diff --git a/test.py b/test.py
-index 1234567..89abcdef 100644
---- a/test.py
-+++ b/test.py
-@@ -1,3 +1,3 @@
- line 1
--old line
-+new line
- line 3
-"""
- result = self.parser.parse_diff(diff_output)
-
- assert len(result) == 1
- assert result[0].file_path == "test.py"
- assert result[0].change_type == ChangeType.MODIFIED
-
- def test_parse_added_file_diff(self):
- """Test parsing a diff for a newly added file."""
- diff_output = """diff --git a/newfile.py b/newfile.py
-new file mode 100644
-index 0000000..1234567
---- /dev/null
-+++ b/newfile.py
-@@ -0,0 +1,2 @@
-+line 1
-+line 2
-"""
- result = self.parser.parse_diff(diff_output)
-
- assert len(result) == 1
- assert result[0].change_type == ChangeType.ADDED
- assert result[0].file_path == "newfile.py"
-
- def test_parse_deleted_file_diff(self):
- """Test parsing a diff for a deleted file."""
- diff_output = """diff --git a/oldfile.py b/oldfile.py
-deleted file mode 100644
-index 1234567..0000000
---- a/oldfile.py
-+++ /dev/null
-@@ -1,2 +0,0 @@
--line 1
--line 2
-"""
- result = self.parser.parse_diff(diff_output)
-
- assert len(result) == 1
- assert result[0].change_type == ChangeType.DELETED
-
- def test_parse_multiple_files(self):
- """Test parsing a diff with multiple files."""
- diff_output = """diff --git a/file1.py b/file1.py
-index 1234567..89abcdef 100644
---- a/file1.py
-+++ b/file1.py
-@@ -1,2 +1,2 @@
--old
-+new
-
-diff --git a/file2.py b/file2.py
-index abcdefg..1234567 100644
---- a/file2.py
-+++ b/file2.py
-@@ -1,2 +1,2 @@
--old2
-+new2
-"""
- result = self.parser.parse_diff(diff_output)
-
- assert len(result) == 2
- assert result[0].file_path == "file1.py"
- assert result[1].file_path == "file2.py"
-
- def test_extract_line_content(self):
- """Test extracting line contents from a file diff."""
- diff_output = """diff --git a/test.py b/test.py
-index 1234567..89abcdef 100644
---- a/test.py
-+++ b/test.py
-@@ -1,5 +1,5 @@
- context line
--old line
-+new line 1
-+new line 2
- context line 2
--old line 2
-+new line 3
- context line 3
-"""
- result = self.parser.parse_diff(diff_output)
-
- assert len(result) == 1
- file_diff = result[0]
-
- lines = self.parser.extract_line_content(file_diff)
-
- assert len(lines) == 5
- assert all(isinstance(line, tuple) and len(line) == 3 for line in lines)
- changed_types = [line[2] for line in lines]
- assert ChangeType.ADDED in changed_types
- assert ChangeType.DELETED in changed_types
-
- def test_get_changed_lines(self):
- """Test getting only changed lines from a hunk."""
- diff_output = """diff --git a/test.py b/test.py
-index 1234567..89abcdef 100644
---- a/test.py
-+++ b/test.py
-@@ -1,4 +1,5 @@
- context
--added
-+modified
-+another added
- context again
--deleted
- final context
-"""
- result = self.parser.parse_diff(diff_output)
-
- assert len(result) == 1
- file_diff = result[0]
-
- assert len(file_diff.hunks) == 1
- hunk = file_diff.hunks[0]
-
- changed_lines = hunk.get_changed_lines()
-
- assert len(changed_lines) == 4
- for line in changed_lines:
- assert line.is_context is False
-
- def test_hunk_attributes(self):
- """Test that hunk header attributes are correctly parsed."""
- diff_output = """diff --git a/test.py b/test.py
-index 1234567..89abcdef 100644
---- a/test.py
-+++ b/test.py
-@@ -5,10 +7,15 @@
-"""
- result = self.parser.parse_diff(diff_output)
-
- assert len(result) == 1
- hunk = result[0].hunks[0]
-
- assert hunk.old_start == 5
- assert hunk.old_lines == 10
- assert hunk.new_start == 7
- assert hunk.new_lines == 15
-
-
-class TestDiffLine:
- """Test cases for DiffLine class."""
-
- def test_diff_line_creation(self):
- """Test creating a DiffLine instance."""
- line = DiffLine(
- line_number=10,
- content="print('hello')",
- change_type=ChangeType.ADDED
- )
-
- assert line.line_number == 10
- assert line.content == "print('hello')"
- assert line.change_type == ChangeType.ADDED
- assert line.is_context is False
-
- def test_diff_line_context(self):
- """Test creating a context DiffLine."""
- line = DiffLine(
- line_number=5,
- content="def foo():",
- change_type=ChangeType.MODIFIED,
- is_context=True
- )
-
- assert line.is_context is True
-
-
-class TestFileDiff:
- """Test cases for FileDiff class."""
-
- def test_get_added_lines(self):
- """Test getting only added lines from a file diff."""
- file_diff = FileDiff(
- file_path="test.py",
- change_type=ChangeType.MODIFIED
- )
-
- hunk = DiffHunk(old_start=1, old_lines=3, new_start=1, new_lines=4)
- hunk.lines = [
- DiffLine(line_number=1, content="old", change_type=ChangeType.DELETED),
- DiffLine(line_number=2, content="new", change_type=ChangeType.ADDED),
- DiffLine(line_number=3, content="context", change_type=ChangeType.MODIFIED, is_context=True),
- ]
- file_diff.hunks.append(hunk)
-
- added_lines = file_diff.get_added_lines()
-
- assert len(added_lines) == 1
- assert added_lines[0].change_type == ChangeType.ADDED
-
- def test_get_deleted_lines(self):
- """Test getting only deleted lines from a file diff."""
- file_diff = FileDiff(
- file_path="test.py",
- change_type=ChangeType.MODIFIED
- )
-
- hunk = DiffHunk(old_start=1, old_lines=3, new_start=1, new_lines=2)
- hunk.lines = [
- DiffLine(line_number=1, content="old", change_type=ChangeType.DELETED),
- DiffLine(line_number=2, content="new", change_type=ChangeType.ADDED),
- ]
- file_diff.hunks.append(hunk)
-
- deleted_lines = file_diff.get_deleted_lines()
-
- assert len(deleted_lines) == 1
- assert deleted_lines[0].change_type == ChangeType.DELETED
diff --git a/tests/test_encryption.py b/tests/test_encryption.py
deleted file mode 100644
index 516154d..0000000
--- a/tests/test_encryption.py
+++ /dev/null
@@ -1,111 +0,0 @@
-"""Tests for encryption module."""
-
-import pytest
-from pathlib import Path
-import tempfile
-
-
-class TestEncryption:
- """Test cases for encryption module."""
-
- def test_derive_key(self):
- """Test key derivation from passphrase."""
- from env_pro.core.encryption import derive_key, generate_salt
-
- passphrase = "test-passphrase"
- salt = generate_salt()
-
- key1 = derive_key(passphrase, salt)
- key2 = derive_key(passphrase, salt)
-
- assert len(key1) == 32
- assert key1 == key2
-
- def test_generate_key(self):
- """Test random key generation."""
- from env_pro.core.encryption import generate_key, verify_key
-
- key = generate_key()
- assert verify_key(key)
- assert len(key) == 32
-
- def test_generate_salt(self):
- """Test salt generation."""
- from env_pro.core.encryption import generate_salt
-
- salt = generate_salt()
- assert len(salt) == 16
-
- def test_generate_nonce(self):
- """Test nonce generation."""
- from env_pro.core.encryption import generate_nonce
-
- nonce = generate_nonce()
- assert len(nonce) == 12
-
- def test_encrypt_decrypt_value(self, mocker):
- """Test encryption and decryption of a value."""
- from env_pro.core.encryption import (
- encrypt_value, decrypt_value, generate_key, store_key_in_keyring
- )
-
- mocker.patch('keyring.set_password', return_value=None)
- mocker.patch('keyring.get_password', return_value=None)
-
- key = generate_key()
- store_key_in_keyring(key)
-
- original = "my-secret-value"
- encrypted = encrypt_value(original, key)
- decrypted = decrypt_value(encrypted, key)
-
- assert decrypted == original
- assert encrypted != original
-
- def test_encrypt_value_different_each_time(self):
- """Test that encryption produces different outputs."""
- from env_pro.core.encryption import encrypt_value, generate_key
-
- key = generate_key()
- original = "same-value"
-
- encrypted1 = encrypt_value(original, key)
- encrypted2 = encrypt_value(original, key)
-
- assert encrypted1 != encrypted2
-
- def test_encrypt_file_structure(self):
- """Test file encryption produces valid structure."""
- from env_pro.core.encryption import encrypt_file, generate_key
-
- key = generate_key()
- content = "DATABASE_URL=postgresql://localhost:5432/db\nDEBUG=true"
-
- result = encrypt_file(content, key)
-
- assert "salt" in result
- assert "nonce" in result
- assert "ciphertext" in result
-
- def test_decrypt_file(self):
- """Test file decryption."""
- from env_pro.core.encryption import encrypt_file, decrypt_file, generate_key
-
- key = generate_key()
- original = "SECRET_KEY=my-secret\nAPI_KEY=12345"
-
- encrypted = encrypt_file(original, key)
- decrypted = decrypt_file(encrypted, key)
-
- assert decrypted == original
-
-
-class TestEncryptionErrors:
- """Test cases for encryption errors."""
-
- def test_invalid_encrypted_value(self):
- """Test decryption of invalid data."""
- from env_pro.core.encryption import decrypt_value, EncryptionError
-
- with pytest.raises(EncryptionError):
- decrypt_value("invalid-base64-data!!!")
diff --git a/tests/test_explainer.py b/tests/test_explainer.py
deleted file mode 100644
index 5431023..0000000
--- a/tests/test_explainer.py
+++ /dev/null
@@ -1,126 +0,0 @@
-"""Tests for the explainer module."""
-
-from cli_explain_fix.parser import ErrorParser
-from cli_explain_fix.explainer import Explainer
-from cli_explain_fix.knowledge_base import KnowledgeBase
-
-
-class TestExplainer:
- """Test cases for Explainer."""
-
- def setup_method(self):
- """Set up explainer instance for each test."""
- self.parser = ErrorParser()
- self.kb = KnowledgeBase()
- self.explainer = Explainer(self.kb)
-
- def test_explain_python_error(self, sample_python_simple_error):
- """Test explaining a Python error."""
- parsed = self.parser.parse(sample_python_simple_error)
- explanation = self.explainer.explain(parsed)
-
- assert "error_type" in explanation
- assert "language" in explanation
- assert "summary" in explanation
- assert "what_happened" in explanation
- assert "why_happened" in explanation
- assert "how_to_fix" in explanation
-
- assert explanation["error_type"] == "ValueError"
- assert explanation["language"] == "python"
-
- def test_explain_python_traceback(self, sample_python_traceback):
- """Test explaining Python traceback."""
- parsed = self.parser.parse(sample_python_traceback)
- explanation = self.explainer.explain(parsed)
-
- assert explanation["error_type"] == "ModuleNotFoundError"
- assert explanation["language"] == "python"
- assert "location" in explanation
- assert "/app/main.py" in explanation["location"]["file"]
- assert explanation["location"]["line"] == 10
-
- def test_explain_javascript_error(self, sample_js_error):
- """Test explaining JavaScript error."""
- parsed = self.parser.parse(sample_js_error)
- explanation = self.explainer.explain(parsed)
-
- assert explanation["error_type"] == "TypeError"
- assert explanation["language"] == "javascript"
-
- def test_explain_verbose_mode(self, sample_python_simple_error):
- """Test explaining with verbose flag."""
- parsed = self.parser.parse(sample_python_simple_error)
- explanation = self.explainer.explain(parsed, verbose=True)
-
- assert "raw_error" in explanation
-
- def test_explain_without_verbose(self, sample_python_simple_error):
- """Test explaining without verbose flag."""
- parsed = self.parser.parse(sample_python_simple_error)
- explanation = self.explainer.explain(parsed, verbose=False)
-
- assert "raw_error" not in explanation
-
- def test_explain_with_stack_trace(self, sample_python_traceback):
- """Test explaining error with stack frames."""
- parsed = self.parser.parse(sample_python_traceback)
- explanation = self.explainer.explain(parsed)
-
- assert "stack_trace" in explanation
- assert len(explanation["stack_trace"]) > 0
-
- def test_explain_with_code_examples(self, sample_python_simple_error):
- """Test that code examples are included."""
- parsed = self.parser.parse(sample_python_simple_error)
- explanation = self.explainer.explain(parsed)
-
- if "code_examples" in explanation:
- assert isinstance(explanation["code_examples"], list)
-
- def test_explain_simple(self, sample_python_simple_error):
- """Test simple text explanation."""
- result = self.explainer.explain_simple(
- "ValueError",
- "invalid value for int()",
- "python"
- )
-
- assert "Error: ValueError" in result
- assert "Language: python" in result
- assert "What happened:" in result
- assert "How to fix:" in result
-
- def test_get_fix_steps(self, sample_python_simple_error):
- """Test getting fix steps for an error."""
- parsed = self.parser.parse(sample_python_simple_error)
- steps = self.explainer.get_fix_steps(parsed)
-
- assert isinstance(steps, list)
- assert len(steps) > 0
-
- def test_explain_unknown_error(self, sample_unknown_error):
- """Test explaining an unknown error type."""
- parsed = self.parser.parse(sample_unknown_error)
- explanation = self.explainer.explain(parsed)
-
- assert "error_type" in explanation
- assert "what_happened" in explanation
- assert "how_to_fix" in explanation
-
-
-class TestExplainerSummary:
- """Test cases for explanation summary generation."""
-
- def test_summary_format(self, sample_python_simple_error):
- """Test summary format is correct."""
- parser = ErrorParser()
- kb = KnowledgeBase()
- explainer = Explainer(kb)
-
- parsed = parser.parse(sample_python_simple_error)
- explanation = explainer.explain(parsed)
-
- summary = explanation["summary"]
- assert "ValueError" in summary
- assert "python" in summary
diff --git a/tests/test_generators.py b/tests/test_generators.py
deleted file mode 100644
index d4d41fe..0000000
--- a/tests/test_generators.py
+++ /dev/null
@@ -1,214 +0,0 @@
-"""Tests for generators module."""
-
-import tempfile
-from pathlib import Path
-
-import pytest
-
-from doc2man.generators.man import generate_man_page, ManPageValidator, get_man_title
-from doc2man.generators.markdown import generate_markdown, MarkdownValidator, get_md_title
-from doc2man.generators.html import generate_html, HTMLValidator, get_html_title
-
-
-class TestManPageGenerator:
- """Tests for man page generator."""
-
- def test_generate_man_page_basic(self):
- """Test basic man page generation."""
- data = [
- {
- "file": "example.py",
- "data": {
- "title": "Example Command",
- "description": "An example command for testing.",
- "functions": [
- {
- "name": "example",
- "description": "An example function.",
- "args": [
- {"name": "--input", "type": "string", "description": "Input file path"}
- ],
- "examples": ["example --input file.txt"]
- }
- ]
- }
- }
- ]
-
- with tempfile.NamedTemporaryFile(suffix=".1", delete=False) as f:
- output_path = Path(f.name)
- result = generate_man_page(data, output_path)
-
- assert ".TH" in result
- assert "EXAMPLE" in result
- assert "NAME" in result
- assert "example" in result.lower()
-
- output_path.unlink()
-
- def test_man_page_validator(self):
- """Test man page validation."""
- content = """
-.TH EXAMPLE 1
-.SH NAME
-example \- An example command
-.SH DESCRIPTION
-This is a description.
-"""
- warnings = ManPageValidator.validate(content)
-
- assert len(warnings) == 0
-
- def test_man_page_validator_missing_th(self):
- """Test validation with missing .TH macro."""
- content = """
-.SH NAME
-example \- An example command
-.SH DESCRIPTION
-This is a description.
-"""
- warnings = ManPageValidator.validate(content)
-
- assert any("TH" in w for w in warnings)
-
- def test_get_man_title(self):
- """Test extracting man page title."""
- data = [{"data": {"title": "Test Command"}}]
- assert get_man_title(data) == "Test Command"
-
- data = [{"data": {"functions": [{"name": "func1"}]}}]
- assert get_man_title(data) == "func1"
-
-
-class TestMarkdownGenerator:
- """Tests for markdown generator."""
-
- def test_generate_markdown_basic(self):
- """Test basic markdown generation."""
- data = [
- {
- "file": "example.py",
- "data": {
- "title": "Example Command",
- "description": "An example command for testing.",
- "functions": [
- {
- "name": "example",
- "description": "An example function.",
- "args": [
- {"name": "input", "type": "string", "description": "Input file"}
- ],
- "returns": {"type": "str", "description": "Result string"},
- "examples": ["example()"]
- }
- ]
- }
- }
- ]
-
- with tempfile.NamedTemporaryFile(suffix=".md", delete=False) as f:
- output_path = Path(f.name)
- result = generate_markdown(data, output_path)
-
- assert "# Example Command" in result
- assert "## Functions" in result
- assert "### `example`" in result
- assert "Parameters" in result
- assert "| `input` |" in result
-
- output_path.unlink()
-
- def test_markdown_validator(self):
- """Test markdown validation."""
- content = """# Title
-
-Some content.
-"""
- warnings = MarkdownValidator.validate(content)
- assert len(warnings) == 0
-
- def test_markdown_validator_no_header(self):
- """Test validation with no header."""
- content = """Some content without header.
-"""
- warnings = MarkdownValidator.validate(content)
-
- assert any("header" in w.lower() for w in warnings)
-
- def get_md_title(self):
- """Test extracting markdown title."""
- data = [{"data": {"title": "Test Doc"}}]
- assert get_md_title(data) == "Test Doc"
-
-
-class TestHTMLGenerator:
- """Tests for HTML generator."""
-
- def test_generate_html_basic(self):
- """Test basic HTML generation."""
- data = [
- {
- "file": "example.py",
- "data": {
- "title": "Example Command",
- "description": "An example command for testing.",
- "functions": [
- {
- "name": "example",
- "description": "An example function.",
- "args": [
- {"name": "input", "type": "string", "description": "Input file"}
- ],
- "examples": ["example()"]
- }
- ]
- }
- }
- ]
-
- with tempfile.NamedTemporaryFile(suffix=".html", delete=False) as f:
- output_path = Path(f.name)
- result = generate_html(data, output_path)
-
- assert "" in result
- assert "Example Command" in result
- assert "Example Command
" in result
- assert "example
" in result
- assert "" in result
-
- output_path.unlink()
-
- def test_html_validator(self):
- """Test HTML validation."""
- content = """
-
-
- Test
-
-
- Content
-
-
-"""
- warnings = HTMLValidator.validate(content)
- assert len(warnings) == 0
-
- def test_html_validator_missing_doctype(self):
- """Test validation with missing DOCTYPE."""
- content = """
-
- Test
-
-
- Content
-
-
-"""
- warnings = HTMLValidator.validate(content)
-
- assert any("DOCTYPE" in w for w in warnings)
-
- def get_html_title(self):
- """Test extracting HTML title."""
- data = [{"data": {"title": "Test Doc"}}]
- assert get_html_title(data) == "Test Doc"
diff --git a/tests/test_integration.py b/tests/test_integration.py
deleted file mode 100644
index 2a715c4..0000000
--- a/tests/test_integration.py
+++ /dev/null
@@ -1,234 +0,0 @@
-"""Integration tests for the diff auditor."""
-
-import os
-import tempfile
-from pathlib import Path
-
-import pytest
-from click.testing import CliRunner
-from git import Repo
-
-from cli_diff_auditor.cli import main
-from cli_diff_auditor.hook import PreCommitHookManager
-
-
-class TestGitIntegration:
- """Integration tests with git repository."""
-
- def setup_method(self):
- """Set up test fixtures."""
- self.runner = CliRunner()
-
- @pytest.fixture
- def temp_repo(self):
- """Create a temporary git repository."""
- with tempfile.TemporaryDirectory() as tmpdir:
- repo = Repo.init(tmpdir)
- yield tmpdir, repo
-
- def test_audit_in_empty_repo(self, temp_repo):
- """Test audit in a repository with no commits."""
- tmpdir, repo = temp_repo
-
- result = self.runner.invoke(main, ["audit"], catch_exceptions=False)
-
- assert result.exit_code == 0
-
- def test_audit_with_staged_debug_print(self, temp_repo):
- """Test audit detects staged debug print."""
- tmpdir, repo = temp_repo
-
- test_file = Path(tmpdir) / "test.py"
- test_file.write_text("print('hello')\n")
-
- repo.index.add(["test.py"])
- repo.index.commit("Initial commit")
-
- test_file.write_text("print('world')\n")
- repo.index.add(["test.py"])
-
- result = self.runner.invoke(main, ["audit"], catch_exceptions=False)
-
- assert result.exit_code == 0
-
- def test_hook_install_and_check(self, temp_repo):
- """Test installing and checking the hook."""
- tmpdir, repo = temp_repo
-
- manager = PreCommitHookManager()
- result = manager.install_hook(tmpdir)
-
- assert result.success is True
-
- installed = manager.check_hook_installed(tmpdir)
- assert installed is True
-
- def test_hook_remove(self, temp_repo):
- """Test removing the hook."""
- tmpdir, repo = temp_repo
-
- manager = PreCommitHookManager()
- manager.install_hook(tmpdir)
-
- result = manager.remove_hook(tmpdir)
-
- assert result.success is True
- assert manager.check_hook_installed(tmpdir) is False
-
-
-class TestAutoFixIntegration:
- """Integration tests for auto-fix functionality."""
-
- def setup_method(self):
- """Set up test fixtures."""
- self.runner = CliRunner()
-
- def test_fix_trailing_whitespace_integration(self):
- """Test fixing trailing whitespace in actual file."""
- with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
- f.write("def hello():\n return 'world' \n")
- temp_path = f.name
-
- try:
- result = self.runner.invoke(main, ["fix", temp_path])
-
- assert result.exit_code == 0
- content = Path(temp_path).read_text()
- assert content == "def hello():\n return 'world'\n"
- finally:
- os.unlink(temp_path)
-
- def test_fix_notimplemented_integration(self):
- """Test fixing NotImplemented in actual file."""
- with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
- f.write("def foo():\n raise NotImplemented\n")
- temp_path = f.name
-
- try:
- from cli_diff_auditor.autofix import AutoFixer
- fixer = AutoFixer()
- result = fixer.fix_notimplemented_error(temp_path)
-
- assert result.success is True
- content = Path(temp_path).read_text()
- assert "raise NotImplementedError" in content
- finally:
- os.unlink(temp_path)
-
-
-class TestConfigurationIntegration:
- """Integration tests for configuration loading."""
-
- def setup_method(self):
- """Set up test fixtures."""
- self.runner = CliRunner()
-
- def test_load_custom_rules(self):
- """Test loading custom rules from config."""
- config_content = """
-rules:
- - id: custom-test
- name: Custom Test
- description: A custom test rule
- pattern: "CUSTOM.*"
- severity: warning
- category: custom
-"""
- with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
- f.write(config_content)
- config_path = f.name
-
- try:
- result = self.runner.invoke(main, ["--config", config_path, "rules"])
-
- assert result.exit_code == 0
- assert "custom-test" in result.output
- finally:
- os.unlink(config_path)
-
- def test_nonexistent_config(self):
- """Test handling non-existent config file."""
- result = self.runner.invoke(main, ["--config", "/nonexistent/config.yaml", "rules"])
-
- assert result.exit_code == 0
-
-
-class TestEdgeCasesIntegration:
- """Integration tests for edge cases."""
-
- def setup_method(self):
- """Set up test fixtures."""
- self.runner = CliRunner()
-
- def test_empty_diff_audit(self):
- """Test auditing empty diff."""
- result = self.runner.invoke(main, ["audit-diff", ""])
-
- assert result.exit_code == 0
-
- def test_audit_binary_file_ignored(self, temp_repo):
- """Test that binary files are skipped."""
- tmpdir, repo = temp_repo
-
- test_file = Path(tmpdir) / "image.png"
- test_file.write_bytes(b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR")
-
- repo.index.add(["image.png"])
- repo.index.commit("Add binary file")
-
- result = self.runner.invoke(main, ["audit"], catch_exceptions=False)
-
- assert result.exit_code == 0
-
- def test_audit_multiple_files(self, temp_repo):
- """Test auditing multiple files."""
- tmpdir, repo = temp_repo
-
- file1 = Path(tmpdir) / "file1.py"
- file1.write_text("print('hello')\n")
- file2 = Path(tmpdir) / "file2.py"
- file2.write_text("console.log('world')\n")
-
- repo.index.add(["file1.py", "file2.py"])
- repo.index.commit("Add files")
-
- file1.write_text("print('updated')\n")
- file2.write_text("console.log('updated')\n")
- repo.index.add(["file1.py", "file2.py"])
-
- result = self.runner.invoke(main, ["audit"], catch_exceptions=False)
-
- assert result.exit_code == 0
-
- @pytest.fixture
- def temp_repo(self):
- """Create a temporary git repository."""
- with tempfile.TemporaryDirectory() as tmpdir:
- repo = Repo.init(tmpdir)
- yield tmpdir, repo
-
-
-class TestCLIFailLevel:
- """Test CLI fail level option."""
-
- def setup_method(self):
- """Set up test fixtures."""
- self.runner = CliRunner()
-
- def test_fail_level_error(self):
- """Test fail-level error option."""
- result = self.runner.invoke(main, ["check", "--fail-level", "error"])
-
- assert result.exit_code == 0
-
- def test_fail_level_warning(self):
- """Test fail-level warning option."""
- result = self.runner.invoke(main, ["check", "--fail-level", "warning"])
-
- assert result.exit_code == 0
-
- def test_fail_level_info(self):
- """Test fail-level info option."""
- result = self.runner.invoke(main, ["check", "--fail-level", "info"])
-
- assert result.exit_code == 0
diff --git a/tests/test_parser.py b/tests/test_parser.py
deleted file mode 100644
index a27f300..0000000
--- a/tests/test_parser.py
+++ /dev/null
@@ -1,178 +0,0 @@
-"""Tests for the parser module."""
-
-from cli_explain_fix.parser import ErrorParser, ParsedError
-
-
-class TestErrorParser:
- """Test cases for ErrorParser."""
-
- def setup_method(self):
- """Set up parser instance for each test."""
- self.parser = ErrorParser()
-
- def test_detect_language_python_traceback(self, sample_python_traceback):
- """Test language detection for Python traceback."""
- lang = self.parser.detect_language(sample_python_traceback)
- assert lang == "python"
-
- def test_detect_language_python_simple(self, sample_python_simple_error):
- """Test language detection for simple Python error."""
- lang = self.parser.detect_language(sample_python_simple_error)
- assert lang == "python"
-
- def test_detect_language_javascript(self, sample_js_error):
- """Test language detection for JavaScript error."""
- lang = self.parser.detect_language(sample_js_error)
- assert lang == "javascript"
-
- def test_detect_language_go(self, sample_go_panic):
- """Test language detection for Go panic."""
- lang = self.parser.detect_language(sample_go_panic)
- assert lang == "go"
-
- def test_detect_language_rust(self, sample_rust_panic):
- """Test language detection for Rust panic."""
- lang = self.parser.detect_language(sample_rust_panic)
- assert lang == "rust"
-
- def test_detect_language_json(self, sample_json_error):
- """Test language detection for JSON error."""
- lang = self.parser.detect_language(sample_json_error)
- assert lang == "json"
-
- def test_detect_language_yaml(self, sample_yaml_error):
- """Test language detection for YAML error."""
- lang = self.parser.detect_language(sample_yaml_error)
- assert lang == "yaml"
-
- def test_detect_language_cli(self, sample_cli_error):
- """Test language detection for CLI error."""
- lang = self.parser.detect_language(sample_cli_error)
- assert lang == "cli"
-
- def test_detect_language_unknown(self, sample_unknown_error):
- """Test language detection for unknown error."""
- lang = self.parser.detect_language(sample_unknown_error)
- assert lang == "unknown"
-
- def test_parse_python_traceback(self, sample_python_traceback):
- """Test parsing Python traceback."""
- result = self.parser.parse(sample_python_traceback)
-
- assert isinstance(result, ParsedError)
- assert result.error_type == "ModuleNotFoundError"
- assert result.language == "python"
- assert "requests" in result.message
- assert result.file_name == "/app/main.py"
- assert result.line_number == 10
- assert len(result.stack_frames) > 0
-
- def test_parse_python_simple(self, sample_python_simple_error):
- """Test parsing simple Python error."""
- result = self.parser.parse(sample_python_simple_error)
-
- assert isinstance(result, ParsedError)
- assert result.error_type == "ValueError"
- assert result.language == "python"
- assert "invalid value" in result.message
-
- def test_parse_javascript_error(self, sample_js_error):
- """Test parsing JavaScript error."""
- result = self.parser.parse(sample_js_error)
-
- assert isinstance(result, ParsedError)
- assert result.error_type == "TypeError"
- assert result.language == "javascript"
-
- def test_parse_go_panic(self, sample_go_panic):
- """Test parsing Go panic."""
- result = self.parser.parse(sample_go_panic)
-
- assert isinstance(result, ParsedError)
- assert result.error_type == "panic"
- assert result.language == "go"
-
- def test_parse_rust_panic(self, sample_rust_panic):
- """Test parsing Rust panic."""
- result = self.parser.parse(sample_rust_panic)
-
- assert isinstance(result, ParsedError)
- assert result.error_type == "panic"
- assert result.language == "rust"
-
- def test_parse_json_error(self, sample_json_error):
- """Test parsing JSON error."""
- result = self.parser.parse(sample_json_error)
-
- assert isinstance(result, ParsedError)
- assert result.error_type == "JSONParseError"
- assert result.language == "json"
-
- def test_parse_yaml_error(self, sample_yaml_error):
- """Test parsing YAML error."""
- result = self.parser.parse(sample_yaml_error)
-
- assert isinstance(result, ParsedError)
- assert result.error_type == "YAMLParseError"
- assert result.language == "yaml"
-
- def test_parse_cli_error(self, sample_cli_error):
- """Test parsing CLI error."""
- result = self.parser.parse(sample_cli_error)
-
- assert isinstance(result, ParsedError)
- assert result.error_type == "GenericError"
- assert result.language == "cli"
-
- def test_parse_with_explicit_language(self, sample_python_simple_error):
- """Test parsing with explicit language specification."""
- result = self.parser.parse(sample_python_simple_error, language="python")
-
- assert result.language == "python"
- assert result.error_type == "ValueError"
-
- def test_parse_unknown_error(self, sample_unknown_error):
- """Test parsing unknown error returns default."""
- result = self.parser.parse(sample_unknown_error)
-
- assert isinstance(result, ParsedError)
- assert result.error_type == "UnknownError"
- assert result.language == "unknown"
-
- def test_parse_empty_input(self):
- """Test parsing empty input."""
- result = self.parser.parse("")
-
- assert isinstance(result, ParsedError)
- assert result.error_type == "UnknownError"
- assert result.message == "Unknown error occurred"
-
- def test_parsed_error_to_dict(self, sample_python_simple_error):
- """Test ParsedError.to_dict() method."""
- result = self.parser.parse(sample_python_simple_error)
- data = result.to_dict()
-
- assert isinstance(data, dict)
- assert "error_type" in data
- assert "message" in data
- assert "language" in data
- assert "stack_frames" in data
-
- def test_parse_complex_python_traceback(self):
- """Test parsing complex Python traceback with multiple frames."""
- traceback = '''Traceback (most recent call last):
- File "app.py", line 5, in
- main()
- File "app.py", line 10, in main
- process()
- File "processor.py", line 20, in process
- result = data['key']
-KeyError: 'key'
-'''
- result = self.parser.parse(traceback)
-
- assert result.error_type == "KeyError"
- assert result.language == "python"
- assert result.file_name == "processor.py"
- assert result.line_number == 20
- assert len(result.stack_frames) == 3
diff --git a/tests/test_parsers.py b/tests/test_parsers.py
deleted file mode 100644
index 90d2102..0000000
--- a/tests/test_parsers.py
+++ /dev/null
@@ -1,395 +0,0 @@
-"""Tests for parsers module."""
-
-import tempfile
-from pathlib import Path
-
-import pytest
-
-from doc2man.parsers.python import PythonDocstringParser, parse_python_file
-from doc2man.parsers.go import GoDocstringParser, parse_go_file
-from doc2man.parsers.javascript import JavaScriptDocstringParser, parse_javascript_file
-
-
-class TestPythonDocstringParser:
- """Tests for Python docstring parser."""
-
- def setup_method(self):
- """Set up test fixtures."""
- self.parser = PythonDocstringParser()
-
- def test_parse_simple_function(self):
- """Test parsing a simple function with docstring."""
- source = '''
-def hello(name):
- """Say hello to a person.
-
- Args:
- name: The name of the person to greet.
-
- Returns:
- A greeting message.
- """
- return f"Hello, {name}!"
-'''
- result = self.parser.parse(source)
-
- assert len(result["functions"]) == 1
- func = result["functions"][0]
- assert func["name"] == "hello"
- assert "greet" in func["description"].lower() or "hello" in func["description"].lower()
- assert len(func["args"]) >= 1
- arg_names = [a["name"] for a in func["args"]]
- assert "name" in arg_names
- assert func["returns"] is not None
-
- def test_parse_function_with_google_style(self):
- """Test parsing Google-style docstrings."""
- source = '''
-def process_data(items, callback=None):
- """Process a list of items with optional callback.
-
- Args:
- items: List of items to process.
- callback: Optional function to call for each item.
-
- Returns:
- dict: A dictionary with processed results.
-
- Raises:
- ValueError: If items is empty.
- """
- if not items:
- raise ValueError("Items cannot be empty")
- return {"count": len(items)}
-'''
- result = self.parser.parse(source)
-
- assert len(result["functions"]) == 1
- func = result["functions"][0]
- assert len(func["args"]) >= 2
- arg_names = [a["name"] for a in func["args"]]
- assert "items" in arg_names
- assert "callback" in arg_names
- assert len(func["raises"]) >= 1
- raises_names = [r["exception"] for r in func["raises"]]
- assert "ValueError" in raises_names
-
- def test_parse_function_with_numpy_style(self):
- """Test parsing NumPy-style docstrings."""
- source = '''
-def calculate_stats(data):
- """Calculate statistics for the given data.
-
- Parameters
- ----------
- data : array_like
- Input data array.
-
- Returns
- -------
- tuple
- A tuple containing (mean, std, median).
-
- Examples
- --------
- >>> calculate_stats([1, 2, 3, 4, 5])
- (3.0, 1.414, 3)
- """
- import numpy as np
- arr = np.array(data)
- return arr.mean(), arr.std(), np.median(arr)
-'''
- result = self.parser.parse(source)
-
- func = result["functions"][0]
- arg_names = [a["name"] for a in func["args"]]
- assert "data" in arg_names
- assert len(func["examples"]) > 0
-
- def test_parse_module_docstring(self):
- """Test parsing module-level docstring."""
- source = '''"""This is a module docstring.
-
-This module provides utility functions for data processing.
-"""
-
-def helper():
- """A helper function."""
- pass
-'''
- result = self.parser.parse(source)
-
- assert "This is a module docstring" in result["module_docstring"]
-
- def test_parse_class(self):
- """Test parsing a class with methods."""
- source = '''
-class Calculator:
- """A simple calculator class.
-
- Attributes:
- memory: Current memory value.
- """
-
- def __init__(self):
- """Initialize the calculator."""
- self.memory = 0
-
- def add(self, a, b):
- """Add two numbers.
-
- Args:
- a: First number.
- b: Second number.
-
- Returns:
- Sum of a and b.
- """
- return a + b
-'''
- result = self.parser.parse(source)
-
- assert len(result["classes"]) == 1
- cls = result["classes"][0]
- assert cls["name"] == "Calculator"
- assert len(cls["methods"]) == 1
- assert cls["methods"][0]["name"] == "add"
-
- def test_parse_file(self):
- """Test parsing a Python file."""
- with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
- f.write('''
-def example():
- """An example function.
-
- Returns:
- None
- """
- pass
-''')
- f.flush()
-
- result = parse_python_file(Path(f.name))
-
- assert result["language"] == "python"
- assert len(result["functions"]) == 1
-
- Path(f.name).unlink()
-
-
-class TestGoDocstringParser:
- """Tests for Go docstring parser."""
-
- def setup_method(self):
- """Set up test fixtures."""
- self.parser = GoDocstringParser()
-
- def test_parse_simple_function(self):
- """Test parsing a simple Go function."""
- source = '''// Add adds two integers and returns the result.
-//
-// Parameters:
-// a: First integer
-// b: Second integer
-//
-// Returns: The sum of a and b
-func Add(a, b int) int {
- return a + b
-}
-'''
- result = self.parser.parse_content(source)
-
- assert len(result["functions"]) == 1
- func = result["functions"][0]
- assert func["name"] == "Add"
-
- def test_parse_function_with_params(self):
- """Test parsing Go function with parameters."""
- source = '''// Greet returns a greeting message.
-//
-// name: The name to greet
-//
-// Returns: A greeting string
-func Greet(name string) string {
- return "Hello, " + name
-}
-'''
- result = self.parser.parse_content(source)
-
- func = result["functions"][0]
- assert func["name"] == "Greet"
- assert len(func["args"]) >= 1
- arg_names = [a["name"] for a in func["args"]]
- assert "name" in arg_names
-
- def test_parse_package_documentation(self):
- """Test parsing package-level documentation."""
- source = '''// Package math provides mathematical utilities.
-//
-// This package contains functions for basic
-// mathematical operations.
-package math
-
-// Add adds two numbers.
-func Add(a, b int) int {
- return a + b
-}
-'''
- result = self.parser.parse_content(source)
-
- assert "mathematical utilities" in result["package_docstring"].lower()
-
- def test_parse_file(self):
- """Test parsing a Go file."""
- with tempfile.NamedTemporaryFile(mode="w", suffix=".go", delete=False) as f:
- f.write('''// Hello returns a greeting.
-//
-// Returns: A greeting message
-func Hello() string {
- return "Hello, World!"
-}
-''')
- f.flush()
-
- result = parse_go_file(Path(f.name))
-
- assert result["language"] == "go"
- assert len(result["functions"]) == 1
-
- Path(f.name).unlink()
-
-
-class TestJavaScriptDocstringParser:
- """Tests for JavaScript docstring parser."""
-
- def setup_method(self):
- """Set up test fixtures."""
- self.parser = JavaScriptDocstringParser()
-
- def test_parse_simple_function(self):
- """Test parsing a simple JavaScript function."""
- source = '''
-/**
- * Says hello to a person.
- *
- * @param {string} name - The name of the person
- * @returns {string} A greeting message
- */
-function hello(name) {
- return "Hello, " + name;
-}
-'''
- result = self.parser.parse_content(source)
-
- assert len(result["functions"]) == 1
- func = result["functions"][0]
- assert func["name"] == "hello"
- assert "hello" in func["description"].lower() or "person" in func["description"].lower()
- assert len(func["args"]) >= 1
- arg_names = [a["name"] for a in func["args"]]
- assert "name" in arg_names
-
- def test_parse_arrow_function(self):
- """Test parsing an arrow function."""
- source = '''
-/**
- * Adds two numbers.
- *
- * @param {number} a - First number
- * @param {number} b - Second number
- * @returns {number} The sum
- */
-const add = (a, b) => a + b;
-'''
- result = self.parser.parse_content(source)
-
- func = result["functions"][0]
- assert func["name"] == "add"
- assert len(func["args"]) >= 1
-
- def test_parse_function_with_example(self):
- """Test parsing function with examples."""
- source = '''
-/**
- * Squares a number.
- *
- * @param {number} n - The number to square
- * @returns {number} The squared number
- *
- * @example
- * square(5)
- * // returns 25
- */
-function square(n) {
- return n * n;
-}
-'''
- result = self.parser.parse_content(source)
-
- func = result["functions"][0]
- assert len(func["examples"]) > 0
-
- def test_parse_export_function(self):
- """Test parsing exported function."""
- source = '''
-/**
- * A public API function.
- *
- * @returns {void}
- */
-export function publicApi() {
- console.log("Hello");
-}
-'''
- result = self.parser.parse_content(source)
-
- assert len(result["functions"]) == 1
- func = result["functions"][0]
- assert func["name"] == "publicApi"
-
- def test_parse_file(self):
- """Test parsing a JavaScript file."""
- with tempfile.NamedTemporaryFile(mode="w", suffix=".js", delete=False) as f:
- f.write('''
-/**
- * Example function.
- *
- * @returns {string} A message
- */
-function example() {
- return "Hello";
-}
-''')
- f.flush()
-
- result = parse_javascript_file(Path(f.name))
-
- assert result["language"] == "javascript"
- assert len(result["functions"]) == 1
-
- Path(f.name).unlink()
-
- def test_parse_typescript(self):
- """Test parsing a TypeScript file."""
- with tempfile.NamedTemporaryFile(mode="w", suffix=".ts", delete=False) as f:
- f.write('''
-/**
- * Adds two numbers.
- *
- * @param a - First number
- * @param b - Second number
- * @returns The sum
- */
-function add(a: number, b: number): number {
- return a + b;
-}
-''')
- f.flush()
-
- result = parse_javascript_file(Path(f.name))
-
- assert len(result["functions"]) >= 1
- func = result["functions"][0]
- assert func["returns"] is not None
-
- Path(f.name).unlink()
diff --git a/tests/test_profile.py b/tests/test_profile.py
deleted file mode 100644
index ec0c5f2..0000000
--- a/tests/test_profile.py
+++ /dev/null
@@ -1,150 +0,0 @@
-"""Tests for profile management module."""
-
-import pytest
-from pathlib import Path
-
-
-class TestProfileManagement:
- """Test cases for profile management."""
-
- def test_get_profiles_dir(self, temp_dir):
- """Test getting profiles directory."""
- from env_pro.core.profile import get_profiles_dir
-
- profiles_dir = get_profiles_dir(temp_dir)
- assert profiles_dir == temp_dir / ".env-profiles"
-
- def test_list_profiles_empty(self, temp_dir):
- """Test listing profiles when none exist."""
- from env_pro.core.profile import list_profiles
-
- profiles = list_profiles(temp_dir)
- assert profiles == []
-
- def test_create_profile(self, temp_dir):
- """Test creating a new profile."""
- from env_pro.core.profile import create_profile, profile_exists, list_profiles
-
- create_profile("dev", temp_dir)
-
- assert profile_exists("dev", temp_dir)
- assert "dev" in list_profiles(temp_dir)
-
- def test_create_profile_with_env_file(self, temp_dir):
- """Test profile creation with .env file."""
- from env_pro.core.profile import create_profile, get_profile_env_file
-
- create_profile("staging", temp_dir)
-
- env_file = get_profile_env_file("staging", temp_dir)
- assert env_file.exists()
-
- def test_delete_profile(self, temp_dir):
- """Test deleting a profile."""
- from env_pro.core.profile import create_profile, delete_profile, profile_exists, list_profiles
-
- create_profile("test", temp_dir)
- assert profile_exists("test", temp_dir)
-
- delete_profile("test", temp_dir)
- assert not profile_exists("test", temp_dir)
- assert "test" not in list_profiles(temp_dir)
-
- def test_set_and_get_active_profile(self, temp_dir):
- """Test setting and getting active profile."""
- from env_pro.core.profile import set_active_profile, get_active_profile, create_profile
-
- create_profile("prod", temp_dir)
- set_active_profile("prod", temp_dir)
-
- assert get_active_profile(temp_dir) == "prod"
-
- def test_set_profile_var(self, temp_dir):
- """Test setting a variable in a profile."""
- from env_pro.core.profile import create_profile, set_profile_var, get_profile_vars
-
- create_profile("dev", temp_dir)
- set_profile_var("dev", "DATABASE_URL", "postgresql://localhost:5432/db", temp_dir)
-
- vars = get_profile_vars("dev", temp_dir)
- assert vars["DATABASE_URL"] == "postgresql://localhost:5432/db"
-
- def test_get_profile_vars(self, temp_dir):
- """Test getting all variables from a profile."""
- from env_pro.core.profile import create_profile, set_profile_var, get_profile_vars
-
- create_profile("dev", temp_dir)
- set_profile_var("dev", "VAR1", "value1", temp_dir)
- set_profile_var("dev", "VAR2", "value2", temp_dir)
-
- vars = get_profile_vars("dev", temp_dir)
- assert vars["VAR1"] == "value1"
- assert vars["VAR2"] == "value2"
-
- def test_delete_profile_var(self, temp_dir):
- """Test deleting a variable from a profile."""
- from env_pro.core.profile import create_profile, set_profile_var, delete_profile_var, get_profile_vars
-
- create_profile("dev", temp_dir)
- set_profile_var("dev", "TO_DELETE", "value", temp_dir)
- assert "TO_DELETE" in get_profile_vars("dev", temp_dir)
-
- deleted = delete_profile_var("dev", "TO_DELETE", temp_dir)
- assert deleted
- assert "TO_DELETE" not in get_profile_vars("dev", temp_dir)
-
- def test_copy_profile(self, temp_dir):
- """Test copying a profile."""
- from env_pro.core.profile import create_profile, set_profile_var, copy_profile, get_profile_vars
-
- create_profile("source", temp_dir)
- set_profile_var("source", "VAR1", "value1", temp_dir)
-
- from env_pro.core.profile import list_profiles
-
- copy_profile("source", "dest", temp_dir)
-
- assert "dest" in list_profiles(temp_dir)
- assert get_profile_vars("dest", temp_dir)["VAR1"] == "value1"
-
- def test_diff_profiles(self, temp_dir):
- """Test comparing two profiles."""
- from env_pro.core.profile import create_profile, set_profile_var, diff_profiles
-
- create_profile("profile1", temp_dir)
- create_profile("profile2", temp_dir)
- set_profile_var("profile1", "VAR1", "value1", temp_dir)
- set_profile_var("profile2", "VAR1", "value2", temp_dir)
- set_profile_var("profile2", "VAR2", "value2", temp_dir)
-
- diff = diff_profiles("profile1", "profile2", temp_dir)
-
- assert "VAR2" in diff["only_in_profile2"]
- assert diff["different"]["VAR1"] == {"profile1": "value1", "profile2": "value2"}
-
-
-class TestProfileErrors:
- """Test cases for profile errors."""
-
- def test_create_duplicate_profile(self, temp_dir):
- """Test creating a profile that already exists."""
- from env_pro.core.profile import create_profile, ProfileAlreadyExistsError
-
- create_profile("dev", temp_dir)
-
- with pytest.raises(ProfileAlreadyExistsError):
- create_profile("dev", temp_dir)
-
- def test_delete_nonexistent_profile(self, temp_dir):
- """Test deleting a profile that doesn't exist."""
- from env_pro.core.profile import delete_profile, ProfileNotFoundError
-
- with pytest.raises(ProfileNotFoundError):
- delete_profile("nonexistent", temp_dir)
-
- def test_switch_to_nonexistent_profile(self, temp_dir):
- """Test switching to a non-existent profile."""
- from env_pro.core.profile import switch_profile, ProfileNotFoundError
-
- with pytest.raises(ProfileNotFoundError):
- switch_profile("nonexistent", temp_dir)
diff --git a/tests/test_rules.py b/tests/test_rules.py
deleted file mode 100644
index 4e0ba70..0000000
--- a/tests/test_rules.py
+++ /dev/null
@@ -1,251 +0,0 @@
-"""Tests for the rules module."""
-
-import pytest
-from cli_diff_auditor.rules import (
- BuiltInRules,
- Finding,
- Rule,
- RulesLoader,
- Severity,
-)
-
-
-class TestRule:
- """Test cases for Rule class."""
-
- def test_rule_creation(self):
- """Test creating a Rule instance."""
- rule = Rule(
- id="test-rule",
- name="Test Rule",
- description="A test rule",
- pattern=r"test.*",
- severity=Severity.WARNING,
- auto_fix=True,
- category="testing"
- )
-
- assert rule.id == "test-rule"
- assert rule.name == "Test Rule"
- assert rule.severity == Severity.WARNING
- assert rule.auto_fix is True
- assert rule.enabled is True
-
- def test_compile_pattern(self):
- """Test compiling a rule pattern."""
- rule = Rule(
- id="test",
- name="Test",
- description="Test",
- pattern=r"\bprint\s*\(",
- severity=Severity.WARNING
- )
-
- compiled = rule.compile_pattern()
-
- assert compiled.search("print('hello')") is not None
- assert compiled.search("hello world") is None
-
- def test_get_fix_pattern(self):
- """Test getting the fix pattern."""
- rule = Rule(
- id="test",
- name="Test",
- description="Test",
- pattern=r"test",
- severity=Severity.INFO,
- auto_fix=True,
- fix_pattern=r"old",
- replacement="new"
- )
-
- fix_pattern = rule.get_fix_pattern()
-
- assert fix_pattern is not None
- assert fix_pattern.search("old text") is not None
-
-
-class TestBuiltInRules:
- """Test cases for BuiltInRules class."""
-
- def test_get_all_rules_returns_list(self):
- """Test that get_all_rules returns a list."""
- rules = BuiltInRules.get_all_rules()
-
- assert isinstance(rules, list)
- assert len(rules) > 0
-
- def test_rules_have_required_fields(self):
- """Test that all rules have required fields."""
- rules = BuiltInRules.get_all_rules()
-
- for rule in rules:
- assert rule.id
- assert rule.name
- assert rule.description
- assert rule.pattern
- assert rule.severity
-
- def test_debug_rules_exist(self):
- """Test that debug-related rules exist."""
- rules = BuiltInRules.get_all_rules()
- rule_ids = [r.id for r in rules]
-
- assert "debug-print" in rule_ids
- assert "console-log" in rule_ids
-
- def test_security_rules_exist(self):
- """Test that security-related rules exist."""
- rules = BuiltInRules.get_all_rules()
- rule_ids = [r.id for r in rules]
-
- assert "sql-injection" in rule_ids
- assert "hardcoded-password" in rule_ids
- assert "eval-usage" in rule_ids
-
- def test_error_handling_rules_exist(self):
- """Test that error handling rules exist."""
- rules = BuiltInRules.get_all_rules()
- rule_ids = [r.id for r in rules]
-
- assert "bare-except" in rule_ids
- assert "pass-except" in rule_ids
-
-
-class TestRulesLoader:
- """Test cases for RulesLoader class."""
-
- def setup_method(self):
- """Set up test fixtures."""
- self.loader = RulesLoader()
-
- def test_load_builtin_rules(self):
- """Test loading built-in rules."""
- self.loader.load_builtin_rules()
-
- rules = self.loader.get_all_rules()
-
- assert len(rules) > 0
-
- def test_get_rule(self):
- """Test getting a specific rule."""
- self.loader.load_builtin_rules()
-
- rule = self.loader.get_rule("debug-print")
-
- assert rule is not None
- assert rule.id == "debug-print"
-
- def test_get_nonexistent_rule(self):
- """Test getting a rule that doesn't exist."""
- self.loader.load_builtin_rules()
-
- rule = self.loader.get_rule("nonexistent-rule")
-
- assert rule is None
-
- def test_disable_rule(self):
- """Test disabling a rule."""
- self.loader.load_builtin_rules()
-
- result = self.loader.disable_rule("debug-print")
-
- assert result is True
-
- rule = self.loader.get_rule("debug-print")
- assert rule.enabled is False
-
- def test_enable_rule(self):
- """Test enabling a rule."""
- self.loader.load_builtin_rules()
- self.loader.disable_rule("debug-print")
-
- result = self.loader.enable_rule("debug-print")
-
- assert result is True
-
- rule = self.loader.get_rule("debug-print")
- assert rule.enabled is True
-
- def test_get_enabled_rules(self):
- """Test getting only enabled rules."""
- self.loader.load_builtin_rules()
- self.loader.disable_rule("debug-print")
- self.loader.disable_rule("console-log")
-
- enabled = self.loader.get_enabled_rules()
-
- rule_ids = [r.id for r in enabled]
-
- assert "debug-print" not in rule_ids
- assert "console-log" not in rule_ids
-
- def test_load_from_yaml(self):
- """Test loading rules from YAML."""
- self.loader.load_builtin_rules()
- yaml_content = """
-rules:
- - id: custom-rule
- name: Custom Rule
- description: A custom rule
- pattern: "custom.*"
- severity: warning
- category: custom
-"""
- rules = self.loader.load_from_yaml(yaml_content)
-
- assert len(rules) == 1
- assert rules[0].id == "custom-rule"
-
- all_rules = self.loader.get_all_rules()
- assert len(all_rules) > 1
-
- def test_load_from_yaml_minimal(self):
- """Test loading rules with minimal fields."""
- yaml_content = """
-rules:
- - id: minimal-rule
- name: Minimal
- description: Minimal description
- pattern: "test"
- severity: error
-"""
- rules = self.loader.load_from_yaml(yaml_content)
-
- assert len(rules) == 1
- assert rules[0].severity == Severity.ERROR
- assert rules[0].auto_fix is False
- assert rules[0].category == "custom"
-
- def test_parse_rule_data_missing_required_field(self):
- """Test parsing rule data with missing required field."""
- data = {
- "id": "test",
- "name": "Test",
- "description": "Test"
- }
-
- with pytest.raises(ValueError):
- self.loader._parse_rule_data(data)
-
-
-class TestFinding:
- """Test cases for Finding class."""
-
- def test_finding_creation(self):
- """Test creating a Finding instance."""
- finding = Finding(
- rule_id="test-rule",
- rule_name="Test Rule",
- severity=Severity.WARNING,
- file_path="test.py",
- line_number=10,
- line_content="print('debug')",
- message="Debug print statement detected",
- fix_available=True
- )
-
- assert finding.rule_id == "test-rule"
- assert finding.severity == Severity.WARNING
- assert finding.line_number == 10
- assert finding.fix_available is True
diff --git a/tests/test_template.py b/tests/test_template.py
deleted file mode 100644
index 3e50e23..0000000
--- a/tests/test_template.py
+++ /dev/null
@@ -1,111 +0,0 @@
-"""Tests for template module."""
-
-import pytest
-from pathlib import Path
-
-
-class TestTemplateManagement:
- """Test cases for template management."""
-
- def test_list_builtin_templates(self):
- """Test listing builtin templates."""
- from env_pro.core.template import list_builtin_templates
-
- templates = list_builtin_templates()
- assert "fastapi" in templates
- assert "django" in templates
- assert "nodejs" in templates
- assert "python" in templates
-
- def test_load_builtin_template(self):
- """Test loading a builtin template."""
- from env_pro.core.template import load_template
-
- content = load_template("minimal")
- assert "ENVIRONMENT" in content
-
- def test_get_template_info(self):
- """Test getting template information."""
- from env_pro.core.template import get_template_info
-
- info = get_template_info("fastapi")
- assert info["name"] == "fastapi"
- assert info["type"] == "builtin"
- assert "APP_NAME" in info["variables"]
-
- def test_render_template(self):
- """Test rendering a template with variables."""
- from env_pro.core.template import render_template
-
- content = "APP_NAME=${APP_NAME}\nDEBUG=${DEBUG}"
- variables = {"APP_NAME": "MyApp", "DEBUG": "true"}
- rendered = render_template(content, variables)
- assert "MyApp" in rendered
- assert "true" in rendered
-
- def test_render_template_missing_variable(self):
- """Test rendering template with missing variable raises error."""
- from env_pro.core.template import render_template, TemplateSyntaxError
-
- content = "APP_NAME=${APP_NAME}\nMISSING=${OTHER}"
- variables = {"APP_NAME": "MyApp"}
-
- with pytest.raises(TemplateSyntaxError):
- render_template(content, variables)
-
- def test_create_and_delete_user_template(self, temp_dir):
- """Test creating and deleting a user template."""
- from env_pro.core.template import (
- create_template, delete_template, load_template,
- get_user_templates_dir, list_user_templates
- )
- import os
-
- original_home = os.environ.get("HOME")
- os.environ["HOME"] = str(temp_dir)
-
- try:
- create_template("my-template", "VAR1=value1\nVAR2=value2", "My custom template")
-
- templates = list_user_templates()
- assert "my-template" in templates
-
- loaded = load_template("my-template")
- assert "VAR1=value1" in loaded
-
- delete_template("my-template")
- templates = list_user_templates()
- assert "my-template" not in templates
- finally:
- if original_home:
- os.environ["HOME"] = original_home
- else:
- del os.environ["HOME"]
-
- def test_apply_template(self, temp_dir):
- """Test applying a template to a file."""
- from env_pro.core.template import apply_template
-
- output_file = temp_dir / ".env"
- apply_template("minimal", {"ENVIRONMENT": "production"}, output_file)
-
- content = output_file.read_text()
- assert "production" in content
-
-
-class TestTemplateErrors:
- """Test cases for template errors."""
-
- def test_load_nonexistent_template(self):
- """Test loading a template that doesn't exist."""
- from env_pro.core.template import load_template, TemplateNotFoundError
-
- with pytest.raises(TemplateNotFoundError):
- load_template("nonexistent-template")
-
- def test_delete_builtin_template(self):
- """Test that builtin templates cannot be deleted."""
- from env_pro.core.template import delete_template, TemplateError
-
- with pytest.raises(TemplateError):
- delete_template("fastapi")
diff --git a/tests/test_validator.py b/tests/test_validator.py
deleted file mode 100644
index ed84f75..0000000
--- a/tests/test_validator.py
+++ /dev/null
@@ -1,182 +0,0 @@
-"""Tests for validation module."""
-
-import pytest
-from pathlib import Path
-
-
-class TestSchemaValidation:
- """Test cases for schema validation."""
-
- def test_load_empty_schema(self, temp_dir):
- """Test loading schema when no file exists."""
- from env_pro.core.validator import load_schema
-
- schema = load_schema(temp_dir)
- assert schema is None
-
- def test_load_schema(self, temp_dir):
- """Test loading a valid schema."""
- from env_pro.core.validator import load_schema, EnvSchema
-
- schema_file = temp_dir / ".env.schema.yaml"
- schema_file.write_text("""
-variables:
- DATABASE_URL:
- type: url
- required: true
- description: Database connection URL
- DEBUG:
- type: bool
- default: false
-""")
-
- schema = load_schema(temp_dir)
- assert schema is not None
- assert "DATABASE_URL" in schema.variables
- assert schema.variables["DATABASE_URL"].type == "url"
- assert schema.variables["DATABASE_URL"].required
-
- def test_validate_valid_string(self):
- """Test validation of valid string value."""
- from env_pro.core.validator import validate_value, VariableSchema
-
- schema = VariableSchema(type="string")
- errors = validate_value("TEST_VAR", "some-value", schema)
- assert errors == []
-
- def test_validate_required_missing(self):
- """Test validation when required variable is missing."""
- from env_pro.core.validator import validate_value, VariableSchema
-
- schema = VariableSchema(type="string", required=True)
- errors = validate_value("TEST_VAR", "", schema)
- assert len(errors) > 0
- assert "required" in errors[0].lower()
-
- def test_validate_int_type(self):
- """Test validation of integer type."""
- from env_pro.core.validator import validate_value, VariableSchema
-
- schema = VariableSchema(type="int")
- errors = validate_value("PORT", "8080", schema)
- assert errors == []
-
- errors = validate_value("PORT", "not-a-number", schema)
- assert len(errors) > 0
-
- def test_validate_bool_type(self):
- """Test validation of boolean type."""
- from env_pro.core.validator import validate_value, VariableSchema
-
- schema = VariableSchema(type="bool")
- errors = validate_value("DEBUG", "true", schema)
- assert errors == []
-
- errors = validate_value("DEBUG", "yes", schema)
- assert errors == []
-
- def test_validate_email_type(self):
- """Test validation of email type."""
- from env_pro.core.validator import validate_value, VariableSchema
-
- schema = VariableSchema(type="email")
- errors = validate_value("EMAIL", "user@example.com", schema)
- assert errors == []
-
- errors = validate_value("EMAIL", "invalid-email", schema)
- assert len(errors) > 0
-
- def test_validate_url_type(self):
- """Test validation of URL type."""
- from env_pro.core.validator import validate_value, VariableSchema
-
- schema = VariableSchema(type="url")
- errors = validate_value("API_URL", "https://api.example.com", schema)
- assert errors == []
-
- errors = validate_value("API_URL", "not-a-url", schema)
- assert len(errors) > 0
-
- def test_validate_pattern(self):
- """Test validation with regex pattern."""
- from env_pro.core.validator import validate_value, VariableSchema
-
- schema = VariableSchema(type="string", pattern=r"^[A-Z]+$")
- errors = validate_value("PREFIX", "ABC123", schema)
- assert len(errors) > 0
-
- errors = validate_value("PREFIX", "ABC", schema)
- assert errors == []
-
- def test_validate_enum(self):
- """Test validation with enum values."""
- from env_pro.core.validator import validate_value, VariableSchema
-
- schema = VariableSchema(type="string", enum=["dev", "staging", "prod"])
- errors = validate_value("ENV", "dev", schema)
- assert errors == []
-
- errors = validate_value("ENV", "invalid", schema)
- assert len(errors) > 0
-
- def test_validate_min_max_length(self):
- """Test validation with min/max length constraints."""
- from env_pro.core.validator import validate_value, VariableSchema
-
- schema = VariableSchema(type="string", min_length=3, max_length=10)
- errors = validate_value("NAME", "ab", schema)
- assert len(errors) > 0
-
- errors = validate_value("NAME", "abcdefghijkl", schema)
- assert len(errors) > 0
-
- errors = validate_value("NAME", "valid", schema)
- assert errors == []
-
- def test_validate_min_max_number(self):
- """Test validation with min/max number constraints."""
- from env_pro.core.validator import validate_value, VariableSchema
-
- schema = VariableSchema(type="int", minimum=1, maximum=100)
- errors = validate_value("PORT", "0", schema)
- assert len(errors) > 0
-
- errors = validate_value("PORT", "101", schema)
- assert len(errors) > 0
-
- errors = validate_value("PORT", "50", schema)
- assert errors == []
-
- def test_validate_env_vars_full(self):
- """Test full environment validation."""
- from env_pro.core.validator import validate_env_vars, EnvSchema, VariableSchema
-
- schema = EnvSchema(variables={
- "DATABASE_URL": VariableSchema(type="string", required=True),
- "DEBUG": VariableSchema(type="bool", default="false")
- })
-
- vars_dict = {
- "DATABASE_URL": "postgresql://localhost:5432/db",
- "DEBUG": "true"
- }
-
- errors = validate_env_vars(vars_dict, schema)
- assert errors == []
-
- def test_check_required_vars(self):
- """Test checking for missing required variables."""
- from env_pro.core.validator import check_required_vars, EnvSchema, VariableSchema
-
- schema = EnvSchema(variables={
- "REQUIRED_VAR": VariableSchema(type="string", required=True),
- "OPTIONAL_VAR": VariableSchema(type="string", required=False)
- })
-
- vars_dict = {"OPTIONAL_VAR": "value"}
- missing = check_required_vars(vars_dict, schema)
- assert "REQUIRED_VAR" in missing
-
- vars_dict = {"REQUIRED_VAR": "value"}
- missing = check_required_vars(vars_dict, schema)
- assert len(missing) == 0
diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py
deleted file mode 100644
index 105478c..0000000
--- a/tests/unit/test_cli.py
+++ /dev/null
@@ -1,60 +0,0 @@
-"""Unit tests for CLI module."""
-
-import os
-import tempfile
-
-from click.testing import CliRunner
-
-from codesnap.__main__ import main
-
-
-class TestCLI:
- """Tests for CLI commands."""
-
- def setup_method(self) -> None:
- self.runner = CliRunner()
-
- def test_main_help(self) -> None:
- result = self.runner.invoke(main, ["--help"])
- assert result.exit_code == 0
- assert "CodeSnap" in result.output
-
- def test_cli_version(self) -> None:
- result = self.runner.invoke(main, ["--version"])
- assert result.exit_code == 0
- assert "0.1.0" in result.output
-
- def test_cli_analyze_nonexistent_path(self) -> None:
- result = self.runner.invoke(main, ["analyze", "/nonexistent/path"])
- assert result.exit_code != 0
-
- def test_cli_analyze_current_directory(self) -> None:
- with tempfile.TemporaryDirectory() as tmpdir:
- with open(os.path.join(tmpdir, "test.py"), "w") as f:
- f.write("def test(): pass\n")
- result = self.runner.invoke(main, ["analyze", tmpdir])
- assert result.exit_code == 0
-
- def test_cli_analyze_with_output_format(self) -> None:
- with tempfile.TemporaryDirectory() as tmpdir:
- with open(os.path.join(tmpdir, "test.py"), "w") as f:
- f.write("def test(): pass\n")
- result = self.runner.invoke(main, ["analyze", tmpdir, "--format", "json"])
- assert result.exit_code == 0
-
- def test_cli_analyze_with_max_files(self) -> None:
- with tempfile.TemporaryDirectory() as tmpdir:
- with open(os.path.join(tmpdir, "test.py"), "w") as f:
- f.write("def test(): pass\n")
- result = self.runner.invoke(main, ["analyze", tmpdir, "--max-files", "10"])
- assert result.exit_code == 0
-
- def test_cli_languages(self) -> None:
- result = self.runner.invoke(main, ["languages"])
- assert result.exit_code == 0
- assert "python" in result.output.lower()
-
- def test_cli_info_languages(self) -> None:
- result = self.runner.invoke(main, ["info", "--languages"])
- assert result.exit_code == 0
- assert "python" in result.output.lower()
diff --git a/tests/unit/test_complexity.py b/tests/unit/test_complexity.py
deleted file mode 100644
index d3c6db9..0000000
--- a/tests/unit/test_complexity.py
+++ /dev/null
@@ -1,269 +0,0 @@
-"""Unit tests for complexity analysis module."""
-
-from codesnap.core.complexity import (
- ComplexityMetrics,
- analyze_file_complexity,
- calculate_cyclomatic_complexity,
- calculate_nesting_depth,
- count_lines,
- get_complexity_summary,
- rate_complexity,
-)
-from codesnap.core.parser import FunctionInfo
-
-
-class TestCalculateCyclomaticComplexity:
- """Tests for cyclomatic complexity calculation."""
-
- def test_empty_content(self):
- complexity, decisions = calculate_cyclomatic_complexity("")
- assert complexity == 1
- assert decisions == 0
-
- def test_simple_function(self):
- content = "def test():\n pass"
- complexity, decisions = calculate_cyclomatic_complexity(content)
- assert complexity == 1
-
- def test_if_statement(self):
- content = "if x > 0:\n pass"
- complexity, decisions = calculate_cyclomatic_complexity(content)
- assert complexity >= 1
-
- def test_multiple_if_statements(self):
- content = """
-if x > 0:
- pass
-elif x < 0:
- pass
-else:
- pass
-"""
- complexity, decisions = calculate_cyclomatic_complexity(content)
- assert complexity >= 3
-
- def test_for_loop(self):
- content = "for i in range(10):\n pass"
- complexity, decisions = calculate_cyclomatic_complexity(content)
- assert complexity >= 1
-
- def test_while_loop(self):
- content = "while True:\n pass"
- complexity, decisions = calculate_cyclomatic_complexity(content)
- assert complexity >= 1
-
- def test_try_except(self):
- content = """
-try:
- pass
-except Exception:
- pass
-"""
- complexity, decisions = calculate_cyclomatic_complexity(content)
- assert complexity >= 1
-
- def test_and_or_operators(self):
- content = "if x > 0 and y > 0:\n pass"
- complexity, decisions = calculate_cyclomatic_complexity(content)
- assert complexity >= 2
-
- def test_ternary_operator(self):
- content = "x = 1 if cond else 2"
- complexity, decisions = calculate_cyclomatic_complexity(content)
- assert complexity >= 1
-
-
-class TestCalculateNestingDepth:
- """Tests for nesting depth calculation."""
-
- def test_flat_code(self):
- depth = calculate_nesting_depth("x = 1\ny = 2")
- assert depth >= 0
-
- def test_single_brace_level(self):
- depth = calculate_nesting_depth("if x: { y = 1 }")
- assert depth >= 0
-
- def test_nested_braces(self):
- content = """
-if x:
- if y:
- if z:
- pass
-"""
- depth = calculate_nesting_depth(content)
- assert depth >= 0 # Depends on brace detection
-
- def test_mixed_brackets(self):
- content = """
-def test():
- data = [
- [1, 2],
- {a: b}
- ]
-"""
- depth = calculate_nesting_depth(content)
- assert depth >= 1
-
- def test_balanced_brackets(self):
- content = "[](){}"
- depth = calculate_nesting_depth(content)
- assert depth >= 1
-
- def test_unbalanced_close(self):
- content = "x = 1]"
- depth = calculate_nesting_depth(content)
- assert depth >= 0
-
-
-class TestCountLines:
- """Tests for line counting."""
-
- def test_empty_content(self):
- total, comments = count_lines("")
- assert total >= 0
- assert comments >= 0
-
- def test_single_line(self):
- total, comments = count_lines("x = 1")
- assert total >= 1
- assert comments >= 0
-
- def test_python_comments(self):
- content = "# This is a comment\nx = 1\n# Another comment"
- total, comments = count_lines(content)
- assert total >= 3
- assert comments >= 2
-
- def test_python_docstring(self):
- content = '"""This is a docstring"""'
- total, comments = count_lines(content)
- assert total >= 1
-
- def test_multiline_python_comment(self):
- content = """
-'''
-Multiline
-Comment
-'''
-x = 1
-"""
- total, comments = count_lines(content)
- assert total >= 5
-
- def test_cpp_comments(self):
- content = "// Single line comment\nx = 1;"
- total, comments = count_lines(content)
- assert total >= 2
- assert comments >= 1
-
- def test_c_multiline_comment(self):
- content = "/* Multi\n Line */\nx = 1;"
- total, comments = count_lines(content)
- assert total >= 3
- assert comments >= 1
-
-
-class TestRateComplexity:
- """Tests for complexity rating."""
-
- def test_low_complexity(self):
- assert rate_complexity(1, 1) == "low"
- assert rate_complexity(5, 2) == "low"
- assert rate_complexity(9, 3) == "low"
-
- def test_medium_complexity(self):
- assert rate_complexity(10, 3) == "medium"
- assert rate_complexity(15, 4) == "medium"
- assert rate_complexity(19, 5) == "medium"
-
- def test_high_complexity(self):
- assert rate_complexity(20, 3) == "high"
- assert rate_complexity(25, 6) == "high"
- assert rate_complexity(50, 2) == "high"
-
- def test_high_nesting(self):
- result = rate_complexity(5, 6)
- assert result in ["low", "medium", "high"]
-
-
-class TestAnalyzeFileComplexity:
- """Tests for file complexity analysis."""
-
- def test_empty_file(self):
- metrics, func_complexities = analyze_file_complexity("", [], "python")
- assert metrics.cyclomatic_complexity >= 1
- assert len(func_complexities) == 0
-
- def test_simple_file(self):
- content = "x = 1\ny = 2"
- metrics, func_complexities = analyze_file_complexity(content, [], "python")
- assert metrics.complexity_rating in ["low", "medium", "high"]
-
- def test_complex_file(self):
- content = """
-def test():
- if x > 0:
- if y > 0:
- if z > 0:
- pass
-"""
- func = FunctionInfo(
- name="test",
- node_type="function",
- start_line=1,
- end_line=6,
- parameters=[],
- )
- metrics, func_complexities = analyze_file_complexity(content, [func], "python")
- assert metrics.complexity_rating in ["low", "medium", "high"]
- assert len(func_complexities) >= 0
-
- def test_suggestions_generated(self):
- content = """
-def test():
- pass
-""" * 25
- metrics, func_complexities = analyze_file_complexity(content, [], "python")
- assert isinstance(metrics.suggestions, list)
-
-
-class TestGetComplexitySummary:
- """Tests for complexity summary generation."""
-
- def test_empty_list(self):
- summary = get_complexity_summary([])
- assert summary["total_files"] == 0
- assert summary["avg_complexity"] == 0
-
- def test_single_file(self):
- metrics = ComplexityMetrics(
- cyclomatic_complexity=10,
- nesting_depth=2,
- lines_of_code=50,
- )
- summary = get_complexity_summary([metrics])
- assert summary["total_files"] == 1
- assert summary["avg_complexity"] == 10
-
- def test_multiple_files(self):
- metrics_list = [
- ComplexityMetrics(cyclomatic_complexity=5),
- ComplexityMetrics(cyclomatic_complexity=15),
- ComplexityMetrics(cyclomatic_complexity=10),
- ]
- summary = get_complexity_summary(metrics_list)
- assert summary["total_files"] == 3
- assert summary["avg_complexity"] == 10
-
- def test_rating_distribution(self):
- metrics_list = [
- ComplexityMetrics(cyclomatic_complexity=5),
- ComplexityMetrics(cyclomatic_complexity=15),
- ComplexityMetrics(cyclomatic_complexity=25),
- ]
- summary = get_complexity_summary(metrics_list)
- assert summary["rating_distribution"]["low"] >= 0
- assert summary["rating_distribution"]["medium"] >= 0
- assert summary["rating_distribution"]["high"] >= 0
- assert summary["rating_distribution"]["low"] + summary["rating_distribution"]["medium"] + summary["rating_distribution"]["high"] == 3
diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py
deleted file mode 100644
index 347bffd..0000000
--- a/tests/unit/test_config.py
+++ /dev/null
@@ -1,71 +0,0 @@
-"""Unit tests for config module."""
-
-import os
-
-from codesnap.utils.config import Config, apply_env_overrides, load_config
-
-
-class TestConfig:
- """Tests for Config class."""
-
- def test_default_values(self) -> None:
- config = Config()
- assert config.max_files == 1000
- assert config.max_tokens == 8000
- assert config.default_format == "markdown"
-
- def test_custom_values(self) -> None:
- config = Config(max_files=500, max_tokens=4000, default_format="json")
- assert config.max_files == 500
- assert config.max_tokens == 4000
- assert config.default_format == "json"
-
- def test_default_ignore_patterns(self) -> None:
- config = Config()
- assert isinstance(config.ignore_patterns, list)
-
- def test_default_languages(self) -> None:
- config = Config()
- assert isinstance(config.included_languages, list)
- assert isinstance(config.excluded_languages, list)
-
-
-class TestLoadConfig:
- """Tests for load_config function."""
-
- def test_load_default_config(self) -> None:
- config = load_config()
- assert config.max_files == 1000
- assert config.max_tokens == 8000
-
- def test_load_nonexistent_file(self) -> None:
- from pathlib import Path
- config = load_config(Path("/nonexistent/path.tomll"))
- assert config.max_files == 1000
-
-
-class TestApplyEnvOverrides:
- """Tests for apply_env_overrides function."""
-
- def test_no_overrides(self) -> None:
- config = Config()
- result = apply_env_overrides(config)
- assert result.max_files == 1000
-
- def test_max_files_override(self) -> None:
- os.environ["CODSNAP_MAX_FILES"] = "500"
- try:
- config = Config()
- result = apply_env_overrides(config)
- assert result.max_files == 500
- finally:
- del os.environ["CODSNAP_MAX_FILES"]
-
- def test_max_tokens_override(self) -> None:
- os.environ["CODSNAP_MAX_TOKENS"] = "4000"
- try:
- config = Config()
- result = apply_env_overrides(config)
- assert result.max_tokens == 4000
- finally:
- del os.environ["CODSNAP_MAX_TOKENS"]
diff --git a/tests/unit/test_dependency_graph.py b/tests/unit/test_dependency_graph.py
deleted file mode 100644
index 5892524..0000000
--- a/tests/unit/test_dependency_graph.py
+++ /dev/null
@@ -1,177 +0,0 @@
-"""Unit tests for dependency graph module."""
-
-from pathlib import Path
-
-from codesnap.core.dependency_graph import Dependency, DependencyGraphBuilder, DependencyParser
-
-
-class TestDependencyParser:
- """Tests for DependencyParser class."""
-
- def setup_method(self) -> None:
- self.parser = DependencyParser()
-
- def test_parse_python_import(self) -> None:
- code = "import os"
- deps = self.parser.parse_file(Path("test.py"), code, "python")
- assert len(deps) >= 1
-
- def test_parse_python_from_import(self) -> None:
- code = "from pathlib import Path"
- deps = self.parser.parse_file(Path("test.py"), code, "python")
- assert len(deps) >= 1
-
- def test_parse_python_multiple_imports(self) -> None:
- code = """
-import os
-import sys
-from pathlib import Path
-from collections import defaultdict
-"""
- deps = self.parser.parse_file(Path("test.py"), code, "python")
- assert len(deps) >= 3
-
- def test_parse_javascript_require(self) -> None:
- code = "const express = require('express');"
- deps = self.parser.parse_file(Path("test.js"), code, "javascript")
- assert len(deps) >= 1
-
- def test_parse_javascript_import(self) -> None:
- code = "import { useState } from 'react';"
- deps = self.parser.parse_file(Path("test.js"), code, "javascript")
- assert len(deps) >= 1
-
- def test_parse_go_import(self) -> None:
- code = 'import "fmt"'
- deps = self.parser.parse_file(Path("test.go"), code, "go")
- assert len(deps) >= 1
-
- def test_parse_rust_use(self) -> None:
- code = "use std::collections::HashMap;"
- deps = self.parser.parse_file(Path("test.rs"), code, "rust")
- assert len(deps) >= 1
-
- def test_parse_java_import(self) -> None:
- code = "import java.util.ArrayList;"
- deps = self.parser.parse_file(Path("test.java"), code, "java")
- assert len(deps) >= 1
-
- def test_parse_unsupported_language(self) -> None:
- code = "some random code"
- deps = self.parser.parse_file(Path("test.xyz"), code, "unsupported")
- assert len(deps) == 0
-
-
-class TestDependencyGraphBuilder:
- """Tests for DependencyGraphBuilder class."""
-
- def setup_method(self) -> None:
- self.graph = DependencyGraphBuilder()
-
- def test_add_file(self) -> None:
- self.graph.add_file(Path("main.py"), "python", 100, 10, 2, 1)
- assert self.graph.graph.number_of_nodes() == 1
- assert Path("main.py") in self.graph.graph.nodes()
-
- def test_add_dependency(self) -> None:
- self.graph.add_file(Path("a.py"), "python", 50, 5, 1, 0)
- self.graph.add_file(Path("b.py"), "python", 60, 6, 1, 0)
-
- dep = Dependency(
- source_file=Path("a.py"),
- target_file=Path("b.py"),
- import_statement="import b",
- import_type="import"
- )
- self.graph.add_dependency(dep)
-
- assert self.graph.graph.has_edge(Path("a.py"), Path("b.py"))
-
- def test_build_from_analysis(self) -> None:
- analysis_result = {
- "files": [
- {"path": "main.py", "language": "python", "size": 100, "lines": 10, "functions": ["main"], "classes": []},
- {"path": "utils.py", "language": "python", "size": 50, "lines": 5, "functions": ["helper"], "classes": []}
- ],
- "dependencies": [
- {"source": "main.py", "target": "utils.py", "type": "import"}
- ]
- }
- self.graph.build_from_analysis(analysis_result)
-
- assert self.graph.graph.number_of_nodes() == 2
- assert self.graph.graph.has_edge(Path("main.py"), Path("utils.py"))
-
- def test_find_cycles(self) -> None:
- self.graph.add_file(Path("a.py"), "python", 50, 5, 1, 0)
- self.graph.add_file(Path("b.py"), "python", 50, 5, 1, 0)
- self.graph.add_file(Path("c.py"), "python", 50, 5, 1, 0)
-
- dep1 = Dependency(Path("a.py"), Path("b.py"), "import b", "import")
- dep2 = Dependency(Path("b.py"), Path("c.py"), "import c", "import")
- dep3 = Dependency(Path("c.py"), Path("a.py"), "import a", "import")
-
- self.graph.add_dependency(dep1)
- self.graph.add_dependency(dep2)
- self.graph.add_dependency(dep3)
-
- cycles = self.graph.find_cycles()
- assert len(cycles) >= 1
-
- def test_find_no_cycles(self) -> None:
- self.graph.add_file(Path("a.py"), "python", 50, 5, 1, 0)
- self.graph.add_file(Path("b.py"), "python", 50, 5, 1, 0)
-
- dep = Dependency(Path("a.py"), Path("b.py"), "import b", "import")
- self.graph.add_dependency(dep)
-
- cycles = self.graph.find_cycles()
- assert len(cycles) == 0
-
- def test_find_orphaned_files(self) -> None:
- self.graph.add_file(Path("orphan.py"), "python", 50, 5, 1, 0)
- self.graph.add_file(Path("main.py"), "python", 100, 10, 2, 1)
- self.graph.add_file(Path("used.py"), "python", 50, 5, 1, 0)
-
- dep = Dependency(Path("main.py"), Path("used.py"), "import used", "import")
- self.graph.add_dependency(dep)
-
- orphaned = self.graph.find_orphaned_files()
- assert Path("orphan.py") in orphaned
- assert Path("main.py") not in orphaned
- assert Path("used.py") not in orphaned
-
- def test_calculate_metrics(self) -> None:
- self.graph.add_file(Path("main.py"), "python", 100, 10, 2, 1)
- self.graph.add_file(Path("utils.py"), "python", 50, 5, 1, 0)
-
- dep = Dependency(Path("main.py"), Path("utils.py"), "import utils", "import")
- self.graph.add_dependency(dep)
-
- metrics = self.graph.calculate_metrics()
-
- assert metrics.total_files == 2
- assert metrics.total_edges == 1
- assert metrics.density >= 0
-
- def test_get_transitive_closure(self) -> None:
- self.graph.add_file(Path("a.py"), "python", 50, 5, 1, 0)
- self.graph.add_file(Path("b.py"), "python", 50, 5, 1, 0)
- self.graph.add_file(Path("c.py"), "python", 50, 5, 1, 0)
-
- self.graph.add_dependency(Dependency(Path("a.py"), Path("b.py"), "import b", "import"))
- self.graph.add_dependency(Dependency(Path("b.py"), Path("c.py"), "import c", "import"))
-
- dependents = self.graph.get_transitive_closure(Path("c.py"))
- assert len(dependents) >= 0 # May or may not find depending on graph structure
-
- def test_get_dependencies(self) -> None:
- self.graph.add_file(Path("a.py"), "python", 50, 5, 1, 0)
- self.graph.add_file(Path("b.py"), "python", 50, 5, 1, 0)
- self.graph.add_file(Path("c.py"), "python", 50, 5, 1, 0)
-
- self.graph.add_dependency(Dependency(Path("a.py"), Path("b.py"), "import b", "import"))
- self.graph.add_dependency(Dependency(Path("a.py"), Path("c.py"), "import c", "import"))
-
- deps = self.graph.get_dependencies(Path("a.py"))
- assert isinstance(deps, set) # Returns a set
diff --git a/tests/unit/test_extractor.py b/tests/unit/test_extractor.py
deleted file mode 100644
index f4ebe85..0000000
--- a/tests/unit/test_extractor.py
+++ /dev/null
@@ -1,112 +0,0 @@
-from codesnap.core.extractor import FunctionExtractor
-
-
-class TestFunctionExtractor:
- def setup_method(self) -> None:
- self.extractor = FunctionExtractor()
-
- def test_extract_simple_function(self) -> None:
- code = """
-def hello():
- print("Hello, World!")
-"""
- functions = self.extractor.extract_functions_python(code)
- assert len(functions) >= 1
- func = functions[0]
- assert func.name == "hello"
- assert len(func.parameters) == 0
-
- def test_extract_function_with_parameters(self) -> None:
- code = """
-def greet(name, greeting="Hello"):
- return f"{greeting}, {name}!"
-"""
- functions = self.extractor.extract_functions_python(code)
- assert len(functions) >= 1
- func = functions[0]
- assert func.name == "greet"
- assert "name" in func.parameters
- assert "greeting" in func.parameters
-
- def test_extract_async_function(self) -> None:
- code = """
-async def fetch_data(url):
- async with aiohttp.ClientSession() as session:
- async with session.get(url) as response:
- return await response.json()
-"""
- functions = self.extractor.extract_functions_python(code)
- assert len(functions) >= 1
- func = functions[0]
- assert func.name == "fetch_data"
- assert func.is_async is True
-
- def test_extract_function_with_return_type(self) -> None:
- code = """
-def add(a: int, b: int) -> int:
- return a + b
-"""
- functions = self.extractor.extract_functions_python(code)
- assert len(functions) >= 1
- func = functions[0]
- assert func.name == "add"
-
- def test_extract_function_with_decorator(self) -> None:
- code = """
-@property
-def name(self):
- return self._name
-"""
- functions = self.extractor.extract_functions_python(code)
- assert len(functions) >= 1
-
- def test_extract_classes(self) -> None:
- code = """
-class MyClass:
- def __init__(self):
- self.value = 42
-
- def get_value(self):
- return self.value
-"""
- classes = self.extractor.extract_classes_python(code)
- assert len(classes) >= 1
- cls = classes[0]
- assert cls.name == "MyClass"
-
- def test_extract_class_with_inheritance(self) -> None:
- code = """
-class ChildClass(ParentClass, MixinClass):
- pass
-"""
- classes = self.extractor.extract_classes_python(code)
- assert len(classes) >= 1
- cls = classes[0]
- assert "ParentClass" in cls.base_classes
- assert "MixinClass" in cls.base_classes
-
- def test_extract_all_python(self) -> None:
- code = """
-def func1():
- pass
-
-class MyClass:
- def method1(self):
- pass
-
-def func2():
- pass
-"""
- functions, classes = self.extractor.extract_all(code, "python")
- assert len(functions) >= 2
- assert len(classes) >= 1
-
- def test_extract_from_file(self) -> None:
- code = """
-def test_function(x, y):
- return x + y
-"""
- result = self.extractor.extract_from_file("test.py", code, "python")
- assert result["file"] == "test.py"
- assert len(result["functions"]) >= 1
- assert result["functions"][0]["name"] == "test_function"
diff --git a/tests/unit/test_file_utils.py b/tests/unit/test_file_utils.py
deleted file mode 100644
index d74ab85..0000000
--- a/tests/unit/test_file_utils.py
+++ /dev/null
@@ -1,98 +0,0 @@
-from codesnap.utils.file_utils import FileUtils
-
-
-class TestFileUtils:
- def test_should_ignore_patterns(self) -> None:
- assert FileUtils.should_ignore("test.pyc", ["*.pyc"]) is True
- assert FileUtils.should_ignore("test.pyc", ["*.pyc", "*.pyo"]) is True
- assert FileUtils.should_ignore("test.py", ["*.pyc"]) is False
-
- def test_should_ignore_directory(self) -> None:
- assert FileUtils.should_ignore("src/__pycache__", ["__pycache__/*"]) is True
- assert FileUtils.should_ignore(".git/config", [".git/*"]) is True
-
- def test_is_text_file(self) -> None:
- assert FileUtils.is_text_file("test.py") is True
- assert FileUtils.is_text_file("test.js") is True
- assert FileUtils.is_text_file("test.tsx") is True
- assert FileUtils.is_text_file("Dockerfile") is True
-
- def test_is_not_binary_file(self) -> None:
- assert FileUtils.is_text_file("test.png") is False
- assert FileUtils.is_text_file("test.jpg") is False
- assert FileUtils.is_text_file("test.so") is False
-
- def test_read_file_content(self) -> None:
- import os
- import tempfile
- with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
- f.write("print('hello')")
- temp_path = f.name
- try:
- content = FileUtils.read_file_content(temp_path)
- assert content == "print('hello')"
- finally:
- os.unlink(temp_path)
-
- def test_read_file_content_not_found(self) -> None:
- content = FileUtils.read_file_content("/nonexistent/file.py")
- assert content is None
-
- def test_count_lines(self) -> None:
- content = "line1\nline2\nline3"
- assert FileUtils.count_lines(content) == 3
- assert FileUtils.count_lines("") == 1
- assert FileUtils.count_lines("single") == 1
-
- def test_get_relative_path(self) -> None:
- import os
- import tempfile
- with tempfile.TemporaryDirectory() as tmpdir:
- subdir = os.path.join(tmpdir, "subdir")
- os.makedirs(subdir)
- filepath = os.path.join(subdir, "test.py")
- rel = FileUtils.get_relative_path(filepath, tmpdir)
- assert rel == os.path.join("subdir", "test.py")
-
- def test_walk_directory(self) -> None:
- import os
- import tempfile
- with tempfile.TemporaryDirectory() as tmpdir:
- os.makedirs(os.path.join(tmpdir, "src"))
- os.makedirs(os.path.join(tmpdir, "tests"))
- with open(os.path.join(tmpdir, "main.py"), "w") as f:
- f.write("print('hello')")
- with open(os.path.join(tmpdir, "src", "module.py"), "w") as f:
- f.write("def test(): pass")
-
- files = FileUtils.walk_directory(tmpdir, ["*.pyc", "__pycache__/*"], 100)
- assert len(files) == 2
-
- def test_walk_directory_with_ignore(self) -> None:
- import os
- import tempfile
- with tempfile.TemporaryDirectory() as tmpdir:
- os.makedirs(os.path.join(tmpdir, "__pycache__"))
- with open(os.path.join(tmpdir, "main.py"), "w") as f:
- f.write("print('hello')")
- with open(os.path.join(tmpdir, "__pycache__", "cache.pyc"), "w") as f:
- f.write("cached")
-
- files = FileUtils.walk_directory(tmpdir, ["*.pyc", "__pycache__/*"], 100)
- assert len(files) == 1
- assert "__pycache__" not in files[0]
-
- def test_get_directory_tree(self) -> None:
- import os
- import tempfile
- with tempfile.TemporaryDirectory() as tmpdir:
- os.makedirs(os.path.join(tmpdir, "src"))
- os.makedirs(os.path.join(tmpdir, "tests"))
- with open(os.path.join(tmpdir, "main.py"), "w") as f:
- f.write("")
- with open(os.path.join(tmpdir, "src", "module.py"), "w") as f:
- f.write("")
-
- tree = FileUtils.get_directory_tree(tmpdir, ["*.pyc"], 3)
- assert len(tree) > 0
- assert any("main.py" in line for line in tree)
diff --git a/tests/unit/test_formatters.py b/tests/unit/test_formatters.py
deleted file mode 100644
index fcc151e..0000000
--- a/tests/unit/test_formatters.py
+++ /dev/null
@@ -1,428 +0,0 @@
-"""Unit tests for output formatters."""
-
-import json
-from pathlib import Path
-
-from codesnap.core.analyzer import AnalysisResult, FileAnalysis
-from codesnap.core.parser import ClassInfo, FunctionInfo
-from codesnap.output.json_exporter import export_json, export_json_file
-from codesnap.output.llm_exporter import (
- estimate_tokens,
- export_llm_optimized,
- truncate_for_token_limit,
-)
-from codesnap.output.markdown_exporter import export_markdown, export_markdown_file
-
-
-class TestJsonExporter:
- """Tests for JSON export functionality."""
-
- def create_test_result(self):
- """Create a test analysis result."""
- func = FunctionInfo(
- name="test_function",
- node_type="function_definition",
- start_line=1,
- end_line=10,
- parameters=[{"name": "x", "type": "int"}],
- return_type="str",
- is_async=False,
- )
-
- cls = ClassInfo(
- name="TestClass",
- start_line=1,
- end_line=20,
- bases=["BaseClass"],
- methods=[func],
- )
-
- file_analysis = FileAnalysis(
- path=Path("/test/project/main.py"),
- language="python",
- size=500,
- lines=50,
- functions=[func],
- classes=[cls],
- )
-
- result = AnalysisResult()
- result.summary = {
- "total_files": 1,
- "total_functions": 1,
- "total_classes": 1,
- "total_dependencies": 0,
- "languages": {"python": 1},
- }
- result.files = [file_analysis]
- result.dependencies = []
- result.metrics = {}
- result.analysis_time = 0.1
- result.error_count = 0
-
- return result
-
- def test_export_json_structure(self):
- result = self.create_test_result()
- root = Path("/test/project")
-
- json_output = export_json(result, root)
-
- data = json.loads(json_output)
-
- assert "metadata" in data
- assert "summary" in data
- assert "files" in data
- assert "dependencies" in data
- assert "metrics" in data
-
- def test_export_json_metadata(self):
- result = self.create_test_result()
- root = Path("/test/project")
-
- json_output = export_json(result, root)
- data = json.loads(json_output)
-
- assert data["metadata"]["tool"] == "CodeSnap"
- assert data["metadata"]["version"] == "0.1.0"
- assert "timestamp" in data["metadata"]
- assert data["metadata"]["root_path"] == "/test/project"
-
- def test_export_json_summary(self):
- result = self.create_test_result()
- root = Path("/test/project")
-
- json_output = export_json(result, root)
- data = json.loads(json_output)
-
- assert data["summary"]["total_files"] == 1
- assert data["summary"]["total_functions"] == 1
- assert data["summary"]["total_classes"] == 1
-
- def test_export_json_functions(self):
- result = self.create_test_result()
- root = Path("/test/project")
-
- json_output = export_json(result, root)
- data = json.loads(json_output)
-
- assert len(data["files"]) == 1
- assert len(data["files"][0]["functions"]) == 1
- assert data["files"][0]["functions"][0]["name"] == "test_function"
-
- def test_export_json_classes(self):
- result = self.create_test_result()
- root = Path("/test/project")
-
- json_output = export_json(result, root)
- data = json.loads(json_output)
-
- assert len(data["files"][0]["classes"]) == 1
- assert data["files"][0]["classes"][0]["name"] == "TestClass"
- assert data["files"][0]["classes"][0]["bases"] == ["BaseClass"]
-
- def test_export_json_file(self, tmp_path):
- result = self.create_test_result()
- root = Path("/test/project")
- output_file = tmp_path / "output.json"
-
- export_json_file(result, root, output_file)
-
- assert output_file.exists()
-
- data = json.loads(output_file.read_text())
- assert "metadata" in data
-
-
-class TestMarkdownExporter:
- """Tests for Markdown export functionality."""
-
- def create_test_result(self):
- """Create a test analysis result."""
- func = FunctionInfo(
- name="process_data",
- node_type="function_definition",
- start_line=5,
- end_line=15,
- parameters=[{"name": "data"}, {"name": "options"}],
- is_async=True,
- )
-
- file_analysis = FileAnalysis(
- path=Path("/test/project/utils.py"),
- language="python",
- size=300,
- lines=30,
- functions=[func],
- classes=[],
- )
-
- result = AnalysisResult()
- result.summary = {
- "total_files": 1,
- "total_functions": 1,
- "total_classes": 0,
- "total_dependencies": 0,
- "languages": {"python": 1},
- }
- result.files = [file_analysis]
- result.dependencies = []
- result.metrics = {}
- result.analysis_time = 0.05
- result.error_count = 0
-
- return result
-
- def test_export_markdown_header(self):
- result = self.create_test_result()
- root = Path("/test/project")
-
- md_output = export_markdown(result, root)
-
- assert "# CodeSnap Analysis Report" in md_output
-
- def test_export_markdown_summary(self):
- result = self.create_test_result()
- root = Path("/test/project")
-
- md_output = export_markdown(result, root)
-
- assert "## Summary" in md_output
- assert "Total Files" in md_output
- assert "1" in md_output
-
- def test_export_markdown_language_breakdown(self):
- result = self.create_test_result()
- root = Path("/test/project")
-
- md_output = export_markdown(result, root)
-
- assert "### Language Breakdown" in md_output
- assert "python" in md_output.lower()
-
- def test_export_markdown_file_structure(self):
- result = self.create_test_result()
- root = Path("/test/project")
-
- md_output = export_markdown(result, root)
-
- assert "## File Structure" in md_output
- assert "```" in md_output
-
- def test_export_markdown_functions(self):
- result = self.create_test_result()
- root = Path("/test/project")
-
- md_output = export_markdown(result, root)
-
- assert "process_data" in md_output
- assert "async" in md_output.lower()
-
- def test_export_markdown_file(self, tmp_path):
- result = self.create_test_result()
- root = Path("/test/project")
- output_file = tmp_path / "output.md"
-
- export_markdown_file(result, root, output_file)
-
- assert output_file.exists()
- content = output_file.read_text()
- assert "# CodeSnap Analysis Report" in content
-
- def test_empty_result(self):
- result = AnalysisResult()
- result.summary = {}
- result.files = []
- result.dependencies = []
- result.metrics = {}
- result.analysis_time = 0
- result.error_count = 0
-
- root = Path("/test")
- md_output = export_markdown(result, root)
-
- assert "# CodeSnap Analysis Report" in md_output
-
-
-class TestLLMExporter:
- """Tests for LLM-optimized export functionality."""
-
- def test_estimate_tokens_python(self):
- text = "def hello():\n print('hello')"
- tokens = estimate_tokens(text, "python")
-
- assert tokens > 0
- assert tokens < len(text)
-
- def test_estimate_tokens_markdown(self):
- text = "# Heading\n\nSome content here."
- tokens = estimate_tokens(text, "markdown")
-
- assert tokens > 0
-
- def test_truncate_under_limit(self):
- text = "Short text"
- result = truncate_for_token_limit(text, 100, "markdown")
-
- assert result == text
-
- def test_truncate_over_limit(self):
- text = "A" * 1000
- result = truncate_for_token_limit(text, 100, "markdown")
-
- assert len(result) < len(text)
- assert "[Output truncated due to token limit]" in result
-
- def test_export_llm_optimized_structure(self):
- func = FunctionInfo(
- name="helper",
- node_type="function",
- start_line=1,
- end_line=5,
- )
-
- file_analysis = FileAnalysis(
- path=Path("/test/main.py"),
- language="python",
- size=100,
- lines=10,
- functions=[func],
- classes=[],
- )
-
- result = AnalysisResult()
- result.summary = {
- "total_files": 1,
- "total_functions": 1,
- "total_classes": 0,
- "total_dependencies": 0,
- "languages": {"python": 1},
- }
- result.files = [file_analysis]
- result.dependencies = []
- result.metrics = {}
- result.analysis_time = 0.01
- result.error_count = 0
-
- root = Path("/test")
- output = export_llm_optimized(result, root)
-
- assert "## CODEBASE ANALYSIS SUMMARY" in output
- assert "### STRUCTURE" in output
- assert "### KEY COMPONENTS" in output
-
- def test_export_llm_with_max_tokens(self):
- func = FunctionInfo(
- name="test",
- node_type="function",
- start_line=1,
- end_line=5,
- )
-
- file_analysis = FileAnalysis(
- path=Path("/test/main.py"),
- language="python",
- size=100,
- lines=10,
- functions=[func],
- classes=[],
- )
-
- result = AnalysisResult()
- result.summary = {
- "total_files": 1,
- "total_functions": 1,
- "total_classes": 0,
- "total_dependencies": 0,
- "languages": {"python": 1},
- }
- result.files = [file_analysis]
- result.dependencies = []
- result.metrics = {}
- result.analysis_time = 0.01
- result.error_count = 0
-
- root = Path("/test")
- output = export_llm_optimized(result, root, max_tokens=100)
-
- tokens = estimate_tokens(output, "markdown")
- assert tokens <= 100 or "[Output truncated" in output
-
-
-class TestFormatterIntegration:
- """Integration tests for formatters."""
-
- def test_json_is_valid_json(self):
- func = FunctionInfo(name="test", node_type="func", start_line=1, end_line=10)
- file_analysis = FileAnalysis(
- path=Path("/test/main.py"),
- language="python",
- size=100,
- lines=10,
- functions=[func],
- )
-
- result = AnalysisResult()
- result.summary = {"total_files": 1}
- result.files = [file_analysis]
- result.dependencies = []
- result.metrics = {}
- result.analysis_time = 0
-
- root = Path("/test")
-
- json_output = export_json(result, root)
-
- data = json.loads(json_output)
- assert data is not None
-
- def test_markdown_is_readable(self):
- func = FunctionInfo(name="test", node_type="func", start_line=1, end_line=10)
- file_analysis = FileAnalysis(
- path=Path("/test/main.py"),
- language="python",
- size=100,
- lines=10,
- functions=[func],
- )
-
- result = AnalysisResult()
- result.summary = {"total_files": 1}
- result.files = [file_analysis]
- result.dependencies = []
- result.metrics = {}
- result.analysis_time = 0
-
- root = Path("/test")
-
- md_output = export_markdown(result, root)
-
- assert md_output is not None
- assert len(md_output) > 0
- assert "#" in md_output
-
- def test_llm_output_has_summary_first(self):
- func = FunctionInfo(name="test", node_type="func", start_line=1, end_line=10)
- file_analysis = FileAnalysis(
- path=Path("/test/main.py"),
- language="python",
- size=100,
- lines=10,
- functions=[func],
- )
-
- result = AnalysisResult()
- result.summary = {"total_files": 1}
- result.files = [file_analysis]
- result.dependencies = []
- result.metrics = {}
- result.analysis_time = 0
-
- root = Path("/test")
-
- output = export_llm_optimized(result, root)
-
- summary_pos = output.find("CODEBASE ANALYSIS SUMMARY")
- structure_pos = output.find("STRUCTURE")
-
- assert summary_pos < structure_pos
diff --git a/tests/unit/test_json_formatter.py b/tests/unit/test_json_formatter.py
deleted file mode 100644
index eff240f..0000000
--- a/tests/unit/test_json_formatter.py
+++ /dev/null
@@ -1,77 +0,0 @@
-from codesnap.output.json_formatter import JSONFormatter
-
-
-class TestJSONFormatter:
- def setup_method(self) -> None:
- self.formatter = JSONFormatter()
-
- def test_format_valid_result(self) -> None:
- result = {
- "files": [
- {
- "file": "test.py",
- "language": "python",
- "lines": 50,
- "functions": [{"name": "test_func", "start_line": 1, "end_line": 10}],
- "classes": [],
- "complexity": {"score": 5, "rating": "low"}
- }
- ],
- "dependency_graph": {
- "total_dependencies": 0,
- "orphaned_files": 0,
- "cycles_detected": 0,
- "cycle_details": [],
- "orphaned_details": [],
- "edges": [],
- "statistics": {}
- }
- }
- output = self.formatter.format(result)
- import json
- parsed = json.loads(output)
- assert "schema_version" in parsed
- assert "summary" in parsed
- assert "files" in parsed
- assert parsed["summary"]["total_files"] == 1
-
- def test_format_empty_result(self) -> None:
- result = {
- "files": [],
- "dependency_graph": {
- "total_dependencies": 0,
- "orphaned_files": 0,
- "cycles_detected": 0,
- "cycle_details": [],
- "orphaned_details": [],
- "edges": [],
- "statistics": {}
- }
- }
- output = self.formatter.format(result)
- import json
- parsed = json.loads(output)
- assert parsed["summary"]["total_files"] == 0
-
- def test_summary_includes_language_counts(self) -> None:
- result = {
- "files": [
- {"file": "a.py", "language": "python", "lines": 10, "functions": [], "classes": [], "complexity": {}},
- {"file": "b.js", "language": "javascript", "lines": 20, "functions": [], "classes": [], "complexity": {}},
- {"file": "c.py", "language": "python", "lines": 30, "functions": [], "classes": [], "complexity": {}}
- ],
- "dependency_graph": {
- "total_dependencies": 0,
- "orphaned_files": 0,
- "cycles_detected": 0,
- "cycle_details": [],
- "orphaned_details": [],
- "edges": [],
- "statistics": {}
- }
- }
- output = self.formatter.format(result)
- import json
- parsed = json.loads(output)
- assert parsed["summary"]["languages"]["python"] == 2
- assert parsed["summary"]["languages"]["javascript"] == 1
diff --git a/tests/unit/test_language_detector.py b/tests/unit/test_language_detector.py
deleted file mode 100644
index 43bd1ab..0000000
--- a/tests/unit/test_language_detector.py
+++ /dev/null
@@ -1,168 +0,0 @@
-"""Unit tests for language detection module."""
-
-from pathlib import Path
-
-from codesnap.core.language_detector import (
- EXTENSION_TO_LANGUAGE,
- detect_language,
- detect_language_by_extension,
- detect_language_by_shebang,
- get_language_info,
- get_supported_extensions,
- get_supported_languages,
-)
-
-
-class TestDetectLanguageByExtension:
- """Tests for extension-based language detection."""
-
- def test_python_extension_py(self):
- assert detect_language_by_extension(Path("test.py")) == "python"
-
- def test_python_extension_pyi(self):
- assert detect_language_by_extension(Path("test.pyi")) == "python"
-
- def test_javascript_extension_js(self):
- assert detect_language_by_extension(Path("test.js")) == "javascript"
-
- def test_typescript_extension_ts(self):
- assert detect_language_by_extension(Path("test.ts")) == "typescript"
-
- def test_go_extension(self):
- assert detect_language_by_extension(Path("main.go")) == "go"
-
- def test_rust_extension(self):
- assert detect_language_by_extension(Path("main.rs")) == "rust"
-
- def test_java_extension(self):
- assert detect_language_by_extension(Path("Main.java")) == "java"
-
- def test_cpp_extension(self):
- assert detect_language_by_extension(Path("test.cpp")) == "cpp"
- assert detect_language_by_extension(Path("test.hpp")) == "cpp"
-
- def test_ruby_extension(self):
- assert detect_language_by_extension(Path("script.rb")) == "ruby"
-
- def test_php_extension(self):
- assert detect_language_by_extension(Path("script.php")) == "php"
-
- def test_unknown_extension(self):
- assert detect_language_by_extension(Path("test.xyz")) is None
-
- def test_case_insensitive(self):
- assert detect_language_by_extension(Path("test.PY")) == "python"
- assert detect_language_by_extension(Path("test.JS")) == "javascript"
-
-
-class TestDetectLanguageByShebang:
- """Tests for shebang-based language detection."""
-
- def test_python_shebang(self):
- content = "#!/usr/bin/env python3\nprint('hello')"
- assert detect_language_by_shebang(content) == "python"
-
- def test_python_shebang_alt(self):
- content = "#!/usr/bin/python\nprint('hello')"
- assert detect_language_by_shebang(content) == "python"
-
- def test_node_shebang(self):
- content = "#!/usr/bin/env node\nconsole.log('hello')"
- assert detect_language_by_shebang(content) == "javascript"
-
- def test_ruby_shebang(self):
- content = "#!/usr/bin/env ruby\nputs 'hello'"
- assert detect_language_by_shebang(content) == "ruby"
-
- def test_php_shebang(self):
- content = "#!/usr/bin/env php\necho 'hello';"
- assert detect_language_by_shebang(content) == "php"
-
- def test_no_shebang(self):
- content = "print('hello')"
- assert detect_language_by_shebang(content) is None
-
- def test_empty_content(self):
- assert detect_language_by_shebang("") is None
-
-
-class TestDetectLanguage:
- """Tests for combined language detection."""
-
- def test_detection_by_extension(self):
- assert detect_language(Path("test.py")) == "python"
- assert detect_language(Path("test.js")) == "javascript"
-
- def test_detection_fallback_to_shebang(self):
- file_path = Path("script")
- assert detect_language(file_path, "#!/usr/bin/env python") == "python"
- assert detect_language(file_path, "#!/usr/bin/env node") == "javascript"
-
- def test_unknown_file_no_content(self):
- assert detect_language(Path("unknown.xyz")) is None
-
-
-class TestGetLanguageInfo:
- """Tests for language info retrieval."""
-
- def test_get_python_info(self):
- info = get_language_info("python")
- assert info is not None
- assert info.name == "python"
- assert ".py" in info.extensions
-
- def test_get_unknown_language(self):
- info = get_language_info("unknown")
- assert info is None
-
-
-class TestGetSupportedExtensions:
- """Tests for supported extensions."""
-
- def test_returns_set(self):
- extensions = get_supported_extensions()
- assert isinstance(extensions, set)
-
- def test_includes_common_extensions(self):
- extensions = get_supported_extensions()
- assert ".py" in extensions
- assert ".js" in extensions
- assert ".ts" in extensions
- assert ".go" in extensions
-
-
-class TestGetSupportedLanguages:
- """Tests for supported programming languages."""
-
- def test_returns_list(self):
- languages = get_supported_languages()
- assert isinstance(languages, list)
-
- def test_includes_main_languages(self):
- languages = get_supported_languages()
- assert "python" in languages
- assert "javascript" in languages
- assert "typescript" in languages
- assert "go" in languages
- assert "rust" in languages
- assert "java" in languages
-
- def test_excludes_config_formats(self):
- languages = get_supported_languages()
- assert "json" not in languages
- assert "yaml" not in languages
- assert "markdown" not in languages
-
-
-class TestExtensionToLanguage:
- """Tests for extension to language mapping."""
-
- def test_mapping_completeness(self):
- for _ext, lang in EXTENSION_TO_LANGUAGE.items():
- assert lang in ["python", "javascript", "typescript", "go", "rust",
- "java", "c", "cpp", "ruby", "php", "shell",
- "json", "yaml", "markdown"]
-
- def test_no_duplicate_extensions(self):
- extensions = list(EXTENSION_TO_LANGUAGE.keys())
- assert len(extensions) == len(set(extensions))
diff --git a/tests/unit/test_llm_formatter.py b/tests/unit/test_llm_formatter.py
deleted file mode 100644
index efdc30d..0000000
--- a/tests/unit/test_llm_formatter.py
+++ /dev/null
@@ -1,112 +0,0 @@
-from codesnap.output.llm_formatter import LLMFormatter
-
-
-class TestLLMFormatter:
- def setup_method(self) -> None:
- self.formatter = LLMFormatter(max_tokens=1000)
-
- def test_format_valid_result(self) -> None:
- result = {
- "files": [
- {
- "file": "test.py",
- "language": "python",
- "lines": 50,
- "functions": [{"name": "test_func", "start_line": 1, "end_line": 10, "parameters": [], "return_type": "str"}],
- "classes": [],
- "complexity": {"score": 5, "rating": "low"}
- }
- ],
- "dependency_graph": {
- "total_dependencies": 0,
- "orphaned_files": 0,
- "cycles_detected": 0,
- "cycle_details": [],
- "orphaned_details": [],
- "edges": [],
- "statistics": {}
- }
- }
- output = self.formatter.format(result)
- assert "## Codebase Summary" in output
- assert "### Key Files" in output
- assert "### Classes and Functions" in output
- assert "### Dependencies" in output
-
- def test_respects_token_limit(self) -> None:
- result = {
- "files": [],
- "dependency_graph": {
- "total_dependencies": 0,
- "orphaned_files": 0,
- "cycles_detected": 0,
- "cycle_details": [],
- "orphaned_details": [],
- "edges": [],
- "statistics": {}
- }
- }
- output = self.formatter.format(result)
- max_chars = 1000 * 4
- assert len(output) <= max_chars + 100
-
- def test_includes_high_level_summary(self) -> None:
- result = {
- "files": [
- {"file": "a.py", "language": "python", "lines": 50, "functions": [], "classes": [], "complexity": {}},
- {"file": "b.py", "language": "python", "lines": 30, "functions": [], "classes": [], "complexity": {}},
- {"file": "c.js", "language": "javascript", "lines": 20, "functions": [], "classes": [], "complexity": {}}
- ],
- "dependency_graph": {
- "total_dependencies": 0,
- "orphaned_files": 0,
- "cycles_detected": 0,
- "cycle_details": [],
- "orphaned_details": [],
- "edges": [],
- "statistics": {}
- }
- }
- output = self.formatter.format(result)
- assert "python" in output.lower()
- assert "3 files" in output or "files" in output
-
- def test_compresses_detailed_file_list(self) -> None:
- result = {
- "files": [
- {"file": f"file{i}.py", "language": "python", "lines": 10,
- "functions": [{"name": f"func{i}a"}, {"name": f"func{i}b"}, {"name": f"func{i}c"}],
- "classes": [], "complexity": {}}
- for i in range(10)
- ],
- "dependency_graph": {
- "total_dependencies": 0,
- "orphaned_files": 0,
- "cycles_detected": 0,
- "cycle_details": [],
- "orphaned_details": [],
- "edges": [],
- "statistics": {}
- }
- }
- output = self.formatter.format(result)
- assert "Detailed File List (compressed)" in output
-
- def test_warns_about_cycles(self) -> None:
- result = {
- "files": [
- {"file": "a.py", "language": "python", "lines": 10, "functions": [], "classes": [], "complexity": {}},
- {"file": "b.py", "language": "python", "lines": 10, "functions": [], "classes": [], "complexity": {}}
- ],
- "dependency_graph": {
- "total_dependencies": 2,
- "orphaned_files": 0,
- "cycles_detected": 1,
- "cycle_details": [["a.py", "b.py", "a.py"]],
- "orphaned_details": [],
- "edges": [],
- "statistics": {}
- }
- }
- output = self.formatter.format(result)
- assert "circular" in output.lower() or "cycle" in output.lower()
diff --git a/tests/unit/test_markdown_formatter.py b/tests/unit/test_markdown_formatter.py
deleted file mode 100644
index 65e116c..0000000
--- a/tests/unit/test_markdown_formatter.py
+++ /dev/null
@@ -1,117 +0,0 @@
-from codesnap.output.markdown_formatter import MarkdownFormatter
-
-
-class TestMarkdownFormatter:
- def setup_method(self) -> None:
- self.formatter = MarkdownFormatter()
-
- def test_format_valid_result(self) -> None:
- result = {
- "files": [
- {
- "file": "test.py",
- "language": "python",
- "lines": 50,
- "functions": [{"name": "test_func", "start_line": 1, "end_line": 10, "parameters": [], "return_type": "str"}],
- "classes": [],
- "complexity": {"score": 5, "rating": "low"}
- }
- ],
- "dependency_graph": {
- "total_dependencies": 0,
- "orphaned_files": 0,
- "cycles_detected": 0,
- "cycle_details": [],
- "orphaned_details": [],
- "edges": [],
- "statistics": {}
- }
- }
- output = self.formatter.format(result)
- assert "# CodeSnap Analysis Report" in output
- assert "## Overview" in output
- assert "## File Structure" in output
- assert "## Key Functions" in output
- assert "## Dependencies" in output
- assert "## Complexity Metrics" in output
-
- def test_format_empty_result(self) -> None:
- result = {
- "files": [],
- "dependency_graph": {
- "total_dependencies": 0,
- "orphaned_files": 0,
- "cycles_detected": 0,
- "cycle_details": [],
- "orphaned_details": [],
- "edges": [],
- "statistics": {}
- }
- }
- output = self.formatter.format(result)
- assert "Total Files" in output
-
- def test_includes_language_breakdown(self) -> None:
- result = {
- "files": [
- {"file": "a.py", "language": "python", "lines": 10, "functions": [], "classes": [], "complexity": {}},
- {"file": "b.js", "language": "javascript", "lines": 20, "functions": [], "classes": [], "complexity": {}}
- ],
- "dependency_graph": {
- "total_dependencies": 0,
- "orphaned_files": 0,
- "cycles_detected": 0,
- "cycle_details": [],
- "orphaned_details": [],
- "edges": [],
- "statistics": {}
- }
- }
- output = self.formatter.format(result)
- assert "python: 1" in output or "python: 2" in output
- assert "javascript:" in output
-
- def test_shows_circular_dependencies(self) -> None:
- result = {
- "files": [
- {"file": "a.py", "language": "python", "lines": 10, "functions": [], "classes": [], "complexity": {}},
- {"file": "b.py", "language": "python", "lines": 10, "functions": [], "classes": [], "complexity": {}}
- ],
- "dependency_graph": {
- "total_dependencies": 2,
- "orphaned_files": 0,
- "cycles_detected": 1,
- "cycle_details": [["a.py", "b.py", "a.py"]],
- "orphaned_details": [],
- "edges": [{"from": "a.py", "to": "b.py"}, {"from": "b.py", "to": "a.py"}],
- "statistics": {}
- }
- }
- output = self.formatter.format(result)
- assert "Circular Dependencies Detected" in output
-
- def test_shows_high_complexity_files(self) -> None:
- result = {
- "files": [
- {
- "file": "complex.py",
- "language": "python",
- "lines": 100,
- "functions": [],
- "classes": [],
- "complexity": {"score": 55, "rating": "high"}
- }
- ],
- "dependency_graph": {
- "total_dependencies": 0,
- "orphaned_files": 0,
- "cycles_detected": 0,
- "cycle_details": [],
- "orphaned_details": [],
- "edges": [],
- "statistics": {}
- }
- }
- output = self.formatter.format(result)
- assert "High Complexity Files" in output
- assert "complex.py" in output