test: Initial unit testing

- Test quests vector loading
- Test default quest on empty fields
- Test correct quest
- Fixed config paths handling
This commit is contained in:
Alexey 2025-11-28 17:00:17 +03:00
commit 94d771107d
7 changed files with 102 additions and 15 deletions

View file

@ -1,30 +1,32 @@
//! Configuration file that handles (de-)serializing other components //! 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 serde::Deserialize;
use crate::quest::{Quest,error::QuestError}; use crate::quest::{Quest,error::QuestError};
/// Struct for containing paths to other (de-)serializable things /// Struct for containing paths to other (de-)serializable things
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(default)]
pub struct Config { pub struct Config {
/// Path to config directory
#[serde(skip)]
path: PathBuf,
/// Path to serialized [quests][`crate::quest::Quest`] folder /// Path to serialized [quests][`crate::quest::Quest`] folder
#[serde(default)]
pub quests_path: PathBuf, pub quests_path: PathBuf,
/// Path to serialized [accounts][`crate::account::Account`] folder /// Path to serialized [accounts][`crate::account::Account`] folder
#[serde(default)]
pub accounts_path: PathBuf, pub accounts_path: PathBuf,
/// Path to serialized [map][`crate::map::Map`] file /// Path to serialized [map][`crate::map::Map`] file
#[serde(default)]
pub map: PathBuf pub map: PathBuf
} }
impl Default for Config { impl Default for Config {
fn default() -> Self { fn default() -> Self {
Config { Config {
path: ".".into(),
quests_path: "quests".into(), quests_path: "quests".into(),
accounts_path: "accounts".into(), accounts_path: "accounts".into(),
map: "map.toml".into() map: "map.toml".into()
@ -60,16 +62,22 @@ impl Config {
/// let config = Config::load(path); /// let config = Config::load(path);
/// ``` /// ```
pub fn load(path: PathBuf) -> Self { pub fn load(path: PathBuf) -> Self {
let dir = path.parent()
.unwrap_or(Path::new("."))
.to_owned();
match fs::read_to_string(path) { match fs::read_to_string(path) {
Ok(string) => { Ok(string) => {
match toml::from_str::<Config>(&string) { match toml::from_str::<Config>(&string) {
Ok(conf) => { Ok(mut conf) => {
println!("Successfully loaded config"); println!("Successfully loaded config");
conf.path = dir;
conf conf
}, },
Err(error) => { Err(error) => {
eprintln!("Error on parsing config: {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<Quest> { pub fn load_quests(&self) -> Vec<Quest> {
let mut out_vec = Vec::new(); 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) => { Ok(iter) => {
for entry in iter { for entry in iter {
match entry { match entry {

View file

@ -8,7 +8,7 @@ use serde::{ Serialize, Deserialize };
use error::QuestError; use error::QuestError;
/// Difficulty of the quest /// Difficulty of the quest
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize, Debug, PartialEq)]
pub enum QuestDifficulty { pub enum QuestDifficulty {
/// Easy quest /// Easy quest
Easy, Easy,
@ -39,33 +39,41 @@ fn default_answer() -> String {
} }
/// Quest struct /// Quest struct
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize, Debug, PartialEq)]
#[serde(default)]
pub struct Quest { pub struct Quest {
/// Quest identifier /// Quest identifier
#[serde(default)]
pub id: u16, pub id: u16,
/// Difficulty of this quest /// Difficulty of this quest
#[serde(default)]
pub difficulty: QuestDifficulty, pub difficulty: QuestDifficulty,
/// Reward for the quest /// Reward for the quest
#[serde(default)]
pub reward: u32, pub reward: u32,
/// Visible quest name /// Visible quest name
#[serde(default = "default_name")]
pub name: String, pub name: String,
/// Visible quest description /// Visible quest description
#[serde(default = "default_description")]
pub description: String, pub description: String,
/// Quest answer, available for admins /// Quest answer, available for admins
#[serde(default = "default_answer")]
pub answer: String, 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 { impl Quest {
/// Parse quest TOML or return error /// Parse quest TOML or return error
pub fn load(path: PathBuf) -> Result<Self, QuestError> { pub fn load(path: PathBuf) -> Result<Self, QuestError> {

10
tests/cfg/config.toml Normal file
View file

@ -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"

2
tests/cfg/quests/0.toml Normal file
View file

@ -0,0 +1,2 @@
# This example demonstrates that empty quests will be loaded correctly with default values
# see main.rs

8
tests/cfg/quests/1.toml Normal file
View file

@ -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"

8
tests/cfg/quests/2.toml Normal file
View file

@ -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

40
tests/main.rs Normal file
View file

@ -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);
}