| 87f601cc Andy McCall ↶ |
1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
mod common; |
| 6 |
|
| 7 |
use common::{run, setup, text, Serving}; |
| 8 |
use serde_json::{json, Value}; |
| 9 |
use spectra_core::keys::KeyPair; |
| 10 |
use spectra_mcp::client::{rpc_result, Client}; |
| 11 |
|
| 12 |
async fn say(c: &Client, channel: &str) -> Value { |
| 13 |
let req = json!({ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "say", "arguments": { "body": format!("hello {channel}"), "channel": channel } } }); |
| 14 |
rpc_result(&c.mcp("2025-11-25", &req).await.unwrap()).unwrap()["result"].clone() |
| 15 |
} |
| 16 |
|
| 17 |
#[tokio::test] |
| 18 |
async fn the_owner_opens_one_channel_to_one_agent() { |
| 19 |
let dir = setup(); |
| 20 |
let d = dir.path(); |
| 21 |
assert!(run(d, &["project", "add", "bubblebobble"]).status.success()); |
| 22 |
let home = d.join("agents/claude-mg"); |
| 23 |
let o = |
| 24 |
run(d, &["agent", "add", "claude-mg", "--kind", "claude-code", "--project", "oxygen", "--home", home.to_str().unwrap(), "--yes"]); |
| 25 |
assert!(o.status.success(), "{}", text(&o)); |
| 26 |
let srv = Serving::start(d); |
| 27 |
let c = Client::new(&srv.socket, Some(KeyPair::load_home(&home).unwrap())); |
| 28 |
|
| 29 |
let r = say(&c, "bubblebobble").await; |
| 30 |
assert_eq!(r["isError"], true); |
| 31 |
assert!(r["content"][0]["text"].as_str().unwrap().contains("spectra agent allow NAME --channel bubblebobble"), "{r}"); |
| 32 |
|
| 33 |
let o = run(d, &["agent", "allow", "claude-mg", "--channel", "#bubblebobble"]); |
| 34 |
assert!(o.status.success(), "{}", text(&o)); |
| 1876f2ce Andy McCall ↶ |
35 |
let listed: Value = serde_json::from_slice(&run(d, &["agent", "list", "--json"]).stdout).unwrap(); |
| 36 |
let mg = listed.as_array().unwrap().iter().find(|a| a["name"] == "claude-mg").unwrap(); |
| 37 |
assert_eq!(mg["channels"], json!(["bubblebobble"]), "the API reports the grant: {mg}"); |
| 87f601cc Andy McCall ↶ |
38 |
assert_eq!(say(&c, "bubblebobble").await["isError"], false, "allowed"); |
| 39 |
assert_eq!(say(&c, "oxygen").await["isError"], false, "its own channel still"); |
| 40 |
|
| 41 |
assert!(run(d, &["agent", "allow", "claude-mg", "--channel", "bubblebobble", "--remove"]).status.success()); |
| 42 |
assert_eq!(say(&c, "bubblebobble").await["isError"], true, "taken back"); |
| 43 |
assert!(!run(d, &["agent", "allow", "claude-mg", "--channel", "Not A Channel"]).status.success()); |
| 44 |
|
| 45 |
|
| 46 |
let me = KeyPair::load_home(&home).unwrap(); |
| 47 |
let forged = spectra_core::principals::signed_event( |
| 48 |
&me, |
| 49 |
"agent:claude-mg", |
| 50 |
"agent.scoped", |
| 51 |
json!({ "name": "claude-mg", "scope": "projects:all" }), |
| 52 |
); |
| 53 |
let w = spectra_core::daemon::WireEvent::from_new(&forged); |
| 54 |
let r = c.send("POST", "/api/v1/ledger/events", &[], serde_json::to_vec(&vec![w]).unwrap()).await.unwrap(); |
| 55 |
assert_eq!(r.status, 403, "{}", String::from_utf8_lossy(&r.body)); |
| 56 |
assert_eq!(say(&c, "bubblebobble").await["isError"], true); |
| 57 |
srv.stop(); |
| 58 |
|
| 59 |
for args in [["admin", "verify"], ["admin", "check"], ["admin", "rebuild"]] { |
| 60 |
let o = run(d, &args); |
| 61 |
assert!(o.status.success(), "{args:?}: {}", text(&o)); |
| 62 |
} |
| 63 |
} |