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
//! The caller's bearer is its own Lys pass for haematite. The HTTP MCP does
//! not judge it: it presents it to the store as `session.pass` on every
//! store connection, before the tool's verb, and answers the store's verdict.

use serde_json::{Value, json};

use super::fixtures::{
    PASS, Reply, Server, TestResult, config, refusal, request, request_with_headers,
};
use super::native_call_fixture::{Asked, Store};

const CAP: usize = 131_072;
const METADATA: &str = "https://identity.example/resource-metadata";
const DESCRIBE: &str = r#"{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"describe_store","arguments":{"selection":{"branch":"main","version":null},"page":{"rows":64,"bytes":65536},"after":{}}}}"#;

fn challenged() -> Result<super::Config, serde_json::Error> {
    let mut config = config()?;
    config.resource_metadata_url = Some(METADATA.to_owned());
    Ok(config)
}

fn challenge(response: &Reply) -> Option<&str> {
    response
        .headers
        .get("www-authenticate")
        .and_then(|value| value.to_str().ok())
}

fn methods(asked: &[Asked], connection: usize) -> Vec<&str> {
    asked
        .iter()
        .filter(|asked| asked.connection == connection)
        .map(|asked| asked.method.as_str())
        .collect()
}

#[test]
fn the_bearer_pass_is_presented_unchanged_before_the_verb_on_every_store_connection() -> TestResult
{
    let store = Store::start()?;
    let mcp = Server::mcp(config()?, store.path.clone(), CAP, 2)?;
    // describe_store reads collections.list and then branches.list, each on
    // its own store connection; the fixture refuses branches.list, so the
    // tool ends there with a tool error the HTTP answer carries.
    let response = Reply::read(request(&mcp, "POST", DESCRIBE, true, &[])?)?;
    assert_eq!(response.status, 200);
    let body: Value = serde_json::from_slice(&response.body)?;
    assert_eq!(body.pointer("/result/isError"), Some(&json!(true)));
    let asked = store.asked()?;
    assert_eq!(
        methods(&asked, 0),
        ["hello", "session.pass", "collections.list"]
    );
    assert_eq!(
        methods(&asked, 1),
        ["hello", "session.pass", "branches.list"]
    );
    assert_eq!(asked.len(), 6, "{asked:?}");
    for presented in asked.iter().filter(|asked| asked.method == "session.pass") {
        assert_eq!(presented.params, json!({ "pass": PASS }));
    }
    mcp.finish()?;
    store.finish()
}

#[test]
fn a_request_without_a_bearer_is_challenged_and_never_reaches_the_store() -> TestResult {
    let store = Store::start()?;
    for (config, expected) in [
        (config()?, "Bearer".to_owned()),
        (
            challenged()?,
            format!("Bearer resource_metadata=\"{METADATA}\""),
        ),
    ] {
        let mcp = Server::mcp(config, store.path.clone(), CAP, 2)?;
        let response = Reply::read(request(&mcp, "POST", DESCRIBE, false, &[])?)?;
        refusal(&response, 401, "mcp_sign_in_required")?;
        assert_eq!(challenge(&response), Some(expected.as_str()));
        let headers = [
            ("Content-Type", "application/json"),
            ("Accept", "application/json, text/event-stream"),
            ("Authorization", "Basic fixture-token"),
        ];
        let response = Reply::read(request_with_headers(&mcp, "POST", DESCRIBE, &headers)?)?;
        refusal(&response, 401, "mcp_sign_in_required")?;
        assert_eq!(challenge(&response), Some(expected.as_str()));
        mcp.finish()?;
    }
    assert!(store.asked()?.is_empty(), "{:?}", store.asked()?);
    store.finish()
}

#[test]
fn a_pass_the_store_refuses_by_a_verification_name_is_answered_401_with_the_challenge() -> TestResult
{
    for code in [
        "pass_expired",
        "pass_not_yet_valid",
        "wrong_audience",
        "signature_refused",
        "pass_missing",
    ] {
        let store = Store::refusing(code)?;
        let mcp = Server::mcp(challenged()?, store.path.clone(), CAP, 2)?;
        let response = Reply::read(request(&mcp, "POST", DESCRIBE, true, &[])?)?;
        refusal(&response, 401, "mcp_pass_refused")?;
        assert_eq!(
            challenge(&response),
            Some(format!("Bearer resource_metadata=\"{METADATA}\"").as_str()),
            "{code}"
        );
        let asked = store.asked()?;
        assert_eq!(methods(&asked, 0), ["hello", "session.pass"], "{code}");
        assert_eq!(asked.len(), 2, "{code}: {asked:?}");
        mcp.finish()?;
        store.finish()?;
    }
    Ok(())
}

#[test]
fn a_store_that_refuses_for_another_reason_or_cannot_be_reached_is_unavailable_never_401()
-> TestResult {
    for code in [
        "lys_could_not_be_asked",
        "lys_keys_pending",
        "store_closing",
    ] {
        let store = Store::refusing(code)?;
        let mcp = Server::mcp(challenged()?, store.path.clone(), CAP, 2)?;
        let response = Reply::read(request(&mcp, "POST", DESCRIBE, true, &[])?)?;
        refusal(&response, 503, "mcp_store_unavailable")?;
        assert_eq!(challenge(&response), None, "{code}");
        let asked = store.asked()?;
        assert_eq!(methods(&asked, 0), ["hello", "session.pass"], "{code}");
        assert_eq!(asked.len(), 2, "{code}: {asked:?}");
        mcp.finish()?;
        store.finish()?;
    }
    let directory = tempfile::tempdir()?;
    let mcp = Server::mcp(challenged()?, directory.path().join("absent.sock"), CAP, 2)?;
    let response = Reply::read(request(&mcp, "POST", DESCRIBE, true, &[])?)?;
    refusal(&response, 503, "mcp_store_unavailable")?;
    assert_eq!(challenge(&response), None);
    mcp.finish()
}