use super::Bound;
use crate::serve::query::parser::{self, Span, Stage};
fn read(text: &str, start: usize) -> Result<parser::Spanned<Stage>, String> {
super::read(text, start).map_err(|error| error.to_string())
}
type TestResult = Result<(), Box<dyn std::error::Error>>;
#[test]
fn window_versions_parse_with_the_original_byte_span() -> TestResult {
let parsed = read("window(1200; 1300) | count", 23)?;
assert_eq!(parsed.span, Span { start: 23, end: 41 });
let Stage::Window(window) = parsed.value else {
return Err("window parsed as another stage".into());
};
assert_eq!(window.from, Bound::Version(1200));
assert_eq!(window.to, Bound::Version(1300));
Ok(())
}
#[test]
fn window_is_dispatched_through_the_shared_parser() -> TestResult {
let plan = parser::pipeline("patients | window(1200; 1300) | count")
.map_err(|error| error.to_string())?;
assert_eq!(plan.stages.len(), 2);
assert!(matches!(plan.stages[0].value, Stage::Window(_)));
assert_eq!(plan.stages[0].span, Span { start: 11, end: 29 });
Ok(())
}
#[test]
fn window_reversed_versions_are_refused_by_name() -> TestResult {
let refused = super::read("window(1400; 1300)", 11)
.err()
.ok_or("reversed window accepted")?;
assert_eq!(refused.code(), "query_window_reversed");
assert_eq!(refused.span(), Span { start: 11, end: 29 });
Ok(())
}
#[test]
fn window_instants_normalize_offsets_and_keep_nanoseconds() -> TestResult {
let parsed = read(
"window(\"1970-01-01T01:00:00.000000001+01:00\"; \"1970-01-01T00:00:01Z\")",
0,
)?;
let Stage::Window(window) = parsed.value else {
return Err("instant window parsed as another stage".into());
};
assert_eq!(window.from, Bound::Instant(1));
assert_eq!(window.to, Bound::Instant(1_000_000_000));
Ok(())
}
#[test]
fn window_reversed_instants_are_refused_by_name() -> TestResult {
let refused = super::read(
"window(\"2026-10-13T01:00:00Z\"; \"2026-10-12T01:00:00Z\")",
0,
)
.err()
.ok_or("reversed instants accepted")?;
assert_eq!(refused.code(), "query_window_reversed");
Ok(())
}
#[test]
fn window_now_is_only_an_upper_bound() -> TestResult {
let parsed = read("window(1200; $now)", 0)?;
let Stage::Window(window) = parsed.value else {
return Err("now window parsed as another stage".into());
};
assert_eq!(window.to, Bound::Now);
let refused = super::read("window($now; 1300)", 0)
.err()
.ok_or("now accepted as from")?;
assert_eq!(refused.code(), "query_window_bound");
Ok(())
}
#[test]
fn window_malformed_bounds_are_named_without_defaults() -> TestResult {
for text in [
"window(1; \"1970-01-01T00:00:01Z\")",
"window(-1; 2)",
"window(1.5; 2)",
"window(18446744073709551616; 2)",
"window(1, 2)",
] {
let refused = super::read(text, 0)
.err()
.ok_or("invalid bounds accepted")?;
assert_eq!(refused.code(), "query_window_bound", "{text}");
}
for instant in [
"2026-02-30T00:00:00Z",
"2026-01-01",
"2026-01-01T24:00:00Z",
"2026-01-01T00:00:60Z",
"1969-12-31T23:59:59Z",
"2026-01-01T00:00:00-00:00",
] {
let text = format!("window(\"{instant}\"; $now)");
let refused = super::read(&text, 0)
.err()
.ok_or("invalid instant accepted")?;
assert_eq!(refused.code(), "query_window_instant", "{instant}");
}
Ok(())
}
#[test]
fn window_cannot_follow_a_jq_value_stage() -> TestResult {
let refused = parser::pipeline("patients | '.' | window(1; 2)")
.err()
.ok_or("window accepted after jq")?;
assert_eq!(refused.code(), "query_parse");
assert!(refused.to_string().contains("before the jq stage"));
Ok(())
}
#[test]
fn window_query_check_returns_the_named_refusal_and_original_span() -> TestResult {
use crate::serve::query::check::{self, CheckRequest, WordsRequest};
let text = "patients | window(1400; 1300)";
let checked = check::check(&CheckRequest {
query: text.to_owned(),
});
assert_eq!(
checked
.get("refusal")
.and_then(|refusal| refusal.get("code")),
Some(&serde_json::json!("query_window_reversed"))
);
assert_eq!(
checked
.get("refusal")
.and_then(|refusal| refusal.get("span")),
Some(&serde_json::json!({"start":11,"end":29}))
);
let words = check::words(&WordsRequest {})?;
let word = words
.as_array()
.ok_or("words missing")?
.iter()
.find(|word| word.get("name") == Some(&serde_json::json!("window")))
.ok_or("window word missing")?;
assert_eq!(word.get("status"), Some(&serde_json::json!("served")));
assert_eq!(word.get("role"), Some(&serde_json::json!("bound")));
Ok(())
}