use super::super::material::read_dir;
use super::super::{Channel, READ_TIMEOUT};
use super::advertised;
use super::ladder::roving_entry;
use crate::rendezvous::say;
use crate::test_support::engine::Engine;
use crate::test_support::engine::punched::{Punched, Turn};
use crate::test_support::roving::{commons, presence, tuned};
use crate::test_support::{Notices, Scratch, mint, roving};
use serde_json::json;
use std::time::Duration;
fn folded(lines: Vec<String>) -> Vec<String> {
lines
.into_iter()
.map(|line| {
let mut out = String::new();
let mut run = false;
for c in line.chars() {
if c.is_ascii_digit() {
if !run {
out.push('N');
}
run = true;
} else {
out.push(c);
run = false;
}
}
out
})
.collect()
}
#[test]
fn a_direct_rung_that_answers_is_said_once() {
let scratch = Scratch::new();
mint::material(scratch.path());
roving::carried(scratch.path());
let _engine = Engine::start(
scratch.path(),
crate::corpus::PROTOCOL,
vec![vec![advertised()], vec![advertised()]],
);
let held = read_dir(scratch.path())
.expect("readable")
.expect("provisioned");
let mut channel = Channel::open(&held).expect("opened");
channel.tune(READ_TIMEOUT, tuned(vec![]));
let (notices, sink) = Notices::new();
channel.speak_to(&sink);
for _ in 0..2 {
assert!(channel.ask(&json!({"op": "advertise"})).is_ok());
}
assert_eq!(notices.heard(), [say::direct_connected()]);
}
#[test]
fn an_entry_that_does_not_rove_says_nothing() {
let scratch = Scratch::new();
mint::material(scratch.path());
let _engine = Engine::start(
scratch.path(),
crate::corpus::PROTOCOL,
vec![vec![advertised()]],
);
let held = read_dir(scratch.path())
.expect("readable")
.expect("provisioned");
let mut channel = Channel::open(&held).expect("opened");
let (notices, sink) = Notices::new();
channel.speak_to(&sink);
assert!(channel.ask(&json!({"op": "advertise"})).is_ok());
assert!(notices.heard().is_empty());
}
#[test]
fn the_rendezvous_says_every_step_and_the_held_line_its_first_ping() {
let scratch = Scratch::new();
let held = roving_entry(scratch.path(), "127.0.0.1:1");
let pinged = vec![json!({"ping": true}), json!({"ping": true}), advertised()];
let engine = Punched::start(
scratch.path(),
vec![
Turn::Answer(vec![advertised()]),
Turn::Answer(pinged.clone()),
Turn::Answer(pinged),
],
);
let (_node, tuning) = commons(vec![presence(engine.port(), 1)]);
let mut channel = Channel::open(&held).expect("opened");
channel.tune(READ_TIMEOUT, tuning);
let (notices, sink) = Notices::new();
channel.speak_to(&sink);
for _ in 0..3 {
assert_eq!(
channel.ask(&json!({"op": "advertise"})),
Ok(vec![advertised()])
);
}
let heard = notices.heard();
let call = heard.get(2).cloned().unwrap_or_default();
assert!(call.ends_with(", 1 ack(s)"), "{call}");
assert_eq!(
folded(heard),
folded(vec![
say::direct_refused(&["127.0.0.1".parse().expect("ip")], false),
"rendezvous: presence read — seq 1, 1 endpoint(s) (1 v4)".to_owned(),
call,
"rendezvous: punch for call nonce 1 at 1 endpoint(s) (1 v4) — window 800ms".to_owned(),
"rendezvous: punch landed (1 v4)".to_owned(),
say::held_kept(),
say::ping(),
])
);
}
#[test]
fn a_hangup_is_said_as_the_held_line_silent_past_it() {
let scratch = Scratch::new();
let held = roving_entry(scratch.path(), "127.0.0.1:1");
let engine = Punched::start(scratch.path(), vec![Turn::Silence]);
let (_node, tuning) = commons(vec![presence(engine.port(), 1)]);
let mut channel = Channel::open(&held).expect("opened");
channel.tune(Duration::from_millis(300), tuning);
let (notices, sink) = Notices::new();
channel.speak_to(&sink);
assert!(channel.ask(&json!({"op": "advertise"})).is_err());
assert_eq!(
notices.heard().last().cloned(),
Some(
"rendezvous: held line dropped — the receive failed (silent past the hangup)"
.to_owned()
)
);
}
#[test]
fn a_re_call_nobody_answers_is_said_and_presence_is_read_again() {
let scratch = Scratch::new();
let held = roving_entry(scratch.path(), "127.0.0.1:1");
let gone = Punched::start(scratch.path(), vec![Turn::Answer(vec![advertised()])]);
let (_node, tuning) = commons(vec![presence(gone.port(), 1)]);
let mut channel = Channel::open(&held).expect("opened");
channel.tune(READ_TIMEOUT, tuning);
let (notices, sink) = Notices::new();
channel.speak_to(&sink);
assert!(channel.ask(&json!({"op": "advertise"})).is_ok());
assert!(channel.ask(&json!({"op": "advertise"})).is_err());
let back = Punched::start(scratch.path(), vec![Turn::Answer(vec![advertised()])]);
let (_node, tuning) = commons(vec![presence(back.port(), 2)]);
channel.tune(READ_TIMEOUT, tuning);
assert!(channel.ask(&json!({"op": "advertise"})).is_ok());
let heard = notices.heard();
let mut tail: Vec<String> = heard.iter().skip(6).cloned().collect();
let written = |l: &str, head: &str| l.starts_with(head) && l.ends_with(", 1 ack(s)");
let recall = tail.get(1).cloned().unwrap_or_default();
let call = tail.get(5).cloned().unwrap_or_default();
assert!(
written(&recall, "rendezvous: re-call from cached presence — nonce "),
"{heard:?}"
);
assert!(written(&call, "rendezvous: call nonce "), "{heard:?}");
tail.retain(|l| *l != recall && *l != call);
assert_eq!(
folded(tail),
folded(vec![
"rendezvous: held line dropped — the receive failed (closed by the far end)".to_owned(),
"rendezvous: punch for call nonce 1 at 1 endpoint(s) (1 v4) — window 800ms".to_owned(),
"rendezvous: punch expired after 800ms with no stream".to_owned(),
"rendezvous: presence read — seq 2, 1 endpoint(s) (1 v4)".to_owned(),
"rendezvous: punch for call nonce 1 at 1 endpoint(s) (1 v4) — window 800ms".to_owned(),
"rendezvous: punch landed (1 v4)".to_owned(),
say::held_kept(),
]),
"{heard:?}"
);
}
#[test]
fn no_rung_answering_says_the_direct_refusal_and_the_presence_unread() {
let scratch = Scratch::new();
let held = roving_entry(scratch.path(), "127.0.0.1:1");
let mut channel = Channel::open(&held).expect("opened");
channel.tune(READ_TIMEOUT, tuned(vec![]));
let (notices, sink) = Notices::new();
channel.speak_to(&sink);
for _ in 0..2 {
assert!(channel.ask(&json!({"op": "advertise"})).is_err());
}
assert_eq!(
notices.heard(),
[
"rendezvous: direct rung refused — 1 address(es) tried (1 v4)".to_owned(),
say::presence_unread(say::NO_WALK),
],
"a second climb to the same outcome says nothing"
);
}
#[test]
fn an_address_that_does_not_resolve_says_nothing_was_tried() {
let scratch = Scratch::new();
let held = roving_entry(scratch.path(), "no-port-here");
let mut channel = Channel::open(&held).expect("opened");
channel.tune(READ_TIMEOUT, tuned(vec![]));
let (notices, sink) = Notices::new();
channel.speak_to(&sink);
assert!(channel.ask(&json!({"op": "advertise"})).is_err());
assert_eq!(
notices.heard().first().cloned(),
Some("rendezvous: direct rung refused — 0 address(es) tried (none)".to_owned())
);
}