From eb21bf0d515a3a98974f2f6195fbe0a34b2e8fab Mon Sep 17 00:00:00 2001 From: Julia Lange Date: Thu, 5 Feb 2026 12:36:28 -0800 Subject: [PATCH] score+test, Tests for a42853a & test_utils mod Tests for all the core functionality implemented to score in a42853a. Optimistically adding a test_utils mod for shared test code --- koucha/src/lib.rs | 4 + koucha/src/score.rs | 196 +++++++++++++++++++++++++++++++++++++++ koucha/src/test_utils.rs | 11 +++ 3 files changed, 211 insertions(+) create mode 100644 koucha/src/test_utils.rs diff --git a/koucha/src/lib.rs b/koucha/src/lib.rs index b0aef7d..3bf607b 100644 --- a/koucha/src/lib.rs +++ b/koucha/src/lib.rs @@ -3,6 +3,10 @@ use std::error::Error; type Result = std::result::Result>; pub mod score; + +#[cfg(test)] +pub mod test_utils; + pub struct AdapterPool(sqlx::SqlitePool); pub struct AdapterBuilder { database_url: String, diff --git a/koucha/src/score.rs b/koucha/src/score.rs index 8b27b09..e289c6e 100644 --- a/koucha/src/score.rs +++ b/koucha/src/score.rs @@ -191,3 +191,199 @@ impl BoostedScore { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_utils::get_datetime; + + #[test] + fn gravity_default() { + let gravity = Gravity::new(None); + assert_eq!(i64::from(gravity), default::GRAVITY); + } + + #[test] + fn boost_default() { + let boost = Boost::new(None); + assert_eq!(i64::from(boost), default::BOOST); + } + + // "Score" Tests + + #[test] + fn new() { + let score = TimedScore::new(); + assert_eq!(score.value, Score(default::INITIAL_SCORE)); + } + + #[test] + fn new_with_values() { + let dt = get_datetime(); + let score = TimedScore::new_with_initial_and_time(Score(10), dt); + assert_eq!(score.value, Score(10)); + assert_eq!(score.last_updated, dt); + } + + #[test] + fn update_score_stays_decaying() { + let dt = get_datetime(); + let score = TimedScore::Decaying( + TimedScore::new_with_initial_and_time(Score(10), dt) + ); + let gravity = Gravity::new(None); + + let dt2 = dt + TimeDelta::seconds(SECONDS_IN_A_DAY); + + score.update_score_at_time(gravity, dt2).get_decaying().unwrap(); + } + + #[test] + fn update_score_stays_frozen() { + let dt = get_datetime(); + let score = TimedScore::Boosted( + BoostedScore { value: Score(10), boosted_at: dt } + ); + let gravity = Gravity::new(None); + + let dt2 = dt + TimeDelta::seconds(default::BOOST_FREEZE_IN_SECONDS); + + score.update_score_at_time(gravity, dt2).get_boosted().unwrap(); + } + + #[test] + fn update_score_thaws_and_decays() { + let dt = get_datetime(); + let score = TimedScore::Boosted( + BoostedScore { value: Score(10), boosted_at: dt } + ); + let gravity = Gravity::new(None); + + let dt2 = dt + TimeDelta::seconds( + default::BOOST_FREEZE_IN_SECONDS + SECONDS_IN_A_DAY + ); + + let updated = score.update_score_at_time(gravity, dt2) + .get_decaying().unwrap(); + assert!(updated.value < Score(10)) + } + + #[test] + fn get_decaying_success() { + let dt = get_datetime(); + let score = TimedScore::Decaying( + TimedScore::new_with_initial_and_time(Score(10), dt) + ); + + score.get_decaying().unwrap(); + } + + #[test] + #[should_panic = "Attempted to get_boosted() of a decaying score"] + fn get_boosted_failure() { + let dt = get_datetime(); + let score = TimedScore::Decaying( + TimedScore::new_with_initial_and_time(Score(10), dt) + ); + + score.get_boosted().unwrap(); + } + + #[test] + #[should_panic = "Attempted to get_decaying() of a boosted score"] + fn get_decaying_failure() { + let dt = get_datetime(); + let boost = Boost::new(None); + let score = TimedScore::Boosted( + TimedScore::new_with_initial_and_time(Score(10), dt) + .boost_at_time(boost, dt) + ); + + score.get_decaying().unwrap(); + } + + #[test] + fn get_boosted_success() { + let dt = get_datetime(); + let boost = Boost::new(None); + let score = TimedScore::Boosted( + TimedScore::new_with_initial_and_time(Score(10), dt) + .boost_at_time(boost, dt) + ); + + score.get_boosted().unwrap(); + } + + // "DecayingScore" Tests + + #[test] + fn apply_gravity_to_future() { + let dt = get_datetime(); + let score = DecayingScore { value: Score(10), last_updated: dt }; + let future = dt + TimeDelta::seconds(SECONDS_IN_A_DAY); + let gravity = Gravity::new(None); + + let updated = score.apply_gravity_to_time(gravity, future); + + assert!(updated.value < Score(10)); + assert_eq!(updated.last_updated, future); + } + + #[test] + fn apply_gravity_to_past() { + let dt = get_datetime(); + let score = DecayingScore { value: Score(10), last_updated: dt }; + let past = dt - TimeDelta::seconds(SECONDS_IN_A_DAY); + let gravity = Gravity::new(None); + + let updated = score.apply_gravity_to_time(gravity, past); + + assert!(updated.value > Score(10)); + assert_eq!(updated.last_updated, past); + } + + #[test] + fn boost() { + let dt = get_datetime(); + let score = DecayingScore { value: Score(10), last_updated: dt }; + let boost = Boost::new(None); + + let boosted = score.boost_at_time(boost, dt); + assert_eq!(boosted.value, Score(10) + Boost(default::BOOST)); + assert_eq!(boosted.boosted_at, dt); + } + + // "BoostedScore" tests + + #[test] + fn unboost() { + let dt = get_datetime(); + let score = DecayingScore { value: Score(10), last_updated: dt }; + let boost = Boost::new(None); + let boosted = score.boost_at_time(boost, dt); + + let unboosted = boosted.unboost(boost); + assert_eq!(unboosted.value, Score(10)); + assert_eq!(unboosted.last_updated, dt); + } + + #[test] + fn boosted_stays_frozen() { + let dt = get_datetime(); + let score = BoostedScore { value: Score(10), boosted_at: dt }; + + let last_second = dt + TimeDelta::seconds(default::BOOST_FREEZE_IN_SECONDS); + + score.try_unfreeze_at_time(last_second).get_boosted().unwrap(); + } + + #[test] + fn boosted_thaws() { + let dt = get_datetime(); + let score = BoostedScore { value: Score(10), boosted_at: dt }; + + let first_second = dt + TimeDelta::days(default::BOOST_FREEZE_IN_SECONDS+1); + + score.try_unfreeze_at_time(first_second).get_decaying().unwrap(); + } +} diff --git a/koucha/src/test_utils.rs b/koucha/src/test_utils.rs new file mode 100644 index 0000000..6492538 --- /dev/null +++ b/koucha/src/test_utils.rs @@ -0,0 +1,11 @@ +#![cfg(test)] + +use chrono::{ + Utc, + TimeZone, + DateTime +}; + +pub fn get_datetime() -> DateTime { + Utc.with_ymd_and_hms(2020,1,1,0,0,0).unwrap() +}