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
use std::fmt::Write;

use super::super::{Refused, Span, Stage};
use super::{Direction, read};

type TestResult = Result<(), String>;

#[test]
fn match_two_hops_keep_every_node_relation_and_byte_span() -> TestResult {
    let query = "match (a:patients)-[:admitted_to]->(w:wards)<-[:staffed_by]-(n:nurses)";
    let parsed = read(query, 11).map_err(|error| error.to_string())?;
    assert_eq!(
        parsed.span,
        Span {
            start: 11,
            end: 11 + query.len()
        }
    );
    let Stage::Match { nodes, relations } = parsed.value else {
        return Err("chain returned another stage".into());
    };
    assert_eq!(nodes.len(), 3);
    assert_eq!(relations.len(), 2);
    for (node, binding, label, start, end) in [
        (&nodes[0], "a", "patients", 6, 18),
        (&nodes[1], "w", "wards", 35, 44),
        (&nodes[2], "n", "nurses", 60, 70),
    ] {
        assert_eq!(node.binding.value, binding);
        assert_eq!(node.label.value, label);
        assert_eq!(
            node.span,
            Span {
                start: 11 + start,
                end: 11 + end
            }
        );
        assert_eq!(
            query
                .get(node.binding.span.start - 11..node.binding.span.end - 11)
                .ok_or("span outside query")?,
            binding
        );
        assert_eq!(
            query
                .get(node.label.span.start - 11..node.label.span.end - 11)
                .ok_or("span outside query")?,
            label
        );
    }
    for (relation, kind, direction, start, end) in [
        (&relations[0], "admitted_to", Direction::Outgoing, 18, 35),
        (&relations[1], "staffed_by", Direction::Incoming, 44, 60),
    ] {
        assert_eq!(relation.kind.value, kind);
        assert_eq!(relation.direction, direction);
        assert_eq!(
            relation.span,
            Span {
                start: 11 + start,
                end: 11 + end
            }
        );
        assert_eq!(
            query
                .get(relation.kind.span.start - 11..relation.kind.span.end - 11)
                .ok_or("span outside query")?,
            kind
        );
    }
    Ok(())
}

#[test]
fn match_seven_hops_are_accepted_and_eighth_is_refused_at_its_arrow() -> TestResult {
    let mut query = "match (a0:patients)".to_owned();
    for index in 1..=7 {
        write!(query, "-[:admitted_to]->(a{index}:patients)").map_err(|error| error.to_string())?;
    }
    let parsed = read(&query, 23).map_err(|error| error.to_string())?;
    let Stage::Match { nodes, relations } = parsed.value else {
        return Err("chain returned another stage".into());
    };
    assert_eq!(nodes.len(), 8);
    assert_eq!(relations.len(), 7);
    let arrow_start = 23 + query.len();
    query.push_str("<-[:staffed_by]-(a8:patients)");
    let refusal = read(&query, 23).err().ok_or("eighth hop accepted")?;
    assert!(matches!(
        refusal,
        Refused::NotYet("more than seven hops in `match`", _)
    ));
    assert_eq!(refusal.code(), "query_stage_not_available");
    assert_eq!(
        refusal.span(),
        Span {
            start: arrow_start,
            end: arrow_start + 16
        }
    );
    Ok(())
}