#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::indexing_slicing,
clippy::string_slice,
clippy::unwrap_in_result,
reason = "integration test code; panics are assertion failures"
)]
use std::sync::{Arc, Mutex};
use std::time::Duration;
use koh::client::{run_client, ClientTerminal, IrohConnector};
use koh::predict::{DisplayPreference, Overlay};
use koh::server::run_session;
use koh::transport_iroh::{
bind_endpoint_local, generate_secret_key, loopback_addr, IrohChannel, ALPN,
};
use tokio::sync::mpsc;
struct MockTerminal {
latest: Arc<Mutex<String>>,
}
impl ClientTerminal for MockTerminal {
fn render(
&mut self,
screen: &vt100::Screen,
_overlay: &Overlay,
_status: Option<&str>,
) -> std::io::Result<()> {
*self.latest.lock().unwrap() = screen.contents();
Ok(())
}
fn size(&self) -> std::io::Result<(u16, u16)> {
Ok((24, 80))
}
}
#[tokio::test]
async fn full_session_over_loopback_pty() {
let server_ep = bind_endpoint_local(generate_secret_key(), true)
.await
.expect("bind server endpoint");
let client_ep = bind_endpoint_local(generate_secret_key(), false)
.await
.expect("bind client endpoint");
let server_addr = loopback_addr(&server_ep);
let server_task = tokio::spawn(async move {
let incoming = server_ep.accept().await.expect("server accept");
let conn = incoming.await.expect("server handshake");
let _ = run_session(conn, Some("sh".into()), 0).await;
});
let connector = IrohConnector::new(
client_ep.clone(),
server_addr.clone(),
std::sync::Arc::new(None),
);
let conn = client_ep
.connect(server_addr, ALPN)
.await
.expect("client connect over loopback");
let channel = IrohChannel::new(conn);
let latest = Arc::new(Mutex::new(String::new()));
let term = MockTerminal {
latest: latest.clone(),
};
let (input_tx, input_rx) = mpsc::channel::<Vec<u8>>(64);
let (resize_tx, resize_rx) = mpsc::channel::<()>(8);
let client_task = tokio::spawn(async move {
let _ = run_client(
channel,
connector,
DisplayPreference::Never, (24, 80),
input_rx,
resize_rx,
term,
)
.await;
});
tokio::time::sleep(Duration::from_millis(700)).await;
input_tx
.send(b"echo koh_e2e_marker\r".to_vec())
.await
.expect("send keystrokes");
let mut seen = false;
for _ in 0..120 {
if latest.lock().unwrap().contains("koh_e2e_marker") {
seen = true;
break;
}
tokio::time::sleep(Duration::from_millis(100)).await;
}
let final_screen = latest.lock().unwrap().clone();
assert!(
seen,
"marker never round-tripped to the client screen; last frame was:\n{final_screen}"
);
drop(input_tx);
drop(resize_tx);
let _ = tokio::time::timeout(Duration::from_secs(3), client_task).await;
let _ = tokio::time::timeout(Duration::from_secs(3), server_task).await;
}