#![cfg_attr(coverage_nightly, coverage(off))]
use crate::services::language_analyzer::{
AnalysisOptions, AnalysisResult, AnalysisType, FileMetadata, LanguageAnalysisRequest,
LanguageAnalysisResult, LanguageAnalyzer, OutputFormat,
};
use crate::services::language_registry::Language;
use std::path::PathBuf;
#[test]
fn test_analysis_options_default() {
let opts = AnalysisOptions::default();
assert_eq!(opts.complexity_threshold, 20);
assert!(opts.include_comments);
assert!(!opts.include_tests);
assert!(opts.parallel_analysis);
matches!(opts.output_format, OutputFormat::Json);
}
#[test]
fn test_analysis_options_custom() {
let opts = AnalysisOptions {
complexity_threshold: 10,
include_comments: false,
include_tests: true,
parallel_analysis: false,
output_format: OutputFormat::Markdown,
};
assert_eq!(opts.complexity_threshold, 10);
assert!(!opts.include_comments);
assert!(opts.include_tests);
assert!(!opts.parallel_analysis);
}
#[test]
fn test_analysis_options_serde() {
let opts = AnalysisOptions::default();
let json = serde_json::to_string(&opts).unwrap();
let back: AnalysisOptions = serde_json::from_str(&json).unwrap();
assert_eq!(back.complexity_threshold, 20);
}
#[test]
fn test_analysis_options_clone() {
let opts = AnalysisOptions::default();
let cloned = opts.clone();
assert_eq!(cloned.complexity_threshold, opts.complexity_threshold);
}
#[test]
fn test_analysis_type_serde_roundtrip() {
let types = vec![
AnalysisType::Complexity,
AnalysisType::Satd,
AnalysisType::DeadCode,
AnalysisType::Security,
AnalysisType::Style,
AnalysisType::Documentation,
AnalysisType::Dependencies,
AnalysisType::Metrics,
];
for ty in types {
let json = serde_json::to_string(&ty).unwrap();
let back: AnalysisType = serde_json::from_str(&json).unwrap();
let _ = format!("{:?}", back);
}
}
#[test]
fn test_analysis_type_snake_case_serde() {
let json = serde_json::to_string(&AnalysisType::DeadCode).unwrap();
assert_eq!(json, "\"dead_code\"");
}
#[test]
fn test_output_format_serde_roundtrip() {
let formats = vec![
OutputFormat::Json,
OutputFormat::Yaml,
OutputFormat::Plain,
OutputFormat::Markdown,
];
for fmt in formats {
let json = serde_json::to_string(&fmt).unwrap();
let back: OutputFormat = serde_json::from_str(&json).unwrap();
let _ = format!("{:?}", back);
}
}
#[test]
fn test_language_analysis_request_construction() {
let req = LanguageAnalysisRequest {
path: PathBuf::from("src/main.rs"),
language: Some(Language::Rust),
analysis_types: vec![AnalysisType::Complexity, AnalysisType::Satd],
options: AnalysisOptions::default(),
};
assert_eq!(req.path, PathBuf::from("src/main.rs"));
assert_eq!(req.language, Some(Language::Rust));
assert_eq!(req.analysis_types.len(), 2);
}
#[test]
fn test_language_analysis_request_no_language() {
let req = LanguageAnalysisRequest {
path: PathBuf::from("unknown.xyz"),
language: None,
analysis_types: vec![AnalysisType::Metrics],
options: AnalysisOptions::default(),
};
assert!(req.language.is_none());
}
#[test]
fn test_language_analysis_request_serde() {
let req = LanguageAnalysisRequest {
path: PathBuf::from("test.py"),
language: Some(Language::Python),
analysis_types: vec![AnalysisType::Security],
options: AnalysisOptions::default(),
};
let json = serde_json::to_string(&req).unwrap();
let back: LanguageAnalysisRequest = serde_json::from_str(&json).unwrap();
assert_eq!(back.path, req.path);
}
#[test]
fn test_analysis_result_success() {
let result = AnalysisResult {
analysis_type: AnalysisType::Complexity,
success: true,
data: serde_json::json!({"cyclomatic": 5}),
error: None,
};
assert!(result.success);
assert!(result.error.is_none());
}
#[test]
fn test_analysis_result_failure() {
let result = AnalysisResult {
analysis_type: AnalysisType::Security,
success: false,
data: serde_json::Value::Null,
error: Some("Parse error".to_string()),
};
assert!(!result.success);
assert_eq!(result.error.as_deref(), Some("Parse error"));
}
#[test]
fn test_analysis_result_serde() {
let result = AnalysisResult {
analysis_type: AnalysisType::Metrics,
success: true,
data: serde_json::json!({"lines": 100}),
error: None,
};
let json = serde_json::to_string(&result).unwrap();
let back: AnalysisResult = serde_json::from_str(&json).unwrap();
assert!(back.success);
}
#[test]
fn test_file_metadata_construction() {
let meta = FileMetadata {
lines_total: 200,
lines_code: 150,
lines_comment: 30,
lines_blank: 20,
file_size_bytes: 4096,
detected_language: Language::Rust,
confidence: 0.95,
};
assert_eq!(meta.lines_total, 200);
assert_eq!(meta.lines_code + meta.lines_comment + meta.lines_blank, 200);
assert!(meta.confidence > 0.9);
}
#[test]
fn test_file_metadata_serde() {
let meta = FileMetadata {
lines_total: 100,
lines_code: 80,
lines_comment: 10,
lines_blank: 10,
file_size_bytes: 2048,
detected_language: Language::Python,
confidence: 1.0,
};
let json = serde_json::to_string(&meta).unwrap();
let back: FileMetadata = serde_json::from_str(&json).unwrap();
assert_eq!(back.lines_total, 100);
}
#[test]
fn test_language_analysis_result_construction() {
let result = LanguageAnalysisResult {
path: PathBuf::from("src/lib.rs"),
language: Language::Rust,
analysis_results: vec![AnalysisResult {
analysis_type: AnalysisType::Complexity,
success: true,
data: serde_json::json!({"total": 10}),
error: None,
}],
metadata: FileMetadata {
lines_total: 500,
lines_code: 400,
lines_comment: 60,
lines_blank: 40,
file_size_bytes: 10240,
detected_language: Language::Rust,
confidence: 1.0,
},
processing_time_ms: 42,
};
assert_eq!(result.analysis_results.len(), 1);
assert_eq!(result.processing_time_ms, 42);
}
#[test]
fn test_language_analyzer_new() {
let analyzer = LanguageAnalyzer::new();
let _ = analyzer.supported_languages();
}
#[test]
fn test_language_analyzer_default() {
let analyzer = LanguageAnalyzer::default();
let langs = analyzer.supported_languages();
assert!(!langs.is_empty());
}
#[test]
fn test_language_analyzer_supported_languages_not_empty() {
let analyzer = LanguageAnalyzer::new();
let langs = analyzer.supported_languages();
assert!(langs.len() > 10); }
#[test]
fn test_language_analyzer_supports_analysis_complexity_rust() {
let analyzer = LanguageAnalyzer::new();
assert!(analyzer.supports_analysis(Language::Rust, &AnalysisType::Complexity));
}
#[test]
fn test_language_analyzer_supports_analysis_satd() {
let analyzer = LanguageAnalyzer::new();
assert!(analyzer.supports_analysis(Language::Rust, &AnalysisType::Satd));
assert!(analyzer.supports_analysis(Language::Python, &AnalysisType::Satd));
}
#[test]
fn test_language_analyzer_supports_analysis_security() {
let analyzer = LanguageAnalyzer::new();
assert!(analyzer.supports_analysis(Language::Python, &AnalysisType::Security));
assert!(analyzer.supports_analysis(Language::JavaScript, &AnalysisType::Security));
}
#[test]
fn test_language_analyzer_supports_analysis_style() {
let analyzer = LanguageAnalyzer::new();
assert!(analyzer.supports_analysis(Language::Rust, &AnalysisType::Style));
}
#[test]
fn test_language_analyzer_supports_analysis_documentation() {
let analyzer = LanguageAnalyzer::new();
let _ = analyzer.supports_analysis(Language::Rust, &AnalysisType::Documentation);
let _ = analyzer.supports_analysis(Language::Python, &AnalysisType::Documentation);
}
#[test]
fn test_language_analyzer_supports_analysis_dependencies() {
let analyzer = LanguageAnalyzer::new();
assert!(analyzer.supports_analysis(Language::Rust, &AnalysisType::Dependencies));
assert!(analyzer.supports_analysis(Language::Python, &AnalysisType::Dependencies));
}
#[test]
fn test_language_analyzer_supports_analysis_metrics() {
let analyzer = LanguageAnalyzer::new();
assert!(analyzer.supports_analysis(Language::Rust, &AnalysisType::Metrics));
}
#[tokio::test]
async fn test_language_analyzer_analyze_rust_file() {
let analyzer = LanguageAnalyzer::new();
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/lib.rs");
if path.exists() {
let result = analyzer
.analyze_file(&path, vec![AnalysisType::Metrics, AnalysisType::Complexity])
.await;
assert!(result.is_ok());
let result = result.unwrap();
assert_eq!(result.language, Language::Rust);
assert!(result.metadata.lines_total > 0);
assert_eq!(result.analysis_results.len(), 2);
}
}
#[tokio::test]
async fn test_language_analyzer_analyze_nonexistent_file() {
let analyzer = LanguageAnalyzer::new();
let result = analyzer
.analyze_file(
&PathBuf::from("/nonexistent/file.rs"),
vec![AnalysisType::Metrics],
)
.await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_language_analyzer_analyze_all_types() {
let analyzer = LanguageAnalyzer::new();
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/lib.rs");
if path.exists() {
let result = analyzer
.analyze_file(
&path,
vec![
AnalysisType::Complexity,
AnalysisType::Satd,
AnalysisType::Security,
AnalysisType::Style,
AnalysisType::Documentation,
AnalysisType::Dependencies,
AnalysisType::Metrics,
AnalysisType::DeadCode,
],
)
.await;
assert!(result.is_ok());
let result = result.unwrap();
assert_eq!(result.analysis_results.len(), 8);
let succeeded = result.analysis_results.iter().filter(|r| r.success).count();
assert!(
succeeded >= 5,
"Expected at least 5 successes, got {succeeded}"
);
}
}
#[tokio::test]
async fn test_language_analyzer_analyze_python_content() {
let analyzer = LanguageAnalyzer::new();
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("test.py");
std::fs::write(
&path,
r#"
import os
import sys
# TODO: Fix this function
def hello(name):
if name:
for i in range(10):
print(f"Hello {name} {i}")
else:
print("Hello world")
class MyClass:
def __init__(self):
self.value = 42
# FIXME: hardcoded value
"#,
)
.unwrap();
let result = analyzer
.analyze_file(
&path,
vec![
AnalysisType::Complexity,
AnalysisType::Satd,
AnalysisType::Style,
AnalysisType::Dependencies,
AnalysisType::Metrics,
],
)
.await
.unwrap();
assert_eq!(result.language, Language::Python);
assert!(result.metadata.lines_total > 10);
assert!(result.metadata.lines_comment > 0);
assert!(result.metadata.lines_code > 0);
assert!(!result.analysis_results.is_empty());
let succeeded = result.analysis_results.iter().filter(|r| r.success).count();
assert!(
succeeded >= 3,
"Expected at least 3 successes, got {succeeded}"
);
}
#[tokio::test]
async fn test_language_analyzer_analyze_javascript_content() {
let analyzer = LanguageAnalyzer::new();
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("test.js");
std::fs::write(
&path,
r#"
const fs = require('fs');
import { useState } from 'react';
// HACK: workaround for bug
function processData(data) {
if (data && data.length > 0) {
for (let i = 0; i < data.length; i++) {
eval(data[i]); // security issue
}
}
}
module.exports = { processData };
"#,
)
.unwrap();
let result = analyzer
.analyze_file(
&path,
vec![
AnalysisType::Security,
AnalysisType::Satd,
AnalysisType::Dependencies,
],
)
.await
.unwrap();
assert_eq!(result.language, Language::JavaScript);
let sec_result = result
.analysis_results
.iter()
.find(|r| matches!(r.analysis_type, AnalysisType::Security))
.unwrap();
assert!(sec_result.success);
}