haematite-cli 0.21.10

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
Documentation
use super::super::{Span, Stage, pipeline};
use super::read;

type TestResult = Result<(), Box<dyn std::error::Error>>;

#[test]
fn group_by_grammar_returns_the_field_and_original_byte_span() -> TestResult {
    let parsed = read("group_by ( .ward ) | count", 11)?;
    assert_eq!(
        parsed.value,
        Stage::GroupBy {
            field: "ward".to_owned()
        }
    );
    assert_eq!(parsed.span, Span { start: 11, end: 29 });
    let plan = pipeline("patients | group_by(.ward) | count")?;
    assert_eq!(
        plan.stages[0].value,
        Stage::GroupBy {
            field: "ward".to_owned()
        }
    );
    assert_eq!(plan.stages[0].span, Span { start: 11, end: 26 });
    assert_eq!(plan.stages[1].value, Stage::Count);
    Ok(())
}

#[test]
fn group_by_refuses_missing_nested_and_multiple_fields_by_name() -> TestResult {
    for text in [
        "group_by",
        "group_by()",
        "group_by(ward)",
        "group_by(.)",
        "group_by(.ward.name)",
        "group_by(.ward; .name)",
        "group_by(.ward",
        "group_by(.ward, .name)",
    ] {
        let error = read(text, 7)
            .err()
            .ok_or("invalid grouping grammar accepted")?;
        assert_eq!(error.code(), "query_parse", "{text}");
        assert!(error.to_string().contains("group_by"), "{error}");
        assert!(error.span().start >= 7, "{error}");
        assert!(error.span().end <= 7 + text.len(), "{error}");
    }
    Ok(())
}

#[test]
fn group_by_is_a_stage_and_requires_a_collection() -> TestResult {
    let error = pipeline("group_by(.ward)")
        .err()
        .ok_or("source-free group accepted")?;
    assert_eq!(error.code(), "query_parse");
    assert!(
        error.to_string().contains("must follow a collection"),
        "{error}"
    );
    Ok(())
}

#[test]
fn quoted_group_by_remains_one_jq_stage_with_its_exact_text() -> TestResult {
    for (text, start) in [("'group_by(.x)'", 0), ("patients | 'group_by(.x)'", 11)] {
        let plan = pipeline(text)?;
        assert_eq!(plan.stages.len(), 1);
        assert_eq!(plan.stages[0].value, Stage::Jq("group_by(.x)".to_owned()));
        assert_eq!(
            plan.stages[0].span,
            Span {
                start,
                end: text.len()
            }
        );
    }
    Ok(())
}

#[test]
fn group_by_refuses_record_grouping_after_jq() -> TestResult {
    let error = pipeline("patients | '.' | group_by(.ward)")
        .err()
        .ok_or("grouping after jq accepted")?;
    assert_eq!(error.code(), "query_parse");
    assert!(error.to_string().contains("before the jq stage"), "{error}");
    Ok(())
}