haematite-cli 0.21.0

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
use crate::door::assistant_test_support::*;
use serde_json::json;
use std::sync::Arc;

#[test]
fn r3_09_an_unrelated_agent_cannot_squat_on_the_store_seat() -> Result {
    let f = Fixture::new(json!({}))?;
    let mut real = f.seat("agent-pass", "wards")?;
    let error = f
        .seat("squatter", "wards")
        .err()
        .ok_or_else(|| anyhow::anyhow!("unrelated agent admitted"))?;
    assert!(error.to_string().contains("seat_not_yours"), "{error}");
    let request = f.request("POST", "", &json!({"text":"real seat"}));
    let (door, site) = (Arc::clone(&f.door), Arc::clone(&f.site));
    let post = std::thread::spawn(move || {
        written(crate::door::route::answer(
            &door,
            &site,
            &request,
            crate::serve::pass::now(),
        ))
    });
    let op = real.read()?;
    assert_eq!(op["op"]["text"], "real seat");
    real.send(&json!({"ack":{"id":op["op"]["id"]}}))?;
    assert!(
        post.join()
            .map_err(|_| anyhow::anyhow!("post"))??
            .starts_with("HTTP/1.1 202 ")
    );
    Ok(())
}

#[test]
fn r3_10_context_is_bound_to_the_url_store_and_a_typed_local_branch() -> Result {
    let f = Fixture::new(json!({}))?;
    let read = ack(f.seat("agent-pass", "wards")?);
    for context in [
        json!({"store":"pharmacy","branch":"main"}),
        json!({"store":"wards","branch":7}),
        json!({"store":"wards","branch":""}),
        json!({"store":"wards","branch":"main\nother"}),
        json!({"branch":"main"}),
    ] {
        let response = written(f.answer("POST", "", &json!({"text":"context","context":context})))?;
        assert!(
            response.starts_with("HTTP/1.1 400 ") && response.contains("assistant_context_"),
            "{response}"
        );
    }
    check(
        f.answer(
            "POST",
            "",
            &json!({"text":"correct","context":{"store":"wards","branch":"main"}}),
        ),
        202,
        "id",
    )?;
    assert_eq!(read.recv()?["op"]["context"]["store"], "wards");
    Ok(())
}

#[test]
fn r3_16_invalid_session_person_never_becomes_a_record_prefix() -> Result {
    for person in [
        "bad/person".to_owned(),
        "bad\\person".into(),
        "bad\nperson".into(),
        String::new(),
        "x".repeat(257),
    ] {
        let f = Fixture::new(json!({"session_person":person}))?;
        let response = written(f.answer("POST", "", &json!({"text":"words"})))?;
        assert!(
            response.starts_with("HTTP/1.1 400 ") && response.contains("assistant_person_invalid"),
            "unsafe person admitted: {response}"
        );
    }
    Ok(())
}

#[test]
fn r3_gap_per_store_pending_bound_and_ack_release_admit_a_third_op() -> Result {
    let f = Fixture::new(json!({"max_per_person":2,"max_per_store":1}))?;
    let mut seat = f.seat("agent-pass", "wards")?;
    let request = f.request("POST", "", &json!({"text":"first"}));
    let (door, site) = (Arc::clone(&f.door), Arc::clone(&f.site));
    let first = std::thread::spawn(move || {
        written(crate::door::route::answer(
            &door,
            &site,
            &request,
            crate::serve::pass::now(),
        ))
    });
    let op = seat.read()?;
    let second = written(f.answer("POST", "", &json!({"text":"second"})))?;
    assert!(second.contains("max_per_store=1; count=1"), "{second}");
    seat.send(&json!({"ack":{"id":op["op"]["id"]}}))?;
    assert!(
        first
            .join()
            .map_err(|_| anyhow::anyhow!("post"))??
            .starts_with("HTTP/1.1 202 ")
    );
    let read = ack(seat);
    check(f.answer("POST", "", &json!({"text":"third"})), 202, "id")?;
    assert_eq!(read.recv()?["op"]["text"], "third");
    Ok(())
}

#[test]
fn r3_gap_outgoing_door_frame_names_the_byte_bound() -> Result {
    let f = Fixture::new(json!({"frame_bytes":1024}))?;
    let read = ack(f.seat("agent-pass", "wards")?);
    let text = written(f.answer("POST", "", &json!({"text":"x".repeat(1024)})))?;
    assert!(
        text.starts_with("HTTP/1.1 429 ") && text.contains("frame_bytes=1024; count="),
        "{text}"
    );
    check(f.answer("POST", "", &json!({"text":"small"})), 202, "id")?;
    assert_eq!(read.recv()?["op"]["text"], "small");
    Ok(())
}