From d48602099718b16fe2d200cf3e768b36727fb8ee Mon Sep 17 00:00:00 2001 From: mohammad kermani Date: Sun, 17 Apr 2022 17:17:19 +0430 Subject: [PATCH] Add unit tests --- src/{config.rs => config/mod.rs} | 48 ++----- src/config/utils.rs | 207 +++++++++++++++++++++++++++++++ src/memory.rs | 8 +- 3 files changed, 224 insertions(+), 39 deletions(-) rename src/{config.rs => config/mod.rs} (60%) create mode 100644 src/config/utils.rs diff --git a/src/config.rs b/src/config/mod.rs similarity index 60% rename from src/config.rs rename to src/config/mod.rs index db105d5..6fe39a0 100644 --- a/src/config.rs +++ b/src/config/mod.rs @@ -4,6 +4,8 @@ use rand::prelude::*; use rand::distributions::WeightedIndex; use confy::{load, store}; +mod utils; + mod default_chance { pub const AJI: f64 = 50.0; pub const KI: f64 = 5.0; @@ -40,14 +42,9 @@ fn write_config(config: AkcConfig) { store(CONFIG_NAME, &config).unwrap_or_else(|_| panic!("Failed to write config file")); } -fn is_name_duplicate(name: &str) -> bool { - let config = read_config(); - config.friends.iter().any(| friend_info | friend_info.name == name) -} - fn add_friend(friend_info: FriendInfo) { let mut config = read_config(); - let is_duplicate = is_name_duplicate(&friend_info.name); + let is_duplicate = utils::is_name_duplicate(&config, &friend_info.name); if is_duplicate { println!("Name \"{}\" already exists, please use a different name", friend_info.name) @@ -83,17 +80,16 @@ pub fn add_chi(name: String) { pub fn suggest() { let config = read_config(); - let filtered_config: Vec<&FriendInfo> = config.friends.iter().filter(| friend_info | friend_info.chance > default_reduction::TEXT ).collect(); + let filtered_config = utils::filter_config_by_enough_chance(&config); let mut rng = thread_rng(); let weighted_dist = WeightedIndex::new(filtered_config.iter().map(| friend_info | friend_info.chance)).expect("Failed to suggest a friend"); println!("Suggested friend: {}", config.friends[weighted_dist.sample(&mut rng)].name); } -fn add_memory(reduction: f64, names: Vec) { +fn add_memory(reduction: f64, names: &[String]) { let mut config = read_config(); - let all_names = config.friends.iter().map(| friend_info | friend_info.name.clone()).collect::>(); - let unknown_names = names.iter().filter(| name | !all_names.iter().any(| inner_name | inner_name == *name )).collect::>(); + let unknown_names = utils::get_unknown_names(&config, names); if !unknown_names.is_empty() { let unknown_names_string = unknown_names.iter() @@ -103,46 +99,28 @@ fn add_memory(reduction: f64, names: Vec) { println!("The following names are not added yet: {}", unknown_names_string); } else { let total_reduction = reduction * names.len() as f64; - let current_total_chance = config.friends.iter() - .filter(| friend_info | !names.contains(&friend_info.name)) - .map(| friend_info | friend_info.chance) - .sum::(); + let current_total_chance = utils::get_config_total_chance(&config, names); let unit_added_chance = total_reduction / current_total_chance; - config.friends.iter_mut() - .filter(| friend_info | !names.contains(&friend_info.name)) - .for_each(| friend_info | { - let level_chance = match friend_info.level.as_str() { - "aji" => default_chance::AJI, - "ki" => default_chance::KI, - "chi" => default_chance::CHI, - _ => 0.0 - }; - friend_info.chance += level_chance * unit_added_chance; - }); - - config.friends.iter_mut() - .filter(| friend_info | names.contains(&friend_info.name)) - .for_each(| friend_info | { - friend_info.chance -= reduction; - }); + utils::increase_chances_by_unit(&mut config, unit_added_chance, names); + utils::decrease_chances_by_reduction(&mut config, reduction, names); write_config(config) } } -pub fn add_hangout(names: Vec) { +pub fn add_hangout(names: &[String]) { add_memory(default_reduction::HANGOUT, names) } -pub fn add_video_call(names: Vec) { +pub fn add_video_call(names: &[String]) { add_memory(default_reduction::VIDEO_CALL, names) } -pub fn add_call(names: Vec) { +pub fn add_call(names: &[String]) { add_memory(default_reduction::CALL, names) } -pub fn add_text(names: Vec) { +pub fn add_text(names: &[String]) { add_memory(default_reduction::TEXT, names) } diff --git a/src/config/utils.rs b/src/config/utils.rs new file mode 100644 index 0000000..d92e25d --- /dev/null +++ b/src/config/utils.rs @@ -0,0 +1,207 @@ +use super::{AkcConfig, FriendInfo, default_chance, default_reduction}; + +pub fn is_name_duplicate(config: &AkcConfig, name: &str) -> bool { + config.friends.iter().any(| friend_info | friend_info.name == name) +} + +pub fn filter_config_by_enough_chance(config: &AkcConfig) -> Vec<&FriendInfo> { + config.friends.iter().filter(| friend_info | friend_info.chance >= default_reduction::TEXT).collect() +} + +pub fn get_unknown_names<'a>(config: &AkcConfig, names: &'a[String]) -> Vec<&'a String> { + let all_names = config.friends.iter().map(| friend_info | friend_info.name.clone()).collect::>(); + names.iter().filter(| name | !all_names.iter().any(| inner_name | inner_name == *name )).collect::>() +} + +pub fn get_config_total_chance(config: &AkcConfig, excluded_names: &[String]) -> f64 { + config.friends.iter() + .filter(| friend_info | !excluded_names.contains(&friend_info.name)) + .map(| friend_info | friend_info.chance) + .sum::() +} + +pub fn increase_chances_by_unit(config: &mut AkcConfig, unit_added_chance: f64, excluded_names: &[String]) { + config.friends.iter_mut() + .filter(| friend_info | !excluded_names.contains(&friend_info.name)) + .for_each(| friend_info | { + let level_chance = match friend_info.level.as_str() { + "aji" => default_chance::AJI, + "ki" => default_chance::KI, + "chi" => default_chance::CHI, + _ => 0.0 + }; + friend_info.chance += level_chance * unit_added_chance; + }) +} + +pub fn decrease_chances_by_reduction(config: &mut AkcConfig, reduction: f64, names: &[String]) { + config.friends.iter_mut() + .filter(| friend_info | names.contains(&friend_info.name)) + .for_each(| friend_info | { + friend_info.chance -= reduction; + }) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_is_name_duplicate() { + let config = AkcConfig { + friends: vec![ + FriendInfo { + name: "John".to_owned(), + chance: default_chance::AJI, + level: "aji".to_owned() + }, + FriendInfo { + name: "Doe".to_owned(), + chance: default_chance::KI, + level: "ki".to_owned() + } + ] + }; + + assert!(is_name_duplicate(&config, "John")); + assert!(is_name_duplicate(&config, "Doe")); + assert!(!is_name_duplicate(&config, "John Doe")); + } + + #[test] + fn test_filter_config_by_enough_chance() { + let config = AkcConfig { + friends: vec![ + FriendInfo { + name: "John".to_owned(), + chance: default_chance::AJI, + level: "aji".to_owned() + }, + FriendInfo { + name: "Doe".to_owned(), + chance: default_reduction::TEXT, + level: "ki".to_owned() + }, + FriendInfo { + name: "Jane".to_owned(), + chance: 0.0, + level: "chi".to_owned() + } + ] + }; + + let filtered_config = filter_config_by_enough_chance(&config); + assert_eq!(filtered_config.len(), 2); + } + + #[test] + fn test_get_unknown_names() { + let config = AkcConfig { + friends: vec![ + FriendInfo { + name: "John".to_owned(), + chance: default_chance::AJI, + level: "aji".to_owned() + }, + FriendInfo { + name: "Doe".to_owned(), + chance: default_chance::KI, + level: "ki".to_owned() + } + ] + }; + + let names = vec![ + "John".to_owned(), + "Doe".to_owned(), + "Jane".to_owned() + ]; + let unknown_names = get_unknown_names(&config, &names); + + assert_eq!(unknown_names.len(), 1); + assert_eq!(unknown_names[0], "Jane"); + } + + #[test] + fn test_get_config_total_chance() { + let config = AkcConfig { + friends: vec![ + FriendInfo { + name: "John".to_owned(), + chance: default_chance::AJI, + level: "aji".to_owned() + }, + FriendInfo { + name: "Doe".to_owned(), + chance: default_chance::KI, + level: "ki".to_owned() + }, + FriendInfo { + name: "Doe2".to_owned(), + chance: default_chance::CHI, + level: "chi".to_owned() + }, + ] + }; + + let total_chance = get_config_total_chance(&config, &["Doe".to_owned()]); + assert_eq!(total_chance, default_chance::AJI + default_chance::CHI); + } + + #[test] + fn test_increase_chances_by_unit() { + let mut config = AkcConfig { + friends: vec![ + FriendInfo { + name: "John".to_owned(), + chance: default_chance::AJI, + level: "aji".to_owned() + }, + FriendInfo { + name: "Doe".to_owned(), + chance: default_chance::KI, + level: "ki".to_owned() + }, + FriendInfo { + name: "Doe2".to_owned(), + chance: default_chance::CHI, + level: "chi".to_owned() + }, + ] + }; + + increase_chances_by_unit(&mut config, 0.1, &["Doe".to_owned()]); + // println("{}", config.friends[0].chance); + assert_eq!(config.friends[0].chance, default_chance::AJI + 0.1 * default_chance::AJI); + assert_eq!(config.friends[1].chance, default_chance::KI); + assert_eq!(config.friends[2].chance, default_chance::CHI + 0.1 * default_chance::CHI); + } + + #[test] + fn test_decrease_chances_by_reduction() { + let mut config = AkcConfig { + friends: vec![ + FriendInfo { + name: "John".to_owned(), + chance: default_chance::AJI, + level: "aji".to_owned() + }, + FriendInfo { + name: "Doe".to_owned(), + chance: default_chance::KI, + level: "ki".to_owned() + }, + FriendInfo { + name: "Doe2".to_owned(), + chance: default_chance::CHI, + level: "chi".to_owned() + }, + ] + }; + + decrease_chances_by_reduction(&mut config, 1.0, &["Doe".to_owned()]); + assert_eq!(config.friends[0].chance, default_chance::AJI); + assert_eq!(config.friends[1].chance, default_chance::KI - 1.0); + assert_eq!(config.friends[2].chance, default_chance::CHI); + } +} \ No newline at end of file diff --git a/src/memory.rs b/src/memory.rs index 0f13de6..64ffccb 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -24,9 +24,9 @@ pub struct Memory { pub fn handle(args: Memory) { match args.command { - MemoryCommand::Hangout(names_wrapper) => config::add_hangout(names_wrapper.names), - MemoryCommand::VideoCall(names_wrapper) => config::add_video_call(names_wrapper.names), - MemoryCommand::Call(names_wrapper) => config::add_call(names_wrapper.names), - MemoryCommand::Text(names_wrapper) => config::add_text(names_wrapper.names), + MemoryCommand::Hangout(names_wrapper) => config::add_hangout(&names_wrapper.names), + MemoryCommand::VideoCall(names_wrapper) => config::add_video_call(&names_wrapper.names), + MemoryCommand::Call(names_wrapper) => config::add_call(&names_wrapper.names), + MemoryCommand::Text(names_wrapper) => config::add_text(&names_wrapper.names), } } \ No newline at end of file