use face_core::{Axis, AxisPlan, Cluster, NullDiagnostics, Record, build_tree};
use serde_json::{Value, json};
fn axis(field: &str, strategy: Value) -> Axis {
let mut object = serde_json::Map::new();
object.insert("field".into(), Value::String(field.into()));
object.insert("auto".into(), Value::Bool(false));
if let Value::Object(map) = strategy {
for (k, v) in map {
object.insert(k, v);
}
} else {
panic!("strategy must be a JSON object");
}
serde_json::from_value(Value::Object(object)).expect("axis must deserialize")
}
fn quantiles(field: &str, count: u8) -> Axis {
axis(field, json!({"strategy": "quantiles", "count": count}))
}
fn exact(field: &str) -> Axis {
axis(field, json!({"strategy": "exact"}))
}
fn run(plan: AxisPlan, records: Vec<Record>) -> Vec<Cluster> {
let mut diag = NullDiagnostics;
build_tree(&plan, records, &mut diag).expect("Quantiles build_tree should succeed")
}
fn records_from(items: Vec<Value>, score_path: Option<&str>) -> Vec<Record> {
Record::from_items(items, score_path)
}
fn value_min(cluster: &Cluster) -> f64 {
cluster.value["min"].as_f64().unwrap_or_else(|| {
panic!("cluster.value[\"min\"] should be a JSON number; got {cluster:?}")
})
}
mod basic {
use super::*;
fn dataset() -> Vec<Record> {
let items: Vec<Value> = (1..=10).map(|i| json!({"score": i as f64})).collect();
records_from(items, None)
}
#[test]
fn equal_population_partitioning() {
let plan = AxisPlan::leaf(quantiles("score", 4));
let clusters = run(plan, dataset());
assert_eq!(
clusters.len(),
4,
"10 distinct records into 4 quantiles → 4 clusters; got {} clusters: {:?}",
clusters.len(),
clusters.iter().map(|c| c.label.clone()).collect::<Vec<_>>(),
);
let totals: Vec<u64> = clusters.iter().map(|c| c.total).collect();
let sum: u64 = totals.iter().sum();
assert_eq!(sum, 10, "totals must sum to 10; got {sum} from {totals:?}");
for total in &totals {
assert!(
*total == 2 || *total == 3,
"each cluster total must be 2 or 3 for 10/4 split; got {total} in {totals:?}",
);
}
}
#[test]
fn clusters_sorted_by_lower_bound_descending() {
let plan = AxisPlan::leaf(quantiles("score", 4));
let clusters = run(plan, dataset());
let lowers: Vec<f64> = clusters.iter().map(value_min).collect();
let mut sorted = lowers.clone();
sorted.sort_by(|a, b| b.total_cmp(a));
assert_eq!(
lowers, sorted,
"clusters must be sorted by lower bound descending; got {lowers:?}",
);
}
}
mod duplicates {
use super::*;
#[test]
fn handles_heavy_duplication() {
let items = vec![
json!({"score": 1.0}),
json!({"score": 1.0}),
json!({"score": 1.0}),
json!({"score": 1.0}),
json!({"score": 5.0}),
json!({"score": 5.0}),
];
let plan = AxisPlan::leaf(quantiles("score", 4));
let clusters = run(plan, records_from(items, None));
assert!(
!clusters.is_empty() && clusters.len() <= 2,
"heavy duplication must collapse to ≤ 2 clusters (2 distinct values); got {} clusters: {:?}",
clusters.len(),
clusters.iter().map(|c| c.label.clone()).collect::<Vec<_>>(),
);
let sum: u64 = clusters.iter().map(|c| c.total).sum();
assert_eq!(
sum, 6,
"total must be preserved across duplication; got {sum}"
);
}
}
mod edges {
use super::*;
#[test]
fn single_record() {
let items = vec![json!({"score": 0.5})];
let plan = AxisPlan::leaf(quantiles("score", 4));
let clusters = run(plan, records_from(items, None));
assert_eq!(clusters.len(), 1, "single record → exactly one cluster");
assert_eq!(clusters[0].total, 1);
}
#[test]
fn empty_records() {
let plan = AxisPlan::leaf(quantiles("score", 4));
let clusters = run(plan, Vec::new());
assert!(
clusters.is_empty(),
"empty input must produce empty cluster vec; got {} clusters",
clusters.len(),
);
}
}
mod recursion {
use super::*;
#[test]
fn quantiles_within_exact() {
let items = vec![
json!({"score": 1.0, "kind": "bug"}),
json!({"score": 2.0, "kind": "feat"}),
json!({"score": 3.0, "kind": "bug"}),
json!({"score": 4.0, "kind": "feat"}),
json!({"score": 5.0, "kind": "feat"}),
json!({"score": 6.0, "kind": "bug"}),
json!({"score": 7.0, "kind": "bug"}),
json!({"score": 8.0, "kind": "feat"}),
];
let outer = quantiles("score", 4);
let inner = exact("kind");
let plan = AxisPlan::with(outer, AxisPlan::leaf(inner));
let clusters = run(plan, records_from(items, None));
assert!(
!clusters.is_empty(),
"outer Quantiles must produce at least one cluster",
);
let outer_total: u64 = clusters.iter().map(|c| c.total).sum();
assert_eq!(outer_total, 8);
for cluster in &clusters {
assert!(
!cluster.clusters.is_empty(),
"each Quantiles cluster must recurse into Exact children; cluster {:?} has none",
cluster.label,
);
let child_total: u64 = cluster.clusters.iter().map(|c| c.total).sum();
assert_eq!(
child_total, cluster.total,
"child totals must add up to parent total in cluster {:?}",
cluster.label,
);
for child in &cluster.clusters {
assert_eq!(child.axis, "kind");
}
}
}
}