haematite-cli 0.21.1

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
Documentation
use std::num::NonZeroU64;

use serde_json::{Value, json};

use super::support::{Fixture, TestResult, patients, request, values};

const FOUR: NonZeroU64 = NonZeroU64::MIN.saturating_add(3);

#[test]
fn group_by_service_returns_three_wards_in_key_order_with_captured_origins() -> TestResult {
    let documents = vec![
        json!({"ward":"4B","name":"Ada"}),
        json!({"ward":"3A","name":"Bea"}),
        json!({"ward":"4B","name":"Cy"}),
        json!({"ward":"5C","name":"Dee"}),
    ];
    let fixture = Fixture::new(None, &documents)?;
    let answer = fixture.run(&request("patients | group_by(.ward)", FOUR), &[])?;
    assert_eq!(
        values(&answer)?,
        vec![
            json!({"key":"3A","count":1,"values":[documents[1]]}),
            json!({"key":"4B","count":2,"values":[documents[0], documents[2]]}),
            json!({"key":"5C","count":1,"values":[documents[3]]}),
        ]
    );
    for row in answer
        .get("rows")
        .and_then(Value::as_array)
        .ok_or("missing rows")?
    {
        let origin = row.get("origin").ok_or("missing origin")?;
        assert_eq!(
            origin.get("collection").ok_or("missing collection")?,
            "patients"
        );
        assert_eq!(origin.get("id").ok_or("missing id")?, "");
        assert_eq!(
            origin.get("version").ok_or("missing version")?,
            answer
                .get("selection")
                .and_then(|selection| selection.get("version"))
                .ok_or("missing captured version")?
        );
    }
    assert_eq!(
        answer.get("stages").ok_or("missing stages")?,
        &json!(["group_by"])
    );
    assert_eq!(answer.get("records_read").ok_or("missing scan count")?, 4);
    assert!(answer.get("next").ok_or("missing next")?.is_null());
    Ok(())
}

#[test]
fn group_by_service_missing_field_is_null_and_denied_members_are_excluded() -> TestResult {
    let documents = vec![
        json!({"name":"Ada"}),
        json!({"ward":null}),
        json!({"ward":"4B","name":"Cy"}),
    ];
    let fixture = Fixture::new(None, &documents)?;
    let answer = fixture.run(
        &request("patients | group_by(.ward)", FOUR),
        &[("patients", "p1")],
    )?;
    assert_eq!(
        values(&answer)?,
        vec![
            json!({"key":null,"count":1,"values":[documents[0]]}),
            json!({"key":"4B","count":1,"values":[documents[2]]}),
        ]
    );
    assert!(answer.get("next").ok_or("missing next")?.is_null());
    Ok(())
}

#[test]
fn group_by_service_scan_limit_refusal_names_two() -> TestResult {
    let mut fixture = Fixture::new(None, &patients())?;
    fixture.scan(NonZeroU64::MIN.saturating_add(1));
    let error = fixture
        .run(&request("patients | group_by(.ward)", FOUR), &[])
        .err()
        .ok_or("incomplete group returned")?;
    assert_eq!(error.code, "query_scan_limit");
    assert_eq!(error.message, "records read exceed scan limit 2");
    Ok(())
}

#[test]
fn group_by_service_small_page_refuses_instead_of_returning_next() -> TestResult {
    let fixture = Fixture::new(None, &patients())?;
    let error = fixture
        .run(&request("patients | group_by(.ward)", NonZeroU64::MIN), &[])
        .err()
        .ok_or("partial grouped page accepted")?;
    assert_eq!(error.code, "query_output_limit");
    assert!(error.message.contains("larger page"), "{}", error.message);
    Ok(())
}

#[test]
fn group_by_service_upstream_filters_and_downstream_count_keep_their_order() -> TestResult {
    let fixture = Fixture::new(None, &patients())?;
    let answer = fixture.run(
        &request("patients(ward: \"4B\") | group_by(.ward) | count", FOUR),
        &[],
    )?;
    assert_eq!(values(&answer)?, vec![json!(1)]);
    assert_eq!(
        answer.get("stages").ok_or("missing stages")?,
        &json!(["filter", "group_by", "count"])
    );
    assert_eq!(answer.get("records_read").ok_or("missing scan count")?, 4);
    assert!(answer.get("next").ok_or("missing next")?.is_null());
    Ok(())
}