use crate::integrations::analyze_client::AnalyzeClient;
use crate::integrations::analyze_client::AnalyzeClientError;
use super::client::{SubprocessAnalyzeClient, spawn_analyze_review};
use super::{
SubprocessComplexity, SubprocessFileReview, SubprocessReviewReport, SubprocessSmellHit,
map_report,
};
#[test]
fn subprocess_client_binary_accessor() {
let client = SubprocessAnalyzeClient::new("trusty-analyze", "http://127.0.0.1:7878")
.expect("TLS init should succeed");
assert_eq!(client.binary(), "trusty-analyze");
}
#[tokio::test]
async fn subprocess_client_health_check_fails_gracefully() {
let client = SubprocessAnalyzeClient::new("trusty-analyze", "http://127.0.0.1:1")
.expect("TLS init should succeed");
let result = client.health().await;
assert!(
result.is_err(),
"health must return Err when trusty-search is down"
);
assert!(
matches!(result.unwrap_err(), AnalyzeClientError::Unavailable(_)),
"expected Unavailable variant"
);
}
#[tokio::test]
async fn subprocess_client_has_analysis_returns_false_on_error() {
let client = SubprocessAnalyzeClient::new("trusty-analyze", "http://127.0.0.1:1")
.expect("TLS init should succeed");
assert!(
!client.has_analysis("main").await,
"has_analysis must return false on error"
);
}
#[tokio::test]
async fn subprocess_client_hotspots_returns_empty() {
let client = SubprocessAnalyzeClient::new("trusty-analyze", "http://127.0.0.1:7878")
.expect("TLS init should succeed");
let result = client.complexity_hotspots("main", Some(10)).await.unwrap();
assert!(
result.is_empty(),
"subprocess model always returns empty hotspots"
);
}
#[tokio::test]
async fn subprocess_client_smells_returns_empty() {
let client = SubprocessAnalyzeClient::new("trusty-analyze", "http://127.0.0.1:7878")
.expect("TLS init should succeed");
let result = client.smells("main").await.unwrap();
assert!(
result.is_empty(),
"subprocess model always returns empty smells"
);
}
#[tokio::test]
async fn subprocess_client_binary_not_found() {
let client =
SubprocessAnalyzeClient::new("trusty-analyze-nonexistent-binary", "http://127.0.0.1:1")
.expect("TLS init should succeed");
let result = client
.analyze_diff("+++ b/foo.rs\n@@ -0,0 +1,1 @@\n+fn f(){}\n", "idx")
.await;
assert!(result.is_err(), "missing binary must error");
assert!(
matches!(result.unwrap_err(), AnalyzeClientError::Unavailable(_)),
"expected Unavailable for missing binary"
);
}
#[test]
fn map_report_to_hotspots_and_smells() {
let report = SubprocessReviewReport {
files: vec![
SubprocessFileReview {
path: "src/foo.rs".to_string(),
complexity: SubprocessComplexity {
cyclomatic: 12,
cognitive: 8,
},
smells: vec![
SubprocessSmellHit {
category: "long_method".to_string(),
line: 42,
severity: "medium".to_string(),
},
SubprocessSmellHit {
category: "deep_nesting".to_string(),
line: 55,
severity: "high".to_string(),
},
],
},
SubprocessFileReview {
path: "src/bar.rs".to_string(),
complexity: SubprocessComplexity {
cyclomatic: 3,
cognitive: 2,
},
smells: vec![],
},
],
};
let (hotspots, smells) = map_report(&report);
assert_eq!(hotspots.len(), 2);
assert_eq!(hotspots[0].file, "src/foo.rs");
assert_eq!(hotspots[0].cyclomatic, 12);
assert_eq!(hotspots[0].cognitive, 8);
assert_eq!(hotspots[1].file, "src/bar.rs");
assert_eq!(hotspots[1].cyclomatic, 3);
assert_eq!(smells.len(), 2);
assert_eq!(smells[0].file, "src/foo.rs");
assert_eq!(smells[0].category, "long_method");
assert_eq!(smells[0].line, Some(42));
assert_eq!(smells[0].severity, "medium");
assert_eq!(smells[1].category, "deep_nesting");
assert_eq!(smells[1].line, Some(55));
assert_eq!(smells[1].severity, "high");
}
#[test]
fn map_report_skips_zero_complexity_hotspots() {
let report = SubprocessReviewReport {
files: vec![SubprocessFileReview {
path: "src/trivial.rs".to_string(),
complexity: SubprocessComplexity {
cyclomatic: 0,
cognitive: 0,
},
smells: vec![],
}],
};
let (hotspots, smells) = map_report(&report);
assert!(hotspots.is_empty(), "zero-complexity files emit no hotspot");
assert!(smells.is_empty());
}
#[test]
fn map_empty_report() {
let report = SubprocessReviewReport { files: vec![] };
let (hotspots, smells) = map_report(&report);
assert!(hotspots.is_empty());
assert!(smells.is_empty());
}
#[test]
fn subprocess_review_report_deserialises_from_wire_json() {
let json = r#"{
"files": [
{
"path": "src/main.rs",
"grade": "B",
"complexity": { "cyclomatic": 7, "cognitive": 4 },
"smells": [
{ "category": "too_many_params", "line": 10, "severity": "medium" }
],
"recommendations": [],
"source": { "kind": "indexed", "modified_chunks": 2 }
}
],
"overall_grade": "B",
"changed_lines": 20,
"smell_count": 1,
"summary": "1 file analyzed (1 indexed, 0 new); 1 smell found; overall grade B"
}"#;
let report: SubprocessReviewReport = serde_json::from_str(json).unwrap();
assert_eq!(report.files.len(), 1);
assert_eq!(report.files[0].path, "src/main.rs");
assert_eq!(report.files[0].complexity.cyclomatic, 7);
assert_eq!(report.files[0].smells.len(), 1);
assert_eq!(report.files[0].smells[0].category, "too_many_params");
}
#[test]
fn spawn_analyze_review_with_fake_binary_that_fails() {
let result = spawn_analyze_review("false", "main", "+++ b/x.rs\n");
assert!(result.is_err(), "exit-1 binary must return Err");
assert!(
matches!(result.unwrap_err(), AnalyzeClientError::Unavailable(_)),
"exit-1 maps to Unavailable"
);
}
#[test]
fn subprocess_client_trait_object_compiles() {
fn _accepts_dyn(_c: &dyn AnalyzeClient) {}
let client = SubprocessAnalyzeClient::new("trusty-analyze", "http://127.0.0.1:7878")
.expect("TLS init should succeed");
_accepts_dyn(&client);
}