"""Integration tests for multi-language detection and processing."""
import pytest
from pathlib import Path
import json
import sys
# Add parent directory to path for imports
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from testing_agent import (
detect_languages,
detect_test_frameworks,
plan_test_runs,
write_scan_snapshot,
build_test_plan,
build_coverage_map,
)
class TestMultiLanguageDetection:
"""Test multi-language project detection."""
def test_python_project_detection(self, tmp_path):
"""Test detection of a Python-only project."""
# Create Python files
(tmp_path / "app.py").write_text("def main(): pass")
(tmp_path / "utils.py").write_text("def helper(): pass")
test_dir = tmp_path / "tests"
test_dir.mkdir()
(test_dir / "test_app.py").write_text("def test_main(): pass")
languages = detect_languages(tmp_path)
assert "python" in languages
assert languages["python"].source_files == 2
assert languages["python"].test_files == 1
assert languages["python"].status == "detected"
def test_javascript_project_detection(self, tmp_path):
"""Test detection of a JavaScript/TypeScript project."""
# Create JS/TS files
(tmp_path / "index.js").write_text("export default function() {}")
(tmp_path / "app.ts").write_text("export const app = () => {}")
(tmp_path / "component.jsx").write_text("export const Component = () => <div/>")
test_dir = tmp_path / "__tests__"
test_dir.mkdir()
(test_dir / "app.test.js").write_text("test('app', () => {})")
languages = detect_languages(tmp_path)
assert "js_ts" in languages
assert languages["js_ts"].source_files == 3
assert languages["js_ts"].test_files == 1
def test_go_project_detection(self, tmp_path):
"""Test detection of a Go project."""
(tmp_path / "main.go").write_text("package main\nfunc main() {}")
(tmp_path / "utils.go").write_text("package main\nfunc helper() {}")
(tmp_path / "main_test.go").write_text("package main\nfunc TestMain(t *testing.T) {}")
languages = detect_languages(tmp_path)
assert "go" in languages
assert languages["go"].source_files == 2
assert languages["go"].test_files == 1
def test_rust_project_detection(self, tmp_path):
"""Test detection of a Rust project."""
src = tmp_path / "src"
src.mkdir()
(src / "main.rs").write_text("fn main() {}")
(src / "lib.rs").write_text("pub fn helper() {}")
tests = tmp_path / "tests"
tests.mkdir()
(tests / "integration_test.rs").write_text("fn test_integration() {}")
languages = detect_languages(tmp_path)
assert "rust" in languages
assert languages["rust"].source_files == 2
assert languages["rust"].test_files == 1
def test_java_project_detection(self, tmp_path):
"""Test detection of a Java project."""
src = tmp_path / "src" / "main" / "java"
src.mkdir(parents=True)
(src / "Main.java").write_text("public class Main {}")
(src / "Utils.java").write_text("public class Utils {}")
test = tmp_path / "src" / "test" / "java"
test.mkdir(parents=True)
(test / "MainTest.java").write_text("public class MainTest {}")
languages = detect_languages(tmp_path)
assert "java" in languages
assert languages["java"].source_files == 2
assert languages["java"].test_files == 1
def test_ruby_project_detection(self, tmp_path):
"""Test detection of a Ruby project."""
lib = tmp_path / "lib"
lib.mkdir()
(lib / "app.rb").write_text("class App\nend")
(lib / "helper.rb").write_text("class Helper\nend")
spec = tmp_path / "spec"
spec.mkdir()
(spec / "app_spec.rb").write_text("RSpec.describe App do\nend")
languages = detect_languages(tmp_path)
assert "ruby" in languages
assert languages["ruby"].source_files == 2
assert languages["ruby"].test_files == 1
def test_polyglot_project_detection(self, tmp_path):
"""Test detection of a project with multiple languages."""
# Python
(tmp_path / "backend.py").write_text("def api(): pass")
py_test = tmp_path / "tests"
py_test.mkdir()
(py_test / "test_backend.py").write_text("def test_api(): pass")
# JavaScript
(tmp_path / "frontend.js").write_text("export const app = {}")
js_test = tmp_path / "__tests__"
js_test.mkdir()
(js_test / "frontend.test.js").write_text("test('frontend', () => {})")
# Go
(tmp_path / "service.go").write_text("package main")
(tmp_path / "service_test.go").write_text("package main")
languages = detect_languages(tmp_path)
# All three languages should be detected
assert languages["python"].status == "detected"
assert languages["js_ts"].status == "detected"
assert languages["go"].status == "detected"
assert languages["python"].source_files >= 1
assert languages["js_ts"].source_files >= 1
assert languages["go"].source_files >= 1
class TestTestFrameworkDetection:
"""Test test framework detection for various languages."""
def test_detect_jest_from_package_json(self, tmp_path):
"""Test Jest detection from package.json."""
package_json = {
"devDependencies": {"jest": "^29.0.0"},
"scripts": {"test": "jest"}
}
(tmp_path / "package.json").write_text(json.dumps(package_json))
frameworks = detect_test_frameworks(tmp_path)
# Returns full command, not just framework name
assert "jest" in frameworks.get("js_ts", "")
def test_detect_maven_from_pom_xml(self, tmp_path):
"""Test Maven detection from pom.xml."""
pom = """<?xml version="1.0"?>
<project>
<modelVersion>4.0.0</modelVersion>
</project>"""
(tmp_path / "pom.xml").write_text(pom)
frameworks = detect_test_frameworks(tmp_path)
# Returns full command
assert "mvn" in frameworks.get("java", "")
def test_detect_cargo_from_cargo_toml(self, tmp_path):
"""Test Cargo detection from Cargo.toml."""
cargo = """[package]
name = "myapp"
version = "0.1.0"
"""
(tmp_path / "Cargo.toml").write_text(cargo)
frameworks = detect_test_frameworks(tmp_path)
# Returns full command
assert "cargo" in frameworks.get("rust", "")
def test_detect_rspec_from_gemfile(self, tmp_path):
"""Test RSpec detection from Gemfile."""
gemfile = """source 'https://rubygems.org'
gem 'rspec'
"""
(tmp_path / "Gemfile").write_text(gemfile)
frameworks = detect_test_frameworks(tmp_path)
# Returns full command
assert "rspec" in frameworks.get("ruby", "")
class TestEndToEndWorkflow:
"""Test complete end-to-end workflows."""
def test_complete_python_workflow(self, tmp_path):
"""Test complete workflow for Python project."""
# Setup Python project
(tmp_path / "calculator.py").write_text("""
def add(a, b):
return a + b
def subtract(a, b):
return a - b
""")
test_dir = tmp_path / "tests"
test_dir.mkdir()
(test_dir / "test_calculator.py").write_text("""
def test_add():
from calculator import add
assert add(2, 3) == 5
def test_subtract():
from calculator import subtract
assert subtract(5, 3) == 2
""")
# Run detection
languages = detect_languages(tmp_path)
assert languages["python"].source_files == 1
assert languages["python"].test_files == 1
# Write snapshot
snapshot = write_scan_snapshot(tmp_path, languages)
assert (tmp_path / ".mcp_scan_snapshot.json").exists()
assert snapshot["languages"]["python"]["source_files"] == 1
# Build test plan
plan_path = build_test_plan(tmp_path, languages)
assert Path(plan_path).exists()
plan_content = Path(plan_path).read_text()
assert "Python Tests" in plan_content
# Plan test runs
planned = plan_test_runs(languages, tmp_path)
assert len(planned) > 0
python_plan = next((p for p in planned if "framework" in p and p["framework"] == "pytest"), None)
assert python_plan is not None
def test_multi_language_snapshot_generation(self, tmp_path):
"""Test snapshot generation for multi-language projects."""
# Create files for multiple languages
(tmp_path / "app.py").write_text("def main(): pass")
(tmp_path / "index.js").write_text("export default {}")
(tmp_path / "main.go").write_text("package main")
languages = detect_languages(tmp_path)
snapshot = write_scan_snapshot(tmp_path, languages)
# Verify snapshot structure (actual field names)
assert "scanned_at" in snapshot
assert "root" in snapshot
assert "languages" in snapshot
assert len(snapshot["languages"]) == 6 # All supported languages
# Check specific languages
assert snapshot["languages"]["python"]["status"] == "detected"
assert snapshot["languages"]["js_ts"]["status"] == "detected"
assert snapshot["languages"]["go"]["status"] == "detected"
def test_coverage_map_generation(self, tmp_path):
"""Test coverage map generation."""
# Setup simple project
(tmp_path / "app.py").write_text("def main(): pass")
test_dir = tmp_path / "tests"
test_dir.mkdir()
(test_dir / "test_app.py").write_text("def test_main(): pass")
languages = detect_languages(tmp_path)
snapshot = write_scan_snapshot(tmp_path, languages)
# Create mock report
report = {
"executed_at": "2025-12-12T00:00:00Z",
"root": str(tmp_path),
"summary": {"total_runs": 1, "success": 1, "failed": 0, "skipped": 0},
"runs": [
{
"framework": "pytest",
"status": "success",
"exit_code": 0,
"reason": None
}
]
}
coverage_path = build_coverage_map(tmp_path, snapshot, report)
assert Path(coverage_path).exists()
coverage_content = Path(coverage_path).read_text()
assert "Test Coverage Map" in coverage_content
assert "python" in coverage_content.lower()
assert "pytest" in coverage_content
class TestEdgeCases:
"""Test edge cases and error conditions."""
def test_empty_project_detection(self, tmp_path):
"""Test detection on an empty project."""
languages = detect_languages(tmp_path)
# All languages should have status "none"
for lang, detection in languages.items():
assert detection.source_files == 0
assert detection.test_files == 0
assert detection.status == "none"
def test_project_with_only_excluded_directories(self, tmp_path):
"""Test that excluded directories are properly ignored."""
# Create files only in excluded directories
venv = tmp_path / ".venv"
venv.mkdir()
(venv / "module.py").write_text("def excluded(): pass")
node_modules = tmp_path / "node_modules"
node_modules.mkdir()
(node_modules / "package.js").write_text("module.exports = {}")
languages = detect_languages(tmp_path)
# Should detect no files
assert languages["python"].source_files == 0
assert languages["js_ts"].source_files == 0
def test_deeply_nested_file_detection(self, tmp_path):
"""Test detection of deeply nested files."""
deep_path = tmp_path / "a" / "b" / "c" / "d" / "e"
deep_path.mkdir(parents=True)
(deep_path / "deep.py").write_text("def deep_function(): pass")
languages = detect_languages(tmp_path)
assert languages["python"].source_files == 1
def test_mixed_source_and_test_files(self, tmp_path):
"""Test projects with various naming patterns."""
# Python files with different test patterns
(tmp_path / "app.py").write_text("def app(): pass")
(tmp_path / "test_app.py").write_text("def test_app(): pass")
(tmp_path / "app_test.py").write_text("def test_app_alt(): pass")
tests = tmp_path / "tests"
tests.mkdir()
(tests / "unit_test.py").write_text("def test_unit(): pass")
languages = detect_languages(tmp_path)
# app.py should be source, others should be tests
assert languages["python"].source_files == 1
assert languages["python"].test_files == 3
def test_special_characters_in_paths(self, tmp_path):
"""Test handling of special characters in file paths."""
special_dir = tmp_path / "my-app" / "src_v2.0"
special_dir.mkdir(parents=True)
(special_dir / "app-v2.py").write_text("def v2(): pass")
languages = detect_languages(tmp_path)
assert languages["python"].source_files == 1
def test_symlinks_handling(self, tmp_path):
"""Test that symlinks are handled correctly."""
real_file = tmp_path / "real.py"
real_file.write_text("def real(): pass")
link_file = tmp_path / "link.py"
try:
link_file.symlink_to(real_file)
languages = detect_languages(tmp_path)
# Should detect 2 files (real + symlink) or handle appropriately
assert languages["python"].source_files >= 1
except OSError:
# Symlinks might not be supported on all systems
pytest.skip("Symlinks not supported on this system")
class TestSnapshotPersistence:
"""Test snapshot file persistence and format."""
def test_snapshot_is_valid_json(self, tmp_path):
"""Test that snapshot is valid JSON."""
(tmp_path / "app.py").write_text("def app(): pass")
languages = detect_languages(tmp_path)
snapshot = write_scan_snapshot(tmp_path, languages)
snapshot_file = tmp_path / ".mcp_scan_snapshot.json"
assert snapshot_file.exists()
# Verify it's valid JSON
with open(snapshot_file) as f:
loaded = json.load(f)
assert loaded == snapshot
def test_snapshot_contains_required_fields(self, tmp_path):
"""Test that snapshot contains all required fields."""
languages = detect_languages(tmp_path)
snapshot = write_scan_snapshot(tmp_path, languages)
# Actual field names used in implementation
required_fields = ["scanned_at", "root", "languages"]
for field in required_fields:
assert field in snapshot
# Check language entries
for lang_key, lang_data in snapshot["languages"].items():
assert "language" in lang_data
assert "source_files" in lang_data
assert "test_files" in lang_data
assert "status" in lang_data