haematite-cli 0.21.3

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
Documentation
#[path = "matching_support.rs"]
mod support;

use std::num::NonZeroU64;

use serde_json::{Value, json};

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

const FOUR: NonZeroU64 = NonZeroU64::MIN.saturating_add(3);
const HOP: &str = "match (c:patients)-[:admitted_to]->(w:wards)";

#[derive(serde::Deserialize)]
struct Answer {
    rows: Vec<Row>,
}
#[derive(serde::Deserialize)]
struct Row {
    value: Bindings,
}
#[derive(serde::Deserialize)]
struct Bindings {
    c: Box<serde_json::value::RawValue>,
    w: Box<serde_json::value::RawValue>,
}

#[test]
fn match_one_hop_returns_one_value_per_edge_in_origin_then_far_id_order() -> TestResult {
    let fixture = Fixture::new()?;
    let answer = fixture.run(&request(HOP, FOUR), &[])?;
    let rows = answer
        .get("rows")
        .and_then(Value::as_array)
        .ok_or("rows missing")?;
    assert_eq!(
        values(&answer)?,
        vec![
            json!({"c":fixture.value("patients", "p0")?,"w":{"ward":"A"}}),
            json!({"c":fixture.value("patients", "p0")?,"w":{"ward":"B"}}),
            json!({"c":{"name":"Bea"},"w":{"ward":"A"}}),
        ]
    );
    assert_eq!(
        rows.iter()
            .map(|row| &row["origin"]["id"])
            .collect::<Vec<_>>(),
        vec![&json!("p0"), &json!("p0"), &json!("p1")]
    );
    for row in rows {
        assert_eq!(row["origin"]["collection"], "patients");
        assert!(row["origin"]["score"].is_null());
        assert_eq!(
            row["origin"]["version"],
            fixture.revision(
                "patients",
                row["origin"]["id"].as_str().ok_or("id missing")?
            )?
        );
    }
    assert_eq!(answer["records_read"], 8);
    assert_eq!(answer["stages"], json!(["match"]));
    assert!(answer["next"].is_null());
    Ok(())
}

#[test]
fn match_incoming_uses_the_first_node_origin_and_filters_kind_and_far_label() -> TestResult {
    let fixture = Fixture::new()?;
    let answer = fixture.run(
        &request("match (w:wards)<-[:admitted_to]-(c:patients)", FOUR),
        &[],
    )?;
    let rows = answer
        .get("rows")
        .and_then(Value::as_array)
        .ok_or("rows missing")?;
    assert_eq!(
        values(&answer)?,
        vec![
            json!({"w":{"ward":"A"},"c":fixture.value("patients", "p0")?}),
            json!({"w":{"ward":"A"},"c":{"name":"Bea"}}),
            json!({"w":{"ward":"B"},"c":fixture.value("patients", "p0")?}),
        ]
    );
    assert_eq!(
        rows.iter()
            .map(|row| &row["origin"]["id"])
            .collect::<Vec<_>>(),
        vec![&json!("w0"), &json!("w0"), &json!("w1")]
    );
    assert!(
        rows.iter()
            .all(|row| row["origin"]["collection"] == "wards")
    );
    Ok(())
}

#[test]
fn match_denied_far_record_and_its_edge_are_absent_and_not_charged() -> TestResult {
    let mut fixture = Fixture::new()?;
    fixture.scan(6)?;
    let answer = fixture.run(&request(HOP, FOUR), &[("wards", "w1")])?;
    assert_eq!(values(&answer)?.len(), 2);
    assert_eq!(answer["records_read"], 6);
    assert!(!serde_json::to_string(&answer)?.contains("\"ward\":\"B\""));
    let all_denied = fixture.run(&request(HOP, FOUR), &[("wards", "w0"), ("wards", "w1")])?;
    assert!(values(&all_denied)?.is_empty());
    assert_eq!(all_denied["records_read"], 2);
    Ok(())
}

#[test]
fn match_denied_first_record_never_expands_its_edges() -> TestResult {
    let fixture = Fixture::new()?;
    let answer = fixture.run(&request(HOP, FOUR), &[("patients", "p0")])?;
    assert_eq!(
        values(&answer)?,
        vec![json!({"c":{"name":"Bea"},"w":{"ward":"A"}})]
    );
    assert_eq!(answer["records_read"], 4);
    Ok(())
}

#[test]
fn match_each_label_must_name_a_collection_even_when_the_source_is_empty() -> TestResult {
    let fixture = Fixture::new()?;
    for text in [
        "match (c:absent)",
        "match (c:patients)-[:admitted_to]->(w:absent)",
        "match (c:empty)-[:admitted_to]->(w:absent)",
        "match (c:empty)-[:admitted_to]->(w:wards)<-[:staffed_by]-(n:absent)",
    ] {
        let failure = fixture
            .run(&request(text, FOUR), &[])
            .err()
            .ok_or("missing label accepted")?;
        assert_eq!(failure.code, "collection_missing", "{text}");
        assert!(failure.message.contains("absent"));
    }
    Ok(())
}

