haematite-cli 0.21.7

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::assistant_test_support::*;
use serde_json::json;

#[path = "assistant_boundary_revision_tests.rs"]
mod revision;
use std::{
    io::{BufRead, BufReader},
    os::unix::{fs::PermissionsExt, net::UnixStream},
    time::Duration,
};

#[test]
fn valid_hello_is_welcomed_and_socket_is_private() -> Result {
    let f = Fixture::new(json!({}))?;
    let seat = f.seat("agent-pass", "wards")?;
    assert_eq!(
        std::fs::metadata(f.files.path().join("assistant.sock"))?
            .permissions()
            .mode()
            & 0o777,
        0o600
    );
    drop(seat);
    Ok(())
}
#[test]
fn wrong_audience_is_refused_by_name() -> Result {
    let f = Fixture::new(json!({}))?;
    for (pass, code) in [
        ("wrong-audience", "wrong_audience"),
        ("wrong-agent", "assistant_agent_mismatch"),
        ("unowned-agent", "contract_refused"),
    ] {
        assert!(
            f.seat(pass, "wards")
                .err()
                .ok_or_else(|| anyhow::anyhow!("welcomed wrong audience"))?
                .to_string()
                .contains(code)
        );
    }
    Ok(())
}
#[test]
fn second_live_seat_is_refused_seat_taken() -> Result {
    let f = Fixture::new(json!({}))?;
    let first = f.seat("agent-pass", "wards")?;
    assert!(
        f.seat("agent-pass", "wards")
            .err()
            .ok_or_else(|| anyhow::anyhow!("duplicate admitted"))?
            .to_string()
            .contains("seat_taken")
    );
    drop(first);
    Ok(())
}
#[test]
fn session_person_and_context_reach_seat_and_ack_returns_202() -> Result {
    let f = Fixture::new(json!({}))?;
    let read = ack(f.seat("agent-pass", "wards")?);
    check(f.answer("POST","",&json!({"text":"ward census","person":"intruder","context":{"store":"wards","branch":"main","screen":"records","selection":["patient"]}})),202,"id")?;
    let op = read.recv_timeout(Duration::from_secs(5))?;
    assert_eq!(op["op"]["person"], PERSON);
    assert_eq!(op["op"]["text"], "ward census");
    assert_eq!(op["op"]["context"]["selection"], json!(["patient"]));
    Ok(())
}
#[test]
fn missing_seat_answers_start_route_and_lys_link() -> Result {
    let f = Fixture::new(json!({}))?;
    let text = written(f.answer("POST", "", &json!({"text":"hello"})))?;
    assert!(
        text.starts_with("HTTP/1.1 409 ")
            && text.contains("assistant_not_started")
            && text.contains("/api/assistant/wards/start")
            && text.contains("https://lys.example"),
        "{text}"
    );
    Ok(())
}
#[test]
fn zero_two_or_malformed_operations_are_refused() -> Result {
    let f = Fixture::new(json!({}))?;
    for body in [
        json!({}),
        json!({"text":"hello","cancel":true}),
        json!({"cancel":false}),
        json!({"text":7}),
        json!({"session":{"list":true,"new":true}}),
        json!({"config":{"option":"model"}}),
        json!({"command":{"name":"","input":""}}),
    ] {
        check(f.answer("POST", "", &body), 400, "assistant_op_invalid")?;
    }
    Ok(())
}
#[test]
fn cross_origin_post_is_refused_before_session() -> Result {
    let f = Fixture::new(json!({}))?;
    let mut r = f.request("POST", "", &json!({"text":"hello"}));
    r.headers = vec![("origin".into(), "https://evil.example".into())];
    check(
        super::route::answer(&f.door, &f.site, &r, crate::serve::pass::now()),
        403,
        "origin_refused",
    )
}
#[test]
fn seat_refusal_reaches_request_by_name() -> Result {
    let f = Fixture::new(json!({}))?;
    let mut seat = f.seat("agent-pass", "wards")?;
    let worker = std::thread::spawn(move || -> Result {
        let op = seat.read()?;
        seat.send(&json!({"refused":{"id":op["op"]["id"],"code":"prompt_queue_full","message":"queue full"}}))
    });
    check(
        f.answer("POST", "", &json!({"text":"hello"})),
        409,
        "prompt_queue_full",
    )?;
    worker.join().map_err(|_| anyhow::anyhow!("seat panic"))??;
    Ok(())
}
#[test]
fn frame_bound_refuses_count_plus_one() -> Result {
    let f = Fixture::new(json!({"frame_bytes":256}))?;
    let mut stream = UnixStream::connect(f.files.path().join("assistant.sock"))?;
    stream.set_read_timeout(Some(Duration::from_secs(5)))?;
    send(&mut stream, &json!({"hello":"x".repeat(244)}))?;
    let mut line = String::new();
    BufReader::new(stream).read_line(&mut line)?;
    assert!(line.contains("frame_bytes=256; count=257"), "{line}");
    Ok(())
}
#[test]
fn person_and_store_seat_bounds_name_count() -> Result {
    for (bounds, pass, store, code) in [
        (
            json!({"max_per_person":1}),
            "agent-pass",
            "other",
            "max_per_person",
        ),
        (
            json!({"max_per_store":1}),
            "other-person",
            "wards",
            "max_per_store",
        ),
        (
            json!({"door_max_connections":1}),
            "other-person",
            "wards",
            "max_connections",
        ),
    ] {
        let f = Fixture::new(bounds)?;
        let first = f.seat("agent-pass", "wards")?;
        let error = if code == "max_connections" {
            let stream = UnixStream::connect(f.files.path().join("assistant.sock"))?;
            stream.set_read_timeout(Some(Duration::from_secs(5)))?;
            let mut refused = String::new();
            BufReader::new(stream).read_line(&mut refused)?;
            refused
        } else {
            f.seat(pass, store)
                .err()
                .ok_or_else(|| anyhow::anyhow!("bound admitted"))?
                .to_string()
        };
        assert!(error.contains(&format!("{code}=1; count=1")), "{error}");
        drop(first);
    }
    Ok(())
}
#[test]
fn active_socket_and_regular_file_are_never_replaced() -> Result {
    let mut f = Fixture::new(json!({}))?;
    let before = std::fs::metadata(f.files.path().join("assistant.sock"))?;
    let second = std::os::unix::net::UnixListener::bind(f.files.path().join("assistant.sock"));
    assert!(second.is_err());
    assert_eq!(
        before.file_type(),
        std::fs::metadata(f.files.path().join("assistant.sock"))?.file_type()
    );
    let active = super::assistant_socket::Listener::start(&f.door, &f.config)
        .err()
        .ok_or_else(|| anyhow::anyhow!("active listener replaced"))?;
    assert_eq!(active.code, "assistant_socket_listening");
    let regular = f.files.path().join("regular");
    std::fs::write(&regular, b"fixture bytes")?;
    f.config
        .assistant
        .as_mut()
        .ok_or_else(|| anyhow::anyhow!("assistant config missing"))?
        .socket = regular.clone();
    let refused = super::assistant_socket::Listener::start(&f.door, &f.config)
        .err()
        .ok_or_else(|| anyhow::anyhow!("regular file replaced"))?;
    assert_eq!(refused.code, "assistant_socket_exists");
    assert_eq!(std::fs::read(&regular)?, b"fixture bytes");
    let failed = f.files.path().join("failed.sock");
    f.config
        .assistant
        .as_mut()
        .ok_or_else(|| anyhow::anyhow!("assistant config missing"))?
        .socket = failed.clone();
    let refused = super::assistant_socket::Listener::start(&f.door, &f.config)
        .err()
        .ok_or_else(|| anyhow::anyhow!("second socket attached"))?;
    assert_eq!(refused.code, "assistant_already_listening");
    assert!(!failed.exists(), "failed startup left its socket file");
    let stale = f.files.path().join("stale.sock");
    drop(std::os::unix::net::UnixListener::bind(&stale)?);
    f.config
        .assistant
        .as_mut()
        .ok_or_else(|| anyhow::anyhow!("assistant config missing"))?
        .socket = stale.clone();
    let fresh = std::sync::Arc::new(super::signin::Door::new(Lys {
        decisions: std::sync::atomic::AtomicUsize::new(0),
        ..Lys::default()
    }));
    let restarted = super::assistant_socket::Listener::start(&fresh, &f.config)?;
    assert_eq!(
        std::fs::metadata(&stale)?.permissions().mode() & 0o777,
        0o600
    );
    drop(restarted);
    Ok(())
}

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