use flagset::{flags, FlagSet};
use futures::{future::Fuse, future::FutureExt};
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::net::IpAddr;
use std::ops::Drop;
use std::sync::atomic::{AtomicI32, AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use tokio::sync::mpsc::error::SendError;
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
use tokio::sync::oneshot;
use tokio::time;
use tokio_util::codec::Framed;
use tracing::*;
use crate::command::*;
use crate::config::*;
use crate::utils::*;
#[derive(Debug)]
pub(super) struct User {
pub(super) hostname: String,
pub(super) sender: UnboundedSender<String>,
pub(super) quit_sender: Option<oneshot::Sender<(String, String)>>,
pub(super) name: String,
pub(super) realname: String,
pub(super) source: String, pub(super) modes: UserModes,
pub(super) away: Option<String>,
pub(super) channels: HashSet<String>,
pub(super) invited_to: HashSet<String>, pub(super) last_activity: u64,
pub(super) signon: u64,
pub(super) history_entry: NickHistoryEntry,
}
impl User {
pub(super) fn new(
config: &MainConfig,
user_state: &ConnUserState,
sender: UnboundedSender<String>,
quit_sender: oneshot::Sender<(String, String)>,
) -> User {
let mut user_modes = config.default_user_modes;
user_modes.registered = user_modes.registered || user_state.registered;
let now_ts = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
User {
hostname: user_state.hostname.clone(),
sender,
quit_sender: Some(quit_sender),
name: user_state.name.as_ref().unwrap().clone(),
realname: user_state.realname.as_ref().unwrap().clone(),
source: user_state.source.clone(),
modes: user_modes,
away: None,
channels: HashSet::new(),
invited_to: HashSet::new(),
last_activity: now_ts,
signon: now_ts,
history_entry: NickHistoryEntry {
username: user_state.name.as_ref().unwrap().clone(),
hostname: user_state.hostname.clone(),
realname: user_state.realname.as_ref().unwrap().clone(),
signon: now_ts,
},
}
}
pub(super) fn update_nick(&mut self, user_state: &ConnUserState) {
self.source = user_state.source.clone();
}
#[cfg(feature = "dns_lookup")]
pub(super) fn update_hostname(&mut self, user_state: &ConnUserState) {
self.hostname = user_state.hostname.clone();
self.source = user_state.source.clone();
}
pub(super) fn send_message(
&self,
msg: &Message<'_>,
source: &str,
) -> Result<(), SendError<String>> {
self.sender.send(msg.to_string_with_source(source))
}
pub(super) fn send_msg_display<T: fmt::Display>(
&self,
source: &str,
t: T,
) -> Result<(), SendError<String>> {
self.sender.send(format!(":{} {}", source, t))
}
}
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
pub(super) struct ChannelUserModes {
pub(super) founder: bool,
pub(super) protected: bool,
pub(super) voice: bool,
pub(super) operator: bool,
pub(super) half_oper: bool,
}
impl ChannelUserModes {
pub(super) fn new_for_created_channel() -> Self {
ChannelUserModes {
founder: true,
protected: false,
voice: false,
operator: true,
half_oper: false,
}
}
pub(super) fn is_protected(&self) -> bool {
self.founder || self.protected
}
pub(super) fn is_operator(&self) -> bool {
self.founder || self.protected || self.operator
}
pub(super) fn is_half_operator(&self) -> bool {
self.founder || self.protected || self.operator || self.half_oper
}
pub(super) fn is_only_half_operator(&self) -> bool {
!self.founder && !self.protected && !self.operator && self.half_oper
}
pub(super) fn is_voice(&self) -> bool {
self.founder || self.protected || self.operator || self.half_oper || self.voice
}
}
flags! {
pub(super) enum PrivMsgTargetType: u8 {
Channel = 0b1,
ChannelFounder = 0b10,
ChannelProtected = 0b100,
ChannelOper = 0b1000,
ChannelHalfOper = 0b10000,
ChannelVoice = 0b100000,
ChannelAll = 0b111111,
ChannelAllSpecial = 0b111110,
}
}
impl ChannelUserModes {
pub(super) fn to_string(self, caps: &CapState) -> String {
let mut out = String::new();
if self.founder {
out.push('~');
}
if (caps.multi_prefix || out.is_empty()) && self.protected {
out.push('&');
}
if (caps.multi_prefix || out.is_empty()) && self.operator {
out.push('@');
}
if (caps.multi_prefix || out.is_empty()) && self.half_oper {
out.push('%');
}
if (caps.multi_prefix || out.is_empty()) && self.voice {
out.push('+');
}
out
}
}
pub(super) fn get_privmsg_target_type(target: &str) -> (FlagSet<PrivMsgTargetType>, &str) {
use PrivMsgTargetType::*;
let mut out = Channel.into();
let mut amp_count = 0;
let mut last_amp = false;
let mut out_str = "";
for (i, c) in target.bytes().enumerate() {
match c {
b'~' => out |= Channel | ChannelFounder,
b'&' => out |= Channel | ChannelProtected,
b'@' => out |= Channel | ChannelOper,
b'%' => out |= Channel | ChannelHalfOper,
b'+' => out |= Channel | ChannelVoice,
b'#' => {
if i + 1 < target.len() {
out_str = &target[i..];
} else {
out &= !ChannelAll;
}
break;
}
_ => {
if last_amp {
if amp_count < 2 {
out &= !ChannelProtected;
}
out_str = &target[i - 1..];
} else {
out &= !ChannelAll;
}
break;
}
}
if c == b'&' {
if i + 1 < target.len() {
last_amp = true;
amp_count += 1;
} else {
out &= !ChannelAll;
}
} else {
last_amp = false;
}
}
(out, out_str)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct ChannelTopic {
pub(super) topic: String,
pub(super) nick: String,
pub(super) set_time: u64,
}
impl ChannelTopic {
pub(super) fn new(topic: String) -> Self {
ChannelTopic {
topic,
nick: String::new(),
set_time: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(),
}
}
pub(super) fn new_with_nick(topic: String, nick: String) -> Self {
ChannelTopic {
topic,
nick,
set_time: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct BanInfo {
pub(super) set_time: u64,
pub(super) who: String,
}
#[derive(Default, Clone, Debug, PartialEq, Eq)]
pub(super) struct ChannelDefaultModes {
pub(super) operators: HashSet<String>,
pub(super) half_operators: HashSet<String>,
pub(super) voices: HashSet<String>,
pub(super) founders: HashSet<String>,
pub(super) protecteds: HashSet<String>,
}
impl ChannelDefaultModes {
pub(super) fn new_from_modes_and_cleanup(modes: &mut ChannelModes) -> Self {
ChannelDefaultModes {
operators: modes.operators.take().unwrap_or_default(),
half_operators: modes.half_operators.take().unwrap_or_default(),
voices: modes.voices.take().unwrap_or_default(),
founders: modes.founders.take().unwrap_or_default(),
protecteds: modes.protecteds.take().unwrap_or_default(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct Channel {
pub(super) topic: Option<ChannelTopic>,
pub(super) modes: ChannelModes,
pub(super) default_modes: ChannelDefaultModes,
pub(super) ban_info: HashMap<String, BanInfo>,
pub(super) users: HashMap<String, ChannelUserModes>,
pub(super) creation_time: u64,
pub(super) preconfigured: bool,
}
impl Channel {
pub(super) fn new_on_user_join(user_nick: String) -> Channel {
let mut users = HashMap::new();
users.insert(
user_nick.clone(),
ChannelUserModes::new_for_created_channel(),
);
Channel {
topic: None,
ban_info: HashMap::new(),
default_modes: ChannelDefaultModes::default(),
modes: ChannelModes::new_for_channel(user_nick),
users,
creation_time: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(),
preconfigured: false,
}
}
pub(super) fn add_user(&mut self, user_nick: &String) {
let mut chum = ChannelUserModes::default();
if self.default_modes.half_operators.contains(user_nick) {
chum.half_oper = true;
let mut half_ops = self.modes.half_operators.take().unwrap_or_default();
half_ops.insert(user_nick.clone());
self.modes.half_operators = Some(half_ops);
}
if self.default_modes.operators.contains(user_nick) {
chum.operator = true;
let mut ops = self.modes.operators.take().unwrap_or_default();
ops.insert(user_nick.clone());
self.modes.operators = Some(ops);
}
if self.default_modes.founders.contains(user_nick) {
chum.founder = true;
let mut founders = self.modes.founders.take().unwrap_or_default();
founders.insert(user_nick.clone());
self.modes.founders = Some(founders);
}
if self.default_modes.voices.contains(user_nick) {
chum.voice = true;
let mut voices = self.modes.voices.take().unwrap_or_default();
voices.insert(user_nick.clone());
self.modes.voices = Some(voices);
}
if self.default_modes.protecteds.contains(user_nick) {
chum.protected = true;
let mut protecteds = self.modes.protecteds.take().unwrap_or_default();
protecteds.insert(user_nick.clone());
self.modes.protecteds = Some(protecteds);
}
self.users.insert(user_nick.clone(), chum);
}
pub(super) fn rename_user(&mut self, old_nick: &String, nick: String) {
let oldchumode = self.users.remove(old_nick).unwrap();
self.users.insert(nick.clone(), oldchumode);
self.modes.rename_user(old_nick, nick);
}
pub(super) fn remove_user(&mut self, nick: &str) {
self.remove_operator(nick);
self.remove_half_operator(nick);
self.remove_founder(nick);
self.remove_voice(nick);
self.remove_protected(nick);
self.users.remove(nick);
}
pub(super) fn add_operator(&mut self, nick: &str) {
let mut ops = self.modes.operators.take().unwrap_or_default();
ops.insert(nick.to_string());
self.modes.operators = Some(ops);
self.users.get_mut(nick).unwrap().operator = true;
}
pub(super) fn remove_operator(&mut self, nick: &str) {
let mut ops = self.modes.operators.take().unwrap_or_default();
ops.remove(nick);
self.modes.operators = Some(ops);
self.users.get_mut(nick).unwrap().operator = false;
}
pub(super) fn add_half_operator(&mut self, nick: &str) {
let mut half_ops = self.modes.half_operators.take().unwrap_or_default();
half_ops.insert(nick.to_string());
self.modes.half_operators = Some(half_ops);
self.users.get_mut(nick).unwrap().half_oper = true;
}
pub(super) fn remove_half_operator(&mut self, nick: &str) {
let mut half_ops = self.modes.half_operators.take().unwrap_or_default();
half_ops.remove(nick);
self.modes.half_operators = Some(half_ops);
self.users.get_mut(nick).unwrap().half_oper = false;
}
pub(super) fn add_voice(&mut self, nick: &str) {
let mut voices = self.modes.voices.take().unwrap_or_default();
voices.insert(nick.to_string());
self.modes.voices = Some(voices);
self.users.get_mut(nick).unwrap().voice = true;
}
pub(super) fn remove_voice(&mut self, nick: &str) {
let mut voices = self.modes.voices.take().unwrap_or_default();
voices.remove(nick);
self.modes.voices = Some(voices);
self.users.get_mut(nick).unwrap().voice = false;
}
pub(super) fn add_founder(&mut self, nick: &str) {
let mut founders = self.modes.founders.take().unwrap_or_default();
founders.insert(nick.to_string());
self.modes.founders = Some(founders);
self.users.get_mut(nick).unwrap().founder = true;
}
pub(super) fn remove_founder(&mut self, nick: &str) {
let mut founders = self.modes.founders.take().unwrap_or_default();
founders.remove(nick);
self.modes.founders = Some(founders);
self.users.get_mut(nick).unwrap().founder = false;
}
pub(super) fn add_protected(&mut self, nick: &str) {
let mut protecteds = self.modes.protecteds.take().unwrap_or_default();
protecteds.insert(nick.to_string());
self.modes.protecteds = Some(protecteds);
self.users.get_mut(nick).unwrap().protected = true;
}
pub(super) fn remove_protected(&mut self, nick: &str) {
let mut protecteds = self.modes.protecteds.take().unwrap_or_default();
protecteds.remove(nick);
self.modes.protecteds = Some(protecteds);
self.users.get_mut(nick).unwrap().protected = false;
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(super) struct NickHistoryEntry {
pub(super) username: String,
pub(super) hostname: String,
pub(super) realname: String,
pub(super) signon: u64,
}
#[derive(Copy, Clone, Debug, Default)]
pub(crate) struct CapState {
pub(super) multi_prefix: bool,
}
impl fmt::Display for CapState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.multi_prefix {
f.write_str("multi-prefix")
} else {
Ok(())
}
}
}
impl CapState {
pub(super) fn apply_cap(&mut self, cap: &str) -> bool {
match cap {
"multi-prefix" => self.multi_prefix = true,
_ => return false,
};
true
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ConnUserState {
pub(super) ip_addr: IpAddr,
pub(super) hostname: String,
pub(super) name: Option<String>,
pub(super) realname: Option<String>,
pub(super) nick: Option<String>,
pub(super) source: String, pub(super) password: Option<String>,
pub(super) authenticated: bool,
pub(super) registered: bool,
}
impl ConnUserState {
pub(super) fn new(ip_addr: IpAddr) -> ConnUserState {
let mut source = "@".to_string();
source.push_str(&ip_addr.to_string());
ConnUserState {
ip_addr,
hostname: ip_addr.to_string(),
name: None,
realname: None,
nick: None,
source,
password: None,
authenticated: false,
registered: false,
}
}
pub(super) fn client_name(&self) -> &str {
if let Some(ref n) = self.nick {
n
} else if let Some(ref n) = self.name {
n
} else {
&self.hostname
}
}
pub(super) fn update_source(&mut self) {
let mut s = String::new();
if let Some(ref nick) = self.nick {
s.push_str(nick);
s.push('!');
}
if let Some(ref name) = self.name {
s.push('~'); s.push_str(name);
}
s.push('@');
s.push_str(&self.hostname);
self.source = s;
}
#[cfg(feature = "dns_lookup")]
pub(super) fn set_hostname(&mut self, hostname: String) {
self.hostname = hostname;
self.update_source();
}
pub(super) fn set_name(&mut self, name: String) {
self.name = Some(name);
self.update_source();
}
pub(super) fn set_nick(&mut self, nick: String) {
self.nick = Some(nick);
self.update_source();
}
}
#[derive(Debug)]
pub(crate) struct ConnState {
pub(super) stream: BufferedLineStream,
pub(super) sender: Option<UnboundedSender<String>>,
pub(super) receiver: UnboundedReceiver<String>,
pub(super) ping_sender: Option<UnboundedSender<()>>,
pub(super) ping_receiver: UnboundedReceiver<()>,
pub(super) timeout_sender: Arc<UnboundedSender<()>>,
pub(super) timeout_receiver: UnboundedReceiver<()>,
pub(super) pong_notifier: Option<oneshot::Sender<()>>,
pub(super) quit_receiver: Fuse<oneshot::Receiver<(String, String)>>,
pub(super) quit_sender: Option<oneshot::Sender<(String, String)>>,
pub(super) dns_lookup_receiver: Fuse<oneshot::Receiver<Option<String>>>,
#[cfg(feature = "dns_lookup")]
pub(super) dns_lookup_sender: Option<oneshot::Sender<Option<String>>>,
pub(super) user_state: ConnUserState,
pub(super) caps_negotation: bool, pub(super) caps: CapState,
pub(super) quit: Arc<AtomicI32>,
pub(super) conns_count: Arc<AtomicUsize>,
}
impl ConnState {
pub(super) fn new(
ip_addr: IpAddr,
stream: Framed<DualTcpStream, IRCLinesCodec>,
conns_count: Arc<AtomicUsize>,
) -> ConnState {
let (sender, receiver) = unbounded_channel();
let (ping_sender, ping_receiver) = unbounded_channel();
let (timeout_sender, timeout_receiver) = unbounded_channel();
let (quit_sender, quit_receiver) = oneshot::channel();
#[cfg(feature = "dns_lookup")]
let (dns_lookup_sender, dns_lookup_receiver) = oneshot::channel();
#[cfg(not(feature = "dns_lookup"))]
let (_, dns_lookup_receiver) = oneshot::channel();
ConnState {
stream: BufferedLineStream::new(stream),
sender: Some(sender),
receiver,
user_state: ConnUserState::new(ip_addr),
ping_sender: Some(ping_sender),
ping_receiver,
timeout_sender: Arc::new(timeout_sender),
timeout_receiver,
pong_notifier: None,
quit_sender: Some(quit_sender),
quit_receiver: quit_receiver.fuse(),
#[cfg(feature = "dns_lookup")]
dns_lookup_sender: Some(dns_lookup_sender),
dns_lookup_receiver: dns_lookup_receiver.fuse(),
caps_negotation: false,
caps: CapState::default(),
quit: Arc::new(AtomicI32::new(0)),
conns_count,
}
}
pub(crate) fn is_quit(&self) -> bool {
self.quit.load(Ordering::SeqCst) != 0
}
pub(super) fn run_ping_waker(&mut self, config: &MainConfig) {
if self.ping_sender.is_some() {
tokio::spawn(ping_client_waker(
Duration::from_secs(config.ping_timeout),
self.quit.clone(),
self.ping_sender.take().unwrap(),
));
} else {
panic!("Ping waker ran!"); }
}
pub(super) fn run_pong_timeout(&mut self, config: &MainConfig) {
let (pong_notifier, pong_receiver) = oneshot::channel();
self.pong_notifier = Some(pong_notifier);
tokio::spawn(pong_client_timeout(
time::timeout(Duration::from_secs(config.pong_timeout), pong_receiver),
self.quit.clone(),
self.timeout_sender.clone(),
));
}
#[cfg(feature = "dns_lookup")]
pub(super) fn run_dns_lookup(&mut self) {
super::dns_lookup(
self.dns_lookup_sender.take().unwrap(),
self.user_state.ip_addr,
);
}
pub(crate) fn is_secure(&self) -> bool {
self.stream.get_ref().is_secure()
}
}
impl Drop for ConnState {
fn drop(&mut self) {
self.conns_count.fetch_sub(1, Ordering::SeqCst);
}
}
async fn ping_client_waker(d: Duration, quit: Arc<AtomicI32>, sender: UnboundedSender<()>) {
time::sleep(d).await;
let mut intv = time::interval(d);
while quit.load(Ordering::SeqCst) == 0 {
intv.tick().await;
sender.send(()).unwrap();
}
}
async fn pong_client_timeout(
tmo: time::Timeout<oneshot::Receiver<()>>,
quit: Arc<AtomicI32>,
sender: Arc<UnboundedSender<()>>,
) {
if tmo.await.is_err() {
if quit.load(Ordering::SeqCst) == 0 {
sender.send(()).unwrap();
}
}
}
pub(super) struct VolatileState {
pub(super) users: HashMap<String, User>,
pub(super) channels: HashMap<String, Channel>,
pub(super) wallops_users: HashSet<String>,
pub(super) invisible_users_count: usize,
pub(super) operators_count: usize,
pub(super) max_users_count: usize,
pub(super) nick_histories: HashMap<String, Vec<NickHistoryEntry>>,
pub(super) quit_sender: Option<oneshot::Sender<String>>,
pub(super) quit_receiver: Option<Fuse<oneshot::Receiver<String>>>,
}
impl VolatileState {
pub(super) fn new_from_config(config: &MainConfig) -> VolatileState {
let mut channels = HashMap::new();
if let Some(ref cfg_channels) = config.channels {
cfg_channels.iter().for_each(|c| {
let mut ch_modes = c.modes.clone();
let def_ch_modes = ChannelDefaultModes::new_from_modes_and_cleanup(&mut ch_modes);
channels.insert(
c.name.clone(),
Channel {
topic: c.topic.as_ref().map(|x| ChannelTopic::new(x.clone())),
ban_info: HashMap::new(),
default_modes: def_ch_modes,
modes: ch_modes,
users: HashMap::new(),
creation_time: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(),
preconfigured: true,
},
);
});
}
let (quit_sender, quit_receiver) = oneshot::channel();
VolatileState {
users: HashMap::new(),
channels,
wallops_users: HashSet::new(),
invisible_users_count: 0,
operators_count: 0,
max_users_count: 0,
nick_histories: HashMap::new(),
quit_sender: Some(quit_sender),
quit_receiver: Some(quit_receiver.fuse()),
}
}
pub(super) fn add_user(&mut self, unick: &str, user: User) {
if user.modes.invisible {
self.invisible_users_count += 1;
}
if user.modes.wallops {
self.wallops_users.insert(unick.to_string());
}
if user.modes.is_local_oper() {
self.operators_count += 1;
}
self.users.insert(unick.to_string(), user);
if self.users.len() > self.max_users_count {
self.max_users_count = self.users.len();
}
}
pub(super) fn remove_user_from_channel<'a>(&mut self, channel: &'a str, nick: &'a str) {
if let Some(chanobj) = self.channels.get_mut(channel) {
chanobj.remove_user(nick);
if chanobj.users.is_empty() && !chanobj.preconfigured {
info!("Channel {} has been removed", channel);
self.channels.remove(channel);
}
}
if let Some(user) = self.users.get_mut(nick) {
user.channels.remove(channel);
}
}
pub(super) fn remove_user(&mut self, nick: &str) {
if let Some(user) = self.users.remove(nick) {
if user.modes.is_local_oper() {
self.operators_count -= 1;
}
if user.modes.invisible {
self.invisible_users_count -= 1;
}
self.wallops_users.remove(nick);
user.channels.iter().for_each(|chname| {
self.remove_user_from_channel(chname, nick);
});
self.insert_to_nick_history(&nick.to_string(), user.history_entry);
}
}
pub(super) fn insert_to_nick_history(&mut self, old_nick: &String, nhe: NickHistoryEntry) {
if !self.nick_histories.contains_key(old_nick) {
self.nick_histories.insert(old_nick.to_string(), vec![]);
}
let nick_hist = self.nick_histories.get_mut(old_nick).unwrap();
nick_hist.push(nhe);
}
}
#[cfg(test)]
mod test {
use super::*;
use std::iter::FromIterator;
#[test]
fn test_user_new() {
let mut config = MainConfig::default();
config.default_user_modes = UserModes {
invisible: true,
oper: false,
local_oper: false,
registered: true,
wallops: false,
};
let user_state = ConnUserState {
ip_addr: "127.0.0.1".parse().unwrap(),
hostname: "bobby.com".to_string(),
name: Some("mati1".to_string()),
realname: Some("Matthew Somebody".to_string()),
nick: Some("matix".to_string()),
source: "matix!mati1@bobby.com".to_string(),
password: None,
authenticated: true,
registered: true,
};
let (sender, _) = unbounded_channel();
let (quit_sender, _) = oneshot::channel();
let user = User::new(&config, &user_state, sender, quit_sender);
let user_nick = user_state.nick.clone().unwrap();
assert_eq!(user_state.hostname, user.hostname);
assert_eq!(user_state.source, user.source);
assert_eq!(user_state.realname.unwrap(), user.realname);
assert_eq!(user_state.name.unwrap(), user.name);
assert_eq!(user_state.nick.unwrap(), user_nick);
assert_eq!(config.default_user_modes, user.modes);
assert_eq!(
NickHistoryEntry {
username: user.name.clone(),
hostname: user.hostname.clone(),
realname: user.realname.clone(),
signon: user.signon
},
user.history_entry
);
}
#[test]
fn test_channel_user_modes() {
let chum = ChannelUserModes {
founder: false,
protected: false,
voice: false,
operator: false,
half_oper: false,
};
assert!(!chum.is_protected());
assert!(!chum.is_operator());
assert!(!chum.is_half_operator());
assert!(!chum.is_only_half_operator());
assert!(!chum.is_voice());
let chum = ChannelUserModes {
founder: true,
protected: false,
voice: false,
operator: false,
half_oper: false,
};
assert!(chum.is_protected());
assert!(chum.is_operator());
assert!(chum.is_half_operator());
assert!(!chum.is_only_half_operator());
assert!(chum.is_voice());
let chum = ChannelUserModes {
founder: false,
protected: true,
voice: false,
operator: false,
half_oper: false,
};
assert!(chum.is_protected());
assert!(chum.is_operator());
assert!(chum.is_half_operator());
assert!(!chum.is_only_half_operator());
assert!(chum.is_voice());
let chum = ChannelUserModes {
founder: false,
protected: false,
voice: false,
operator: true,
half_oper: false,
};
assert!(!chum.is_protected());
assert!(chum.is_operator());
assert!(chum.is_half_operator());
assert!(!chum.is_only_half_operator());
assert!(chum.is_voice());
let chum = ChannelUserModes {
founder: false,
protected: false,
voice: false,
operator: true,
half_oper: true,
};
assert!(!chum.is_protected());
assert!(chum.is_operator());
assert!(chum.is_half_operator());
assert!(!chum.is_only_half_operator());
assert!(chum.is_voice());
let chum = ChannelUserModes {
founder: false,
protected: false,
voice: false,
operator: false,
half_oper: true,
};
assert!(!chum.is_protected());
assert!(!chum.is_operator());
assert!(chum.is_half_operator());
assert!(chum.is_only_half_operator());
assert!(chum.is_voice());
let chum = ChannelUserModes {
founder: false,
protected: false,
voice: true,
operator: false,
half_oper: false,
};
assert!(!chum.is_protected());
assert!(!chum.is_operator());
assert!(!chum.is_half_operator());
assert!(!chum.is_only_half_operator());
assert!(chum.is_voice());
}
#[test]
fn test_channel_user_modes_to_string() {
let chum = ChannelUserModes {
founder: true,
protected: true,
voice: false,
operator: true,
half_oper: false,
};
assert_eq!(
"~",
chum.to_string(&CapState {
multi_prefix: false
})
);
assert_eq!("~&@", chum.to_string(&CapState { multi_prefix: true }));
let chum = ChannelUserModes {
founder: false,
protected: false,
voice: true,
operator: false,
half_oper: true,
};
assert_eq!(
"%",
chum.to_string(&CapState {
multi_prefix: false
})
);
assert_eq!("%+", chum.to_string(&CapState { multi_prefix: true }));
}
#[test]
fn test_get_privmsg_target_type() {
use PrivMsgTargetType::*;
assert_eq!((Channel.into(), "#abc"), get_privmsg_target_type("#abc"));
assert_eq!((Channel.into(), "&abc"), get_privmsg_target_type("&abc"));
assert_eq!(
(Channel | ChannelFounder, "#abc"),
get_privmsg_target_type("~#abc")
);
assert_eq!(
(Channel | ChannelFounder, "&abc"),
get_privmsg_target_type("~&abc")
);
assert_eq!(
(Channel | ChannelProtected, "#abc"),
get_privmsg_target_type("&#abc")
);
assert_eq!(
(Channel | ChannelProtected, "&abc"),
get_privmsg_target_type("&&abc")
);
assert_eq!(
(Channel | ChannelVoice, "#abc"),
get_privmsg_target_type("+#abc")
);
assert_eq!(
(Channel | ChannelVoice, "&abc"),
get_privmsg_target_type("+&abc")
);
assert_eq!(
(Channel | ChannelHalfOper, "#abc"),
get_privmsg_target_type("%#abc")
);
assert_eq!(
(Channel | ChannelHalfOper, "&abc"),
get_privmsg_target_type("%&abc")
);
assert_eq!(
(Channel | ChannelVoice | ChannelFounder, "#abc"),
get_privmsg_target_type("+~#abc")
);
assert_eq!(
(Channel | ChannelVoice | ChannelFounder, "&abc"),
get_privmsg_target_type("+~&abc")
);
assert_eq!(
(Channel | ChannelOper, "#abc"),
get_privmsg_target_type("@#abc")
);
assert_eq!(
(Channel | ChannelOper, "&abc"),
get_privmsg_target_type("@&abc")
);
assert_eq!(
(Channel | ChannelOper | ChannelProtected, "#abc"),
get_privmsg_target_type("&@#abc")
);
assert_eq!(
(Channel | ChannelOper | ChannelProtected, "&abc"),
get_privmsg_target_type("&@&abc")
);
assert_eq!(
(FlagSet::new(0).unwrap(), ""),
get_privmsg_target_type("abc")
);
assert_eq!((FlagSet::new(0).unwrap(), ""), get_privmsg_target_type("#"));
assert_eq!((FlagSet::new(0).unwrap(), ""), get_privmsg_target_type("&"));
}
#[test]
fn test_channel_default_modes_new_from_modes_and_cleanup() {
let mut chm = ChannelModes::default();
chm.founders = Some(["founder".to_string()].into());
chm.protecteds = Some(["protected".to_string()].into());
chm.operators = Some(["operator".to_string()].into());
chm.half_operators = Some(["half_operator".to_string()].into());
chm.voices = Some(["voice".to_string()].into());
let exp_chdm = ChannelDefaultModes {
founders: ["founder".to_string()].into(),
protecteds: ["protected".to_string()].into(),
operators: ["operator".to_string()].into(),
half_operators: ["half_operator".to_string()].into(),
voices: ["voice".to_string()].into(),
};
let chdm = ChannelDefaultModes::new_from_modes_and_cleanup(&mut chm);
assert_eq!(exp_chdm, chdm);
assert_eq!(ChannelModes::default(), chm);
let mut chm = ChannelModes::default();
chm.operators = Some(["operator".to_string()].into());
chm.half_operators = Some(["half_operator".to_string()].into());
chm.voices = Some(["voice".to_string()].into());
let exp_chdm = ChannelDefaultModes {
founders: HashSet::new(),
protecteds: HashSet::new(),
operators: ["operator".to_string()].into(),
half_operators: ["half_operator".to_string()].into(),
voices: ["voice".to_string()].into(),
};
let chdm = ChannelDefaultModes::new_from_modes_and_cleanup(&mut chm);
assert_eq!(exp_chdm, chdm);
assert_eq!(ChannelModes::default(), chm);
}
#[test]
fn test_channel_new() {
let channel = Channel::new_on_user_join("dizzy".to_string());
assert_eq!(
Channel {
topic: None,
modes: ChannelModes::new_for_channel("dizzy".to_string()),
default_modes: ChannelDefaultModes::default(),
ban_info: HashMap::new(),
users: [(
"dizzy".to_string(),
ChannelUserModes::new_for_created_channel()
)]
.into(),
creation_time: channel.creation_time,
preconfigured: false
},
channel
);
}
#[test]
fn test_channel_join_remove_user() {
let mut channel = Channel::new_on_user_join("runner".to_string());
channel.default_modes.founders.insert("fasty".to_string());
channel
.default_modes
.protecteds
.insert("quicker".to_string());
channel.default_modes.operators.insert("leader".to_string());
channel
.default_modes
.half_operators
.insert("rover".to_string());
channel.default_modes.voices.insert("cyclist".to_string());
channel.add_user(&"fasty".to_string());
channel.add_user(&"quicker".to_string());
channel.add_user(&"leader".to_string());
channel.add_user(&"rover".to_string());
channel.add_user(&"cyclist".to_string());
channel.add_user(&"doer".to_string());
let mut exp_channel = Channel::new_on_user_join("runner".to_string());
exp_channel.default_modes = channel.default_modes.clone();
exp_channel.users.insert(
"fasty".to_string(),
ChannelUserModes {
founder: true,
protected: false,
operator: false,
half_oper: false,
voice: false,
},
);
exp_channel.users.insert(
"quicker".to_string(),
ChannelUserModes {
founder: false,
protected: true,
operator: false,
half_oper: false,
voice: false,
},
);
exp_channel.users.insert(
"leader".to_string(),
ChannelUserModes {
founder: false,
protected: false,
operator: true,
half_oper: false,
voice: false,
},
);
exp_channel.users.insert(
"rover".to_string(),
ChannelUserModes {
founder: false,
protected: false,
operator: false,
half_oper: true,
voice: false,
},
);
exp_channel.users.insert(
"cyclist".to_string(),
ChannelUserModes {
founder: false,
protected: false,
operator: false,
half_oper: false,
voice: true,
},
);
exp_channel
.users
.insert("doer".to_string(), ChannelUserModes::default());
exp_channel.modes.founders = Some(["fasty".to_string(), "runner".to_string()].into());
exp_channel.modes.protecteds = Some(["quicker".to_string()].into());
exp_channel.modes.operators = Some(["leader".to_string(), "runner".to_string()].into());
exp_channel.modes.half_operators = Some(["rover".to_string()].into());
exp_channel.modes.voices = Some(["cyclist".to_string()].into());
assert_eq!(exp_channel, channel);
channel.remove_user(&"doer".to_string());
exp_channel.users.remove(&"doer".to_string());
assert_eq!(exp_channel, channel);
channel.remove_user(&"cyclist".to_string());
exp_channel.users.remove(&"cyclist".to_string());
exp_channel.modes.voices = Some(HashSet::new());
assert_eq!(exp_channel, channel);
channel.remove_user(&"rover".to_string());
exp_channel.users.remove(&"rover".to_string());
exp_channel.modes.half_operators = Some(HashSet::new());
assert_eq!(exp_channel, channel);
channel.remove_user(&"leader".to_string());
exp_channel.users.remove(&"leader".to_string());
exp_channel.modes.operators = Some(["runner".to_string()].into());
assert_eq!(exp_channel, channel);
channel.remove_user(&"quicker".to_string());
exp_channel.users.remove(&"quicker".to_string());
exp_channel.modes.protecteds = Some(HashSet::new());
assert_eq!(exp_channel, channel);
channel.remove_user(&"fasty".to_string());
exp_channel.users.remove(&"fasty".to_string());
exp_channel.modes.founders = Some(["runner".to_string()].into());
assert_eq!(exp_channel, channel);
}
#[test]
fn test_channel_rename_user() {
let mut channel = Channel::new_on_user_join("dizzy".to_string());
channel.rename_user(&"dizzy".to_string(), "diggy".to_string());
assert_eq!(
Channel {
topic: None,
modes: ChannelModes::new_for_channel("diggy".to_string()),
default_modes: ChannelDefaultModes::default(),
ban_info: HashMap::new(),
users: [(
"diggy".to_string(),
ChannelUserModes::new_for_created_channel()
)]
.into(),
creation_time: channel.creation_time,
preconfigured: false
},
channel
);
}
#[test]
fn test_channel_add_remove_mode() {
let mut channel = Channel::new_on_user_join("dizzy".to_string());
let mut exp_channel = Channel {
topic: None,
modes: ChannelModes::new_for_channel("dizzy".to_string()),
default_modes: ChannelDefaultModes::default(),
ban_info: HashMap::new(),
users: [
(
"dizzy".to_string(),
ChannelUserModes::new_for_created_channel(),
),
("inventor".to_string(), ChannelUserModes::default()),
("guru".to_string(), ChannelUserModes::default()),
("halfguru".to_string(), ChannelUserModes::default()),
("vip".to_string(), ChannelUserModes::default()),
("talker".to_string(), ChannelUserModes::default()),
]
.into(),
creation_time: channel.creation_time,
preconfigured: false,
};
channel
.users
.insert("inventor".to_string(), ChannelUserModes::default());
channel
.users
.insert("guru".to_string(), ChannelUserModes::default());
channel
.users
.insert("halfguru".to_string(), ChannelUserModes::default());
channel
.users
.insert("vip".to_string(), ChannelUserModes::default());
channel
.users
.insert("talker".to_string(), ChannelUserModes::default());
channel.add_founder("inventor");
exp_channel.modes.founders = Some(["dizzy".to_string(), "inventor".to_string()].into());
exp_channel.users.insert(
"inventor".to_string(),
ChannelUserModes {
founder: true,
protected: false,
operator: false,
half_oper: false,
voice: false,
},
);
assert_eq!(exp_channel, channel);
channel.remove_founder("inventor");
exp_channel.modes.founders = Some(["dizzy".to_string()].into());
exp_channel
.users
.insert("inventor".to_string(), ChannelUserModes::default());
assert_eq!(exp_channel, channel);
channel.add_operator("guru");
exp_channel.modes.operators = Some(["dizzy".to_string(), "guru".to_string()].into());
exp_channel.users.insert(
"guru".to_string(),
ChannelUserModes {
founder: false,
protected: false,
operator: true,
half_oper: false,
voice: false,
},
);
assert_eq!(exp_channel, channel);
channel.remove_operator("guru");
exp_channel.modes.operators = Some(["dizzy".to_string()].into());
exp_channel
.users
.insert("guru".to_string(), ChannelUserModes::default());
assert_eq!(exp_channel, channel);
channel.add_half_operator("halfguru");
exp_channel.modes.half_operators = Some(["halfguru".to_string()].into());
exp_channel.users.insert(
"halfguru".to_string(),
ChannelUserModes {
founder: false,
protected: false,
operator: false,
half_oper: true,
voice: false,
},
);
assert_eq!(exp_channel, channel);
channel.remove_half_operator("halfguru");
exp_channel.modes.half_operators = Some(HashSet::new());
exp_channel
.users
.insert("halfguru".to_string(), ChannelUserModes::default());
assert_eq!(exp_channel, channel);
channel.add_protected("vip");
exp_channel.modes.protecteds = Some(["vip".to_string()].into());
exp_channel.users.insert(
"vip".to_string(),
ChannelUserModes {
founder: false,
protected: true,
operator: false,
half_oper: false,
voice: false,
},
);
assert_eq!(exp_channel, channel);
channel.remove_protected("vip");
exp_channel.modes.protecteds = Some(HashSet::new());
exp_channel
.users
.insert("vip".to_string(), ChannelUserModes::default());
assert_eq!(exp_channel, channel);
channel.add_voice("talker");
exp_channel.modes.voices = Some(["talker".to_string()].into());
exp_channel.users.insert(
"talker".to_string(),
ChannelUserModes {
founder: false,
protected: false,
operator: false,
half_oper: false,
voice: true,
},
);
assert_eq!(exp_channel, channel);
channel.remove_voice("talker");
exp_channel.modes.voices = Some(HashSet::new());
exp_channel
.users
.insert("talker".to_string(), ChannelUserModes::default());
assert_eq!(exp_channel, channel);
}
#[test]
fn test_conn_user_state() {
let mut cus = ConnUserState::new("192.168.1.7".parse().unwrap());
assert_eq!(
ConnUserState {
ip_addr: "192.168.1.7".parse().unwrap(),
hostname: "192.168.1.7".to_string(),
name: None,
realname: None,
nick: None,
source: "@192.168.1.7".to_string(),
password: None,
authenticated: false,
registered: false
},
cus
);
assert_eq!("192.168.1.7", cus.client_name());
cus.set_name("boro".to_string());
assert_eq!(
ConnUserState {
ip_addr: "192.168.1.7".parse().unwrap(),
hostname: "192.168.1.7".to_string(),
name: Some("boro".to_string()),
realname: None,
nick: None,
source: "~boro@192.168.1.7".to_string(),
password: None,
authenticated: false,
registered: false
},
cus
);
assert_eq!("boro", cus.client_name());
cus.set_nick("buru".to_string());
assert_eq!(
ConnUserState {
ip_addr: "192.168.1.7".parse().unwrap(),
hostname: "192.168.1.7".to_string(),
name: Some("boro".to_string()),
realname: None,
nick: Some("buru".to_string()),
source: "buru!~boro@192.168.1.7".to_string(),
password: None,
authenticated: false,
registered: false
},
cus
);
assert_eq!("buru", cus.client_name());
let mut cus = ConnUserState::new("192.168.1.7".parse().unwrap());
assert_eq!(
ConnUserState {
ip_addr: "192.168.1.7".parse().unwrap(),
hostname: "192.168.1.7".to_string(),
name: None,
realname: None,
nick: None,
source: "@192.168.1.7".to_string(),
password: None,
authenticated: false,
registered: false
},
cus
);
assert_eq!("192.168.1.7", cus.client_name());
cus.set_nick("boro".to_string());
assert_eq!(
ConnUserState {
ip_addr: "192.168.1.7".parse().unwrap(),
hostname: "192.168.1.7".to_string(),
nick: Some("boro".to_string()),
realname: None,
name: None,
source: "boro!@192.168.1.7".to_string(),
password: None,
authenticated: false,
registered: false
},
cus
);
assert_eq!("boro", cus.client_name());
cus.set_name("buru".to_string());
assert_eq!(
ConnUserState {
ip_addr: "192.168.1.7".parse().unwrap(),
hostname: "192.168.1.7".to_string(),
nick: Some("boro".to_string()),
realname: None,
name: Some("buru".to_string()),
source: "boro!~buru@192.168.1.7".to_string(),
password: None,
authenticated: false,
registered: false
},
cus
);
assert_eq!("boro", cus.client_name());
}
#[test]
fn test_volatile_state_new() {
let mut config = MainConfig::default();
config.channels = Some(vec![
ChannelConfig {
name: "#gooddays".to_string(),
topic: Some("About good days".to_string()),
modes: ChannelModes::default(),
},
ChannelConfig {
name: "#pets".to_string(),
topic: Some("About pets".to_string()),
modes: ChannelModes::default(),
},
ChannelConfig {
name: "&cactuses".to_string(),
topic: None,
modes: ChannelModes::default(),
},
]);
let state = VolatileState::new_from_config(&config);
assert_eq!(
HashMap::from([
(
"#gooddays".to_string(),
Channel {
topic: Some(ChannelTopic::new("About good days".to_string())),
modes: ChannelModes::default(),
default_modes: ChannelDefaultModes::default(),
ban_info: HashMap::new(),
users: HashMap::new(),
creation_time: state.channels.get("#gooddays").unwrap().creation_time,
preconfigured: true
}
),
(
"#pets".to_string(),
Channel {
topic: Some(ChannelTopic::new("About pets".to_string())),
modes: ChannelModes::default(),
default_modes: ChannelDefaultModes::default(),
ban_info: HashMap::new(),
users: HashMap::new(),
creation_time: state.channels.get("#pets").unwrap().creation_time,
preconfigured: true
}
),
(
"&cactuses".to_string(),
Channel {
topic: None,
modes: ChannelModes::default(),
default_modes: ChannelDefaultModes::default(),
ban_info: HashMap::new(),
users: HashMap::new(),
creation_time: state.channels.get("&cactuses").unwrap().creation_time,
preconfigured: true
}
)
]),
state.channels
);
}
#[test]
fn test_volatile_remove_user_from_channel() {
let mut config = MainConfig::default();
config.channels = Some(vec![ChannelConfig {
name: "#something".to_string(),
topic: None,
modes: ChannelModes::default(),
}]);
let mut state = VolatileState::new_from_config(&config);
let user_state = ConnUserState {
ip_addr: "127.0.0.1".parse().unwrap(),
hostname: "bobby.com".to_string(),
name: Some("matix".to_string()),
realname: Some("Matthew Somebody".to_string()),
nick: Some("matixi".to_string()),
source: "matixi!matix@bobby.com".to_string(),
password: None,
authenticated: true,
registered: true,
};
let (sender, _) = unbounded_channel();
let (quit_sender, _) = oneshot::channel();
let user = User::new(&config, &user_state, sender, quit_sender);
state.add_user(&user_state.nick.clone().unwrap(), user);
[("#matixichan", "matixi"), ("#tulipchan", "matixi")]
.iter()
.for_each(|(chname, nick)| {
state.channels.insert(
chname.to_string(),
Channel::new_on_user_join(nick.to_string()),
);
state
.users
.get_mut(&nick.to_string())
.unwrap()
.channels
.insert(chname.to_string());
});
state
.channels
.get_mut(&"#something".to_string())
.unwrap()
.users
.insert("matixi".to_string(), ChannelUserModes::default());
state
.users
.get_mut("matixi")
.unwrap()
.channels
.insert("#something".to_string());
state.remove_user_from_channel("#something", "matixi");
assert!(state.channels.contains_key("#something"));
assert_eq!(
HashMap::new(),
state.channels.get("#something").unwrap().users
);
state.remove_user_from_channel("#matixichan", "matixi");
assert!(!state.channels.contains_key("#matixichan"));
state.remove_user_from_channel("#tulipan", "matixi");
assert!(!state.channels.contains_key("#tulipan"));
}
#[test]
fn test_volatile_state_add_remove_user() {
let mut config = MainConfig::default();
config.channels = Some(vec![ChannelConfig {
name: "#something".to_string(),
topic: None,
modes: ChannelModes::default(),
}]);
let mut state = VolatileState::new_from_config(&config);
let user_state = ConnUserState {
ip_addr: "127.0.0.1".parse().unwrap(),
hostname: "bobby.com".to_string(),
name: Some("matix".to_string()),
realname: Some("Matthew Somebody".to_string()),
nick: Some("matixi".to_string()),
source: "matixi!matix@bobby.com".to_string(),
password: None,
authenticated: true,
registered: true,
};
let (sender, _) = unbounded_channel();
let (quit_sender, _) = oneshot::channel();
let user = User::new(&config, &user_state, sender, quit_sender);
state.add_user(&user_state.nick.clone().unwrap(), user);
assert_eq!(1, state.max_users_count);
let user_state = ConnUserState {
ip_addr: "127.0.0.1".parse().unwrap(),
hostname: "flowers.com".to_string(),
name: Some("tulip".to_string()),
realname: Some("Tulipan".to_string()),
nick: Some("tulipan".to_string()),
source: "tulipan!tulip@flowers.com".to_string(),
password: None,
authenticated: true,
registered: true,
};
let (sender, _) = unbounded_channel();
let (quit_sender, _) = oneshot::channel();
let user = User::new(&config, &user_state, sender, quit_sender);
state.add_user(&user_state.nick.clone().unwrap(), user);
assert_eq!(2, state.max_users_count);
let user_state = ConnUserState {
ip_addr: "127.0.0.1".parse().unwrap(),
hostname: "digger.com".to_string(),
name: Some("greggy".to_string()),
realname: Some("Gregory Digger".to_string()),
nick: Some("greg".to_string()),
source: "greg!greggy@digger.com".to_string(),
password: None,
authenticated: true,
registered: true,
};
let (sender, _) = unbounded_channel();
let (quit_sender, _) = oneshot::channel();
let mut user = User::new(&config, &user_state, sender, quit_sender);
user.modes.invisible = true;
state.add_user(&user_state.nick.clone().unwrap(), user);
assert_eq!(3, state.max_users_count);
assert_eq!(1, state.invisible_users_count);
let user_state = ConnUserState {
ip_addr: "127.0.0.1".parse().unwrap(),
hostname: "miller.com".to_string(),
name: Some("johnny".to_string()),
realname: Some("John Miller".to_string()),
nick: Some("john".to_string()),
source: "john!johnny@miller.com".to_string(),
password: None,
authenticated: true,
registered: true,
};
let (sender, _) = unbounded_channel();
let (quit_sender, _) = oneshot::channel();
let mut user = User::new(&config, &user_state, sender, quit_sender);
user.modes.wallops = true;
state.add_user(&user_state.nick.clone().unwrap(), user);
assert_eq!(4, state.max_users_count);
assert_eq!(1, state.invisible_users_count);
assert_eq!(HashSet::from(["john".to_string()]), state.wallops_users);
let user_state = ConnUserState {
ip_addr: "127.0.0.1".parse().unwrap(),
hostname: "guru.com".to_string(),
name: Some("admin".to_string()),
realname: Some("Great Admin".to_string()),
nick: Some("admini".to_string()),
source: "admini!admin@guru.com".to_string(),
password: None,
authenticated: true,
registered: true,
};
let (sender, _) = unbounded_channel();
let (quit_sender, _) = oneshot::channel();
let mut user = User::new(&config, &user_state, sender, quit_sender);
user.modes.oper = true;
state.add_user(&user_state.nick.clone().unwrap(), user);
assert_eq!(5, state.max_users_count);
assert_eq!(1, state.invisible_users_count);
assert_eq!(1, state.operators_count);
assert_eq!(
HashSet::from([
"matixi".to_string(),
"tulipan".to_string(),
"greg".to_string(),
"john".to_string(),
"admini".to_string()
]),
HashSet::from_iter(state.users.keys().cloned())
);
assert_eq!(
HashSet::from([
"matix".to_string(),
"tulip".to_string(),
"greggy".to_string(),
"johnny".to_string(),
"admin".to_string()
]),
HashSet::from_iter(state.users.values().map(|u| u.name.clone()))
);
assert_eq!(
HashSet::from([
"Matthew Somebody".to_string(),
"Tulipan".to_string(),
"Gregory Digger".to_string(),
"John Miller".to_string(),
"Great Admin".to_string()
]),
HashSet::from_iter(state.users.values().map(|u| u.realname.clone()))
);
[
("#matixichan", "matixi"),
("#tulipchan", "tulipan"),
("#gregchan", "greg"),
("#johnchan", "john"),
("#guruchan", "admini"),
]
.iter()
.for_each(|(chname, nick)| {
state.channels.insert(
chname.to_string(),
Channel::new_on_user_join(nick.to_string()),
);
state
.users
.get_mut(&nick.to_string())
.unwrap()
.channels
.insert(chname.to_string());
});
state
.channels
.get_mut("#something")
.unwrap()
.users
.insert("john".to_string(), ChannelUserModes::default());
state
.users
.get_mut("john")
.unwrap()
.channels
.insert("#something".to_string());
state.remove_user("matixi");
assert_eq!(5, state.max_users_count);
assert_eq!(1, state.invisible_users_count);
assert_eq!(1, state.operators_count);
assert_eq!(HashSet::from(["john".to_string()]), state.wallops_users);
assert_eq!(
HashSet::from([
"tulipan".to_string(),
"greg".to_string(),
"john".to_string(),
"admini".to_string()
]),
HashSet::from_iter(state.users.keys().cloned())
);
assert_eq!(
HashSet::from([
"tulip".to_string(),
"greggy".to_string(),
"johnny".to_string(),
"admin".to_string()
]),
HashSet::from_iter(state.users.values().map(|u| u.name.clone()))
);
assert!(!state.channels.contains_key("#matixichan"));
state.remove_user("greg");
assert_eq!(5, state.max_users_count);
assert_eq!(0, state.invisible_users_count);
assert_eq!(1, state.operators_count);
assert_eq!(HashSet::from(["john".to_string()]), state.wallops_users);
assert_eq!(
HashSet::from([
"tulipan".to_string(),
"john".to_string(),
"admini".to_string()
]),
HashSet::from_iter(state.users.keys().cloned())
);
assert_eq!(
HashSet::from([
"tulip".to_string(),
"johnny".to_string(),
"admin".to_string()
]),
HashSet::from_iter(state.users.values().map(|u| u.name.clone()))
);
assert!(!state.channels.contains_key("#gregchan"));
state.remove_user("john");
assert_eq!(5, state.max_users_count);
assert_eq!(0, state.invisible_users_count);
assert_eq!(1, state.operators_count);
assert_eq!(HashSet::new(), state.wallops_users);
assert_eq!(
HashSet::from(["tulipan".to_string(), "admini".to_string()]),
HashSet::from_iter(state.users.keys().cloned())
);
assert_eq!(
HashSet::from(["tulip".to_string(), "admin".to_string()]),
HashSet::from_iter(state.users.values().map(|u| u.name.clone()))
);
assert!(!state.channels.contains_key("#johnchan"));
assert!(state.channels.contains_key("#something"));
state.remove_user("admini");
assert_eq!(5, state.max_users_count);
assert_eq!(0, state.invisible_users_count);
assert_eq!(0, state.operators_count);
assert_eq!(HashSet::new(), state.wallops_users);
assert_eq!(
HashSet::from(["tulipan".to_string()]),
HashSet::from_iter(state.users.keys().cloned())
);
assert_eq!(
HashSet::from(["tulip".to_string()]),
HashSet::from_iter(state.users.values().map(|u| u.name.clone()))
);
assert!(!state.channels.contains_key("#guruchan"));
}
#[test]
fn test_volatile_state_insert_to_nick_history() {
let config = MainConfig::default();
let mut state = VolatileState::new_from_config(&config);
state.insert_to_nick_history(
&"mati".to_string(),
NickHistoryEntry {
username: "mati1".to_string(),
hostname: "gugg.com".to_string(),
realname: "Mati1".to_string(),
signon: 12344555555,
},
);
state.insert_to_nick_history(
&"mati".to_string(),
NickHistoryEntry {
username: "mati2".to_string(),
hostname: "bip.com".to_string(),
realname: "Mati2".to_string(),
signon: 12377411100,
},
);
assert_eq!(
HashMap::from([(
"mati".to_string(),
vec![
NickHistoryEntry {
username: "mati1".to_string(),
hostname: "gugg.com".to_string(),
realname: "Mati1".to_string(),
signon: 12344555555
},
NickHistoryEntry {
username: "mati2".to_string(),
hostname: "bip.com".to_string(),
realname: "Mati2".to_string(),
signon: 12377411100
}
]
)]),
state.nick_histories
);
}
}