1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
//! End-to-end test: a fixture plugin binary is copied into a temp
//! plugins dir and dispatched via `objectiveai-cli plugins <name> <args>`.
//! We assert the host's JSONL output contains the expected re-emitted
//! notification between the `begin` / `end` markers.
mod cli_test_util;
use serde_json::Value;
use std::process::Command;
#[test]
fn hello_plugin_dispatch_produces_expected_output() {
let base = cli_test_util::test_base_dir();
let cli = cli_test_util::cli_binary();
let output = Command::new(cli)
.env("CONFIG_BASE_DIR", &base)
.args([
"plugins",
"run",
"--owner",
"objectiveai",
"--name",
"hello",
"--version",
"0.0.1",
"--args",
"[\"world\"]",
])
.output()
.expect("failed to run cli");
assert!(
output.status.success(),
"cli exited {:?}; stderr: {}",
output.status,
String::from_utf8_lossy(&output.stderr)
);
// Each cli stdout line is the leaf `plugins::run::ResponseItem`
// serialized at the wire. Every `cli/command` aggregator
// `Response`/`ResponseItem` is `#[serde(untagged)]` (sdk commit
// 39c3320e7), so the root `RunItem::Command(_)`,
// `cli::command::ResponseItem`, and `plugins::ResponseItem`
// wrappers all collapse. The leaf `plugins::run::ResponseItem`
// is itself untagged with variants `Mcp { type: "mcp", url }`,
// `Error { type: "error", ... }`, and `Notification(Value)` —
// so a notification carrying the plugin's raw payload
// `{"hello":"world"}` lands on the wire as bare `{"hello":"world"}`,
// not wrapped in any `Plugins/Run/` envelope.
let stdout = String::from_utf8(output.stdout).expect("cli stdout not utf-8");
let lines: Vec<&str> = stdout.lines().collect();
assert!(
!lines.is_empty(),
"expected at least one notification, got: {lines:?}"
);
let hello_count = lines
.iter()
.filter(|line| {
let Ok(v) = serde_json::from_str::<Value>(line) else {
return false;
};
v.pointer("/hello") == Some(&Value::String("world".into()))
})
.count();
assert_eq!(
hello_count, 1,
"expected exactly one hello/world notification in {lines:?}"
);
}