use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Duration;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::TcpListener;
use ircbot::State;
const WELCOME: &[u8] = b":server 001 testbot :Welcome to the mock IRC server\r\n";
#[tokio::test]
async fn test_reconnects_after_tcp_disconnect() {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap().to_string();
let conn_count = Arc::new(AtomicUsize::new(0));
let cc = Arc::clone(&conn_count);
tokio::spawn(async move {
let (mut sock1, _) = listener.accept().await.unwrap();
cc.fetch_add(1, Ordering::Relaxed);
let _ = sock1.write_all(WELCOME).await;
drop(sock1);
let (mut sock2, _) = listener.accept().await.unwrap();
cc.fetch_add(1, Ordering::Relaxed);
let _ = sock2.write_all(WELCOME).await;
tokio::time::sleep(Duration::from_secs(60)).await;
});
let state = State::connect("testbot".to_string(), &addr, vec![])
.await
.expect("failed to connect to mock server");
let bot = Arc::new(());
let bot_task = tokio::spawn(ircbot::internal::run_bot(bot, state, vec![]));
tokio::time::timeout(Duration::from_secs(10), async {
loop {
if conn_count.load(Ordering::Relaxed) >= 2 {
return;
}
tokio::time::sleep(Duration::from_millis(50)).await;
}
})
.await
.expect("bot did not reconnect within 10 s after TCP disconnect");
bot_task.abort();
}
#[tokio::test]
async fn test_keepalive_timeout_triggers_reconnect() {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap().to_string();
let conn_count = Arc::new(AtomicUsize::new(0));
let cc = Arc::clone(&conn_count);
let ping_seen = Arc::new(std::sync::atomic::AtomicBool::new(false));
let ps = Arc::clone(&ping_seen);
tokio::spawn(async move {
loop {
let (sock, _) = listener.accept().await.unwrap();
cc.fetch_add(1, Ordering::Relaxed);
let (read_half, mut write_half) = sock.into_split();
let _ = write_half.write_all(WELCOME).await;
let mut reader = BufReader::new(read_half);
let mut line = String::new();
loop {
line.clear();
match tokio::time::timeout(Duration::from_secs(10), reader.read_line(&mut line))
.await
{
Ok(Ok(0)) | Err(_) => break, Ok(Ok(_)) => {
if line.contains("PING") && line.contains("ircbot-keepalive") {
ps.store(true, std::sync::atomic::Ordering::Relaxed);
}
}
Ok(Err(_)) => break,
}
}
}
});
let state = State::connect("testbot".to_string(), &addr, vec![])
.await
.expect("failed to connect to mock server")
.with_keepalive(Duration::from_secs(1), Duration::from_secs(1));
let bot = Arc::new(());
let bot_task = tokio::spawn(ircbot::internal::run_bot(bot, state, vec![]));
tokio::time::timeout(Duration::from_secs(15), async {
loop {
if conn_count.load(Ordering::Relaxed) >= 2 {
return;
}
tokio::time::sleep(Duration::from_millis(50)).await;
}
})
.await
.expect("bot did not reconnect within 15 s after keepalive timeout");
assert!(
ping_seen.load(std::sync::atomic::Ordering::Relaxed),
"bot never sent PING ircbot-keepalive"
);
bot_task.abort();
}