liminal-cli 0.3.1

The liminal command: serve the network, maintain its stores, or serve its discovery MCP over stdio
Documentation
//! Service tool contracts over the socket-free rail.

use serde_json::{Map, Value, json};

use super::tools::{Server, ToolResult, tool_table};
use crate::network::fake::{Answer, Fake};

type TestResult = Result<(), Box<dyn std::error::Error>>;

pub(super) fn fixture(subjects: &[&str]) -> Fake {
    fixture_services(&json!([{"id":"svc-one", "name":"one", "subjects":subjects}]))
}

fn fixture_services(services: &Value) -> Fake {
    Fake::new(
        json!({}),
        &[("services.list", Answer::Reply(json!({"services":services})))],
    )
}

pub(super) fn args(value: Value) -> Map<String, Value> {
    match value {
        Value::Object(arguments) => arguments,
        _ => Map::new(),
    }
}

fn tool(server: &Server<Fake>, name: &str, service: &str) -> ToolResult {
    let answer = server.tool(name, &args(json!({"service":service})));
    assert!(answer.is_ok(), "{answer:?}");
    answer.unwrap_or_else(|refusal| ToolResult::failed(&refusal.message, json!({})))
}

#[test]
fn net027_tools_list_service_schemas() {
    for name in ["subscribe", "unsubscribe"] {
        let table = tool_table();
        let entry = table
            .as_array()
            .and_then(|tools| tools.iter().find(|t| t["name"] == name));
        assert!(entry.is_some(), "missing {name} in tools/list");
        let schema = entry.map(|t| t["inputSchema"].clone()).unwrap_or_default();
        assert_eq!(schema["required"], json!(["service"]));
        assert_eq!(schema["properties"]["service"]["type"], "string");
        assert_eq!(schema["additionalProperties"], false);
    }
}

#[test]
fn net027_subscribe_by_name_is_idempotent() -> TestResult {
    let server = Server::new(fixture(&["orders.one", "events.one"]), 65536);
    let expected =
        json!({"service":"one", "station":"svc-one", "subjects":["orders.one", "events.one"]});
    for _ in 0..2 {
        let answer = tool(&server, "subscribe", "one");
        assert!(!answer.is_error, "{:?}", answer.structured);
        assert_eq!(answer.structured, expected);
    }
    assert_eq!(
        server.rail().taps.opened()?,
        ["console.tap.orders.one", "console.tap.events.one"]
    );
    assert_eq!(server.rail().pushed().len(), 1);
    Ok(())
}

#[test]
fn net027_unknown_service_is_a_structured_refusal() {
    let server = Server::new(fixture(&["orders.one"]), 65536);
    let answer = tool(&server, "subscribe", "missing");
    assert!(answer.is_error);
    assert_eq!(answer.structured["service"], "missing");
    assert_eq!(answer.structured["error"], "no service named 'missing'");
}

#[test]
fn net027_unsubscribe_closes_all_and_refuses_absence() -> TestResult {
    let server = Server::new(fixture(&["orders.one", "events.one"]), 65536);
    assert!(!tool(&server, "subscribe", "one").is_error);
    let answer = tool(&server, "unsubscribe", "one");
    assert!(!answer.is_error);
    assert_eq!(
        answer.structured,
        json!({"service":"one", "subjects":["orders.one", "events.one"]})
    );
    assert_eq!(server.rail().taps.active()?, 0);
    for service in ["one", "missing"] {
        let answer = tool(&server, "unsubscribe", service);
        assert!(answer.is_error);
        assert_eq!(
            answer.structured["error"],
            format!("no subscription to service '{service}'")
        );
    }
    Ok(())
}

#[test]
fn net027_a_refused_subject_rolls_back_the_entire_service() -> TestResult {
    let server = Server::new(fixture(&["orders.one", "events.one"]), 65536);
    server
        .rail()
        .taps
        .refuse("console.tap.events.one", "missing_grant: events.one")?;
    let answer = tool(&server, "subscribe", "one");
    assert!(answer.is_error);
    assert_eq!(answer.structured["subject"], "events.one");
    assert_eq!(answer.structured["error"], "missing_grant: events.one");
    assert_eq!(server.rail().taps.active()?, 0);
    assert!(tool(&server, "unsubscribe", "one").is_error);
    Ok(())
}