#[test]
fn match_raw_bindings_are_byte_identical_to_both_stored_records() -> TestResult {
    let fixture = Fixture::new()?;
    let raw = fixture.run_raw(&request(HOP, FOUR), &[])?;
    let answer: Answer = serde_json::from_str(raw.get())?;
    assert_eq!(answer.rows.len(), 3);
    assert_eq!(
        answer.rows[0].value.c.get().as_bytes(),
        fixture.bytes("patients", "p0")?
    );
    assert_eq!(
        answer.rows[0].value.w.get().as_bytes(),
        fixture.bytes("wards", "w0")?
    );
    assert_eq!(
        answer.rows[1].value.w.get().as_bytes(),
        fixture.bytes("wards", "w1")?
    );
    let one = fixture.run_raw(&request("match (c:patients)", FOUR), &[])?;
    assert!(
        one.get()
            .contains(std::str::from_utf8(&fixture.bytes("patients", "p0")?)?)
    );
    Ok(())
}

#[test]
fn match_scan_bound_charges_edges_and_far_reads_and_never_returns_a_partial_hop() -> TestResult {
    let mut fixture = Fixture::new()?;
    fixture.scan(7)?;
    let failure = fixture
        .run(&request(HOP, FOUR), &[])
        .err()
        .ok_or("incomplete match accepted")?;
    assert_eq!(failure.code, "query_scan_limit");
    fixture.scan(8)?;
    assert_eq!(fixture.run(&request(HOP, FOUR), &[])?["records_read"], 8);
    let small = fixture
        .run(&request(HOP, NonZeroU64::MIN), &[])
        .err()
        .ok_or("partial hop output page accepted")?;
    assert_eq!(small.code, "query_output_limit");
    Ok(())
}

#[test]
fn match_one_node_pages_and_keeps_the_record_revision() -> TestResult {
    let fixture = Fixture::new()?;
    let mut ask = request("match (c:patients)", NonZeroU64::MIN);
    let first = fixture.run(&ask, &[])?;
    assert_eq!(
        values(&first)?,
        vec![json!({"c":fixture.value("patients", "p0")?})]
    );
    ask.continuation = Some(
        first
            .get("next")
            .and_then(Value::as_str)
            .ok_or("next missing")?
            .to_owned(),
    );
    let second = fixture.run(&ask, &[])?;
    assert_eq!(values(&second)?, vec![json!({"c":{"name":"Bea"}})]);
    assert_eq!(second["records_read"], 2);
    assert!(second["next"].is_null());
    Ok(())
}

#[test]
fn match_limit_count_and_jq_receive_individual_bindings() -> TestResult {
    let fixture = Fixture::new()?;
    let answer = fixture.run(&request(&format!("{HOP} | limit(2) | count"), FOUR), &[])?;
    assert_eq!(values(&answer)?, vec![json!(2)]);
    let jq = fixture.run(
        &request(&format!("{HOP} | '.c.name' | limit(2)"), FOUR),
        &[],
    )?;
    assert_eq!(values(&jq)?, vec![json!("Ada"), json!("Ada")]);
    let scores = fixture.run(&request(&format!("{HOP} | '$score'"), FOUR), &[])?;
    assert_eq!(
        values(&scores)?,
        vec![json!(null), json!(null), json!(null)]
    );
    assert!(
        fixture
            .inputs()?
            .iter()
            .all(|frame| frame["bindings"]["score"].is_null())
    );
    let empty = fixture.run(
        &request("match (c:empty)-[:admitted_to]->(w:wards) | count", FOUR),
        &[],
    )?;
    assert_eq!(values(&empty)?, vec![json!(0)]);
    Ok(())
}

#[test]
fn match_execution_library_defaults_run_the_complete_graph_contract() -> TestResult {
    let fixture = Fixture::new()?;
    let group: Value = serde_json::from_str(include_str!("../library/matching.json"))?;
    assert_eq!(group["group"], "Executing labelled matches");
    let entries = group
        .get("entries")
        .and_then(Value::as_array)
        .ok_or("entries missing")?;
    assert_eq!(entries.len(), 2);
    for entry in entries {
        let mut text = entry
            .get("query")
            .and_then(Value::as_str)
            .ok_or("query missing")?
            .to_owned();
        let change = entry
            .get("change")
            .and_then(Value::as_str)
            .ok_or("change missing")?;
        for (name, value) in entry
            .get("defaults")
            .and_then(Value::as_object)
            .ok_or("defaults missing")?
        {
            let token = format!("${{{name}}}");
            assert!(text.contains(&token));
            assert!(change.contains(&token));
            text = text.replace(&token, value.as_str().ok_or("default is not text")?);
        }
        assert!(!text.contains("${"));
        let answer = fixture.run(&request(&text, FOUR), &[])?;
        assert_eq!(values(&answer)?.len(), 2);
        assert!(answer["next"].is_null());
    }
    Ok(())
}

#[path = "matching_chain_tests.rs"]
mod chain_tests;