use assert_cmd::cargo::cargo_bin;
use serde_json::{Value, json};
use std::fs;
use std::io::{BufRead, BufReader, Write};
use std::process::{Command, Stdio};
use tempfile::tempdir;
#[test]
fn daemon_should_match_direct_lifecycle_read_behavior() {
let temp = tempdir().unwrap();
let config_path = temp.path().join("spool.toml");
fs::write(&config_path, "[vault]\nroot = \"/tmp\"\n").unwrap();
let create = Command::new(cargo_bin("spool"))
.args([
"memory",
"record-manual",
"--config",
config_path.to_str().unwrap(),
"--title",
"简洁输出",
"--summary",
"偏好简洁",
"--memory-type",
"preference",
"--scope",
"user",
"--source-ref",
"manual:cli",
"--user-id",
"long",
])
.output()
.unwrap();
assert!(create.status.success());
let stdout = String::from_utf8(create.stdout).unwrap();
let record_id = stdout
.lines()
.find(|line| line.contains("record_id"))
.unwrap()
.split('`')
.nth(1)
.unwrap()
.to_string();
let mut child = Command::new(cargo_bin("spool-daemon"))
.args(["--config", config_path.to_str().unwrap()])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
let mut stdin = child.stdin.take().unwrap();
let stdout = child.stdout.take().unwrap();
let mut reader = BufReader::new(stdout);
write_message(&mut stdin, json!({ "command": "workbench" }));
let workbench = read_message(&mut reader);
assert_eq!(workbench["ok"], json!(true));
assert_eq!(workbench["wakeup_ready"].as_array().unwrap().len(), 1);
write_message(
&mut stdin,
json!({ "command": "record", "record_id": record_id }),
);
let record = read_message(&mut reader);
assert_eq!(record["ok"], json!(true));
assert_eq!(record["record"]["record"]["state"], json!("accepted"));
drop(stdin);
let status = child.wait().unwrap();
assert!(status.success());
}
fn write_message(stdin: &mut std::process::ChildStdin, value: Value) {
serde_json::to_writer(&mut *stdin, &value).unwrap();
stdin.write_all(b"\n").unwrap();
stdin.flush().unwrap();
}
#[test]
fn daemon_should_return_structured_error_for_malformed_json_and_continue() {
let temp = tempdir().unwrap();
let config_path = temp.path().join("spool.toml");
fs::write(&config_path, "[vault]\nroot = \"/tmp\"\n").unwrap();
let mut child = Command::new(cargo_bin("spool-daemon"))
.args(["--config", config_path.to_str().unwrap()])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
let mut stdin = child.stdin.take().unwrap();
let stdout = child.stdout.take().unwrap();
let mut reader = BufReader::new(stdout);
stdin.write_all(b"{not json}\n").unwrap();
stdin.flush().unwrap();
let invalid = read_message(&mut reader);
assert_eq!(invalid["ok"], json!(false));
assert!(invalid["error"].as_str().unwrap().contains("invalid json"));
write_message(&mut stdin, json!({ "command": "ping" }));
let ping = read_message(&mut reader);
assert_eq!(ping["ok"], json!(true));
assert_eq!(ping["command"], json!("pong"));
drop(stdin);
let status = child.wait().unwrap();
assert!(status.success());
}
fn read_message(reader: &mut BufReader<std::process::ChildStdout>) -> Value {
let mut line = String::new();
reader.read_line(&mut line).unwrap();
serde_json::from_str(line.trim()).unwrap()
}