forked from lavina/lavina
1
0
Fork 0
lavina/crates/projection-irc/tests/lib.rs

530 lines
18 KiB
Rust

use std::io::ErrorKind;
use std::net::SocketAddr;
use std::time::Duration;
use anyhow::{anyhow, Result};
use prometheus::Registry as MetricsRegistry;
use tokio::io::{AsyncReadExt, AsyncWriteExt, BufReader};
use tokio::net::tcp::{ReadHalf, WriteHalf};
use tokio::net::TcpStream;
use lavina_core::repo::{Storage, StorageConfig};
use lavina_core::{player::PlayerRegistry, room::RoomRegistry};
use projection_irc::APP_VERSION;
use projection_irc::{launch, read_irc_message, RunningServer, ServerConfig};
struct TestScope<'a> {
reader: BufReader<ReadHalf<'a>>,
writer: WriteHalf<'a>,
buffer: Vec<u8>,
pub timeout: Duration,
}
impl<'a> TestScope<'a> {
fn new(stream: &mut TcpStream) -> TestScope<'_> {
let (reader, writer) = stream.split();
let reader = BufReader::new(reader);
let buffer = vec![];
let timeout = Duration::from_millis(100);
TestScope {
reader,
writer,
buffer,
timeout,
}
}
async fn send(&mut self, str: &(impl AsRef<str> + ?Sized)) -> Result<()> {
self.writer.write_all(str.as_ref().as_bytes()).await?;
self.writer.write_all(b"\r\n").await?;
self.writer.flush().await?;
Ok(())
}
async fn expect(&mut self, str: &str) -> Result<()> {
tracing::debug!("Expecting {}", str);
let len = tokio::time::timeout(self.timeout, read_irc_message(&mut self.reader, &mut self.buffer)).await??;
assert_eq!(std::str::from_utf8(&self.buffer[..len - 2])?, str);
self.buffer.clear();
Ok(())
}
async fn expect_that(&mut self, validate: impl FnOnce(&str) -> bool) -> Result<()> {
let len = tokio::time::timeout(self.timeout, read_irc_message(&mut self.reader, &mut self.buffer)).await??;
let msg = std::str::from_utf8(&self.buffer[..len - 2])?;
if !validate(msg) {
return Err(anyhow!("unexpected message: {:?}", msg));
}
self.buffer.clear();
Ok(())
}
async fn expect_server_introduction(&mut self, nick: &str) -> Result<()> {
self.expect(&format!(":testserver 001 {nick} :Welcome to testserver Server")).await?;
self.expect(&format!(":testserver 002 {nick} :Welcome to testserver Server")).await?;
self.expect(&format!(":testserver 003 {nick} :Welcome to testserver Server")).await?;
self.expect(&format!(
":testserver 004 {nick} testserver {APP_VERSION} r CFILPQbcefgijklmnopqrstvz"
))
.await?;
self.expect(&format!(
":testserver 005 {nick} CHANTYPES=# :are supported by this server"
))
.await?;
Ok(())
}
async fn expect_eof(&mut self) -> Result<()> {
let mut buf = [0; 1];
let len = tokio::time::timeout(self.timeout, self.reader.read(&mut buf)).await??;
if len != 0 {
return Err(anyhow!("not a eof"));
}
Ok(())
}
async fn expect_nothing(&mut self) -> Result<()> {
let mut buf = [0; 1];
match tokio::time::timeout(self.timeout, self.reader.read(&mut buf)).await {
Ok(res) => Err(anyhow!("received something: {:?}", res)),
Err(_) => Ok(()),
}
}
}
struct TestServer {
metrics: MetricsRegistry,
storage: Storage,
rooms: RoomRegistry,
players: PlayerRegistry,
server: RunningServer,
}
impl TestServer {
async fn start() -> Result<TestServer> {
let _ = tracing_subscriber::fmt::try_init();
let config = ServerConfig {
listen_on: "127.0.0.1:0".parse().unwrap(),
server_name: "testserver".into(),
};
let mut metrics = MetricsRegistry::new();
let mut storage = Storage::open(StorageConfig {
db_path: ":memory:".into(),
})
.await?;
let rooms = RoomRegistry::new(&mut metrics, storage.clone()).unwrap();
let players = PlayerRegistry::empty(rooms.clone(), storage.clone(), &mut metrics).unwrap();
let server = launch(config, players.clone(), rooms.clone(), metrics.clone(), storage.clone()).await.unwrap();
Ok(TestServer {
metrics,
storage,
rooms,
players,
server,
})
}
async fn reboot(mut self) -> Result<TestServer> {
let config = ServerConfig {
listen_on: "127.0.0.1:0".parse().unwrap(),
server_name: "testserver".into(),
};
let TestServer {
mut metrics,
mut storage,
rooms,
mut players,
server,
} = self;
server.terminate().await?;
players.shutdown_all().await.unwrap();
drop(players);
drop(rooms);
let mut metrics = MetricsRegistry::new();
let rooms = RoomRegistry::new(&mut metrics, storage.clone()).unwrap();
let players = PlayerRegistry::empty(rooms.clone(), storage.clone(), &mut metrics).unwrap();
let server = launch(config, players.clone(), rooms.clone(), metrics.clone(), storage.clone()).await.unwrap();
Ok(TestServer {
metrics,
storage,
rooms,
players,
server,
})
}
}
#[tokio::test]
async fn scenario_basic() -> Result<()> {
let mut server = TestServer::start().await?;
// test scenario
server.storage.create_user("tester").await?;
server.storage.set_password("tester", "password").await?;
let mut stream = TcpStream::connect(server.server.addr).await?;
let mut s = TestScope::new(&mut stream);
s.send("PASS password").await?;
s.send("NICK tester").await?;
s.send("USER UserName 0 * :Real Name").await?;
s.expect_server_introduction("tester").await?;
s.expect_nothing().await?;
s.send("QUIT :Leaving").await?;
s.expect(":testserver ERROR :Leaving the server").await?;
s.expect_eof().await?;
stream.shutdown().await?;
// wrap up
server.server.terminate().await?;
Ok(())
}
#[tokio::test]
async fn scenario_join_and_reboot() -> Result<()> {
let mut server = TestServer::start().await?;
// test scenario
server.storage.create_user("tester").await?;
server.storage.set_password("tester", "password").await?;
let mut stream = TcpStream::connect(server.server.addr).await?;
let mut s = TestScope::new(&mut stream);
// Open a connection and join a channel
s.send("PASS password").await?;
s.send("NICK tester").await?;
s.send("USER UserName 0 * :Real Name").await?;
s.expect_server_introduction("tester").await?;
s.expect_nothing().await?;
s.send("JOIN #test").await?;
s.expect(":tester JOIN #test").await?;
s.expect(":testserver 332 tester #test :New room").await?;
s.expect(":testserver 353 tester = #test :tester").await?;
s.expect(":testserver 366 tester #test :End of /NAMES list").await?;
s.send("PRIVMSG #test :Hello").await?;
s.send("QUIT :Leaving").await?;
s.expect(":testserver ERROR :Leaving the server").await?;
s.expect_eof().await?;
stream.shutdown().await?;
// Open a new connection and expect to be force-joined to the channel
let mut stream = TcpStream::connect(server.server.addr).await?;
let mut s = TestScope::new(&mut stream);
async fn test(s: &mut TestScope<'_>) -> Result<()> {
s.send("PASS password").await?;
s.send("NICK tester").await?;
s.send("USER UserName 0 * :Real Name").await?;
s.expect_server_introduction("tester").await?;
s.expect(":tester JOIN #test").await?;
s.expect(":testserver 332 tester #test :New room").await?;
s.expect(":testserver 353 tester = #test :tester").await?;
s.expect(":testserver 366 tester #test :End of /NAMES list").await?;
s.send("QUIT :Leaving").await?;
s.expect(":testserver ERROR :Leaving the server").await?;
s.expect_eof().await?;
Ok(())
}
test(&mut s).await?;
stream.shutdown().await?;
// Reboot the server
let server = server.reboot().await?;
// Open a new connection and expect to be force-joined to the channel
let mut stream = TcpStream::connect(server.server.addr).await?;
let mut s = TestScope::new(&mut stream);
test(&mut s).await?;
stream.shutdown().await?;
// wrap up
server.server.terminate().await?;
Ok(())
}
#[tokio::test]
async fn scenario_force_join_msg() -> Result<()> {
let mut server = TestServer::start().await?;
// test scenario
server.storage.create_user("tester").await?;
server.storage.set_password("tester", "password").await?;
let mut stream1 = TcpStream::connect(server.server.addr).await?;
let mut s1 = TestScope::new(&mut stream1);
let mut stream2 = TcpStream::connect(server.server.addr).await?;
let mut s2 = TestScope::new(&mut stream2);
s1.send("PASS password").await?;
s1.send("NICK tester").await?;
s1.send("USER UserName 0 * :Real Name").await?;
s1.expect_server_introduction("tester").await?;
s1.expect_nothing().await?;
s2.send("PASS password").await?;
s2.send("NICK tester").await?;
s2.send("USER UserName 0 * :Real Name").await?;
s2.expect_server_introduction("tester").await?;
s2.expect_nothing().await?;
// We join the channel from the first connection
s1.send("JOIN #test").await?;
s1.expect(":tester JOIN #test").await?;
s1.expect(":testserver 332 tester #test :New room").await?;
s1.expect(":testserver 353 tester = #test :tester").await?;
s1.expect(":testserver 366 tester #test :End of /NAMES list").await?;
// And the second connection should receive the JOIN message (forced JOIN)
s2.expect(":tester JOIN #test").await?;
s2.expect(":testserver 332 tester #test :New room").await?;
s2.expect(":testserver 353 tester = #test :tester").await?;
s2.expect(":testserver 366 tester #test :End of /NAMES list").await?;
// We send a message to the channel from the second connection
s2.send("PRIVMSG #test :Hello").await?;
// We should not receive an acknowledgement from the server
s2.expect_nothing().await?;
// But we should receive this message from the first connection
s1.expect(":tester PRIVMSG #test :Hello").await?;
s1.send("QUIT :Leaving").await?;
s1.expect(":testserver ERROR :Leaving the server").await?;
s1.expect_eof().await?;
// Closing a connection does not kick you from the channel on a different connection
s2.expect_nothing().await?;
s2.send("QUIT :Leaving").await?;
s2.expect(":testserver ERROR :Leaving the server").await?;
s2.expect_eof().await?;
stream1.shutdown().await?;
stream2.shutdown().await?;
// wrap up
server.server.terminate().await?;
Ok(())
}
#[tokio::test]
async fn scenario_two_users() -> Result<()> {
let mut server = TestServer::start().await?;
// test scenario
server.storage.create_user("tester1").await?;
server.storage.set_password("tester1", "password").await?;
server.storage.create_user("tester2").await?;
server.storage.set_password("tester2", "password").await?;
let mut stream1 = TcpStream::connect(server.server.addr).await?;
let mut s1 = TestScope::new(&mut stream1);
let mut stream2 = TcpStream::connect(server.server.addr).await?;
let mut s2 = TestScope::new(&mut stream2);
s1.send("PASS password").await?;
s1.send("NICK tester1").await?;
s1.send("USER UserName 0 * :Real Name").await?;
s1.expect_server_introduction("tester1").await?;
s1.expect_nothing().await?;
s2.send("PASS password").await?;
s2.send("NICK tester2").await?;
s2.send("USER UserName 0 * :Real Name").await?;
s2.expect_server_introduction("tester2").await?;
s2.expect_nothing().await?;
// Join the channel from the first user
s1.send("JOIN #test").await?;
s1.expect(":tester1 JOIN #test").await?;
s1.expect(":testserver 332 tester1 #test :New room").await?;
s1.expect(":testserver 353 tester1 = #test :tester1").await?;
s1.expect(":testserver 366 tester1 #test :End of /NAMES list").await?;
// Then join the channel from the second user
s2.send("JOIN #test").await?;
s2.expect(":tester2 JOIN #test").await?;
s2.expect(":testserver 332 tester2 #test :New room").await?;
s2.expect_that(|msg| {
msg == ":testserver 353 tester2 = #test :tester1 tester2"
|| msg == ":testserver 353 tester2 = #test :tester2 tester1"
})
.await?;
s2.expect(":testserver 366 tester2 #test :End of /NAMES list").await?;
// The first user should receive the JOIN message from the second user
s1.expect(":tester2 JOIN #test").await?;
s1.expect_nothing().await?;
s2.expect_nothing().await?;
// Send a message from the second user
s2.send("PRIVMSG #test :Hello").await?;
// The first user should receive the message
s1.expect(":tester2 PRIVMSG #test :Hello").await?;
// Leave the channel from the first user
s1.send("PART #test").await?;
s1.expect(":tester1 PART #test").await?;
// The second user should receive the PART message
s2.expect(":tester1 PART #test").await?;
Ok(())
}
/*
IRC SASL doc: https://ircv3.net/specs/extensions/sasl-3.1.html
AUTHENTICATE doc: https://modern.ircdocs.horse/#authenticate-message
*/
#[tokio::test]
async fn scenario_cap_full_negotiation() -> Result<()> {
let mut server = TestServer::start().await?;
// test scenario
server.storage.create_user("tester").await?;
server.storage.set_password("tester", "password").await?;
let mut stream = TcpStream::connect(server.server.addr).await?;
let mut s = TestScope::new(&mut stream);
s.send("CAP LS 302").await?;
s.send("NICK tester").await?;
s.send("USER UserName 0 * :Real Name").await?;
s.expect(":testserver CAP * LS :sasl=PLAIN").await?;
s.send("CAP REQ :sasl").await?;
s.expect(":testserver CAP tester ACK :sasl").await?;
s.send("AUTHENTICATE PLAIN").await?;
s.expect(":testserver AUTHENTICATE +").await?;
s.send("AUTHENTICATE dGVzdGVyAHRlc3RlcgBwYXNzd29yZA==").await?; // base64-encoded 'tester\x00tester\x00password'
s.expect(":testserver 900 tester tester tester :You are now logged in as tester").await?;
s.expect(":testserver 903 tester :SASL authentication successful").await?;
s.send("CAP END").await?;
s.expect_server_introduction("tester").await?;
s.expect_nothing().await?;
s.send("QUIT :Leaving").await?;
s.expect(":testserver ERROR :Leaving the server").await?;
s.expect_eof().await?;
stream.shutdown().await?;
// wrap up
server.server.terminate().await?;
Ok(())
}
#[tokio::test]
async fn scenario_cap_short_negotiation() -> Result<()> {
let mut server = TestServer::start().await?;
// test scenario
server.storage.create_user("tester").await?;
server.storage.set_password("tester", "password").await?;
let mut stream = TcpStream::connect(server.server.addr).await?;
let mut s = TestScope::new(&mut stream);
s.send("NICK tester").await?;
s.send("CAP REQ :sasl").await?;
s.send("USER UserName 0 * :Real Name").await?;
s.expect(":testserver CAP tester ACK :sasl").await?;
s.send("AUTHENTICATE PLAIN").await?;
s.expect(":testserver AUTHENTICATE +").await?;
s.send("AUTHENTICATE dGVzdGVyAHRlc3RlcgBwYXNzd29yZA==").await?; // base64-encoded 'tester\x00tester\x00password'
s.expect(":testserver 900 tester tester tester :You are now logged in as tester").await?;
s.expect(":testserver 903 tester :SASL authentication successful").await?;
s.send("CAP END").await?;
s.expect_server_introduction("tester").await?;
s.expect_nothing().await?;
s.send("QUIT :Leaving").await?;
s.expect(":testserver ERROR :Leaving the server").await?;
s.expect_eof().await?;
stream.shutdown().await?;
// wrap up
server.server.terminate().await?;
Ok(())
}
#[tokio::test]
async fn scenario_cap_sasl_fail() -> Result<()> {
let mut server = TestServer::start().await?;
// test scenario
server.storage.create_user("tester").await?;
server.storage.set_password("tester", "password").await?;
let mut stream = TcpStream::connect(server.server.addr).await?;
let mut s = TestScope::new(&mut stream);
s.send("CAP LS 302").await?;
s.send("NICK tester").await?;
s.send("USER UserName 0 * :Real Name").await?;
s.expect(":testserver CAP * LS :sasl=PLAIN").await?;
s.send("CAP REQ :sasl").await?;
s.expect(":testserver CAP tester ACK :sasl").await?;
s.send("AUTHENTICATE SHA256").await?;
s.expect(":testserver 904 tester :Unsupported mechanism").await?;
s.send("AUTHENTICATE PLAIN").await?;
s.expect(":testserver AUTHENTICATE +").await?;
s.send("AUTHENTICATE dGVzdGVyAHRlc3RlcgBwYXNzd29yZDE=").await?;
s.expect(":testserver 904 tester :Bad credentials").await?;
s.send("AUTHENTICATE dGVzdGVyAHRlc3RlcgBwYXNzd29yZA==").await?; // base64-encoded 'tester\x00tester\x00password'
s.expect(":testserver 900 tester tester tester :You are now logged in as tester").await?;
s.expect(":testserver 903 tester :SASL authentication successful").await?;
s.send("CAP END").await?;
s.expect_server_introduction("tester").await?;
s.expect_nothing().await?;
s.send("QUIT :Leaving").await?;
s.expect(":testserver ERROR :Leaving the server").await?;
s.expect_eof().await?;
stream.shutdown().await?;
// wrap up
server.server.terminate().await?;
Ok(())
}
#[tokio::test]
async fn terminate_socket_scenario() -> Result<()> {
let mut server = TestServer::start().await?;
// test scenario
server.storage.create_user("tester").await?;
server.storage.set_password("tester", "password").await?;
let mut stream = TcpStream::connect(server.server.addr).await?;
let mut s = TestScope::new(&mut stream);
s.send("NICK tester").await?;
s.send("CAP REQ :sasl").await?;
s.send("USER UserName 0 * :Real Name").await?;
s.expect(":testserver CAP tester ACK :sasl").await?;
s.send("AUTHENTICATE PLAIN").await?;
s.expect(":testserver AUTHENTICATE +").await?;
server.server.terminate().await?;
assert_eq!(stream.read_u8().await.unwrap_err().kind(), ErrorKind::UnexpectedEof);
Ok(())
}