use face_core::{Axis, AxisPlan, Cluster, ClusterId, 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 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("nested build_tree should succeed")
}
fn find<'a>(clusters: &'a [Cluster], label: &str) -> &'a Cluster {
clusters
.iter()
.find(|c| c.label == label)
.unwrap_or_else(|| {
panic!(
"cluster with label {label:?} not found among {:?}",
clusters.iter().map(|c| c.label.clone()).collect::<Vec<_>>(),
)
})
}
mod two_axis_via_within {
use super::*;
fn dataset() -> Vec<Record> {
let items = vec![
json!({"kind": "bug", "severity": "high"}),
json!({"kind": "bug", "severity": "high"}),
json!({"kind": "bug", "severity": "low"}),
json!({"kind": "feat", "severity": "high"}),
];
Record::from_items(items, None)
}
fn plan() -> AxisPlan {
AxisPlan::with(exact("kind"), AxisPlan::leaf(exact("severity")))
}
#[test]
fn outer_cluster_count() {
let clusters = run(plan(), dataset());
assert_eq!(clusters.len(), 2);
assert_eq!(clusters[0].label, "bug");
assert_eq!(clusters[0].total, 3);
assert_eq!(clusters[1].label, "feat");
assert_eq!(clusters[1].total, 1);
}
#[test]
fn bug_has_two_inner_clusters() {
let clusters = run(plan(), dataset());
let bug = find(&clusters, "bug");
assert_eq!(
bug.clusters.len(),
2,
"bug must have 2 severity sub-clusters"
);
assert_eq!(bug.clusters[0].label, "high");
assert_eq!(bug.clusters[0].total, 2);
assert_eq!(bug.clusters[1].label, "low");
assert_eq!(bug.clusters[1].total, 1);
}
#[test]
fn feat_has_one_inner_cluster() {
let clusters = run(plan(), dataset());
let feat = find(&clusters, "feat");
assert_eq!(feat.clusters.len(), 1);
assert_eq!(feat.clusters[0].label, "high");
assert_eq!(feat.clusters[0].total, 1);
}
#[test]
fn inner_cluster_id_extends_outer() {
let clusters = run(plan(), dataset());
let bug = find(&clusters, "bug");
let bug_high = &bug.clusters[0];
let expected = ClusterId::parse_canonical("kind:bug,severity:high")
.expect("canonical two-axis id parses");
assert_eq!(bug_high.id, expected);
assert_eq!(bug_high.id.depth(), 2);
}
#[test]
fn inner_cluster_axis_propagates() {
let clusters = run(plan(), dataset());
let bug = find(&clusters, "bug");
for c in &bug.clusters {
assert_eq!(
c.axis, "severity",
"inner cluster's axis is the inner field path",
);
}
}
#[test]
fn outer_total_includes_inner() {
let clusters = run(plan(), dataset());
let bug = find(&clusters, "bug");
let inner_sum: u64 = bug.clusters.iter().map(|c| c.total).sum();
assert_eq!(bug.total, inner_sum, "outer total = sum of inner totals");
assert_eq!(bug.total, 3);
}
}
mod three_axis_chain {
use super::*;
#[test]
fn three_deep_nesting() {
let items = vec![
json!({"a": "x", "b": "p", "c": "1"}),
json!({"a": "x", "b": "p", "c": "1"}),
json!({"a": "x", "b": "q", "c": "2"}),
json!({"a": "y", "b": "p", "c": "1"}),
];
let plan = AxisPlan::with(
exact("a"),
AxisPlan::with(exact("b"), AxisPlan::leaf(exact("c"))),
);
let clusters = run(plan, Record::from_items(items, None));
assert_eq!(clusters.len(), 2, "two top-level clusters");
let x = find(&clusters, "x");
assert_eq!(x.clusters.len(), 2);
let xp = find(&x.clusters, "p");
assert_eq!(xp.total, 2);
assert!(!xp.clusters.is_empty(), "xp has at least one leaf");
let leaf = &xp.clusters[0];
assert_eq!(
leaf.id.depth(),
3,
"leaf id should have three segments (a, b, c), got {}",
leaf.id.depth(),
);
let expected = ClusterId::parse_canonical("a:x,b:p,c:1").expect("three-axis id parses");
assert_eq!(leaf.id, expected);
}
}
mod within_with_siblings {
use super::*;
#[test]
fn outer_cluster_has_combined_children() {
let items = vec![
json!({"kind": "bug", "severity": "high", "status": "open"}),
json!({"kind": "bug", "severity": "low", "status": "closed"}),
];
let plan = AxisPlan::with_many(
exact("kind"),
vec![
AxisPlan::leaf(exact("severity")),
AxisPlan::leaf(exact("status")),
],
);
let clusters = run(plan, Record::from_items(items, None));
assert_eq!(clusters.len(), 1, "one outer cluster");
let bug = &clusters[0];
assert_eq!(bug.label, "bug");
assert_eq!(
bug.clusters.len(),
4,
"outer cluster's children should be the concatenation of both sibling axes",
);
}
#[test]
fn child_axes_distinguishable_by_axis_field() {
let items = vec![
json!({"kind": "bug", "severity": "high", "status": "open"}),
json!({"kind": "bug", "severity": "low", "status": "closed"}),
];
let plan = AxisPlan::with_many(
exact("kind"),
vec![
AxisPlan::leaf(exact("severity")),
AxisPlan::leaf(exact("status")),
],
);
let clusters = run(plan, Record::from_items(items, None));
let bug = &clusters[0];
let axes: std::collections::HashSet<&str> =
bug.clusters.iter().map(|c| c.axis.as_str()).collect();
assert!(
axes.contains("severity"),
"at least one child has axis: severity, got {axes:?}",
);
assert!(
axes.contains("status"),
"at least one child has axis: status, got {axes:?}",
);
}
}