2023-10-09 11:35:41 +00:00
|
|
|
use std::time::Duration;
|
|
|
|
|
2023-10-13 14:30:31 +00:00
|
|
|
use anyhow::{anyhow, Result};
|
2023-10-09 11:35:41 +00:00
|
|
|
use prometheus::Registry as MetricsRegistry;
|
2023-10-13 14:30:31 +00:00
|
|
|
use tokio::io::{AsyncReadExt, AsyncWriteExt, BufReader};
|
2023-10-09 11:35:41 +00:00
|
|
|
use tokio::net::tcp::{ReadHalf, WriteHalf};
|
|
|
|
use tokio::net::TcpStream;
|
|
|
|
|
|
|
|
use lavina_core::repo::{Storage, StorageConfig};
|
|
|
|
use lavina_core::{player::PlayerRegistry, room::RoomRegistry};
|
2023-10-13 14:30:31 +00:00
|
|
|
use projection_irc::{launch, read_irc_message, RunningServer, ServerConfig};
|
2023-10-09 11:35:41 +00:00
|
|
|
|
|
|
|
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<()> {
|
2023-12-08 02:47:09 +00:00
|
|
|
tracing::debug!("Expecting {}", str);
|
2023-10-09 11:35:41 +00:00
|
|
|
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(())
|
|
|
|
}
|
2023-10-13 14:30:31 +00:00
|
|
|
|
|
|
|
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> {
|
2023-12-08 02:47:09 +00:00
|
|
|
let _ = tracing_subscriber::fmt::try_init();
|
2023-10-13 14:30:31 +00:00
|
|
|
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(), &mut metrics).unwrap();
|
|
|
|
let server = launch(config, players.clone(), rooms.clone(), metrics.clone(), storage.clone()).await.unwrap();
|
|
|
|
Ok(TestServer {
|
|
|
|
metrics,
|
|
|
|
storage,
|
|
|
|
rooms,
|
|
|
|
players,
|
|
|
|
server,
|
|
|
|
})
|
|
|
|
}
|
2023-10-09 11:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn scenario_basic() -> Result<()> {
|
2023-10-13 14:30:31 +00:00
|
|
|
let mut server = TestServer::start().await?;
|
2023-10-09 11:35:41 +00:00
|
|
|
|
|
|
|
// test scenario
|
|
|
|
|
2023-10-13 14:30:31 +00:00
|
|
|
server.storage.create_user("tester").await?;
|
|
|
|
server.storage.set_password("tester", "password").await?;
|
2023-10-09 11:35:41 +00:00
|
|
|
|
2023-10-13 14:30:31 +00:00
|
|
|
let mut stream = TcpStream::connect(server.server.addr).await?;
|
2023-10-09 11:35:41 +00:00
|
|
|
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?;
|
2023-12-08 02:47:09 +00:00
|
|
|
s.expect(":testserver 001 tester :Welcome to Kek Server").await?;
|
|
|
|
s.expect(":testserver 002 tester :Welcome to Kek Server").await?;
|
|
|
|
s.expect(":testserver 003 tester :Welcome to Kek Server").await?;
|
|
|
|
s.expect(":testserver 004 tester testserver kek-0.1.alpha.3 r CFILPQbcefgijklmnopqrstvz").await?;
|
|
|
|
s.expect(":testserver 005 tester CHANTYPES=# :are supported by this server").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(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
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(":testserver 001 tester :Welcome to Kek Server").await?;
|
|
|
|
s.expect(":testserver 002 tester :Welcome to Kek Server").await?;
|
|
|
|
s.expect(":testserver 003 tester :Welcome to Kek Server").await?;
|
|
|
|
s.expect(":testserver 004 tester testserver kek-0.1.alpha.3 r CFILPQbcefgijklmnopqrstvz").await?;
|
|
|
|
s.expect(":testserver 005 tester CHANTYPES=# :are supported by this server").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?;
|
2024-01-28 17:44:13 +00:00
|
|
|
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(":testserver 001 tester :Welcome to Kek Server").await?;
|
|
|
|
s.expect(":testserver 002 tester :Welcome to Kek Server").await?;
|
|
|
|
s.expect(":testserver 003 tester :Welcome to Kek Server").await?;
|
|
|
|
s.expect(":testserver 004 tester testserver kek-0.1.alpha.3 r CFILPQbcefgijklmnopqrstvz").await?;
|
|
|
|
s.expect(":testserver 005 tester CHANTYPES=# :are supported by this server").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?;
|
2024-01-31 20:40:58 +00:00
|
|
|
s.expect(":testserver 904 tester :Unsupported mechanism").await?;
|
2024-01-28 17:44:13 +00:00
|
|
|
s.send("AUTHENTICATE PLAIN").await?;
|
|
|
|
s.expect(":testserver AUTHENTICATE +").await?;
|
2024-01-31 20:40:58 +00:00
|
|
|
s.send("AUTHENTICATE dGVzdGVyAHRlc3RlcgBwYXNzd29yZDE=").await?;
|
|
|
|
s.expect(":testserver 904 tester :Bad credentials").await?;
|
2023-12-08 02:47:09 +00:00
|
|
|
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?;
|
|
|
|
|
2023-10-09 11:35:41 +00:00
|
|
|
s.expect(":testserver 001 tester :Welcome to Kek Server").await?;
|
|
|
|
s.expect(":testserver 002 tester :Welcome to Kek Server").await?;
|
|
|
|
s.expect(":testserver 003 tester :Welcome to Kek Server").await?;
|
|
|
|
s.expect(":testserver 004 tester testserver kek-0.1.alpha.3 r CFILPQbcefgijklmnopqrstvz").await?;
|
|
|
|
s.expect(":testserver 005 tester CHANTYPES=# :are supported by this server").await?;
|
2023-10-13 14:30:31 +00:00
|
|
|
s.expect_nothing().await?;
|
|
|
|
s.send("QUIT :Leaving").await?;
|
|
|
|
s.expect(":testserver ERROR :Leaving the server").await?;
|
|
|
|
s.expect_eof().await?;
|
2023-10-09 11:35:41 +00:00
|
|
|
|
|
|
|
stream.shutdown().await?;
|
|
|
|
|
|
|
|
// wrap up
|
|
|
|
|
2023-10-13 14:30:31 +00:00
|
|
|
server.server.terminate().await?;
|
2023-10-09 11:35:41 +00:00
|
|
|
Ok(())
|
|
|
|
}
|