OXYGENHUB
# 98a16830a3d3 spectra / tests / cli_json.rs
3.1 KiB · Rust · last changed The human at a terminal: spectra say, inbox --follow, search and api, signed with the owner's key (#12) 2 days ago source blame history raw
1
//! Every `--json` output is one JSON document on stdout and nothing else
2
//! (as `oxy`'s are), with and without the daemon; and `spectra say` with
3
//! `spectra inbox --follow` work as a terminal chat with the agents.
4
5
mod common;
6
7
use common::{add_agent, hermetic, run, setup, text, Serving};
8
use serde_json::{json, Value};
9
use spectra_mcp::client::Client;
10
use std::io::{BufRead, BufReader};
11
use std::process::Stdio;
12
use std::time::Duration;
13
14
fn json_of(d: &std::path::Path, args: &[&str]) -> Value {
15
    let o = run(d, args);
16
    assert!(o.status.success(), "{args:?}: {}", text(&o));
17
    serde_json::from_slice(&o.stdout)
18
        .unwrap_or_else(|e| panic!("{args:?} printed more than one JSON document ({e}): {}", String::from_utf8_lossy(&o.stdout)))
19
}
20
21
#[test]
22
fn every_json_output_parses_without_the_daemon() {
23
    let dir = setup();
24
    let d = dir.path();
25
    add_agent(d, "claude-oxygen", &[]);
26
    assert_eq!(json_of(d, &["version", "--json"])["ledger_format"], 1);
27
    assert_eq!(json_of(d, &["identity", "show", "--json"])["owner"]["name"], "Andy McCall");
28
    assert_eq!(json_of(d, &["agent", "list", "--json"])[0]["name"], "claude-oxygen");
29
    assert_eq!(json_of(d, &["project", "list", "--json"])[0]["name"], "oxygen");
30
    let posted = json_of(d, &["say", "--json", "-c", "general", "the release is tomorrow @claude-oxygen"]);
31
    assert_eq!(posted["channel"], "#general");
32
    assert!(json_of(d, &["inbox", "--json"]).as_array().unwrap().is_empty());
33
    assert_eq!(json_of(d, &["search", "--json", "release"]).as_array().unwrap().len(), 1);
34
    let v = json_of(d, &["admin", "verify", "--json"]);
35
    assert!(v["findings"].as_array().unwrap().is_empty(), "a CLI post verifies: {v}");
36
}
37
38
#[tokio::test]
39
async fn a_terminal_chat_with_the_agents() {
40
    let dir = setup();
41
    let d = dir.path();
42
    let claude = add_agent(d, "claude-oxygen", &[]);
43
    let srv = Serving::start(d);
44
    let a = Client::new(&srv.socket, Some(claude));
45
46
    // Through the running daemon: say, and the api proxy.
47
    assert_eq!(json_of(d, &["say", "--json", "hello from the terminal"])["channel"], "#general");
48
    let api: Value = serde_json::from_slice(&run(d, &["api", "GET", "/api/v1/brief"]).stdout).unwrap();
49
    assert_eq!(api["actor"], "human");
50
51
    let mut follow = hermetic(d).args(["inbox", "--follow", "--json"]).stdout(Stdio::piped()).stderr(Stdio::null()).spawn().unwrap();
52
    let mut lines = BufReader::new(follow.stdout.take().unwrap()).lines();
53
    tokio::time::sleep(Duration::from_millis(500)).await;
54
    let say = json!({ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "say", "arguments": { "body": "@andy the build is green", "channel": "general" } } });
55
    assert_eq!(a.mcp("2025-11-25", &say).await.unwrap().status, 200);
56
    let got = tokio::task::spawn_blocking(move || lines.next()).await.unwrap().expect("a line").unwrap();
57
    let item: Value = serde_json::from_str(&got).unwrap();
58
    assert_eq!(item["from"], "agent:claude-oxygen");
59
    assert!(item["summary"].as_str().unwrap().contains("the build is green"));
60
    follow.kill().ok();
61
    follow.wait().ok();
62
    srv.stop();
63
}