use std::io::{Read, Write};
use std::net::TcpListener;
use std::thread;
use std::time::Duration;
use digit::protocol::{finger, finger_raw};
use digit::query::Query;
#[test]
fn read_timeout_reports_timed_out() {
let listener = TcpListener::bind("127.0.0.1:0").expect("bind to ephemeral port");
let port = listener.local_addr().unwrap().port();
let handle = thread::spawn(move || {
let (mut stream, _) = listener.accept().expect("accept connection");
stream.set_read_timeout(Some(Duration::from_secs(5))).ok();
let mut buf = [0u8; 1024];
let _ = stream.read(&mut buf);
thread::sleep(Duration::from_secs(5));
});
let q = Query::parse(Some(&format!("user@127.0.0.1")), false, port).expect("valid query");
let result = finger(&q, Duration::from_secs(1), 1_048_576);
assert!(result.is_err());
let err = result.unwrap_err();
let msg = format!("{}", err);
assert!(
msg.contains("timed out"),
"expected 'timed out' but got: {}",
msg
);
handle.join().expect("server thread");
}
fn mock_finger_server(
response: &str,
on_query: impl FnOnce(String) + Send + 'static,
) -> (u16, thread::JoinHandle<()>) {
let listener = TcpListener::bind("127.0.0.1:0").expect("bind to ephemeral port");
let port = listener.local_addr().unwrap().port();
let response = response.to_string();
let handle = thread::spawn(move || {
let (mut stream, _) = listener.accept().expect("accept connection");
stream.set_read_timeout(Some(Duration::from_secs(2))).ok();
let mut buf = Vec::new();
let mut tmp = [0u8; 1024];
loop {
match stream.read(&mut tmp) {
Ok(0) => break,
Ok(n) => {
buf.extend_from_slice(&tmp[..n]);
if buf.windows(2).any(|w| w == b"\r\n") {
break;
}
}
Err(_) => break,
}
}
let query_str = String::from_utf8_lossy(&buf).into_owned();
on_query(query_str);
stream
.write_all(response.as_bytes())
.expect("write response");
});
(port, handle)
}
#[test]
fn end_to_end_user_query() {
let (port, handle) = mock_finger_server("Login: user\r\nName: Test User\r\n", |query| {
assert_eq!(query, "user\r\n");
});
let q = Query::parse(Some(&format!("user@127.0.0.1")), false, port).expect("valid query");
let result = finger(&q, Duration::from_secs(5), 1_048_576).expect("finger should succeed");
assert!(result.contains("Login: user"));
assert!(result.contains("Test User"));
handle.join().expect("server thread");
}
#[test]
fn end_to_end_list_users() {
let (port, handle) = mock_finger_server("user1\r\nuser2\r\n", |query| {
assert_eq!(query, "\r\n");
});
let q = Query::parse(Some(&format!("@127.0.0.1")), false, port).expect("valid query");
let result = finger(&q, Duration::from_secs(5), 1_048_576).expect("finger should succeed");
assert!(result.contains("user1"));
assert!(result.contains("user2"));
handle.join().expect("server thread");
}
#[test]
fn end_to_end_verbose_query() {
let (port, handle) = mock_finger_server("Verbose info\r\n", |query| {
assert_eq!(query, "/W user\r\n");
});
let q = Query::parse(Some(&format!("user@127.0.0.1")), true, port).expect("valid query");
let result = finger(&q, Duration::from_secs(5), 1_048_576).expect("finger should succeed");
assert!(result.contains("Verbose info"));
handle.join().expect("server thread");
}
#[test]
fn connection_refused_returns_error() {
let listener = TcpListener::bind("127.0.0.1:0").expect("bind");
let port = listener.local_addr().unwrap().port();
drop(listener);
let q = Query::parse(Some(&format!("user@127.0.0.1")), false, port).expect("valid query");
let result = finger(&q, Duration::from_secs(2), 1_048_576);
assert!(result.is_err());
let err = result.unwrap_err();
let msg = format!("{}", err);
assert!(
msg.contains("could not connect"),
"unexpected error: {}",
msg
);
}
#[test]
fn utf8_lossy_handles_invalid_bytes() {
let response_bytes: Vec<u8> = vec![72, 101, 108, 108, 111, 0xFF, 0xFE, 10];
let response_str = unsafe { String::from_utf8_unchecked(response_bytes) };
let (port, handle) = mock_finger_server(&response_str, |_| {});
let q = Query::parse(Some(&format!("user@127.0.0.1")), false, port).expect("valid query");
let result = finger(&q, Duration::from_secs(5), 1_048_576).expect("finger should succeed");
assert!(result.contains("Hello"));
assert!(result.contains('\u{FFFD}'));
handle.join().expect("server thread");
}
#[test]
fn response_capped_at_max_size() {
let big_response = "X".repeat(1000);
let (port, handle) = mock_finger_server(&big_response, |_| {});
let q = Query::parse(Some(&format!("user@127.0.0.1")), false, port).expect("valid query");
let result = finger(&q, Duration::from_secs(5), 100).expect("finger should succeed");
assert_eq!(result.len(), 100);
assert!(result.chars().all(|c| c == 'X'));
handle.join().expect("server thread");
}
#[test]
fn finger_raw_returns_bytes() {
let (port, handle) = mock_finger_server("Hello\r\n", |_| {});
let q = Query::parse(Some(&format!("user@127.0.0.1")), false, port).expect("valid query");
let result =
finger_raw(&q, Duration::from_secs(5), 1_048_576).expect("finger_raw should succeed");
assert_eq!(result, b"Hello\r\n");
handle.join().expect("server thread");
}
#[test]
fn finger_raw_preserves_invalid_utf8() {
let response_bytes: Vec<u8> = vec![72, 101, 108, 108, 111, 0xFF, 0xFE, 10];
let response_str = unsafe { String::from_utf8_unchecked(response_bytes.clone()) };
let (port, handle) = mock_finger_server(&response_str, |_| {});
let q = Query::parse(Some(&format!("user@127.0.0.1")), false, port).expect("valid query");
let result =
finger_raw(&q, Duration::from_secs(5), 1_048_576).expect("finger_raw should succeed");
assert_eq!(result, response_bytes);
handle.join().expect("server thread");
}