use std::io::{self, Cursor, Read, Write}; use std::sync::{Arc, Mutex}; use rustbot::bot::Bot; use rustbot::config::Config; use rustbot::irc::Connection; struct Mock { input: Cursor>, output: Arc>>, } impl Read for Mock { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.input.read(buf) } } impl Write for Mock { fn write(&mut self, buf: &[u8]) -> io::Result { self.output.lock().unwrap().extend_from_slice(buf); Ok(buf.len()) } fn flush(&mut self) -> io::Result<()> { Ok(()) } } fn run(server: &str) -> String { let output = Arc::new(Mutex::new(Vec::new())); let mock = Mock { input: Cursor::new(server.as_bytes().to_vec()), output: output.clone(), }; let conn = Connection::from_stream(mock); let mut bot = Bot::new(Config::default(), conn); let _ = bot.run(); let bytes = output.lock().unwrap().clone(); String::from_utf8_lossy(&bytes).into_owned() } #[test] fn sends_registration() { let out = run(""); assert!(out.contains("CAP LS"), "{out}"); assert!(out.contains("NICK rubot"), "{out}"); assert!(out.contains("USER rubot"), "{out}"); } #[test] fn answers_ping() { let out = run("PING :tok123\r\n"); assert!(out.contains("PONG :tok123"), "{out}"); } #[test] fn joins_configured_channels_on_welcome() { let out = run(":srv 001 rubot :welcome\r\n"); assert!(out.contains("JOIN #devs"), "{out}"); } #[test] fn appends_underscore_on_nick_in_use() { let out = run(":srv 433 * rubot :nickname is already in use\r\n"); assert!(out.contains("NICK rubot_"), "{out}"); } #[test] fn dispatches_command_to_module() { let out = run(":alice!a@h PRIVMSG #chan :!ping\r\n"); assert!(out.contains("PRIVMSG #chan :pong"), "{out}"); } #[test] fn ignores_own_echoed_message() { let out = run(":rubot!r@h PRIVMSG #chan :!ping\r\n"); assert!(!out.contains(":pong"), "{out}"); }