From 2b81393fc93ef4902730fe2cf1613a5f8b4a8d00 Mon Sep 17 00:00:00 2001 From: Niklas Kapelle Date: Thu, 12 Mar 2026 17:47:56 +0100 Subject: [PATCH] added unit tests --- src/file_conflict_solver.rs | 132 ++++++++++++++++++++++++++++++ src/types/game.rs | 6 ++ src/types/mod_file.rs | 2 +- src/types/modded_instance.rs | 65 +++++++++++++++ src/types/root_config.rs | 152 +++++++++++++++++++++++++++++------ 5 files changed, 333 insertions(+), 24 deletions(-) diff --git a/src/file_conflict_solver.rs b/src/file_conflict_solver.rs index 9e37533..0b8daa0 100644 --- a/src/file_conflict_solver.rs +++ b/src/file_conflict_solver.rs @@ -85,3 +85,135 @@ impl<'a> ConflictSolver<'a> { self.files.iter().map(|e| e.1.to_owned()).collect() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn no_conflict() { + let mut solver = ConflictSolver::new(); + + let mod1 = InstalledMod::new("mod1", 0); + let m1f1 = ModFile::new("Data/Plugin1.esp", "Data/Plugin1.esp", 0); + + let mod2 = InstalledMod::new("mod2", 0); + let m2f1 = ModFile::new("Data/Plugin2.esp", "Data/Plugin2.esp", 0); + + assert!(solver.add_file(&m1f1, &mod1).is_none()); + assert!(solver.add_file(&m2f1, &mod2).is_none()); + + let export = solver.export_files(); + assert_eq!(export.len(), 2); + assert!( + export + .iter() + .find(|e| e.1 == &mod1 && e.0 == &m1f1) + .is_some(), + "Missing mod1 file1" + ); + assert!( + export + .iter() + .find(|e| e.1 == &mod2 && e.0 == &m2f1) + .is_some(), + "Missing mod2 file1" + ); + } + + #[test] + fn conflict_same_mod_solved() { + let mut solver = ConflictSolver::new(); + + let mod1 = InstalledMod::new("mod1", 0); + let m1f1 = ModFile::new("Data/Plugin1.esp", "Data/Plugin.esp", 0); + let m1f2 = ModFile::new("Data/Plugin_alt.esp", "Data/Plugin.esp", 1); + + assert!(solver.add_file(&m1f1, &mod1).is_none()); + assert!(solver.add_file(&m1f2, &mod1).is_none()); + + let export = solver.export_files(); + assert_eq!(export.len(), 1); + assert!( + export + .iter() + .find(|e| e.1 == &mod1 && e.0 == &m1f2) + .is_some(), + "Missing mod1 file2" + ); + } + + #[test] + fn conflict_diff_mod_solved() { + let mut solver = ConflictSolver::new(); + + let mod1 = InstalledMod::new("mod1", 0); + let m1f1 = ModFile::new("Data/Plugin1.esp", "Data/Plugin.esp", 0); + + let mod2 = InstalledMod::new("mod2", 1); + let m2f1 = ModFile::new("Data/Plugin.esp", "Data/Plugin.esp", 0); + + assert!(solver.add_file(&m1f1, &mod1).is_none()); + assert!(solver.add_file(&m2f1, &mod2).is_none()); + + let export = solver.export_files(); + assert_eq!(export.len(), 1); + assert!( + export + .iter() + .find(|e| e.1 == &mod2 && e.0 == &m2f1) + .is_some(), + "Missing mod2 file1" + ); + } + + #[test] + fn conflict_same_mod() { + let mut solver = ConflictSolver::new(); + + let mod1 = InstalledMod::new("mod1", 0); + let m1f1 = ModFile::new("Data/Plugin1.esp", "Data/Plugin.esp", 0); + let m1f2 = ModFile::new("Data/Plugin_alt.esp", "Data/Plugin.esp", 0); + + assert!(solver.add_file(&m1f1, &mod1).is_none()); + let conflict = solver.add_file(&m1f2, &mod1); + + assert!(conflict.is_some()); + let unwraped = conflict.expect("Aserted before"); + + assert!( + unwraped.rhs_mod == unwraped.lhs_mod, + "Not same mod in conflict" + ); + assert!(unwraped.rhs_file != unwraped.lhs_file, "Files are the same"); + assert!( + unwraped.rhs_file == &m1f1 || unwraped.rhs_file == &m1f2, + "One file not found in conflict " + ); + } + + #[test] + fn conflict_diff_mod() { + let mut solver = ConflictSolver::new(); + + let mod1 = InstalledMod::new("mod1", 0); + let m1f1 = ModFile::new("Data/Plugin1.esp", "Data/Plugin.esp", 0); + + let mod2 = InstalledMod::new("mod2", 0); + let m2f1 = ModFile::new("Data/Plugin.esp", "Data/Plugin.esp", 0); + + assert!(solver.add_file(&m1f1, &mod1).is_none()); + let conflict = solver.add_file(&m2f1, &mod2); + + assert!(conflict.is_some()); + let unwraped = conflict.expect("Aserted before"); + + assert!(unwraped.rhs_mod != unwraped.lhs_mod, "Same mod in conflict"); + assert!(unwraped.rhs_file != unwraped.lhs_file, "Files are the same"); + assert!( + unwraped.rhs_file == &m1f1 || unwraped.lhs_file == &m1f1, + "One file not found in conflict " + ); + assert_eq!(unwraped.rhs_file.dst(), "Data/Plugin.esp"); + } +} diff --git a/src/types/game.rs b/src/types/game.rs index 0ce1675..61a827b 100644 --- a/src/types/game.rs +++ b/src/types/game.rs @@ -14,6 +14,12 @@ pub struct Game { } impl Game { + pub fn new(path: impl AsRef) -> Self { + Self { + path: path.as_ref().to_owned(), + } + } + pub fn export_links(&self) -> Result, io::Error> { let links: Vec = walk_all_files(&self.path)? .map(|entry| { diff --git a/src/types/mod_file.rs b/src/types/mod_file.rs index 28fb475..7568f27 100644 --- a/src/types/mod_file.rs +++ b/src/types/mod_file.rs @@ -7,7 +7,7 @@ use crate::{ utils::walk_all_files, }; -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, Deserialize, PartialEq, Eq)] pub struct ModFile { /// Relative path in the mod src: PathBuf, diff --git a/src/types/modded_instance.rs b/src/types/modded_instance.rs index 1ef0b37..7207c11 100644 --- a/src/types/modded_instance.rs +++ b/src/types/modded_instance.rs @@ -29,6 +29,22 @@ pub struct ModdedInstance { } impl ModdedInstance { + pub fn new( + game: &str, + mods: &[InstalledMod], + load_order: &[String], + overrides: &[Link], + self_path: impl AsRef, + ) -> Self { + Self { + game: game.to_owned(), + mods: mods.to_owned(), + load_order: load_order.to_owned(), + game_file_overrides: overrides.to_owned(), + self_path: self_path.as_ref().to_owned(), + } + } + pub fn load_from_file(path: impl AsRef) -> Result { debug!( "Loading ModdedInstance from file: {}", @@ -94,3 +110,52 @@ impl ModdedInstance { self.mods.iter().flat_map(|e| e.active_plugins()) } } + +#[cfg(test)] +mod tests { + use super::*; + + fn create_config() -> ModdedInstance { + ModdedInstance::new( + "sse", + &[InstalledMod::new("mod1", 0), InstalledMod::new("mod1", 0)], + &["Plugin1.esp".to_owned(), "Plugin2.esp".to_owned()], + &[Link::new("file1.txt", "file2.txt")], + "/config/instance.toml", + ) + } + + #[test] + fn basic_members() { + let cfg = create_config(); + + assert_eq!(cfg.game_file_overrides().len(), 1); + assert_eq!(cfg.mods().len(), 2); + assert_eq!(cfg.load_order().len(), 2); + } + + #[test] + fn add_mod() { + let mut cfg = create_config(); + + let new_mod = InstalledMod::new("mod3", 1); + + cfg.update_or_create_mod(&new_mod); + + let mods = cfg.mods(); + assert_eq!(mods.len(), 3); + + let found_mod = mods.iter().find(|e| e.mod_id() == "mod3"); + + assert!(found_mod.is_some()); + } + + #[test] + fn update_mod() { + let mut cfg = create_config(); + let new_mod = InstalledMod::new("mod1", 1); + + cfg.update_or_create_mod(&new_mod); + + } +} diff --git a/src/types/root_config.rs b/src/types/root_config.rs index 845d775..9b0a20b 100644 --- a/src/types/root_config.rs +++ b/src/types/root_config.rs @@ -52,25 +52,6 @@ impl RootConfig { .to_owned(); config.self_path = absolute; - if config.mod_location.is_relative() { - config.mod_location = config.self_parent.join(config.mod_location).to_owned(); - debug!( - "Resolved mod_location to absolue path: {}", - config.mod_location.to_string_lossy() - ); - } - - if let Some(dl_location) = &config.download_location - && dl_location.is_relative() - { - let dl_abs_path = config.self_parent.join(dl_location).to_owned(); - debug!( - "Resolve download_location to absolute path {}", - dl_abs_path.to_string_lossy() - ); - config.download_location = Some(dl_abs_path); - } - Ok(config) } @@ -106,16 +87,26 @@ impl RootConfig { } } - pub fn mod_location(&self) -> &Path { - &self.mod_location + pub fn mod_location(&self) -> PathBuf { + if self.mod_location.is_relative() { + self.self_parent.join(&self.mod_location) + } else { + self.mod_location.clone() + } } pub fn nexus_api_key(&self) -> Option<&str> { self.nexus_api_key.as_deref() } - pub fn download_location(&self) -> Option<&Path> { - self.download_location.as_deref() + pub fn download_location(&self) -> Option { + self.download_location.as_ref().map(|e| { + if e.is_relative() { + self.self_parent.join(e) + } else { + e.clone() + } + }) } } @@ -123,3 +114,118 @@ impl RootConfig { struct InstancePointer { path: PathBuf, } + +#[cfg(test)] +mod tests { + use super::*; + + fn create_config() -> RootConfig { + RootConfig { + games: HashMap::from([("sse".to_owned(), Game::new("/games/sse"))]), + mod_location: PathBuf::from("mods"), + download_location: Some(PathBuf::from("download")), + nexus_api_key: Some("1234".to_owned()), + instances: HashMap::from([( + "instance1".to_owned(), + InstancePointer { + path: PathBuf::from("instances/instance1.toml"), + }, + )]), + mods: HashMap::from([( + "mod1".to_owned(), + ModConfig::new("mod1", PathBuf::from("mod1")), + )]), + self_path: PathBuf::from("/config/root.toml"), + self_parent: PathBuf::from("/config"), + } + } + + #[test] + fn get_game() { + let cfg = create_config(); + + let game = cfg.game_by_id("sse"); + + assert!(game.is_some()); + + let unwraped = game.expect("Asserted before"); + assert_eq!(unwraped.install_location(), "/games/sse"); + } + + #[test] + fn get_missing_game() { + let cfg = create_config(); + + let game = cfg.game_by_id("starfield"); + + assert!(game.is_none()); + } + + #[test] + fn get_mod() { + let cfg = create_config(); + + let found_mod = cfg.mod_by_id("mod1"); + + assert!(found_mod.is_some()); + + let unwraped = found_mod.expect("Asserted before"); + assert_eq!(unwraped.path(), "mod1"); + } + + #[test] + fn get_missing_mod() { + let cfg = create_config(); + + let found_mod = cfg.mod_by_id("mod200"); + + assert!(found_mod.is_none()); + } + + #[test] + fn get_api_key() { + let cfg = create_config(); + + assert_eq!(cfg.nexus_api_key(), Some("1234")); + } + + #[test] + fn get_download_location() { + let cfg = create_config(); + + let dl = cfg.download_location(); + + assert!(dl.is_some()); + + let unwraped = dl.expect("Asserted before"); + + assert!(unwraped.is_absolute(), "Path not absolute"); + assert_eq!(unwraped, PathBuf::from("/config/download")); + } + + #[test] + fn get_mod_location() { + let cfg = create_config(); + + let mod_dir = cfg.mod_location(); + + assert!(mod_dir.is_absolute(), "Path not absolute"); + assert_eq!(mod_dir, PathBuf::from("/config/mods")); + } + + #[test] + fn add_mod() { + let mut cfg = create_config(); + + let new_mod = ModConfig::new("new_mod", "new_mod_path"); + + cfg.add_mod(&new_mod); + + let found_mod = cfg.mod_by_id("new_mod"); + + assert!(found_mod.is_some()); + let unwraped = found_mod.expect("Asserted before"); + + assert_eq!(unwraped.path(), new_mod.path()); + } +}