mod common;
use common::drive;
fn bug_jsonl(n: usize) -> String {
let mut s = String::new();
for i in 0..n {
s.push_str(&format!("{{\"kind\":\"bug\",\"idx\":{i}}}\n"));
}
s
}
mod cluster_canonical {
use super::drive;
#[test]
fn drills_into_named_cluster() {
let stdin = "{\"kind\":\"bug\",\"id\":1}\n\
{\"kind\":\"bug\",\"id\":2}\n\
{\"kind\":\"feat\",\"id\":3}\n";
let (code, stdout, stderr) = drive(
&[
"face",
"--by",
"kind",
"--cluster",
"kind:bug",
"--format=json",
],
stdin,
);
assert_eq!(code, 0, "stderr={stderr:?} stdout={stdout:?}");
let v: serde_json::Value = serde_json::from_str(&stdout).expect("parseable JSON envelope");
let page = &v["page"];
let cluster_id = page["cluster_id"]
.as_str()
.expect("page.cluster_id is a string when --cluster set: {v}");
assert_eq!(
cluster_id, "kind:bug",
"page.cluster_id round-trips canonical form: {v}",
);
let items = page["items"].as_array().expect("page.items is array: {v}");
assert!(
!items.is_empty(),
"page.items is non-empty for a populated cluster: {v}",
);
for item in items {
assert_eq!(
item["kind"].as_str(),
Some("bug"),
"page items are filtered to bug: {item}",
);
}
}
}
mod cluster_structured {
use super::drive;
#[test]
fn drills_via_structured_form() {
let stdin = "{\"kind\":\"bug\",\"id\":1}\n\
{\"kind\":\"bug\",\"id\":2}\n\
{\"kind\":\"feat\",\"id\":3}\n";
let (code, stdout, stderr) = drive(
&[
"face",
"--by",
"kind",
"--cluster",
"kind=bug",
"--format=json",
],
stdin,
);
assert_eq!(code, 0, "stderr={stderr:?} stdout={stdout:?}");
let v: serde_json::Value = serde_json::from_str(&stdout).expect("parseable JSON envelope");
let items = v["page"]["items"]
.as_array()
.expect("page.items is array: {v}");
assert!(
!items.is_empty(),
"structured form populates the same page as canonical: {v}",
);
for item in items {
assert_eq!(
item["kind"].as_str(),
Some("bug"),
"page items are filtered to bug: {item}",
);
}
}
}
mod cluster_ordinal {
use super::drive;
#[test]
fn drills_into_first_rendered_cluster_by_ordinal() {
let stdin = "{\"kind\":\"bug\",\"id\":1}\n\
{\"kind\":\"bug\",\"id\":2}\n\
{\"kind\":\"feat\",\"id\":3}\n";
let (code, stdout, stderr) = drive(
&["face", "--by", "kind", "--cluster", "1", "--format=json"],
stdin,
);
assert_eq!(code, 0, "stderr={stderr:?} stdout={stdout:?}");
let v: serde_json::Value = serde_json::from_str(&stdout).expect("parseable JSON envelope");
assert_eq!(v["page"]["cluster_id"].as_str(), Some("kind:bug"));
let items = v["page"]["items"]
.as_array()
.expect("page.items is array: {v}");
assert_eq!(items.len(), 2, "first rendered cluster is kind:bug: {v}");
for item in items {
assert_eq!(item["kind"].as_str(), Some("bug"));
}
}
#[test]
fn drills_into_first_numeric_band_by_ordinal() {
let stdin = "{\"score\":0,\"id\":0}\n\
{\"score\":1,\"id\":1}\n\
{\"score\":2,\"id\":2}\n\
{\"score\":3,\"id\":3}\n";
let (code, stdout, stderr) = drive(
&[
"face",
"--by",
"score",
"--strategy=bands=2",
"--cluster",
"1",
"--format=json",
],
stdin,
);
assert_eq!(code, 0, "stderr={stderr:?} stdout={stdout:?}");
let v: serde_json::Value = serde_json::from_str(&stdout).expect("parseable JSON envelope");
let first_cluster_id = v["clusters"][0]["id"].as_str().expect("cluster id string");
assert_eq!(v["page"]["cluster_id"].as_str(), Some(first_cluster_id));
assert_eq!(v["page"]["total_items"].as_u64(), Some(2));
let items = v["page"]["items"]
.as_array()
.expect("page.items is array: {v}");
assert_eq!(items.len(), 2, "top band contains scores 2 and 3: {v}");
assert!(
items.iter().any(|item| item["score"].as_f64() == Some(3.0)),
"top band includes the maximum score: {v}",
);
}
}
mod pagination {
use super::{bug_jsonl, drive};
#[test]
fn default_per_page_caps_at_20() {
let stdin = bug_jsonl(50);
let (code, stdout, stderr) = drive(
&[
"face",
"--by",
"kind",
"--cluster",
"kind:bug",
"--format=json",
],
&stdin,
);
assert_eq!(code, 0, "stderr={stderr:?} stdout={stdout:?}");
let v: serde_json::Value = serde_json::from_str(&stdout).expect("parseable JSON envelope");
let page = &v["page"];
let items = page["items"].as_array().expect("page.items is array: {v}");
assert_eq!(items.len(), 20, "default per-page is 20: {v}");
assert_eq!(
page["total_items"].as_u64(),
Some(50),
"page.total_items reflects the full cluster: {v}",
);
}
#[test]
fn explicit_page_2() {
let stdin = bug_jsonl(50);
let (code, stdout, stderr) = drive(
&[
"face",
"--by",
"kind",
"--cluster",
"kind:bug",
"--page=2",
"--format=json",
],
&stdin,
);
assert_eq!(code, 0, "stderr={stderr:?} stdout={stdout:?}");
let v: serde_json::Value = serde_json::from_str(&stdout).expect("parseable JSON envelope");
let items = v["page"]["items"]
.as_array()
.expect("page.items is array: {v}");
assert_eq!(items.len(), 20, "page 2 with default per-page=20: {v}");
assert_eq!(
v["page"]["page"].as_u64(),
Some(2),
"page.page reports the requested page: {v}",
);
assert_eq!(
v["page"]["total_items"].as_u64(),
Some(50),
"page.total_items reflects the full cluster: {v}",
);
}
#[test]
fn last_page_partial() {
let stdin = bug_jsonl(50);
let (code, stdout, stderr) = drive(
&[
"face",
"--by",
"kind",
"--cluster",
"kind:bug",
"--page=3",
"--per-page=20",
"--format=json",
],
&stdin,
);
assert_eq!(code, 0, "stderr={stderr:?} stdout={stdout:?}");
let v: serde_json::Value = serde_json::from_str(&stdout).expect("parseable JSON envelope");
let items = v["page"]["items"]
.as_array()
.expect("page.items is array: {v}");
assert_eq!(
items.len(),
10,
"last page is partial (50 items, per_page=20): {v}",
);
assert_eq!(
v["page"]["per_page"].as_u64(),
Some(20),
"page.per_page echoes the request: {v}",
);
}
}
mod strategy_membership {
use super::drive;
#[test]
fn prefix_cluster_drills_by_prefix_membership() {
let stdin = "{\"path\":\"src/cli.rs\",\"id\":1}\n\
{\"path\":\"src/lib.rs\",\"id\":2}\n\
{\"path\":\"docs/design.md\",\"id\":3}\n";
let (code, stdout, stderr) = drive(
&[
"face",
"--by",
"path",
"--strategy=prefix=1",
"--cluster",
"path:src",
"--format=json",
],
stdin,
);
assert_eq!(code, 0, "stderr={stderr:?} stdout={stdout:?}");
let v: serde_json::Value = serde_json::from_str(&stdout).expect("parseable envelope");
let items = v["page"]["items"].as_array().expect("page.items array");
assert_eq!(items.len(), 2, "src prefix has two records: {v}");
for item in items {
let path = item["path"].as_str().expect("path is string");
assert!(
path.starts_with("src/"),
"drilled item is under src/: {item}"
);
}
}
#[test]
fn bands_cluster_drills_by_numeric_range() {
let stdin = "{\"score\":0,\"id\":0}\n\
{\"score\":1,\"id\":1}\n\
{\"score\":2,\"id\":2}\n\
{\"score\":3,\"id\":3}\n";
let (code, envelope, stderr) = drive(
&[
"face",
"--by",
"score",
"--strategy=bands=2",
"--format=json",
],
stdin,
);
assert_eq!(code, 0, "stderr={stderr:?} stdout={envelope:?}");
let v: serde_json::Value = serde_json::from_str(&envelope).expect("parseable envelope");
let first_cluster = &v["clusters"][0];
let cluster_id = first_cluster["id"].as_str().expect("cluster id string");
let lower = first_cluster["value"]["min"].as_f64().expect("band min");
let upper = first_cluster["value"]["max"].as_f64().expect("band max");
let expected_total = first_cluster["total"].as_u64().expect("cluster total");
let (code, stdout, stderr) = drive(
&[
"face",
"--by",
"score",
"--strategy=bands=2",
"--cluster",
cluster_id,
"--format=json",
],
stdin,
);
assert_eq!(code, 0, "stderr={stderr:?} stdout={stdout:?}");
let drilled: serde_json::Value = serde_json::from_str(&stdout).expect("parseable envelope");
let items = drilled["page"]["items"]
.as_array()
.expect("page.items array");
assert_eq!(
items.len() as u64,
expected_total,
"band drill returns every record in the cluster: {drilled}",
);
for item in items {
let score = item["score"].as_f64().expect("score is numeric");
assert!(
(lower..=upper).contains(&score),
"first rendered band is closed over the global maximum: {item}"
);
}
assert!(
items
.iter()
.any(|item| item["score"].as_f64() == Some(upper)),
"highest rendered band includes the maximum value: {drilled}",
);
}
}
mod envelope_reprocessing {
use super::drive;
fn saved_summary_envelope() -> String {
let stdin = "{\"kind\":\"bug\",\"id\":1}\n\
{\"kind\":\"bug\",\"id\":2}\n\
{\"kind\":\"feat\",\"id\":3}\n";
let (code, stdout, stderr) = drive(&["face", "--by", "kind", "--format=json"], stdin);
assert_eq!(code, 0, "stderr={stderr:?} stdout={stdout:?}");
stdout
}
#[test]
fn saved_envelope_re_renders_to_markdown() {
let envelope = saved_summary_envelope();
let (code, stdout, stderr) = drive(&["face", "--format=markdown", "--verbose"], &envelope);
assert_eq!(code, 0, "stderr={stderr:?} stdout={stdout:?}");
assert!(stdout.starts_with("# face"), "markdown heading: {stdout:?}");
assert!(
stdout.contains("bug"),
"saved clusters are rendered: {stdout:?}"
);
assert!(
stderr.contains("from face envelope"),
"provenance distinguishes re-processing: {stderr:?}",
);
}
#[test]
fn saved_summary_envelope_drills_to_cached_items() {
let envelope = saved_summary_envelope();
let (code, stdout, stderr) = drive(
&["face", "--cluster", "kind:bug", "--format=json"],
&envelope,
);
assert_eq!(code, 0, "stderr={stderr:?} stdout={stdout:?}");
let v: serde_json::Value = serde_json::from_str(&stdout).expect("parseable envelope");
assert_eq!(v["result"]["input_total"].as_u64(), Some(2));
assert_eq!(v["page"]["cluster_id"].as_str(), Some("kind:bug"));
assert_eq!(v["page"]["total_items"].as_u64(), Some(2));
let items = v["page"]["items"].as_array().expect("page.items array");
assert_eq!(
items.len(),
2,
"summary envelope has cached raw page items: {v}"
);
assert_eq!(items[0]["kind"].as_str(), Some("bug"));
assert_eq!(items[1]["kind"].as_str(), Some("bug"));
}
#[test]
fn saved_summary_envelope_auto_drill_emits_jsonl_items() {
let envelope = saved_summary_envelope();
let (code, stdout, stderr) = drive(&["face", "--cluster", "kind:bug"], &envelope);
assert_eq!(code, 0, "stderr={stderr:?} stdout={stdout:?}");
let lines = stdout.lines().collect::<Vec<_>>();
assert_eq!(
lines.len(),
2,
"two cached bug records as JSONL: {stdout:?}"
);
for line in lines {
let item: serde_json::Value = serde_json::from_str(line).expect("jsonl item");
assert_eq!(item["kind"].as_str(), Some("bug"));
}
}
#[test]
fn saved_summary_envelope_reclusters_cached_items() {
let stdin = "{\"query\":\"TODO\",\"items\":[\
{\"kind\":\"bug\",\"tag\":\"ui\"},\
{\"kind\":\"bug\",\"tag\":\"backend\"},\
{\"kind\":\"feat\",\"tag\":\"ui\"}]}";
let (code, envelope, stderr) = drive(&["face", "--by", "kind", "--format=json"], stdin);
assert_eq!(code, 0, "stderr={stderr:?} stdout={envelope:?}");
let (code, stdout, stderr) =
drive(&["face", "--within", "tag", "--format=json"], &envelope);
assert_eq!(code, 0, "stderr={stderr:?} stdout={stdout:?}");
let v: serde_json::Value = serde_json::from_str(&stdout).expect("parseable envelope");
assert_eq!(v["meta"]["query"].as_str(), Some("TODO"));
let axes = v["result"]["axes"].as_array().expect("axes array");
assert_eq!(
axes.len(),
2,
"kind > tag reclustering from cached records: {v}"
);
assert_eq!(axes[1]["field"].as_str(), Some("tag"));
let nested_tag = v["clusters"]
.as_array()
.expect("clusters array")
.iter()
.flat_map(|cluster| cluster["clusters"].as_array().into_iter().flatten())
.any(|cluster| cluster["label"].as_str() == Some("ui"));
assert!(
nested_tag,
"cached raw `tag` field is available for reclustering: {v}"
);
}
#[test]
fn saved_summary_envelope_drills_by_ordinal() {
let envelope = saved_summary_envelope();
let (code, stdout, stderr) = drive(&["face", "--cluster", "1", "--format=json"], &envelope);
assert_eq!(code, 0, "stderr={stderr:?} stdout={stdout:?}");
let v: serde_json::Value = serde_json::from_str(&stdout).expect("parseable envelope");
assert_eq!(v["page"]["cluster_id"].as_str(), Some("kind:bug"));
assert_eq!(v["page"]["total_items"].as_u64(), Some(2));
}
#[test]
fn saved_drilled_envelope_reuses_cached_page_items() {
let stdin = "{\"kind\":\"bug\",\"id\":1}\n\
{\"kind\":\"bug\",\"id\":2}\n\
{\"kind\":\"feat\",\"id\":3}\n";
let (code, envelope, stderr) = drive(
&[
"face",
"--by",
"kind",
"--cluster",
"kind:bug",
"--format=json",
],
stdin,
);
assert_eq!(code, 0, "stderr={stderr:?} stdout={envelope:?}");
let (code, stdout, stderr) = drive(
&["face", "--cluster", "kind:bug", "--format=json"],
&envelope,
);
assert_eq!(code, 0, "stderr={stderr:?} stdout={stdout:?}");
let v: serde_json::Value = serde_json::from_str(&stdout).expect("parseable envelope");
let items = v["page"]["items"].as_array().expect("page.items array");
assert_eq!(items.len(), 2, "cached page items survive: {v}");
assert_eq!(items[0]["kind"].as_str(), Some("bug"));
assert_eq!(items[1]["kind"].as_str(), Some("bug"));
}
}