use mcpmesh_node::{NodeBuilder, StartError};
#[tokio::test(flavor = "multi_thread")]
async fn a_node_starts_in_an_empty_root_and_answers_status() {
let root = tempfile::tempdir().unwrap();
let node = NodeBuilder::new(root.path()).start().await.expect("start");
let mut control = node.control().await.expect("control");
let status = control.status().await.expect("status");
assert_eq!(status.stack_version, mcpmesh_node::VERSION);
assert!(status.services.is_empty());
node.shutdown().await;
}
#[tokio::test(flavor = "multi_thread")]
async fn set_nickname_renames_live_and_persists() {
let root = tempfile::tempdir().unwrap();
let node = NodeBuilder::new(root.path()).start().await.expect("start");
let mut control = node.control().await.expect("control");
control.set_nickname("workbench").await.expect("rename");
let status = control.status().await.expect("status");
assert_eq!(status.self_nickname, "workbench");
control
.register_service_with(
"notes",
mcpmesh_local_api::BackendSpec::Socket {
path: root.path().join("notes.sock").display().to_string(),
},
vec![],
true,
)
.await
.expect("register ephemeral service");
let invite = control.invite(vec!["notes".into()]).await.expect("invite");
let decoded = mcpmesh_node::pairing::Invite::decode(&invite.invite_line).expect("decode");
assert_eq!(decoded.nickname, "workbench");
let cfg_text = std::fs::read_to_string(root.path().join("config/config.toml")).unwrap();
assert!(
cfg_text.contains("nickname = \"workbench\""),
"config must carry the rename: {cfg_text}"
);
for bad in ["", " ", "a/b"] {
control
.set_nickname(bad)
.await
.expect_err("invalid nickname must be refused");
}
let status = control.status().await.expect("status after refusals");
assert_eq!(status.self_nickname, "workbench");
node.shutdown().await;
}
#[tokio::test(flavor = "multi_thread")]
async fn shutdown_frees_the_root_even_with_a_live_subscription_attached() {
let root = tempfile::tempdir().unwrap();
let node = NodeBuilder::new(root.path()).start().await.expect("start");
let control = node.control().await.expect("control");
let _sub = control.subscribe().await.expect("subscribe");
tokio::time::timeout(std::time::Duration::from_secs(5), node.shutdown())
.await
.expect("shutdown must complete promptly even with a live subscription attached");
let restarted = tokio::time::timeout(
std::time::Duration::from_secs(5),
NodeBuilder::new(root.path()).start(),
)
.await
.expect("restart must not hang")
.expect("restart must succeed once the old node's resources are released");
restarted.shutdown().await;
}
#[tokio::test(flavor = "multi_thread")]
async fn a_second_node_on_the_same_root_is_refused() {
let root = tempfile::tempdir().unwrap();
let first = NodeBuilder::new(root.path()).start().await.expect("first");
let err = NodeBuilder::new(root.path())
.start()
.await
.expect_err("second node on the same root must refuse");
assert!(
matches!(err, StartError::DataDirInUse { .. }),
"want DataDirInUse, got: {err:?}"
);
first.shutdown().await;
}