use crate::serve::query::parser::{Plan, Source, Span, Stage, pipeline, pipeline_with};
type TestResult = Result<(), String>;
fn parsed(text: &str) -> Result<Plan, String> {
pipeline(text).map_err(|error| error.to_string())
}
#[test]
fn match_source_forms_are_accepted_with_exact_stage_spans() -> TestResult {
for (text, expected) in [
("match (c:patients)", Span { start: 0, end: 18 }),
(
"match (c:patients)-[:admitted_to]->(w:wards)",
Span { start: 0, end: 44 },
),
(
"match (c:patients)<-[:admitted_to]-(w:wards)",
Span { start: 0, end: 44 },
),
(
"match (c:patients)-[:in]->(w:wards)-[:in]->(h:hospitals)",
Span { start: 0, end: 56 },
),
] {
let plan = parsed(text)?;
assert_eq!(plan.source, Source::All, "{text}");
assert_eq!(plan.stages.len(), 1, "{text}");
assert_eq!(plan.stages[0].span, expected, "{text}");
}
Ok(())
}
#[test]
fn match_follows_only_its_named_collection() -> TestResult {
let plan = parsed("patients | match (c:patients)")?;
assert_eq!(
plan.source,
Source::Collection {
name: "patients".to_owned(),
arguments: Vec::new(),
}
);
assert_eq!(plan.stages.len(), 1);
assert_eq!(plan.stages[0].span, Span { start: 11, end: 29 });
for (text, construct) in [
("wards | match (c:patients)", "first node's label"),
("patients | limit(1) | match (c:patients)", "immediately"),
("'.' | match (c:patients)", "immediately"),
("match (c:patients) | match (w:wards)", "immediately"),
] {
let error = pipeline(text).err().ok_or("misplaced match accepted")?;
assert_eq!(error.code(), "query_stage_not_available", "{text}");
assert!(error.to_string().contains(construct), "{error}");
}
Ok(())
}
#[test]
fn match_refuses_each_unsupported_construct_by_name() -> TestResult {
for (text, construct) in [
("match (c)", "no label"),
("match (c:patients)-[:in]->(w)", "no label"),
("match (c:patients {id: 1})", "property map"),
("match (c:patients) WHERE c.id = 1", "WHERE"),
("match (c:patients) where c.id = 1", "WHERE"),
("match (C:patients)", "binding name"),
("match (c-1:patients)", "binding name"),
("match (c:patients)-[r:in]->(w:wards)", "relation binding"),
("match (c:patients)-[:in]-(w:wards)", "undirected"),
("match (c:patients)<-[:in]->(w:wards)", "bidirectional"),
] {
let error = pipeline(text).err().ok_or("unsupported match accepted")?;
assert_eq!(error.code(), "query_stage_not_available", "{text}");
assert!(error.to_string().contains(construct), "{error}");
assert!(text.get(error.span().start..error.span().end).is_some());
}
Ok(())
}
#[test]
fn quoted_match_remains_jq_with_its_exact_span() -> TestResult {
for (text, span) in [
("'match(re)'", Span { start: 0, end: 11 }),
("patients | 'match(re)'", Span { start: 11, end: 22 }),
] {
let plan = parsed(text)?;
assert_eq!(plan.stages.len(), 1);
assert_eq!(plan.stages[0].value, Stage::Jq("match(re)".to_owned()));
assert_eq!(plan.stages[0].span, span);
}
Ok(())
}
#[test]
fn match_pipeline_preserves_following_stages_and_collection_arguments() -> TestResult {
let plan = parsed("patients(ward: \"4B\") | match (c:patients) | count")?;
let Source::Collection { name, arguments } = plan.source else {
return Err("match lost its collection source".to_owned());
};
assert_eq!(name, "patients");
assert_eq!(arguments.len(), 1);
assert_eq!(arguments[0].field, "ward");
assert_eq!(arguments[0].value, serde_json::json!("4B"));
assert_eq!(plan.stages.len(), 2);
let mut words = Vec::new();
pipeline_with(
"patients(ward: \"4B\") | match (c:patients) | count",
|word, _| {
words.push(word.to_owned());
},
)
.map_err(|error| error.to_string())?;
assert_eq!(words, vec!["patients", "match", "count"]);
assert_eq!(plan.stages[0].span, Span { start: 23, end: 41 });
assert_eq!(plan.stages[1].value, Stage::Count);
assert_eq!(plan.stages[1].span, Span { start: 44, end: 49 });
Ok(())
}
#[test]
fn match_query_check_reports_the_stage_and_construct_byte_ranges() -> TestResult {
use crate::serve::query::check::{CheckRequest, check};
use serde_json::json;
let checked = check(&CheckRequest {
query: "match (c:patients) | count".to_owned(),
});
assert_eq!(
checked,
json!({
"stages": [
{"word": "match", "span": {"start": 0, "end": 18}},
{"word": "count", "span": {"start": 21, "end": 26}},
],
"refusal": null,
})
);
let query = "patients | match (c:patients) WHERE c.id = 1";
let refused = pipeline(query).err().ok_or("WHERE was accepted")?;
let checked = check(&CheckRequest {
query: query.to_owned(),
});
assert_eq!(
checked["stages"],
json!([{"word": "patients", "span": {"start": 0, "end": 8}}])
);
assert_eq!(checked["refusal"]["code"], "query_stage_not_available");
assert_eq!(checked["refusal"]["message"], refused.to_string());
assert_eq!(checked["refusal"]["span"], json!({"start": 30, "end": 35}));
Ok(())
}