use face_core::{Cluster, ClusterId, ClusterIdSegment, FlatCluster, from_flat, to_flat};
fn cid(pairs: &[(&str, &str)]) -> ClusterId {
ClusterId::new(
pairs
.iter()
.map(|(a, v)| ClusterIdSegment::new(*a, *v))
.collect(),
)
}
fn cluster(value: serde_json::Value) -> Cluster {
serde_json::from_value(value).expect("Cluster JSON")
}
fn flat_cluster(value: serde_json::Value) -> FlatCluster {
serde_json::from_value(value).expect("FlatCluster JSON")
}
fn leaf(id: &str, axis: &str, value: &str, total: u64) -> Cluster {
cluster(serde_json::json!({
"id": id,
"label": value,
"axis": axis,
"value": value,
"total": total,
"score_min": null,
"score_max": null,
"clusters": []
}))
}
fn parent_cluster(
id: &str,
axis: &str,
value: &str,
total: u64,
children: Vec<Cluster>,
) -> Cluster {
let kids = serde_json::to_value(&children).expect("serialize children");
cluster(serde_json::json!({
"id": id,
"label": value,
"axis": axis,
"value": value,
"total": total,
"score_min": null,
"score_max": null,
"clusters": kids
}))
}
fn assert_round_trip(label: &str, tree: Vec<Cluster>) {
let flat = to_flat(&tree);
let back = from_flat(&flat).unwrap_or_else(|e| panic!("[{label}] from_flat failed: {e:?}"));
assert_eq!(back, tree, "[{label}] round-trip mismatch");
}
mod round_trip {
use super::*;
#[test]
fn empty_tree() {
assert_round_trip("empty", Vec::<Cluster>::new());
}
#[test]
fn single_leaf_one_axis() {
let tree = vec![leaf("file:src/cli.rs", "file", "src/cli.rs", 38)];
assert_round_trip("single-leaf", tree);
}
#[test]
fn two_axis_one_root_three_children() {
let root = parent_cluster(
"file:src/cli.rs",
"file",
"src/cli.rs",
38,
vec![
leaf("file:src/cli.rs,score:excellent", "score", "excellent", 12),
leaf("file:src/cli.rs,score:strong", "score", "strong", 18),
leaf("file:src/cli.rs,score:weak", "score", "weak", 8),
],
);
assert_round_trip("two-axis", vec![root]);
}
#[test]
fn three_axis_tree() {
let inner = parent_cluster(
"repo:oops-rs,file:src/cli.rs",
"file",
"src/cli.rs",
38,
vec![leaf(
"repo:oops-rs,file:src/cli.rs,score:excellent",
"score",
"excellent",
12,
)],
);
let root = parent_cluster("repo:oops-rs", "repo", "oops-rs", 38, vec![inner]);
assert_round_trip("three-axis", vec![root]);
}
#[test]
fn sibling_roots() {
let r1 = leaf("file:a.rs", "file", "a.rs", 10);
let r2 = leaf("file:b.rs", "file", "b.rs", 20);
let r3 = leaf("file:c.rs", "file", "c.rs", 30);
assert_round_trip("sibling-roots", vec![r1, r2, r3]);
}
}
mod to_flat_shape {
use super::*;
#[test]
fn root_clusters_have_none_parent() {
let tree = vec![
leaf("file:a.rs", "file", "a.rs", 10),
leaf("file:b.rs", "file", "b.rs", 20),
];
let flat = to_flat(&tree);
for fc in &flat {
assert!(
fc.parent_id.is_none(),
"root cluster {:?} should have parent_id=None",
fc.id
);
}
}
#[test]
fn child_parent_id_matches_parent() {
let parent_id = cid(&[("file", "src/cli.rs")]);
let child_id = cid(&[("file", "src/cli.rs"), ("score", "excellent")]);
let tree = vec![parent_cluster(
"file:src/cli.rs",
"file",
"src/cli.rs",
38,
vec![leaf(
"file:src/cli.rs,score:excellent",
"score",
"excellent",
12,
)],
)];
let flat = to_flat(&tree);
let parent_fc = flat
.iter()
.find(|fc| fc.id == parent_id)
.expect("parent present in flat list");
assert!(parent_fc.parent_id.is_none());
let child_fc = flat
.iter()
.find(|fc| fc.id == child_id)
.expect("child present in flat list");
assert_eq!(child_fc.parent_id.as_ref(), Some(&parent_id));
}
#[test]
fn flat_length_equals_total_cluster_count() {
let gc = leaf(
"repo:oops-rs,file:src/cli.rs,score:excellent",
"score",
"excellent",
12,
);
let c1 = parent_cluster(
"repo:oops-rs,file:src/cli.rs",
"file",
"src/cli.rs",
38,
vec![gc],
);
let c2 = leaf("repo:oops-rs,file:src/core.rs", "file", "src/core.rs", 27);
let c3 = leaf(
"repo:oops-rs,file:src/parser.rs",
"file",
"src/parser.rs",
19,
);
let root = parent_cluster("repo:oops-rs", "repo", "oops-rs", 84, vec![c1, c2, c3]);
let flat = to_flat(&[root]);
assert_eq!(flat.len(), 5, "expected 5 flattened clusters");
}
#[test]
fn flat_field_subset_matches_nested() {
let tree = vec![leaf("file:src/cli.rs", "file", "src/cli.rs", 38)];
let flat = to_flat(&tree);
assert_eq!(flat.len(), 1);
let fc = &flat[0];
assert_eq!(fc.label, "src/cli.rs");
assert_eq!(fc.axis, "file");
assert_eq!(fc.value, serde_json::Value::String("src/cli.rs".into()));
assert_eq!(fc.total, 38);
assert_eq!(fc.score_min, None);
assert_eq!(fc.score_max, None);
}
}
mod from_flat_errors {
use super::*;
#[test]
fn dangling_parent_id_is_err() {
let flat = vec![flat_cluster(serde_json::json!({
"id": "file:missing,score:excellent",
"parent_id": "file:missing",
"label": "excellent",
"axis": "score",
"value": "excellent",
"total": 12,
"score_min": null,
"score_max": null
}))];
let result = from_flat(&flat);
assert!(
result.is_err(),
"from_flat must error on dangling parent_id; got {result:?}"
);
}
}