use mcpmesh_node::{Config, NodeBuilder};
fn custom_hermetic(relay_urls: &str) -> Config {
Config::from_toml_str(&format!(
"[network]\nrelay_mode = \"custom\"\nrelay_urls = [{relay_urls}]\n\
discovery_mode = \"custom\"\ndiscovery_urls = [\"https://dns.example\"]\n"
))
.expect("valid custom test config")
}
#[tokio::test(flavor = "multi_thread")]
async fn set_relays_applies_live_on_the_custom_path() {
let root = tempfile::tempdir().unwrap();
let node = NodeBuilder::new(root.path())
.config(custom_hermetic("\"https://relay-a.example\""))
.start()
.await
.expect("custom-mode node starts");
let mut ctl = node.control().await.expect("control");
let r = ctl
.set_relays(&[
"https://relay-a.example".into(),
"https://relay-b.example".into(),
])
.await
.expect("set_relays add");
assert!(r.changed, "the set differed → changed");
assert!(
!r.restart_required,
"custom→custom is applied live, no restart"
);
ctl.status().await.expect("status after a live relay add");
let again = ctl
.set_relays(&[
"https://relay-a.example".into(),
"https://relay-b.example".into(),
])
.await
.expect("set_relays idempotent");
assert!(!again.changed, "same set → changed=false");
assert!(!again.restart_required);
let removed = ctl
.set_relays(&["https://relay-b.example".into()])
.await
.expect("set_relays remove");
assert!(removed.changed);
assert!(!removed.restart_required);
ctl.status()
.await
.expect("status after a live relay remove");
let cfg = Config::load(&root.path().join("config").join("config.toml")).expect("reload config");
assert_eq!(cfg.network.relay_mode, "custom");
assert_eq!(
cfg.network.relay_urls,
vec!["https://relay-b.example/".to_string()]
);
}
#[tokio::test(flavor = "multi_thread")]
async fn set_relays_treats_a_respelled_url_as_unchanged() {
let root = tempfile::tempdir().unwrap();
let node = NodeBuilder::new(root.path())
.config(custom_hermetic("\"https://relay-a.example\""))
.start()
.await
.expect("custom-mode node starts");
let mut ctl = node.control().await.expect("control");
let r = ctl
.set_relays(&["https://Relay-A.example/".into()])
.await
.expect("set_relays respelled");
assert!(
!r.changed,
"a re-spelling of the same relay must be unchanged, not a drop+reinsert"
);
assert!(!r.restart_required);
ctl.status().await.expect("node still serving");
}
#[tokio::test(flavor = "multi_thread")]
async fn set_relays_persists_but_requires_restart_from_a_non_custom_mode() {
let root = tempfile::tempdir().unwrap();
let node = NodeBuilder::new(root.path())
.config(Config::from_toml_str("[network]\nrelay_mode = \"disabled\"\n").unwrap())
.start()
.await
.expect("hermetic node starts");
let mut ctl = node.control().await.expect("control");
let r = ctl
.set_relays(&["https://relay-a.example".into()])
.await
.expect("set_relays from disabled");
assert!(r.changed);
assert!(
r.restart_required,
"a non-custom current mode can't be live-transitioned"
);
let cfg = Config::load(&root.path().join("config").join("config.toml")).expect("reload config");
assert_eq!(cfg.network.relay_mode, "custom");
assert_eq!(
cfg.network.relay_urls,
vec!["https://relay-a.example/".to_string()]
);
let empty = ctl.set_relays(&[]).await;
assert!(empty.is_err(), "empty relay set is rejected");
let bad = ctl.set_relays(&["not a url".into()]).await;
assert!(bad.is_err(), "a malformed relay url is rejected");
let cfg = Config::load(&root.path().join("config").join("config.toml")).expect("reload config");
assert_eq!(
cfg.network.relay_urls,
vec!["https://relay-a.example/".to_string()],
"a rejected edit does not touch the persisted set"
);
}