use std::collections::BTreeSet;
use super::*;
const VALID_KEY: &str = "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a";
fn key(hex: &str) -> [u8; 32] {
let mut out = [0u8; 32];
for (i, byte) in out.iter_mut().enumerate() {
*byte = u8::from_str_radix(&hex[i * 2..i * 2 + 2], 16).expect("hex");
}
out
}
fn endpoint_addr(addrs: Vec<TransportAddr>) -> EndpointAddr {
EndpointAddr {
id: EndpointId::from_bytes(&key(VALID_KEY)).expect("a real curve point"),
addrs: addrs.into_iter().collect::<BTreeSet<_>>(),
}
}
#[test]
fn the_alpn_carries_a_version() {
let text = std::str::from_utf8(ALPN).expect("ASCII");
assert_eq!(text, "modelpipe/0");
let (name, version) = text.split_once('/').expect("a version component");
assert_eq!(name, "modelpipe");
assert!(
version.parse::<u32>().is_ok(),
"the version must be a number a later one can follow: {version}"
);
}
#[tokio::test]
async fn a_ticket_round_trips_through_an_iroh_address() {
let original = endpoint_addr(vec![
TransportAddr::Relay("https://relay.example.com./".parse().expect("relay url")),
TransportAddr::Ip("192.168.1.7:4433".parse().unwrap()),
TransportAddr::Ip("[2001:db8::1]:8080".parse().unwrap()),
]);
let ticket = ticket_from(&original);
let back = addr_from(&ticket).expect("a valid key must convert back");
assert_eq!(back.id, original.id, "the identity survives");
assert_eq!(back.addrs, original.addrs, "and so does every address");
}
#[test]
fn an_address_free_ticket_still_names_its_endpoint() {
let original = endpoint_addr(vec![]);
let ticket = ticket_from(&original);
let back = addr_from(&ticket).expect("valid");
assert_eq!(back.id, original.id);
assert!(back.addrs.is_empty());
}
#[test]
fn an_address_v0_cannot_describe_is_dropped_rather_than_failing_the_mint() {
let original = endpoint_addr(vec![
TransportAddr::Relay("https://relay.example.com./".parse().expect("relay url")),
TransportAddr::Ip("192.168.1.7:4433".parse().unwrap()),
]);
let ticket = ticket_from(&original);
assert_eq!(ticket.addrs().len(), 2);
let back = addr_from(&ticket).expect("valid");
assert_eq!(back.addrs.len(), 2);
}
#[test]
fn an_endpoint_id_is_taken_as_opaque_bytes_and_judged_at_dial_time() {
for bytes in [[0x00u8; 32], [0xFFu8; 32]] {
let ticket = Ticket::new(bytes, vec![], BackendHint::OpenAiCompatible);
let addr = addr_from(&ticket)
.expect("iroh accepts any 32 bytes; validity is decided when dialling");
assert_eq!(addr.id.as_bytes(), &bytes, "carried through unaltered");
}
}
#[test]
fn a_relay_url_iroh_will_not_parse_costs_one_path_and_not_the_pairing() {
let ticket = Ticket::new(
key(VALID_KEY),
vec![
TicketAddr::Relay("not a url at all".to_owned()),
TicketAddr::V4("192.168.1.7:4433".parse().unwrap()),
],
BackendHint::OpenAiCompatible,
);
let back = addr_from(&ticket).expect("the pairing survives");
assert_eq!(back.addrs.len(), 1, "only the usable address remains");
}
#[test]
fn a_well_formed_relay_url_is_accepted() {
for url in [
"https://relay.example.com/",
"http://127.0.0.1:3340/",
"https://relay.example.com.:443/",
] {
assert!(validate_relay(url).is_ok(), "{url}");
}
}
#[test]
fn a_value_that_is_not_a_url_is_refused_before_the_listener_starts() {
for url in ["", "not a url", "relay.example.com", "://missing-scheme"] {
match validate_relay(url) {
Err(ServeError::InvalidRelay { url: named }) => {
assert_eq!(named, url, "the error must name what was refused");
}
other => panic!("{url:?} should be InvalidRelay, got {other:?}"),
}
}
}
#[test]
fn validation_yields_a_verdict_and_never_a_normalized_url() {
let awkward = "https://Relay.Example.COM.:443/";
assert!(validate_relay(awkward).is_ok());
let ticket = Ticket::new(
key(VALID_KEY),
vec![TicketAddr::Relay(awkward.to_owned())],
BackendHint::OpenAiCompatible,
);
let reparsed: Ticket = ticket.to_string().parse().expect("round trips");
assert_eq!(
reparsed.addrs(),
[TicketAddr::Relay(awkward.to_owned())],
"the operator's spelling survives the ticket"
);
}
#[tokio::test]
async fn an_endpoint_binds_and_reports_its_own_identity() {
let endpoint = bind(None, None, NetOptions::default())
.await
.expect("binding must succeed");
let addr = endpoint.addr();
let ticket = ticket_from(&addr);
assert_eq!(
ticket.endpoint_id(),
addr.id.as_bytes(),
"the ticket names the endpoint that minted it"
);
assert_eq!(ticket.fingerprint().len(), 12);
endpoint.close().await;
}
#[tokio::test]
async fn binding_with_an_unparseable_relay_fails_before_the_endpoint_exists() {
let Err(failure) = bind(Some("not a url"), None, NetOptions::default()).await else {
panic!("an unparseable relay must be refused");
};
let err = ServeError::from(failure);
match &err {
ServeError::InvalidRelay { url } => assert_eq!(url, "not a url"),
other => panic!("expected InvalidRelay, got {other:?}"),
}
assert!(!err.is_retryable(), "and it is the operator's to fix");
}
#[tokio::test]
async fn the_same_key_binds_to_the_same_endpoint_and_a_different_one_does_not() {
let key = [7u8; crate::identity::KEY_BYTES];
let other = [9u8; crate::identity::KEY_BYTES];
let first = bind(None, Some(key), NetOptions::default())
.await
.expect("binds");
let again = bind(None, Some(key), NetOptions::default())
.await
.expect("binds");
let elsewhere = bind(None, Some(other), NetOptions::default())
.await
.expect("binds");
assert_eq!(
ticket_from(&first.addr()).endpoint_id(),
ticket_from(&again.addr()).endpoint_id(),
"a stored key is what makes a ticket outlive the process"
);
assert_ne!(
ticket_from(&first.addr()).endpoint_id(),
ticket_from(&elsewhere.addr()).endpoint_id(),
"and a different key is a different listener"
);
first.close().await;
again.close().await;
elsewhere.close().await;
}
#[tokio::test]
async fn an_endpoint_binds_with_every_network_contact_switched_off() {
for (port_mapping, discovery) in [(false, true), (true, false), (false, false)] {
let net = NetOptions {
port_mapping,
discovery,
};
let endpoint = bind(None, None, net)
.await
.expect("binding must succeed whatever is switched off");
assert_eq!(ticket_from(&endpoint.addr()).fingerprint().len(), 12);
endpoint.close().await;
}
}
#[test]
fn a_connect_relay_that_is_not_a_url_is_refused_as_the_connect_error() {
match validate_relay_for_connect("not a url") {
Err(ConnectError::InvalidRelay { url }) => assert_eq!(url, "not a url"),
other => panic!("expected InvalidRelay, got {other:?}"),
}
assert!(validate_relay_for_connect("https://relay.example.com/").is_ok());
}
#[tokio::test]
async fn binding_without_a_key_mints_a_new_identity_each_time() {
let first = bind(None, None, NetOptions::default())
.await
.expect("binds");
let second = bind(None, None, NetOptions::default())
.await
.expect("binds");
assert_ne!(
ticket_from(&first.addr()).endpoint_id(),
ticket_from(&second.addr()).endpoint_id(),
"the default must stay ephemeral"
);
first.close().await;
second.close().await;
}