From e4ad766315879e1ff05bb111229f073f8f0ed68e Mon Sep 17 00:00:00 2001 From: Andreas Grois Date: Mon, 10 Oct 2022 21:30:02 +0200 Subject: PassFish: Initial Commit Well, that's a lie. But nobody needs to see all the iterations I decided to sweep under the rug. That said, I think the repo is, while not clean, clean enough now, to not be embarrassed by uploading it to github. --- rust/src/implementation/profiles.rs | 1127 +++++++++++++++++++++++++++++++++++ 1 file changed, 1127 insertions(+) create mode 100644 rust/src/implementation/profiles.rs (limited to 'rust/src/implementation/profiles.rs') diff --git a/rust/src/implementation/profiles.rs b/rust/src/implementation/profiles.rs new file mode 100644 index 0000000..5d9bbfc --- /dev/null +++ b/rust/src/implementation/profiles.rs @@ -0,0 +1,1127 @@ +use std::convert::{From, Into, TryFrom, TryInto}; +use std::cell::RefCell; +use std::fmt::Display; +use std::sync::Arc; +use serde::{Serialize, Deserialize}; +use mockall_double::double; +#[cfg(test)] +use crate::implementation::pwm_macros::*; + +#[double] +use crate::interface::{ProfilesEmitter, ProfilesList}; +use crate::interface::ProfilesTrait; + +use crate::implementation::{LoadError, StoreError}; + +#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Debug)] +#[cfg_attr(test, derive(EnumVariantCount))] +pub(crate) enum HashAlgorithm { + Md4, + HmacMd4, + Md5, + Md5Version06, + HmacMd5, + HmacMd5Version06, + Sha1, + HmacSha1, + Sha256, + HmacSha256, + Ripemd160, + HmacRipemd160, +} + +impl Display for HashAlgorithm { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + HashAlgorithm::Md4 => write!(f,"MD4"), + HashAlgorithm::HmacMd4 => write!(f, "MD4-HMAC"), + HashAlgorithm::Md5 => write!(f, "MD5"), + HashAlgorithm::Md5Version06 => write!(f, "MD5 PWM Version 0.6"), + HashAlgorithm::HmacMd5 => write!(f, "MD5-HMAC"), + HashAlgorithm::HmacMd5Version06 => write!(f, "MD5-HMAC PWM Version 0.6"), + HashAlgorithm::Sha1 => write!(f,"SHA-1"), + HashAlgorithm::HmacSha1 => write!(f, "SHA-1-HMAC"), + HashAlgorithm::Sha256 => write!(f, "SHA-256"), + HashAlgorithm::HmacSha256 => write!(f, "SHA-256-HMAC"), + HashAlgorithm::Ripemd160 => write!(f, "RIPEMD-160"), + HashAlgorithm::HmacRipemd160 => write!(f, "RIPEMD-160-HMAC"), + } + } +} + +impl From for u8 { + fn from(h : HashAlgorithm) -> u8 { + h as u8 + } +} +impl TryFrom for HashAlgorithm { + type Error = (); + fn try_from(i : u8) -> Result { + match i { + 0 => { Ok(HashAlgorithm::Md4) } + 1 => { Ok(HashAlgorithm::HmacMd4) } + 2 => { Ok(HashAlgorithm::Md5) } + 3 => { Ok(HashAlgorithm::Md5Version06) } + 4 => { Ok(HashAlgorithm::HmacMd5) } + 5 => { Ok(HashAlgorithm::HmacMd5Version06) } + 6 => { Ok(HashAlgorithm::Sha1) } + 7 => { Ok(HashAlgorithm::HmacSha1) } + 8 => { Ok(HashAlgorithm::Sha256) } + 9 => { Ok(HashAlgorithm::HmacSha256) } + 10 => {Ok(HashAlgorithm::Ripemd160) } + 11 => {Ok(HashAlgorithm::HmacRipemd160)} + _ => { Err(()) } + } + } +} + +#[derive(Serialize, Deserialize, Clone, Copy, PartialEq)] +#[cfg_attr(test, derive(EnumVariantCount))] +pub(crate) enum LeetLevel { + One, + Two, + Three, + Four, + Five, + Six, + Seven, + Eight, + Nine, +} + +impl From for u8 { + fn from(l : LeetLevel) -> u8 { + l as u8 + 1 + } +} +impl TryFrom for LeetLevel { + type Error = (); + fn try_from(i : u8) -> Result { + match i { + 1 => { Ok(LeetLevel::One) } + 2 => { Ok(LeetLevel::Two) } + 3 => { Ok(LeetLevel::Three) } + 4 => { Ok(LeetLevel::Four) } + 5 => { Ok(LeetLevel::Five) } + 6 => { Ok(LeetLevel::Six) } + 7 => { Ok(LeetLevel::Seven) } + 8 => { Ok(LeetLevel::Eight) } + 9 => { Ok(LeetLevel::Nine) } + _ => { Err(()) } + } + } +} + + +#[derive(Serialize, Deserialize, Clone, PartialEq)] +#[serde(tag = "UseLeet")] +pub(crate) enum UseLeetWhenGenerating { + NotAtAll, + Before { + level : LeetLevel, + }, + After { + level : LeetLevel, + }, + BeforeAndAfter { + level : LeetLevel, + }, +} + +impl UseLeetWhenGenerating { + pub(crate) fn get_leet_level(&self) -> Option { + match self { + UseLeetWhenGenerating::NotAtAll => { None } + UseLeetWhenGenerating::Before { level } + | UseLeetWhenGenerating::After { level } + | UseLeetWhenGenerating::BeforeAndAfter { level } => { Some(*level) } + } + } + pub(crate) fn set_leet_level(&mut self, lvl : LeetLevel) -> Result<(),()> { + match self { + UseLeetWhenGenerating::NotAtAll => { Err(()) } + UseLeetWhenGenerating::Before { level } + | UseLeetWhenGenerating::After { level } + | UseLeetWhenGenerating::BeforeAndAfter { level} => { *level = lvl; Ok(()) } + } + } +} + +#[allow(clippy::struct_excessive_bools)] +#[derive(Serialize, Deserialize, Clone)] +pub(super) struct UrlParsingSettings { + pub(super) use_protocol : bool, + pub(super) use_userinfo : bool, + pub(super) use_subdomains : bool, + pub(super) use_domain : bool, + pub(super) use_port_path : bool, + pub(super) use_undefined_as_protocol_fallback : bool +} + +impl std::default::Default for UrlParsingSettings { + fn default() -> Self { + Self { + use_protocol: false, + use_userinfo : false, + use_subdomains: false, + use_domain: true, + use_port_path: false , + use_undefined_as_protocol_fallback: true + } + } +} + +#[derive(Serialize, Deserialize, Clone)] +pub(crate) struct GenerationSettings { + pub(crate) password_length : u32, + pub(crate) username : String, + pub(crate) modifier : String, + pub(crate) characters : String, + pub(crate) prefix : String, + pub(crate) suffix : String, + pub(crate) hash_algorithm : HashAlgorithm, + pub(crate) leet : UseLeetWhenGenerating, +} + +impl std::default::Default for GenerationSettings { + fn default() -> Self { + Self { + leet : UseLeetWhenGenerating::NotAtAll, + hash_algorithm : HashAlgorithm::Md5, + password_length : 8, + username : String::new(), + modifier : String::new(), + characters : String::from( + r#"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789`~!@#$%^&*()_-+={}|[]\:";'<>?,./"# + ), + prefix : String::new(), + suffix : String::new(), + } + } +} + +#[derive(Serialize, Deserialize, Clone, Default)] +struct ProfileSettings { + generation_settings : Arc, + url_parsing_settings : UrlParsingSettings, +} + +#[derive(Serialize, Deserialize)] +struct Profile { + name : String, + settings : ProfileSettings, +} + +#[derive(Serialize, Deserialize)] +struct StoredProfiles { + profiles : Vec, +} + +impl std::default::Default for StoredProfiles { + fn default() -> Self { + Self { + profiles : vec![Profile { + name : String::from("Default"), + settings : ProfileSettings::default() + }] + } + } +} + +impl StoredProfiles { + fn load() -> Result { + super::get_config_folder() + .map(|p| p.join("profiles")) + .ok_or(LoadError::Xdg) + .and_then(|p| std::fs::read_to_string(p).map_err(Into::into)) + .and_then(|s| toml::from_str(&s).map_err(Into::into)) + } + + fn store(&self) -> Result<(), StoreError> { + toml::to_string(self).map_err(Into::into) + .and_then(|s| Self::write_serialized_profile_data(&s)) + } + fn write_serialized_profile_data(data : &str) -> Result<(), StoreError> { + super::get_config_folder() + .ok_or(StoreError::Xdg) + .and_then(|p| std::fs::create_dir_all(p.clone()).map_err(Into::into).map(|()| p)) + .map(|p| p.join("profiles")) + .and_then(|f| std::fs::write(f, data).map_err(Into::into)) + } +} + +pub struct Profiles { + emit : ProfilesEmitter, + model : ProfilesList, + + data : RefCell, + + current_profile_idx : RefCell, +} + +#[derive(Clone, Debug)] +pub(crate) enum ProfileAccessError { + CurrentProfileOutOfBounds +} +impl Display for ProfileAccessError{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ProfileAccessError::CurrentProfileOutOfBounds => write!(f, "error reading current profile, please reselect profile") + } + } +} +impl std::error::Error for ProfileAccessError{} + +impl Profiles { + fn notify_current_profile_changed(&self) { + self.emit().current_profile_changed(); + self.emit().current_profile_name_changed(); + } + + fn model(&self) -> &ProfilesList { + #[cfg(debug_assertions)] + assert!(!self.is_any_borrowed()); + &self.model + } + + pub(super) fn do_with_current_url_parsing_settings(&self, operation : F) -> Result + where F : FnOnce(&UrlParsingSettings) -> R + { + self.data.borrow().profiles.get(*self.current_profile_idx.borrow()) + .map(|p| &p.settings.url_parsing_settings).map(operation) + .ok_or(ProfileAccessError::CurrentProfileOutOfBounds) + } + + pub(crate) fn get_copy_current_generation_settings(&self) -> Result, ProfileAccessError> { + self.data.borrow().profiles.get(*self.current_profile_idx.borrow()) + .map(|p| p.settings.generation_settings.clone()) + .ok_or(ProfileAccessError::CurrentProfileOutOfBounds) + } + + #[cfg(debug_assertions)] + fn is_any_borrowed(&self) -> bool { + self.data.try_borrow_mut().is_err() || + self.current_profile_idx.try_borrow_mut().is_err() + } +} + +impl ProfilesTrait for Profiles { + fn new(emit : ProfilesEmitter, model : ProfilesList) -> Self { + let data = RefCell::new(StoredProfiles::load().unwrap_or_default()); + Profiles { emit, model, data, current_profile_idx : RefCell::new(0) } + } + fn emit(&self) -> &ProfilesEmitter { + #[cfg(debug_assertions)] + assert!(!self.is_any_borrowed()); + &self.emit + } + + fn row_count(&self) -> usize { + self.data.borrow().profiles.len() + } + fn insert_rows(&self, row : usize, count : usize) -> bool { + let max_index = self.row_count(); + let valid_index = row <= self.data.borrow().profiles.len(); + if valid_index { + self.model().begin_insert_rows(row, row + count - 1); + let new_profiles = (0..count) + .map(|c| format!("New Profile {}", max_index + c)) + .map(|name| Profile { name, settings : ProfileSettings::default() }); + self.data.borrow_mut().profiles.splice(row..row,new_profiles); + self.model().end_insert_rows(); + //if the current profile is at or beyond row, we need to update it, so it still points + //to the same profile + if *self.current_profile_idx.borrow() >= row { + *self.current_profile_idx.borrow_mut() += count; + //no need to notify about field changes - those are still the same. Only the + //internal index changed. + self.emit().current_profile_changed(); + } + } + valid_index + } + fn remove_rows(&self, row : usize, count : usize) -> bool { + let valid_index = row + count <= self.row_count(); + let at_least_one_left = self.row_count() > count; + let operation_allowed = valid_index && at_least_one_left; + if operation_allowed { + self.model().begin_remove_rows(row, row + count - 1); + self.data.borrow_mut().profiles.drain(row..(row+count)); + //we have to update the current profile index before we complete the row removal. + //this is to ensure the index is not going invalid. + //here we can have a destructive operation: the current index might have been removed. + //in that case set it to 0. + let current_idx = *self.current_profile_idx.borrow(); + if row + count <= current_idx { + *self.current_profile_idx.borrow_mut() -= count; + //same as in the insert_rows case here: The field values are still the same, only + //the index changed. + self.emit().current_profile_changed(); + } + else if row <= current_idx { + *self.current_profile_idx.borrow_mut() = 0; + self.notify_current_profile_changed(); + } + self.model().end_remove_rows(); + } + operation_allowed + } + + fn characters(&self, index : usize, setter : F) { + setter(self.data.borrow().profiles.get(index) + .map(|p| &*p.settings.generation_settings.characters) + .unwrap_or_default()); + } + fn set_characters(&self, index : usize, val : String) -> bool { + if let Some(chars) = self.data.borrow_mut().profiles.get_mut(index) + .map(|p| &mut Arc::make_mut(&mut p.settings.generation_settings).characters) + .filter(|c| **c != val) { + *chars = val; + true + } else { + false + } + } + fn hash_algorithm(&self, index : usize) -> u8 { + self.data.borrow().profiles.get(index) + .map(|p| p.settings.generation_settings.hash_algorithm.into()) + .unwrap_or_default() + } + fn set_hash_algorithm(&self, index : usize, val : u8) -> bool { + let hash = val.try_into().ok(); + let mut borrow = self.data.borrow_mut(); + let profile = borrow.profiles.get_mut(index); + if let Some((p, h)) = profile.zip(hash) + .map(|(p, h)| (&mut Arc::make_mut(&mut p.settings.generation_settings).hash_algorithm, h)) + .filter(|(p, h)| *p != h) { + *p = h; + true + } else { + false + } + } + fn leet_level(&self, index : usize) -> u8 { + self.data.borrow().profiles.get(index) + .and_then(|p| p.settings.generation_settings.leet.get_leet_level()) + .map(Into::into) + .unwrap_or_default() + } + fn set_leet_level(&self, index : usize, level : u8) -> bool { + let level = level.try_into().ok(); + let mut borrow = self.data.borrow_mut(); + let profile = borrow.profiles.get_mut(index); + if let Some((p, l)) = profile.zip(level) + .map(|(p,l)| (&mut Arc::make_mut(&mut p.settings.generation_settings).leet, l)) + .filter(|(p,l)| p.get_leet_level() != Some(*l)) { + p.set_leet_level(l).is_ok() + } else { + false + } + } + fn modifier(&self, index : usize, setter : F) { + setter(self.data.borrow().profiles.get(index) + .map(|p| &*p.settings.generation_settings.modifier) + .unwrap_or_default()); + } + fn set_modifier(&self, index : usize, val : String) -> bool { + if let Some(m) = self.data.borrow_mut().profiles.get_mut(index) + .map(|p| &mut Arc::make_mut(&mut p.settings.generation_settings).modifier) + .filter(|m| **m != val) { + *m = val; + true + } else { + false + } + } + fn name(&self, index : usize, setter : F) { + setter(self.data.borrow().profiles.get(index) + .map_or("profile index invalid",|p| &*p.name)); + } + fn set_name(&self, index : usize, val : String) -> bool { + let changed = { + if let Some(n) = self.data.borrow_mut().profiles.get_mut(index) + .map(|p| &mut p.name) + .filter(|n| **n != val) { + *n = val; + true + } else { + false + } + }; + if changed && index == *self.current_profile_idx.borrow() { + self.emit().current_profile_name_changed(); + } + changed + } + fn password_length(&self, index : usize) -> u32 { + self.data.borrow().profiles.get(index) + .map(|p| p.settings.generation_settings.password_length) + .unwrap_or_default() + } + fn set_password_length(&self, index : usize, len : u32) -> bool { + if let Some(pl) = self.data.borrow_mut().profiles.get_mut(index) + .map(|p| &mut Arc::make_mut(&mut p.settings.generation_settings).password_length) + .filter(|pl| **pl != len) { + *pl = len; + true + } else { + false + } + } + fn prefix(&self, index : usize, setter : F) { + setter(self.data.borrow().profiles.get(index) + .map(|p| &*p.settings.generation_settings.prefix) + .unwrap_or_default()); + } + fn set_prefix(&self, index : usize, pref : String) -> bool { + if let Some(p) = self.data.borrow_mut().profiles.get_mut(index) + .map(|p| &mut Arc::make_mut(&mut p.settings.generation_settings).prefix) + .filter(|p| **p != pref) { + *p = pref; + true + } else { + false + } + } + fn suffix(&self, index : usize, setter : F) { + setter(self.data.borrow_mut().profiles.get(index) + .map(|p| &*p.settings.generation_settings.suffix) + .unwrap_or_default()); + } + fn set_suffix(&self, index : usize, suf : String) -> bool { + if let Some(s) = self.data.borrow_mut().profiles.get_mut(index) + .map(|p| &mut Arc::make_mut(&mut p.settings.generation_settings).suffix) + .filter(|s| **s != suf) { + *s=suf; + true + } else { + false + } + } + fn use_domain(&self, index : usize) -> bool { + self.data.borrow().profiles.get(index) + .map_or(true,|p| p.settings.url_parsing_settings.use_domain) + } + fn set_use_domain(&self, index : usize, val : bool) -> bool { + if let Some(d) = self.data.borrow_mut().profiles.get_mut(index) + .map(|p| &mut p.settings.url_parsing_settings.use_domain) + .filter(|d| **d != val) { + *d = val; + true + } else { + false + } + } + fn use_leet(&self, index : usize) -> u8 { + self.data.borrow().profiles.get(index) + .map(|p| match p.settings.generation_settings.leet { + UseLeetWhenGenerating::NotAtAll => { 0 } + UseLeetWhenGenerating::Before{..} => { 1 } + UseLeetWhenGenerating::After {..} => { 2 } + UseLeetWhenGenerating::BeforeAndAfter{..} => { 3 } + }) + .unwrap_or_default() + } + fn set_use_leet(&self, index : usize, l : u8) -> bool { + let mut borrow = self.data.borrow_mut(); + let profile = borrow.profiles.get_mut(index); + let level = |profile : Option<&Profile>| { + profile.as_ref().and_then(|p| p.settings.generation_settings.leet.get_leet_level()) + .unwrap_or(LeetLevel::One) + }; + let use_leet = match l { + 0 => { Some(UseLeetWhenGenerating::NotAtAll) } + 1 => { Some(UseLeetWhenGenerating::Before { level : level(profile.as_deref()) }) } + 2 => { Some(UseLeetWhenGenerating::After { level : level(profile.as_deref()) }) } + 3 => { Some(UseLeetWhenGenerating::BeforeAndAfter{ level : level(profile.as_deref()) }) } + _ => { None } + }; + if let Some((p, l)) = profile.zip(use_leet) + .map(|(p,l)| (&mut Arc::make_mut(&mut p.settings.generation_settings).leet, l)) + .filter(|(p,l)| *p != l) { + *p = l; + true + } else { + false + } + } + fn use_port_path(&self, index : usize) -> bool { + self.data.borrow().profiles.get(index) + .map(|p| p.settings.url_parsing_settings.use_port_path) + .unwrap_or_default() + } + fn set_use_port_path(&self, index : usize, val : bool) -> bool { + if let Some(pp) = self.data.borrow_mut().profiles.get_mut(index) + .map(|p| &mut p.settings.url_parsing_settings.use_port_path) + .filter(|pp| **pp != val) { + *pp = val; + true + } else { + false + } + } + fn use_protocol(&self, index : usize) -> bool { + self.data.borrow().profiles.get(index) + .map(|p| p.settings.url_parsing_settings.use_protocol) + .unwrap_or_default() + } + fn set_use_protocol(&self, index : usize, val : bool) -> bool { + if let Some(pr) = self.data.borrow_mut().profiles.get_mut(index) + .map(|p| &mut p.settings.url_parsing_settings.use_protocol) + .filter(|pr| **pr != val) { + *pr = val; + true + } else { + false + } + } + fn use_subdomains(&self, index : usize) -> bool { + self.data.borrow().profiles.get(index) + .map(|p| p.settings.url_parsing_settings.use_subdomains) + .unwrap_or_default() + } + fn set_use_subdomains(&self, index : usize, val : bool) -> bool { + if let Some(sd) = self.data.borrow_mut().profiles.get_mut(index) + .map(|p| &mut p.settings.url_parsing_settings.use_subdomains) + .filter(|sd| **sd != val) { + *sd = val; + true + } else { + false + } + } + fn username(&self, index : usize, setter : F) { + setter(self.data.borrow().profiles.get(index) + .map(|p| &*p.settings.generation_settings.username) + .unwrap_or_default()); + } + fn set_username(&self, index : usize, name : String) -> bool { + if let Some(u) = self.data.borrow_mut().profiles.get_mut(index) + .map(|p| &mut Arc::make_mut(&mut p.settings.generation_settings).username) + .filter(|u| **u != name) { + *u = name; + true + } else { + false + } + } + + #[allow(clippy::cast_possible_truncation)] + fn current_profile(&self) -> u32 { + *self.current_profile_idx.borrow() as u32 + } + fn set_current_profile(&self, value : u32) { + let valid_index = self.row_count() > value as usize; + let does_change = *self.current_profile_idx.borrow() != value as usize; + if valid_index && does_change { + *self.current_profile_idx.borrow_mut() = value as usize; + self.notify_current_profile_changed(); + } + } + fn current_profile_name(&self, setter : F) { + self.name(*self.current_profile_idx.borrow(), setter); + } + + fn store(&self) -> bool { + self.data.borrow().store() + .map_err(|e| println!("{}", e)) + .is_ok() + } + + fn use_undefined_as_protocol_fallback(&self, index: usize) -> bool { + self.data.borrow().profiles.get(index) + .map(|p| p.settings.url_parsing_settings.use_undefined_as_protocol_fallback) + .unwrap_or_default() + } + + fn set_use_undefined_as_protocol_fallback(&self, index: usize, val: bool) -> bool { + if let Some(sd) = self.data.borrow_mut().profiles.get_mut(index) + .map(|p| &mut p.settings.url_parsing_settings.use_undefined_as_protocol_fallback) + .filter(|sd| **sd != val) { + *sd = val; + true + } else { + false + } + } + + fn use_user_info(&self, index: usize) -> bool { + self.data.borrow().profiles.get(index) + .map(|p| p.settings.url_parsing_settings.use_userinfo) + .unwrap_or_default() + } + + fn set_use_user_info(&self, index: usize, val: bool) -> bool { + if let Some(sd) = self.data.borrow_mut().profiles.get_mut(index) + .map(|p| &mut p.settings.url_parsing_settings.use_userinfo) + .filter(|sd| **sd != val) { + *sd = val; + true + } else { + false + } + } +} + +#[cfg(test)] +mod profiles_tests { + use super::*; + fn get_test_profiles_three_entries(emit : ProfilesEmitter, model : ProfilesList) -> Profiles { + Profiles { + emit, + model, + data : RefCell::new( StoredProfiles { + profiles : vec![ + Profile { + name : String::from("First"), + settings : ProfileSettings::default(), + }, + Profile { + name : String::from("Second"), + settings : ProfileSettings::default(), + }, + Profile { + name : String::from("Third"), + settings : ProfileSettings::default(), + }, + ], + }), + current_profile_idx : RefCell::new(1), + } + } + #[test] + fn hash_algo_reciprocicity_test() { + for i in 0..(HashAlgorithm::variant_count() as u8) { + let x : HashAlgorithm = i.try_into().unwrap(); + let y = x.into(); + assert_eq!(i,y); + } + let z: Result = (HashAlgorithm::variant_count() as u8).try_into(); + assert!(z.is_err()); + } + #[test] + fn leet_level_reciprocicity_test() { + for i in 1..=(LeetLevel::variant_count() as u8) { + let x : LeetLevel = i.try_into().unwrap(); + let y = x.into(); + assert_eq!(i,y); + } + let z : Result = (1+LeetLevel::variant_count() as u8).try_into(); + assert!(z.is_err()); + let a : Result = 0.try_into(); + assert!(a.is_err()); + } + #[test] + fn set_name_test() { + let (emit_nothing, model_doesnt_emit) = expect_no_emission(); + let profiles_assert_on_emit = get_test_profiles_three_entries(emit_nothing, model_doesnt_emit); + //profiles_assert_on_emit has profile 1 set as active. We can therefore modify the name of 0 (or 2) without emission. + let should_change = profiles_assert_on_emit.set_name(0, "SomethingIJustMadeUp".into()); + assert!(should_change); + profiles_assert_on_emit.name(0, |x| {assert_eq!(x, "SomethingIJustMadeUp")}); + } + #[test] + fn set_current_profile_test() { + { + //setting to same index should not do anything. + let (emit_nothing, model_doesnt_emit) = expect_no_emission(); + let profiles_assert_on_emit = get_test_profiles_three_entries(emit_nothing, model_doesnt_emit); + profiles_assert_on_emit.set_current_profile(profiles_assert_on_emit.current_profile()); + } + { + //setting to different index should emit both name and index changed. + let mut emit = ProfilesEmitter::new(); + emit.expect_clone().never(); + emit.expect_current_profile_changed().return_once(||{()}).once(); + emit.expect_current_profile_name_changed().return_once(||{()}).once(); + emit.expect_new_data_ready().never(); + let mut list = ProfilesList::new(); + list.expect_layout_about_to_be_changed().never(); + list.expect_layout_changed().never(); + list.expect_data_changed().never(); + list.expect_begin_reset_model().never(); + list.expect_end_reset_model().never(); + list.expect_begin_insert_rows().never(); + list.expect_end_insert_rows().never(); + list.expect_begin_move_rows().never(); + list.expect_end_move_rows().never(); + list.expect_begin_remove_rows().never(); + list.expect_end_remove_rows().never(); + let profiles_wants_one_name_change = get_test_profiles_three_entries(emit, list); + let previous_profile = profiles_wants_one_name_change.current_profile(); + profiles_wants_one_name_change.set_current_profile(previous_profile + 1); + assert_eq!(previous_profile + 1, profiles_wants_one_name_change.current_profile()); + } + { + //Last, but not least, even if the _name_ of the profiles is the same, we WANT to emit current_profile_name_changed. + //This is because the UI is also functioning as Controller, so every profile change (even if it's the same name) should refresh the + //corresponding UI. + + let mut emit = ProfilesEmitter::new(); + emit.expect_clone().never(); + emit.expect_current_profile_changed().return_once(||{()}).once(); + emit.expect_current_profile_name_changed().return_once(||{()}).once(); + emit.expect_new_data_ready().never(); + let mut list = ProfilesList::new(); + list.expect_layout_about_to_be_changed().never(); + list.expect_layout_changed().never(); + list.expect_data_changed().never(); + list.expect_begin_reset_model().never(); + list.expect_end_reset_model().never(); + list.expect_begin_insert_rows().never(); + list.expect_end_insert_rows().never(); + list.expect_begin_move_rows().never(); + list.expect_end_move_rows().never(); + list.expect_begin_remove_rows().never(); + list.expect_end_remove_rows().never(); + let p = Profiles { + emit, + model :list, + data : RefCell::new( StoredProfiles { + profiles : vec![ + Profile { + name : String::from("Twin"), + settings : ProfileSettings::default(), + }, + Profile { + name : String::from("Twin"), + settings : ProfileSettings::default(), + }, + ], + }), + current_profile_idx : RefCell::new(1), + }; + p.set_current_profile(0); + assert_eq!(p.current_profile(), 0); + } + } + #[test] + fn current_profile_name_test() { + //let's check if setting the same value we already have emits. Hopefully not. Set the index 1 profile's name to "Second" + { + let (emit_nothing, model_doesnt_emit) = expect_no_emission(); + let profiles_assert_on_emit = get_test_profiles_three_entries(emit_nothing, model_doesnt_emit); + let should_not_change = profiles_assert_on_emit.set_name(1,"Second".into()); + assert!(!should_not_change); + } + + let mut emit = ProfilesEmitter::new(); + emit.expect_clone().never(); + emit.expect_current_profile_changed().never(); + emit.expect_current_profile_name_changed().return_once(||{()}).once(); + emit.expect_new_data_ready().never(); + let mut list = ProfilesList::new(); + list.expect_layout_about_to_be_changed().never(); + list.expect_layout_changed().never(); + list.expect_data_changed().never(); + list.expect_begin_reset_model().never(); + list.expect_end_reset_model().never(); + list.expect_begin_insert_rows().never(); + list.expect_end_insert_rows().never(); + list.expect_begin_move_rows().never(); + list.expect_end_move_rows().never(); + list.expect_begin_remove_rows().never(); + list.expect_end_remove_rows().never(); + let profiles_wants_one_name_change = get_test_profiles_three_entries(emit, list); + let should_change2 = profiles_wants_one_name_change.set_name(1, "Agadlagugu".into()); + assert!(should_change2); + profiles_wants_one_name_change.name(1, |x| {assert_eq!(x, "Agadlagugu")}); + } + #[test] + fn insert_rows_before_current_test() { + //if we add a row before the current, only, current_profile should emit. No need to emit profile_name, as the data is still the same. + let mut emit = ProfilesEmitter::new(); + emit.expect_clone().never(); + emit.expect_current_profile_changed().return_once(||{()}).once(); + emit.expect_current_profile_name_changed().never(); + emit.expect_new_data_ready().never(); + let mut list = ProfilesList::new(); + list.expect_layout_about_to_be_changed().never(); + list.expect_layout_changed().never(); + list.expect_data_changed().never(); + list.expect_begin_reset_model().never(); + list.expect_end_reset_model().never(); + list.expect_begin_insert_rows().return_once(|_,_|{()}).once(); + list.expect_end_insert_rows().return_once(||{()}).once(); + list.expect_begin_move_rows().never(); + list.expect_end_move_rows().never(); + list.expect_begin_remove_rows().never(); + list.expect_end_remove_rows().never(); + //must make sure that index changes, but data remains same + let profile_tests_insert_before_current = get_test_profiles_three_entries(emit, list); + let get_current_name_copy = || { let mut res = String::new(); profile_tests_insert_before_current.current_profile_name(|x| res = String::from(x)); res}; + let old_name = get_current_name_copy(); + let old_index = profile_tests_insert_before_current.current_profile(); + let worked = profile_tests_insert_before_current.insert_rows(1, 1); + assert!(worked); + let new_name = get_current_name_copy(); + let new_index = profile_tests_insert_before_current.current_profile(); + assert_eq!(old_name, new_name); + assert_eq!(new_index, old_index +1); + } + #[test] + fn insert_rows_after_current_test() { + //if we add a row after current, no signals other than insert_rows related ones should emit: + //if we add a row before the current, only, current_profile should emit. No need to emit profile_name, as the data is still the same. + let mut emit = ProfilesEmitter::new(); + emit.expect_clone().never(); + emit.expect_current_profile_changed().never(); + emit.expect_current_profile_name_changed().never(); + emit.expect_new_data_ready().never(); + let mut list = ProfilesList::new(); + list.expect_layout_about_to_be_changed().never(); + list.expect_layout_changed().never(); + list.expect_data_changed().never(); + list.expect_begin_reset_model().never(); + list.expect_end_reset_model().never(); + list.expect_begin_insert_rows().return_once(|_,_|{()}).once(); + list.expect_end_insert_rows().return_once(||{()}).once(); + list.expect_begin_move_rows().never(); + list.expect_end_move_rows().never(); + list.expect_begin_remove_rows().never(); + list.expect_end_remove_rows().never(); + //must make sure that index changes, but data remains same + let profile_tests_insert_before_current = get_test_profiles_three_entries(emit, list); + let get_current_name_copy = || { let mut res = String::new(); profile_tests_insert_before_current.current_profile_name(|x| res = String::from(x)); res}; + let old_name = get_current_name_copy(); + let old_index = profile_tests_insert_before_current.current_profile(); + let worked = profile_tests_insert_before_current.insert_rows(3, 1); + assert!(worked); + let new_name = get_current_name_copy(); + let new_index = profile_tests_insert_before_current.current_profile(); + assert_eq!(old_name, new_name); + assert_eq!(new_index, old_index); + } + #[test] + fn remove_rows_before_current_test() { + //if we remove a row before the current, only current_profile should emit. No need to emit profile_name, as the data is still the same. + let mut emit = ProfilesEmitter::new(); + emit.expect_clone().never(); + emit.expect_current_profile_changed().return_once(||{()}).once(); + emit.expect_current_profile_name_changed().never(); + emit.expect_new_data_ready().never(); + let mut list = ProfilesList::new(); + list.expect_layout_about_to_be_changed().never(); + list.expect_layout_changed().never(); + list.expect_data_changed().never(); + list.expect_begin_reset_model().never(); + list.expect_end_reset_model().never(); + list.expect_begin_insert_rows().never(); + list.expect_end_insert_rows().never(); + list.expect_begin_move_rows().never(); + list.expect_end_move_rows().never(); + list.expect_begin_remove_rows().return_once(|_,_|{()}).once(); + list.expect_end_remove_rows().return_once(||{()}).once(); + //must make sure that index changes, but data remains same + let profile_tests_remove_before_current = get_test_profiles_three_entries(emit, list); + let get_current_name_copy = || { let mut res = String::new(); profile_tests_remove_before_current.current_profile_name(|x| res = String::from(x)); res}; + let old_name = get_current_name_copy(); + let old_index = profile_tests_remove_before_current.current_profile(); + let worked = profile_tests_remove_before_current.remove_rows(0, 1); + assert!(worked); + let new_name = get_current_name_copy(); + let new_index = profile_tests_remove_before_current.current_profile(); + assert_eq!(old_name, new_name); + assert_eq!(new_index, old_index - 1); + } + #[test] + fn remove_rows_containign_current_test() { + //if we remove the current row, both, index and name will change and index should be zero. + let mut emit = ProfilesEmitter::new(); + emit.expect_clone().never(); + emit.expect_current_profile_changed().return_once(||{()}).once(); + emit.expect_current_profile_name_changed().return_once(||{()}).once(); + emit.expect_new_data_ready().never(); + let mut list = ProfilesList::new(); + list.expect_layout_about_to_be_changed().never(); + list.expect_layout_changed().never(); + list.expect_data_changed().never(); + list.expect_begin_reset_model().never(); + list.expect_end_reset_model().never(); + list.expect_begin_insert_rows().never(); + list.expect_end_insert_rows().never(); + list.expect_begin_move_rows().never(); + list.expect_end_move_rows().never(); + list.expect_begin_remove_rows().return_once(|_,_|{()}).once(); + list.expect_end_remove_rows().return_once(||{()}).once(); + //must make sure that index changes, but data remains same + let profile_tests_remove_current = get_test_profiles_three_entries(emit, list); + let get_current_name_copy = || { let mut res = String::new(); profile_tests_remove_current.current_profile_name(|x| res = String::from(x)); res}; + let old_name = get_current_name_copy(); + let old_index = profile_tests_remove_current.current_profile(); + let worked = profile_tests_remove_current.remove_rows(1, 1); + assert!(worked); + let new_name = get_current_name_copy(); + let new_index = profile_tests_remove_current.current_profile(); + assert_ne!(old_name, new_name); + assert_eq!(new_index, 0); + assert_ne!(new_index, old_index); + } + #[test] + fn remove_rows_after_current_test() { + //only remove rows emit, no profile related emit + let mut emit = ProfilesEmitter::new(); + emit.expect_clone().never(); + emit.expect_current_profile_changed().never(); + emit.expect_current_profile_name_changed().never(); + emit.expect_new_data_ready().never(); + let mut list = ProfilesList::new(); + list.expect_layout_about_to_be_changed().never(); + list.expect_layout_changed().never(); + list.expect_data_changed().never(); + list.expect_begin_reset_model().never(); + list.expect_end_reset_model().never(); + list.expect_begin_insert_rows().never(); + list.expect_end_insert_rows().never(); + list.expect_begin_move_rows().never(); + list.expect_end_move_rows().never(); + list.expect_begin_remove_rows().return_once(|_,_|{()}).once(); + list.expect_end_remove_rows().return_once(||{()}).once(); + //must make sure that index changes, but data remains same + let profile_tests_remove_after_current = get_test_profiles_three_entries(emit, list); + let get_current_name_copy = || { let mut res = String::new(); profile_tests_remove_after_current.current_profile_name(|x| res = String::from(x)); res}; + let old_name = get_current_name_copy(); + let old_index = profile_tests_remove_after_current.current_profile(); + let worked = profile_tests_remove_after_current.remove_rows(2, 1); + assert!(worked); + let new_name = get_current_name_copy(); + let new_index = profile_tests_remove_after_current.current_profile(); + assert_eq!(old_name, new_name); + assert_eq!(new_index, old_index); + } + // Leet level needs its own test, as setting leet level depends on use_leet. + #[test] + fn set_leet_level_test() { + let (emit, model) = expect_no_emission(); + //we're not going through the new() method, to avoid touching the filesystem. + let profiles = get_test_profiles_three_entries(emit, model); + + //remember the old values in other rows to confimr they aren't touched + let oldvals = [profiles.leet_level(0), profiles.leet_level(2)]; + + //make sure that leet isn't enabled. + assert_eq!(profiles.use_leet(1), u8::default()); + //make sure we are changing something. + assert_ne!(profiles.leet_level(1), 2); + //assure that nothing happens if we try to change leet level with leet disabled. + let changed = profiles.set_leet_level(1,2); + assert!(!changed); + assert_eq!(profiles.use_leet(1), u8::default()); + + //enable leet. + let changed = profiles.set_use_leet(1,1); + assert!(changed); + //make sure we are changing something. + assert_ne!(profiles.leet_level(1), 2); + //actually change something: + let changed = profiles.set_leet_level(1,2); + assert!(changed); + assert_eq!(profiles.leet_level(1), 2); + //check that other values are unaffected + assert_eq!(profiles.leet_level(0), oldvals[0]); + assert_eq!(profiles.leet_level(2), oldvals[1]); + //IMPORTANT: check that assigning the same value returns not-changed. + let changed = profiles.set_leet_level(1,2); + assert!(!changed); + //disable leet again and make sure leet_level returns 0 again. + let changed = profiles.set_use_leet(1,0); + assert!(changed); + assert_eq!(profiles.leet_level(1), u8::default()); + } + + // The remaining properties should (at the time of writing) not emit. + // This means we can use one and the same test for all of them :-) + // Except that complex types have a different getter syntax... + // Oh well. + fn expect_no_emission() -> (ProfilesEmitter, ProfilesList) { + let mut emit = ProfilesEmitter::new(); + emit.expect_clone().never(); + emit.expect_current_profile_changed().never(); + emit.expect_current_profile_name_changed().never(); + emit.expect_new_data_ready().never(); + let mut list = ProfilesList::new(); + list.expect_layout_about_to_be_changed().never(); + list.expect_layout_changed().never(); + list.expect_data_changed().never(); + list.expect_begin_reset_model().never(); + list.expect_end_reset_model().never(); + list.expect_begin_insert_rows().never(); + list.expect_end_insert_rows().never(); + list.expect_begin_move_rows().never(); + list.expect_end_move_rows().never(); + list.expect_begin_remove_rows().never(); + list.expect_end_remove_rows().never(); + (emit, list) + } + macro_rules! simple_setter_tests { + ( $( $test_name:ident, $setter:ident, $getter:ident, $val:literal ),* ) => { + $(#[test] + fn $test_name() { + let (emit, model) = expect_no_emission(); + //we're not going through the new() method, to avoid touching the filesystem. + let profiles = get_test_profiles_three_entries(emit, model); + + //remember the old values in other rows to confimr they aren't touched + let oldvals = [profiles.$getter(0), profiles.$getter(2)]; + + //make sure we are changing something. + assert_ne!(profiles.$getter(1), $val); + //assure that something changed + let changed = profiles.$setter(1,$val); + assert!(changed); + assert_eq!(profiles.$getter(1), $val); + //check that other values are unaffected + assert_eq!(profiles.$getter(0), oldvals[0]); + assert_eq!(profiles.$getter(2), oldvals[1]); + //IMPORTANT: check that assigning the same value returns not-changed. + let changed = profiles.$setter(1,$val); + assert!(!changed); + })* + }; + } + macro_rules! string_setter_tests { + ( $( $test_name:ident, $setter:ident, $getter:ident, $val:literal ),* ) => { + $(#[test] + fn $test_name() { + let (emit, model) = expect_no_emission(); + //we're not going through the new() method, to avoid touching the filesystem. + let profiles = get_test_profiles_three_entries(emit, model); + + //remember the old values in other rows to confirm they aren't touched. + let mut oldvals = [String::new(), String::new()]; + profiles.$getter(0, |x| { oldvals[0] = String::from(x); }); + profiles.$getter(2, |x| { oldvals[1] = String::from(x); }); + //make sure we are changing something. + profiles.$getter(1, |x| { assert_ne!(x, $val); }); + //assure that something changed: + let changed = profiles.$setter(1,String::from($val)); + assert!(changed); + profiles.$getter(1, |x| { assert_eq!(x, $val); }); + //check that the other values are unaffected. + profiles.$getter(0, |x| { assert_eq!(oldvals[0], x); }); + profiles.$getter(2, |x| { assert_eq!(oldvals[1], x); }); + //IMPORTANT: check that assigning the same value returns not-changed. + let changed = profiles.$setter(1,String::from($val)); + assert!(!changed); + })* + }; + } + string_setter_tests!( + set_characters_test,set_characters,characters,"ABCDEFG", + set_modifier_test, set_modifier, modifier, "ab", + set_prefix_test, set_prefix, prefix, "xy", + set_suffix_test, set_suffix, suffix, "zw", + set_username_test, set_username, username, "Zargothrax" + ); + simple_setter_tests!( + set_hash_algorithm_test, set_hash_algorithm, hash_algorithm, 5, + set_password_length_test, set_password_length, password_length, 12, + set_use_domain_test, set_use_domain, use_domain, false, + set_use_leet_test, set_use_leet, use_leet, 1, + set_use_port_path_test, set_use_port_path, use_port_path, true, + set_use_protocol_test, set_use_protocol, use_protocol, true, + set_use_subdomains_test, set_use_subdomains, use_subdomains, true, + set_use_user_info_test, set_use_user_info, use_user_info, true, + set_use_undefined_as_protocol_fallback_test, set_use_undefined_as_protocol_fallback, use_undefined_as_protocol_fallback, false + ); +} -- cgit v1.2.3