#[path = "shape_support.rs"]
mod support;
#[path = "shape_roots_tests.rs"]
mod roots;
use std::num::NonZeroU64;
use serde_json::{Value, json};
use crate::serve::query::{Continuation, Rows};
use support::{Fixture, TestResult, request};
const FOUR: NonZeroU64 = NonZeroU64::MIN.saturating_add(3);
const ONE: NonZeroU64 = NonZeroU64::MIN;
fn rows(answer: &Value) -> Result<Vec<Value>, Box<dyn std::error::Error>> {
answer
.get("rows")
.and_then(Value::as_array)
.ok_or_else(|| "missing rows".into())
.cloned()
}
fn values(answer: &Value) -> Result<Vec<Value>, Box<dyn std::error::Error>> {
rows(answer)?
.iter()
.map(|row| {
row.get("value")
.cloned()
.ok_or_else(|| "missing value".into())
})
.collect()
}
fn documents() -> Vec<Value> {
vec![
json!({"name":"Ada", "ward":{"code":"4B", "beds":12}, "private":"hidden"}),
json!({"name":"Bea", "ward":{"code":"3A", "beds":8}, "private":"hidden"}),
json!({"name":"Cy", "private":"hidden"}),
]
}
#[test]
fn later_shape_cuts_fields_and_preserves_input_order_and_origins() -> TestResult {
let documents = documents();
let fixture = Fixture::new(&documents)?;
let before = fixture.run(&request("patients | '.'", FOUR), &[])?;
let after = fixture.run(&request("patients | '.' | { name ward }", FOUR), &[])?;
assert_eq!(
values(&after)?,
vec![
json!({"name":"Ada", "ward":{"code":"4B", "beds":12}}),
json!({"name":"Bea", "ward":{"code":"3A", "beds":8}}),
json!({"name":"Cy"}),
]
);
for (old, cut) in rows(&before)?.iter().zip(rows(&after)?) {
assert_eq!(
old.get("origin").ok_or("missing old origin")?,
cut.get("origin").ok_or("missing cut origin")?
);
}
assert_eq!(
after.get("stages").ok_or("missing stages")?,
&json!(["jq", "{"])
);
assert_eq!(
fixture.inputs()?,
documents
.iter()
.chain(&documents)
.cloned()
.collect::<Vec<_>>()
);
Ok(())
}
#[test]
fn nested_shape_cuts_inside_objects_and_never_invents_absent_fields() -> TestResult {
let fixture = Fixture::new(&documents())?;
let answer = fixture.run(
&request("patients | { name ward { code missing } absent }", FOUR),
&[],
)?;
assert_eq!(
values(&answer)?,
vec![
json!({"name":"Ada", "ward":{"code":"4B"}}),
json!({"name":"Bea", "ward":{"code":"3A"}}),
json!({"name":"Cy"}),
]
);
assert_eq!(
answer.get("records_read").ok_or("missing count")?,
&json!(3)
);
Ok(())
}
#[test]
fn first_shape_reads_one_collection_and_worker_input_is_already_cut() -> TestResult {
let fixture = Fixture::new(&documents())?;
let answer = fixture.run(
&request("{ patients { name ward { code } } } | '.'", FOUR),
&[],
)?;
let expected = vec![
json!({"name":"Ada", "ward":{"code":"4B"}}),
json!({"name":"Bea", "ward":{"code":"3A"}}),
json!({"name":"Cy"}),
];
assert_eq!(fixture.inputs()?, expected);
assert_eq!(values(&answer)?, expected);
let frames = fixture.frames()?;
assert_eq!(frames.len(), 3);
for (index, frame) in frames.iter().enumerate() {
let bindings = frame.get("bindings").ok_or("missing Input bindings")?;
assert_eq!(
bindings
.get("collection")
.ok_or("missing Input collection")?,
&json!("patients")
);
assert_eq!(
bindings.get("id").ok_or("missing Input id")?,
&json!(format!("p{index}"))
);
let written = fixture
.written
.get(&format!("patients/p{index}"))
.ok_or("Input record was never written")?;
let written = serde_json::to_value(written)?;
assert_ne!(
&written,
answer
.get("selection")
.and_then(|value| value.get("version"))
.ok_or("missing selected version")?
);
assert_eq!(
bindings.get("version").ok_or("missing Input version")?,
&written
);
assert_eq!(
bindings.get("score").ok_or("missing Input score")?,
&Value::Null
);
}
let origins = rows(&answer)?;
assert_eq!(origins.len(), 3);
for (index, row) in origins.iter().enumerate() {
let origin = row.get("origin").ok_or("missing origin")?;
assert_eq!(
origin.get("collection").ok_or("missing collection")?,
&json!("patients")
);
assert_eq!(
origin.get("id").ok_or("missing id")?,
&json!(format!("p{index}"))
);
let written = fixture
.written
.get(&format!("patients/p{index}"))
.ok_or("origin record was never written")?;
assert_eq!(
origin.get("version").ok_or("missing version")?,
&serde_json::to_value(written)?
);
}
Ok(())
}
#[test]
fn shapes_before_and_after_jq_replay_without_skipping_or_repeating_rows() -> TestResult {
let fixture = Fixture::new(&documents())?;
let mut ask = request(
"{ patients { name ward } } | '.' | { name } | limit(3)",
ONE,
);
let mut names = Vec::new();
for index in 0..3 {
let answer = fixture.run(&ask, &[])?;
names.extend(values(&answer)?);
let next = answer.get("next").ok_or("missing next")?;
if index < 2 {
let next = next.as_str().ok_or("missing continuation")?;
let token = Continuation::decode(next, &fixture.seal)?;
assert_eq!(token.resume.pending.id, format!("p{}", index + 1));
ask.continuation = Some(next.to_owned());
} else {
assert!(next.is_null());
}
}
assert_eq!(
names,
vec![
json!({"name":"Ada"}),
json!({"name":"Bea"}),
json!({"name":"Cy"})
]
);
assert!(
fixture
.inputs()?
.iter()
.all(|value| value.get("private").is_none())
);
Ok(())
}
#[test]
fn shape_keeps_access_denials_and_origin_only_queries() -> TestResult {
let fixture = Fixture::new(&documents())?;
let mut ask = request("{ patients { name } } | '.'", FOUR);
ask.rows = Rows::Origins;
let answer = fixture.run(&ask, &[("patients", "p1")])?;
let rows = rows(&answer)?;
assert_eq!(rows.len(), 2);
assert!(rows.iter().all(|row| row.get("value").is_none()));
assert_eq!(
fixture.inputs()?,
vec![json!({"name":"Ada"}), json!({"name":"Cy"})]
);
Ok(())
}
#[test]
fn first_shape_without_jq_projects_and_preserves_the_collection_source() -> TestResult {
let fixture = Fixture::new(&documents())?;
let answer = fixture.run(&request("{ patients { name } }", FOUR), &[])?;
assert_eq!(
values(&answer)?,
vec![
json!({"name":"Ada"}),
json!({"name":"Bea"}),
json!({"name":"Cy"})
]
);
assert_eq!(answer.get("stages").ok_or("missing stages")?, &json!(["{"]));
assert!(fixture.inputs()?.is_empty());
Ok(())
}
#[test]
fn shape_after_expanding_jq_replays_the_pending_output_with_its_original_origin() -> TestResult {
let fixture = Fixture::new(&[json!({"items":[
{"name":"Ada", "private":1}, {"name":"Bea", "private":2},
{"name":"Cy", "private":3}
], "private":4})])?;
let mut ask = request(
"{ patients { items } } | '.items[]' | { name } | limit(3)",
ONE,
);
let mut names = Vec::new();
let mut origin = None;
for index in 0..3 {
let answer = fixture.run(&ask, &[])?;
names.extend(values(&answer)?);
let rows = rows(&answer)?;
assert_eq!(rows.len(), 1);
let current = rows[0].get("origin").ok_or("missing origin")?;
if let Some(previous) = &origin {
assert_eq!(previous, current);
}
origin = Some(current.clone());
let next = answer.get("next").ok_or("missing next")?;
if index < 2 {
let next = next.as_str().ok_or("missing continuation")?;
let token = Continuation::decode(next, &fixture.seal)?;
assert_eq!(token.resume.pending.id, "p0");
assert_eq!(token.resume.discard_outputs, index + 1);
assert_eq!(token.resume.passed_counts, vec![index + 1]);
ask.continuation = Some(next.to_owned());
} else {
assert!(next.is_null());
}
}
assert_eq!(
names,
vec![
json!({"name":"Ada"}),
json!({"name":"Bea"}),
json!({"name":"Cy"})
]
);
let inputs = fixture.inputs()?;
assert_eq!(inputs.len(), 3);
assert!(inputs.iter().all(|value| value.get("private").is_none()));
Ok(())
}
#[test]
fn shape_before_count_keeps_one_result_for_each_incoming_value() -> TestResult {
let fixture = Fixture::new(&documents())?;
for query in [
"patients | { absent } | count",
"patients | '.' | { absent } | count",
] {
let answer = fixture.run(&request(query, FOUR), &[])?;
assert_eq!(values(&answer)?, vec![json!(3)]);
}
Ok(())
}
#[test]
fn stage_braces_fix_the_pipeline_reading_even_if_the_collection_is_a_jq_word() -> TestResult {
let query = "empty | { name }";
let parsed = super::super::shape::parse(query)?;
assert_eq!(super::super::shape::program(query, &parsed), ".");
for query in ["{ name }", "{ name: .name }", ". | { name }"] {
let error = super::super::shape::parse(query)
.err()
.ok_or("shape became jq")?;
assert_eq!(error.code, "query_parse");
}
for query in ["'{name}'", "patients(field: {\"x\":1}) | '.'"] {
let parsed = super::super::shape::parse(query)?;
assert_eq!(super::super::shape::program(query, &parsed), query);
}
Ok(())
}