use super::{Source, Span, Stage, pipeline};
type TestResult = Result<(), Box<dyn std::error::Error>>;
fn read(text: &str) -> Result<super::Plan, String> {
pipeline(text).map_err(|error| error.to_string())
}
#[test]
fn later_shape_has_its_own_stage_and_span() -> TestResult {
let text = "patients | { name ward { code } } | limit(2)";
let plan = read(text)?;
assert_eq!(plan.stages.len(), 2);
assert_eq!(plan.stages[0].span, Span { start: 11, end: 33 });
assert!(!matches!(plan.stages[0].value, Stage::Jq(_)));
assert!(matches!(plan.stages[1].value, Stage::Limit(limit) if limit.get() == 2));
Ok(())
}
#[test]
fn first_shape_names_the_collection_and_its_projection() -> TestResult {
let text = " { patients { name ward } } | '.'";
let plan = read(text)?;
assert_eq!(
plan.source,
Source::Collection {
name: "patients".to_owned(),
arguments: Vec::new()
}
);
assert_eq!(plan.stages.len(), 2);
assert_eq!(plan.stages[0].span, Span { start: 2, end: 28 });
assert!(!matches!(plan.stages[0].value, Stage::Jq(_)));
assert_eq!(plan.stages[1].value, Stage::Jq(".".to_owned()));
Ok(())
}
#[test]
fn two_collection_roots_keep_their_selections_and_spans() -> TestResult {
use haematite_query::stage::shape::{Field, Selection};
use super::Root;
let plan = read("{ patients { name } wards { code } }")?;
assert_eq!(
plan.source,
Source::Shaped(vec![
Root {
name: "patients".to_owned(),
arguments: Vec::new(),
selection: Selection::new(vec![Field {
name: "name".to_owned(),
selection: None,
}])?,
span: Span { start: 2, end: 19 },
},
Root {
name: "wards".to_owned(),
arguments: Vec::new(),
selection: Selection::new(vec![Field {
name: "code".to_owned(),
selection: None,
}])?,
span: Span { start: 20, end: 34 },
},
])
);
assert!(plan.stages.is_empty());
Ok(())
}
#[test]
fn repeated_collection_root_is_named_at_the_second_root() -> TestResult {
let error = pipeline("{ patients { name } patients { ward } }")
.err()
.ok_or("repeated root accepted")?;
assert_eq!(error.code(), "query_parse");
assert!(
error
.to_string()
.contains("a shape repeats the root \"patients\"")
);
assert_eq!(error.span(), Span { start: 20, end: 37 });
Ok(())
}
#[test]
fn match_after_several_shaped_roots_is_refused_by_name() -> TestResult {
let query = "{ patients { name } wards { code } } | match (p:patients)";
let error = pipeline(query).err().ok_or("match accepted")?;
assert_eq!(error.code(), "query_stage_not_available");
assert!(
error
.to_string()
.contains("`match` after a shape with several roots")
);
assert_eq!(
error.span(),
Span {
start: 39,
end: query.len()
}
);
Ok(())
}
#[test]
fn shaped_roots_reuse_each_collections_argument_reader() -> TestResult {
let plan =
read("{ patients(ward: \"4B\" and admitted: 2) { name } wards(code: \"4B\") { code } }")?;
let Source::Shaped(roots) = plan.source else {
return Err("missing shaped source".into());
};
assert_eq!(roots.len(), 2);
assert_eq!(
roots[0].arguments,
vec![
super::Argument {
field: "ward".to_owned(),
value: serde_json::json!("4B"),
},
super::Argument {
field: "admitted".to_owned(),
value: serde_json::json!(2),
},
]
);
assert_eq!(
roots[1].arguments,
vec![super::Argument {
field: "code".to_owned(),
value: serde_json::json!("4B"),
}]
);
Ok(())
}
#[test]
fn a_brace_inside_quotes_is_jq() -> TestResult {
let plan = read("patients | '{ name: .name, ward: { code: .ward } }'")?;
assert_eq!(plan.stages.len(), 1);
assert_eq!(
plan.stages[0].value,
Stage::Jq("{ name: .name, ward: { code: .ward } }".to_owned())
);
let plan = read("'{ patients: . }'")?;
assert_eq!(plan.source, Source::All);
assert_eq!(
plan.stages[0].value,
Stage::Jq("{ patients: . }".to_owned())
);
Ok(())
}
#[test]
fn malformed_shapes_are_parse_refusals() -> TestResult {
for text in [
"patients | { }",
"patients | { name",
"patients | { name name }",
"patients | { ward { } }",
"patients | { name, ward }",
"{ patients }",
"{ }",
"{ patients { name }",
] {
let error = pipeline(text).err().ok_or("malformed shape accepted")?;
assert_eq!(error.code(), "query_parse", "{text}: {error}");
}
Ok(())
}
#[test]
fn unicode_whitespace_retains_byte_positions() -> TestResult {
let text = "patients | \u{2003}{ name\u{2003}ward { code } } | count";
let plan = read(text)?;
let start = text.find('{').ok_or("missing brace")?;
let end = text.rfind('}').ok_or("missing closing brace")? + 1;
assert_eq!(plan.stages[0].span, Span { start, end });
assert!(matches!(plan.stages[1].value, Stage::Count));
Ok(())
}
#[test]
fn shape_word_and_query_checker_share_the_served_brace_spans() -> TestResult {
use crate::serve::query::check::{CheckRequest, WordsRequest, check, words};
use serde_json::json;
let vocabulary = words(&WordsRequest {})?;
let word = vocabulary
.as_array()
.ok_or("missing vocabulary")?
.iter()
.find(|word| word.get("name") == Some(&json!("{")))
.ok_or("missing brace word")?;
assert_eq!(
word.get("status").ok_or("missing status")?,
&json!("served")
);
assert_eq!(word.get("role").ok_or("missing role")?, &json!("shape"));
let query = "{ patients { name } } | { name }";
let answer = check(&CheckRequest {
query: query.to_owned(),
});
assert!(answer.get("refusal").ok_or("missing refusal")?.is_null());
assert_eq!(
answer.get("stages").ok_or("missing checked stages")?,
&json!([
{"word":"{", "span":{"start":0, "end":21}},
{"word":"{", "span":{"start":24, "end":32}}
])
);
Ok(())
}
#[test]
fn excessive_nested_braces_are_refused_before_recursive_execution() -> TestResult {
let text = format!("patients | {}name{}", "{ ward ".repeat(65), " }".repeat(65));
let error = pipeline(&text).err().ok_or("unbounded grammar accepted")?;
assert_eq!(error.code(), "query_parse");
assert!(error.to_string().contains("64 selection levels"));
Ok(())
}