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 super::{SCHEMA, registered, run, schema_client};
use std::io::{Read, Write};
use std::net::TcpListener;
use std::sync::mpsc;
use std::time::Duration;
type TestResult = Result<(), Box<dyn std::error::Error>>;

#[test]
fn schema_request_has_a_total_deadline() -> TestResult {
    let listener = TcpListener::bind("127.0.0.1:0")?;
    let address = url::Url::parse(&format!("http://{}/schema", listener.local_addr()?))?;
    let (release, released) = mpsc::channel();
    let server = std::thread::spawn(move || -> std::io::Result<()> {
        let (_stream, _) = listener.accept()?;
        released.recv().map_err(std::io::Error::other)?;
        Ok(())
    });
    let (send, receive) = mpsc::channel();
    let worker = std::thread::spawn(move || -> Result<(), String> {
        let runtime = tokio::runtime::Builder::new_current_thread()
            .enable_all()
            .build()
            .map_err(|error| error.to_string())?;
        let http = schema_client(Duration::from_millis(50))?;
        let result = runtime.block_on(registered(&http, &address, "fixture"));
        send.send(result).map_err(|error| error.to_string())
    });
    let answer = receive.recv_timeout(Duration::from_secs(2));
    release.send(())?;
    server.join().map_err(|_| "server panicked")??;
    worker.join().map_err(|_| "request panicked")??;
    assert!(
        answer.is_ok(),
        "schema request outlived its configured total timeout"
    );
    assert!(answer?.is_err());
    Ok(())
}

#[test]
fn schema_status_leaves_stdout_available_for_install_json() -> TestResult {
    let listener = TcpListener::bind("127.0.0.1:0")?;
    let issuer = format!("http://{}/", listener.local_addr()?);
    let server = std::thread::spawn(move || -> std::io::Result<()> {
        let (mut stream, _) = listener.accept()?;
        let mut byte = [0];
        let mut head = Vec::new();
        while !head.ends_with(b"\r\n\r\n") {
            stream.read_exact(&mut byte)?;
            head.push(byte[0]);
        }
        let schema: serde_json::Value = serde_json::from_str(SCHEMA)?;
        let body = serde_json::json!({"version": 1, "schema": schema}).to_string();
        write!(
            stream,
            "HTTP/1.1 200 OK\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}",
            body.len()
        )?;
        Ok(())
    });
    let output = std::process::Command::new(std::env::current_exe()?)
        .args([
            "--exact",
            "serve::lys_schema::deadline_tests::schema_output_child",
            "--nocapture",
        ])
        .env("HAEMATITE_SCHEMA_TEST_ISSUER", issuer)
        .output()?;
    server.join().map_err(|_| "server panicked")??;
    assert!(output.status.success());
    let stdout = String::from_utf8(output.stdout)?;
    let stderr = String::from_utf8(output.stderr)?;
    assert!(
        !stdout.contains("registered:"),
        "schema status polluted stdout: {stdout}"
    );
    assert!(
        stderr.contains("registered:"),
        "schema status was absent from stderr: {stderr}"
    );
    Ok(())
}

#[test]
fn schema_output_child() -> TestResult {
    let Ok(issuer) = std::env::var("HAEMATITE_SCHEMA_TEST_ISSUER") else {
        return Ok(());
    };
    let dir = tempfile::tempdir()?;
    let path = dir.path().join("credential");
    std::fs::write(&path, "lys-app.haematite.fixture")?;
    run([
        "--issuer".to_owned(),
        issuer,
        "--credential-file".to_owned(),
        path.display().to_string(),
        "--check".to_owned(),
    ]
    .into_iter())?;
    Ok(())
}