use super::*;
#[test]
fn test_summary_counts() {
let results = vec![
make_result("integrate_a", Classification::Integration),
make_result("integrate_b", Classification::Integration),
make_result("operate", Classification::Operation),
make_result(
"violate",
Classification::Violation {
has_logic: true,
has_own_calls: true,
logic_locations: vec![LogicOccurrence {
kind: "if".into(),
line: 5,
}],
call_locations: vec![CallOccurrence {
name: "foo".into(),
line: 6,
}],
},
),
make_result("trivial_fn", Classification::Trivial),
];
let summary = Summary::from_results(&results);
assert_eq!(summary.total, 5);
assert_eq!(summary.integrations, 2);
assert_eq!(summary.operations, 1);
assert_eq!(summary.violations, 1);
assert_eq!(summary.trivial, 1);
}
#[test]
fn test_summary_empty() {
let results: Vec<FunctionAnalysis> = vec![];
let summary = Summary::from_results(&results);
assert_eq!(summary.total, 0);
assert_eq!(summary.integrations, 0);
assert_eq!(summary.operations, 0);
assert_eq!(summary.violations, 0);
assert_eq!(summary.trivial, 0);
}
#[test]
fn test_suppressed_not_counted_as_violation() {
let mut func = make_result(
"suppressed_fn",
Classification::Violation {
has_logic: true,
has_own_calls: true,
logic_locations: vec![LogicOccurrence {
kind: "if".into(),
line: 1,
}],
call_locations: vec![CallOccurrence {
name: "foo".into(),
line: 2,
}],
},
);
func.suppressed = true;
let results = vec![func];
let summary = Summary::from_results(&results);
assert_eq!(summary.violations, 0);
assert_eq!(summary.suppressed, 1);
}
#[test]
fn test_json_structure() {
let results = vec![make_result("my_func", Classification::Integration)];
let summary = Summary::from_results(&results);
let json_value = serde_json::json!({
"summary": {
"total": summary.total,
"integrations": summary.integrations,
"operations": summary.operations,
"violations": summary.violations,
"trivial": summary.trivial,
},
"functions": [
{
"name": "my_func",
"file": "test.rs",
"line": 1,
"parent_type": null,
"classification": "integration",
}
]
});
assert!(
json_value.get("summary").is_some(),
"JSON must have a 'summary' key"
);
assert!(
json_value.get("functions").is_some(),
"JSON must have a 'functions' key"
);
let funcs = json_value["functions"].as_array().unwrap();
assert_eq!(funcs.len(), 1);
assert_eq!(funcs[0]["classification"], "integration");
}
fn project_violation_results(results: &[FunctionAnalysis]) -> serde_json::Value {
let summary = Summary::from_results(results);
let json_functions: Vec<serde_json::Value> = results
.iter()
.map(|f| {
let (classification, logic, calls) = match &f.classification {
Classification::Violation {
logic_locations,
call_locations,
..
} => {
let logic: Vec<serde_json::Value> = logic_locations
.iter()
.map(|l| serde_json::json!({"kind": l.kind, "line": l.line.to_string()}))
.collect();
let calls: Vec<serde_json::Value> = call_locations
.iter()
.map(|c| serde_json::json!({"name": c.name, "line": c.line.to_string()}))
.collect();
("violation", logic, calls)
}
_ => unreachable!(),
};
serde_json::json!({
"name": f.name,
"file": f.file,
"line": f.line,
"parent_type": f.parent_type,
"classification": classification,
"logic": logic,
"calls": calls,
})
})
.collect();
serde_json::json!({
"summary": {
"total": summary.total,
"integrations": summary.integrations,
"operations": summary.operations,
"violations": summary.violations,
"trivial": summary.trivial,
},
"functions": json_functions,
})
}
#[test]
fn test_json_violation_has_logic_and_calls() {
let results = vec![make_result(
"bad_fn",
Classification::Violation {
has_logic: true,
has_own_calls: true,
logic_locations: vec![
LogicOccurrence {
kind: "if".into(),
line: 3,
},
LogicOccurrence {
kind: "match".into(),
line: 7,
},
],
call_locations: vec![CallOccurrence {
name: "helper".into(),
line: 5,
}],
},
)];
let output = project_violation_results(&results);
let func = &output["functions"][0];
assert_eq!(func["classification"], "violation");
let logic = func["logic"].as_array().unwrap();
assert_eq!(logic.len(), 2);
assert_eq!(logic[0]["kind"], "if");
assert_eq!(logic[1]["kind"], "match");
let calls = func["calls"].as_array().unwrap();
assert_eq!(calls.len(), 1);
assert_eq!(calls[0]["name"], "helper");
}
#[test]
fn test_json_integration_no_logic() {
let results = vec![make_result("orchestrator", Classification::Integration)];
let summary = Summary::from_results(&results);
let json_value = serde_json::json!({
"summary": {
"total": summary.total,
"integrations": summary.integrations,
"operations": summary.operations,
"violations": summary.violations,
"trivial": summary.trivial,
},
"functions": [
{
"name": "orchestrator",
"file": "test.rs",
"line": 1,
"parent_type": null,
"classification": "integration",
}
]
});
let func = &json_value["functions"][0];
assert!(
func.get("logic").is_none(),
"Integration should not have logic array"
);
assert!(
func.get("calls").is_none(),
"Integration should not have calls array"
);
}
#[test]
fn test_summary_total_matches() {
let results = vec![
make_result("a", Classification::Integration),
make_result("b", Classification::Operation),
make_result("c", Classification::Trivial),
make_result(
"d",
Classification::Violation {
has_logic: true,
has_own_calls: true,
logic_locations: vec![],
call_locations: vec![],
},
),
];
let summary = Summary::from_results(&results);
assert_eq!(summary.total, results.len());
}
#[test]
fn test_baseline_roundtrip() {
let results = vec![
make_result("good_fn", Classification::Integration),
make_result(
"bad_fn",
Classification::Violation {
has_logic: true,
has_own_calls: true,
logic_locations: vec![LogicOccurrence {
kind: "if".into(),
line: 1,
}],
call_locations: vec![CallOccurrence {
name: "helper".into(),
line: 2,
}],
},
),
];
let summary = Summary::from_results(&results);
let baseline_json = create_baseline(&results, &summary);
let parsed: serde_json::Value = serde_json::from_str(&baseline_json).unwrap();
assert!(parsed["iosp_score"].as_f64().is_some());
assert_eq!(parsed["violations"].as_u64().unwrap(), 1);
}