From 94d771107d5ae3b2399faa9ab66badbd5522e6b4 Mon Sep 17 00:00:00 2001 From: 2ndbeam <2ndbeam@disroot.org> Date: Fri, 28 Nov 2025 17:00:17 +0300 Subject: [PATCH] test: Initial unit testing - Test quests vector loading - Test default quest on empty fields - Test correct quest - Fixed config paths handling --- src/config/mod.rs | 25 ++++++++++++++++++------- src/quest/mod.rs | 24 ++++++++++++++++-------- tests/cfg/config.toml | 10 ++++++++++ tests/cfg/quests/0.toml | 2 ++ tests/cfg/quests/1.toml | 8 ++++++++ tests/cfg/quests/2.toml | 8 ++++++++ tests/main.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 102 insertions(+), 15 deletions(-) create mode 100644 tests/cfg/config.toml create mode 100644 tests/cfg/quests/0.toml create mode 100644 tests/cfg/quests/1.toml create mode 100644 tests/cfg/quests/2.toml create mode 100644 tests/main.rs diff --git a/src/config/mod.rs b/src/config/mod.rs index 178bb79..45fccac 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,30 +1,32 @@ //! Configuration file that handles (de-)serializing other components -use std::{fs::{self, DirEntry},path::PathBuf}; +use std::{fs::{self, DirEntry},path::{Path, PathBuf}}; use serde::Deserialize; use crate::quest::{Quest,error::QuestError}; /// Struct for containing paths to other (de-)serializable things #[derive(Deserialize)] +#[serde(default)] pub struct Config { + /// Path to config directory + #[serde(skip)] + path: PathBuf, /// Path to serialized [quests][`crate::quest::Quest`] folder - #[serde(default)] pub quests_path: PathBuf, /// Path to serialized [accounts][`crate::account::Account`] folder - #[serde(default)] pub accounts_path: PathBuf, /// Path to serialized [map][`crate::map::Map`] file - #[serde(default)] pub map: PathBuf } impl Default for Config { fn default() -> Self { Config { + path: ".".into(), quests_path: "quests".into(), accounts_path: "accounts".into(), map: "map.toml".into() @@ -60,16 +62,22 @@ impl Config { /// let config = Config::load(path); /// ``` pub fn load(path: PathBuf) -> Self { + let dir = path.parent() + .unwrap_or(Path::new(".")) + .to_owned(); match fs::read_to_string(path) { Ok(string) => { match toml::from_str::(&string) { - Ok(conf) => { + Ok(mut conf) => { println!("Successfully loaded config"); + conf.path = dir; conf }, Err(error) => { eprintln!("Error on parsing config: {error}"); - Config::default() + let mut cfg = Config::default(); + cfg.path = dir; + cfg } } }, @@ -99,7 +107,10 @@ impl Config { pub fn load_quests(&self) -> Vec { let mut out_vec = Vec::new(); - match fs::read_dir(&self.quests_path) { + let mut path = self.path.clone(); + path.push(self.quests_path.clone()); + + match fs::read_dir(path) { Ok(iter) => { for entry in iter { match entry { diff --git a/src/quest/mod.rs b/src/quest/mod.rs index fbd818b..64607cb 100644 --- a/src/quest/mod.rs +++ b/src/quest/mod.rs @@ -8,7 +8,7 @@ use serde::{ Serialize, Deserialize }; use error::QuestError; /// Difficulty of the quest -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug, PartialEq)] pub enum QuestDifficulty { /// Easy quest Easy, @@ -39,33 +39,41 @@ fn default_answer() -> String { } /// Quest struct -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug, PartialEq)] +#[serde(default)] pub struct Quest { /// Quest identifier - #[serde(default)] pub id: u16, /// Difficulty of this quest - #[serde(default)] pub difficulty: QuestDifficulty, /// Reward for the quest - #[serde(default)] pub reward: u32, /// Visible quest name - #[serde(default = "default_name")] pub name: String, /// Visible quest description - #[serde(default = "default_description")] pub description: String, /// Quest answer, available for admins - #[serde(default = "default_answer")] pub answer: String, } +impl Default for Quest { + fn default() -> Self { + Quest { + id: u16::default(), + difficulty: QuestDifficulty::default(), + reward: u32::default(), + name: default_name(), + description: default_description(), + answer: default_answer() + } + } +} + impl Quest { /// Parse quest TOML or return error pub fn load(path: PathBuf) -> Result { diff --git a/tests/cfg/config.toml b/tests/cfg/config.toml new file mode 100644 index 0000000..87858ef --- /dev/null +++ b/tests/cfg/config.toml @@ -0,0 +1,10 @@ +# Default config + +# Path to quests folder relative to config +quests_path = "quests" + +# Path to accounts folder relative to config +accounts_path = "accounts" + +# Path to map .toml file relative to config +map = "map.toml" diff --git a/tests/cfg/quests/0.toml b/tests/cfg/quests/0.toml new file mode 100644 index 0000000..c7ef92b --- /dev/null +++ b/tests/cfg/quests/0.toml @@ -0,0 +1,2 @@ +# This example demonstrates that empty quests will be loaded correctly with default values +# see main.rs diff --git a/tests/cfg/quests/1.toml b/tests/cfg/quests/1.toml new file mode 100644 index 0000000..5b67270 --- /dev/null +++ b/tests/cfg/quests/1.toml @@ -0,0 +1,8 @@ +# This example demonstrates typical quest +# see main.rs +id = 1 +difficulty = "Easy" +reward = 100 +name = "Example easy quest" +description = "Answer this quest without any attachments or comments" +answer = "Accept the answer if it has no attachments and an empty comment" diff --git a/tests/cfg/quests/2.toml b/tests/cfg/quests/2.toml new file mode 100644 index 0000000..ce50391 --- /dev/null +++ b/tests/cfg/quests/2.toml @@ -0,0 +1,8 @@ +# This example demonstrates incorrect quest which will not be loaded from config +# see main.rs +id = 133713371337 +difficulty = sick +reward = an infinite amount of coffee +name = "Impossible quest" +description = "This quest won't be loaded in the game, therefore you can't solve it" +answer = 42 diff --git a/tests/main.rs b/tests/main.rs new file mode 100644 index 0000000..af0742d --- /dev/null +++ b/tests/main.rs @@ -0,0 +1,40 @@ +use squad_quest::{config::Config, quest::Quest}; + +#[test] +fn load_quests() { + let config = Config::load("./tests/cfg/config.toml".into()); + let quests = config.load_quests(); + + assert_eq!(quests.len(), 2); +} + +#[test] +fn empty_quest_is_default() { + // First loaded quest should be 0.toml, which is empty + let config = Config::load("./tests/cfg/config.toml".into()); + let mut quests = config.load_quests(); + quests.sort_by(|a,b| a.id.cmp(&b.id)); + let quest = quests.first().unwrap(); + + let default = Quest::default(); + + assert_eq!(*quest, default); +} + +#[test] +fn quest_one() { + let config = Config::load("./tests/cfg/config.toml".into()); + let quests = config.load_quests(); + let quest = quests.iter().find(|q| q.id == 1).unwrap(); + + let expected = Quest { + id: 1, + difficulty: squad_quest::quest::QuestDifficulty::Easy, + reward: 100, + name: "Example easy quest".to_owned(), + description: "Answer this quest without any attachments or comments".to_owned(), + answer: "Accept the answer if it has no attachments and an empty comment".to_owned() + }; + + assert_eq!(*quest, expected); +}