use iroh_http_core::{server::ServeOptions, IrohEndpoint, NetworkingOptions, NodeOptions};
#[tokio::test]
async fn endpoint_node_id_is_stable() {
let opts = NodeOptions {
networking: NetworkingOptions {
disabled: true,
..Default::default()
},
..Default::default()
};
let ep = IrohEndpoint::bind(opts).await.unwrap();
let id1 = ep.node_id().to_string();
let id2 = ep.node_id().to_string();
assert_eq!(id1, id2);
assert!(!id1.is_empty());
}
#[tokio::test]
async fn endpoint_deterministic_key() {
let key = [42u8; 32];
let opts1 = NodeOptions {
key: Some(key),
networking: NetworkingOptions {
disabled: true,
..Default::default()
},
..Default::default()
};
let opts2 = NodeOptions {
key: Some(key),
networking: NetworkingOptions {
disabled: true,
..Default::default()
},
..Default::default()
};
let ep1 = IrohEndpoint::bind(opts1).await.unwrap();
let ep2 = IrohEndpoint::bind(opts2).await.unwrap();
assert_eq!(ep1.node_id(), ep2.node_id());
}
#[tokio::test]
async fn endpoint_secret_key_round_trip() {
let opts = NodeOptions {
networking: NetworkingOptions {
disabled: true,
..Default::default()
},
..Default::default()
};
let ep = IrohEndpoint::bind(opts).await.unwrap();
let key_bytes = ep.secret_key_bytes();
let opts2 = NodeOptions {
key: Some(key_bytes),
networking: NetworkingOptions {
disabled: true,
..Default::default()
},
..Default::default()
};
let ep2 = IrohEndpoint::bind(opts2).await.unwrap();
assert_eq!(ep.node_id(), ep2.node_id());
}
#[tokio::test]
async fn endpoint_bound_sockets_non_empty() {
let opts = NodeOptions {
networking: NetworkingOptions {
disabled: true,
..Default::default()
},
..Default::default()
};
let ep = IrohEndpoint::bind(opts).await.unwrap();
let sockets = ep.bound_sockets();
assert!(!sockets.is_empty(), "bound_sockets should not be empty");
}
#[tokio::test]
async fn endpoint_close() {
let opts = NodeOptions {
networking: NetworkingOptions {
disabled: true,
..Default::default()
},
..Default::default()
};
let ep = IrohEndpoint::bind(opts).await.unwrap();
ep.close().await;
}
#[tokio::test]
async fn serve_options_defaults() {
let opts = ServeOptions::default();
assert_eq!(opts.max_serve_errors, None);
assert_eq!(opts.max_concurrency, None);
assert_eq!(opts.drain_timeout_ms, None);
}
#[tokio::test]
async fn registry_get_after_remove_returns_none() {
let opts = NodeOptions {
networking: NetworkingOptions {
disabled: true,
bind_addrs: vec!["127.0.0.1:0".into()],
..Default::default()
},
..Default::default()
};
let ep = IrohEndpoint::bind(opts).await.unwrap();
let handle = iroh_http_core::insert_endpoint(ep);
let got = iroh_http_core::get_endpoint(handle);
assert!(got.is_some());
let removed = iroh_http_core::remove_endpoint(handle);
assert!(removed.is_some());
let removed_again = iroh_http_core::remove_endpoint(handle);
assert!(removed_again.is_none());
let got_after = iroh_http_core::get_endpoint(handle);
assert!(got_after.is_none());
}
#[tokio::test]
async fn registry_bogus_handle_returns_none() {
let got = iroh_http_core::get_endpoint(999_999);
assert!(got.is_none());
}
#[tokio::test]
async fn sweep_interval_ms_evicts_handles() {
let ep = iroh_http_core::IrohEndpoint::bind(iroh_http_core::NodeOptions {
networking: iroh_http_core::NetworkingOptions {
disabled: true,
..Default::default()
},
streaming: iroh_http_core::StreamingOptions {
handle_ttl_ms: Some(50),
sweep_interval_ms: Some(100),
..Default::default()
},
..Default::default()
})
.await
.unwrap();
let (writer_handle, _reader) = ep.handles().alloc_body_writer().unwrap();
assert!(
ep.handles().finish_body(writer_handle).is_ok(),
"writer handle should be valid immediately after alloc"
);
let (leaked_handle, _reader2) = ep.handles().alloc_body_writer().unwrap();
tokio::time::sleep(std::time::Duration::from_millis(80)).await;
ep.sweep_now();
let evicted = ep.handles().finish_body(leaked_handle).is_err();
assert!(
evicted,
"leaked handle was not evicted by sweep_now() after TTL expired"
);
}
#[tokio::test]
async fn bind_rejects_unknown_alpn_capability() {
let opts = NodeOptions {
networking: NetworkingOptions {
disabled: true,
bind_addrs: vec!["127.0.0.1:0".into()],
..Default::default()
},
capabilities: vec!["totally-bogus-alpn".into()],
..Default::default()
};
let result = IrohEndpoint::bind(opts).await;
let err = match result {
Err(e) => e,
Ok(_) => panic!("bind should reject unknown ALPN capability"),
};
assert!(
err.message.contains("unknown ALPN capability"),
"error message should mention the invalid value, got: {err}"
);
}
#[tokio::test]
async fn bind_accepts_known_alpn_capabilities() {
let opts = NodeOptions {
networking: NetworkingOptions {
disabled: true,
bind_addrs: vec!["127.0.0.1:0".into()],
..Default::default()
},
capabilities: vec![
iroh_http_core::ALPN_STR.to_string(),
iroh_http_core::ALPN_DUPLEX_STR.to_string(),
],
..Default::default()
};
let ep = IrohEndpoint::bind(opts)
.await
.expect("valid ALPNs should be accepted");
ep.close().await;
}
#[tokio::test]
async fn bind_rejects_out_of_range_compression_level() {
let opts = NodeOptions {
networking: NetworkingOptions {
disabled: true,
bind_addrs: vec!["127.0.0.1:0".into()],
..Default::default()
},
compression: Some(iroh_http_core::CompressionOptions {
min_body_bytes: 512,
level: Some(99),
}),
..Default::default()
};
let result = IrohEndpoint::bind(opts).await;
let err = match result {
Err(e) => e,
Ok(_) => panic!("bind should reject compression level 99"),
};
assert!(
err.message.contains("compression level"),
"error should mention compression level, got: {err}"
);
}