use crate::adapters::analyzers::coupling::sdp::*;
use crate::adapters::analyzers::coupling::{metrics::compute_coupling_metrics, ModuleGraph};
fn sdp_graph() -> ModuleGraph {
ModuleGraph {
modules: vec![
"a".into(),
"b".into(),
"x".into(),
"y".into(),
"p".into(),
"q".into(),
],
forward: vec![vec![1], vec![4, 5], vec![0], vec![0], vec![], vec![]],
}
}
fn sdp_first_violation_suppressed(suppress_idx: Option<usize>) -> bool {
let graph = sdp_graph();
let mut metrics = compute_coupling_metrics(&graph);
if let Some(i) = suppress_idx {
metrics[i].suppressed = true;
}
let violations = check_sdp(&graph, &metrics);
assert_eq!(
violations.len(),
1,
"fixture must have exactly one violation"
);
violations[0].suppressed
}
#[test]
fn test_no_violations_all_same_instability() {
let graph = ModuleGraph {
modules: vec!["a".into(), "b".into()],
forward: vec![vec![1], vec![0]],
};
let metrics = compute_coupling_metrics(&graph);
let violations = check_sdp(&graph, &metrics);
assert!(
violations.is_empty(),
"Equal instability should not trigger SDP"
);
}
#[test]
fn sdp_flags_stable_module_depending_on_unstable_one_with_exact_instabilities() {
let graph = sdp_graph();
let metrics = compute_coupling_metrics(&graph);
let violations = check_sdp(&graph, &metrics);
assert_eq!(violations.len(), 1, "exactly the a→b edge violates");
let v = &violations[0];
assert_eq!(v.from_module, "a");
assert_eq!(v.to_module, "b");
assert!(
v.from_instability < v.to_instability,
"stable from-module is less unstable than the to-module"
);
assert!(
(v.from_instability - 1.0 / 3.0).abs() < 1e-9,
"from-instability ≈ 1/3, got {}",
v.from_instability
);
assert!(
(v.to_instability - 2.0 / 3.0).abs() < 1e-9,
"to-instability ≈ 2/3, got {}",
v.to_instability
);
}
#[test]
fn sdp_collects_every_violating_edge_not_just_the_first() {
let graph = ModuleGraph {
modules: vec![
"a".into(),
"b".into(),
"c".into(),
"x".into(),
"y".into(),
"p".into(),
"q".into(),
"r".into(),
"s".into(),
],
forward: vec![
vec![1, 2], vec![5, 6], vec![7, 8], vec![0], vec![0], vec![], vec![], vec![], vec![], ],
};
let metrics = compute_coupling_metrics(&graph);
let mut violations = check_sdp(&graph, &metrics);
violations.sort_by(|l, r| l.to_module.cmp(&r.to_module));
let edges: Vec<(&str, &str)> = violations
.iter()
.map(|v| (v.from_module.as_str(), v.to_module.as_str()))
.collect();
assert_eq!(edges, vec![("a", "b"), ("a", "c")], "both edges collected");
}
#[test]
fn test_no_violation_unstable_depends_on_stable() {
let graph = ModuleGraph {
modules: vec!["a".into(), "b".into()],
forward: vec![vec![1], vec![]],
};
let metrics = compute_coupling_metrics(&graph);
let violations = check_sdp(&graph, &metrics);
assert!(
violations.is_empty(),
"Unstable depending on stable is correct SDP"
);
}
#[test]
fn test_no_violations_empty_graph() {
let graph = ModuleGraph {
modules: vec![],
forward: vec![],
};
let violations = check_sdp(&graph, &[]);
assert!(violations.is_empty());
}
#[test]
fn test_no_violations_single_module() {
let graph = ModuleGraph {
modules: vec!["a".into()],
forward: vec![vec![]],
};
let metrics = compute_coupling_metrics(&graph);
let violations = check_sdp(&graph, &metrics);
assert!(violations.is_empty());
}
#[test]
fn test_zero_violations_for_stable_leaves() {
let graph = ModuleGraph {
modules: vec!["a".into(), "b".into(), "c".into(), "d".into(), "e".into()],
forward: vec![
vec![1, 2], vec![], vec![], vec![0], vec![0], ],
};
let metrics = compute_coupling_metrics(&graph);
let violations = check_sdp(&graph, &metrics);
assert!(violations.is_empty());
}
#[test]
fn sdp_violation_inherits_suppression_from_either_endpoint() {
let cases: &[(&str, Option<usize>, bool)] = &[
("no module suppressed → not suppressed", None, false),
("from-module (a) suppressed", Some(0), true),
("to-module (b) suppressed", Some(1), true),
];
for (label, suppress_idx, expected) in cases {
assert_eq!(
sdp_first_violation_suppressed(*suppress_idx),
*expected,
"case {label}"
);
}
}