#[test]
fn net027_sixteen_services_is_the_ticket_bound() -> TestResult {
    let services: Vec<_> = (0..17).map(|n| json!({"id":format!("svc-{n}"), "name":format!("s{n}"), "subjects":[format!("orders.s{n}")]})).collect();
    let server = Server::new(fixture_services(&json!(services)), 65536);
    for n in 0..16 {
        assert!(!tool(&server, "subscribe", &format!("s{n}")).is_error);
    }
    let answer = tool(&server, "subscribe", "s16");
    assert!(answer.is_error);
    assert_eq!(
        answer.structured["error"],
        "the ticket already subscribes to 16 services"
    );
    assert_eq!(server.rail().taps.active()?, 16);
    assert!(!tool(&server, "unsubscribe", "s0").is_error);
    assert!(!tool(&server, "subscribe", "s16").is_error);
    Ok(())
}

#[test]
fn net027_malformed_service_listing_is_named() {
    let server = Server::new(
        fixture_services(&json!([{"id":"svc-one", "name":"one", "subjects":[7]}])),
        65536,
    );
    let answer = tool(&server, "subscribe", "one");
    assert!(answer.is_error);
    assert!(
        answer.structured["error"]
            .as_str()
            .is_some_and(|words| words.contains("subjects"))
    );
}

#[test]
fn net027_logging_is_declared_and_levels_are_validated() {
    let mut server = Server::new(fixture(&["orders.one"]), 65536);
    let answer = server.answer(
        &json!(1),
        "initialize",
        &args(json!({"protocolVersion":"2025-11-25"})),
    );
    assert_eq!(answer["result"]["capabilities"]["logging"], json!({}));
    assert_eq!(
        super::negotiate::Session::discover()["capabilities"]["logging"],
        json!({})
    );
    let answer = server.answer(
        &json!(2),
        "logging/setLevel",
        &args(json!({"level":"info"})),
    );
    assert_eq!(answer["result"], json!({}));
    for arguments in [json!({}), json!({"level":7}), json!({"level":"wrong"})] {
        let answer = server.answer(&json!(3), "logging/setLevel", &args(arguments));
        assert_eq!(answer["error"]["code"], -32602);
    }
}

#[test]
fn net027_subscribe_validates_arguments_before_opening() -> TestResult {
    let mut server = Server::new(fixture(&["orders.one"]), 65536);
    let _init = server.answer(
        &json!(1),
        "initialize",
        &args(json!({"protocolVersion":"2025-11-25"})),
    );
    for arguments in [
        json!({}),
        json!({"service":7}),
        json!({"service":""}),
        json!({"service":"one", "other":true}),
    ] {
        let answer = server.answer(
            &json!(2),
            "tools/call",
            &args(json!({"name":"subscribe", "arguments":arguments})),
        );
        assert_eq!(answer["error"]["code"], -32602, "{answer}");
    }
    assert!(server.rail().pushed().is_empty());
    assert_eq!(server.rail().taps.active()?, 0);
    Ok(())
}

#[test]
fn net027_service_listing_refusal_is_verbatim() {
    let rail = Fake::new(
        json!({}),
        &[(
            "services.list",
            Answer::Refuse("missing_grant: services.list".to_owned()),
        )],
    );
    let answer = tool(&Server::new(rail, 65536), "subscribe", "one");
    assert!(answer.is_error);
    assert_eq!(answer.structured["error"], "missing_grant: services.list");
}

#[test]
fn net027_ambiguous_names_and_duplicate_subjects_are_refused() -> TestResult {
    for services in [
        json!([{"id":"svc-a","name":"one","subjects":["a"]},{"id":"svc-b","name":"one","subjects":["b"]}]),
        json!([{"id":"svc-a","name":"one","subjects":["a","a"]}]),
    ] {
        let server = Server::new(fixture_services(&services), 65536);
        assert!(tool(&server, "subscribe", "one").is_error);
        assert_eq!(server.rail().taps.active()?, 0);
    }
    Ok(())
}