Score tests, and get_datetime test-util

This commit is contained in:
Julia Lange 2026-02-03 16:15:50 -08:00
parent e353977da9
commit 91229287a8
Signed by: Julia
SSH key fingerprint: SHA256:5DJcfxa5/fKCYn57dcabJa2vN2e6eT0pBerYi5SUbto
3 changed files with 246 additions and 19 deletions

View file

@ -93,9 +93,9 @@ mod tests {
use super::*;
use crate::test_utils::{
ITEM_TITLE, ITEM_GUID, ITEM_GUID2, ITEM_DESC, ITEM_CONT,
CHANNEL_TITLE, CHANNEL_DESC, FEED1
CHANNEL_TITLE, CHANNEL_DESC, FEED1,
get_datetime
};
use chrono::TimeZone;
fn create_guid(value: String) -> rss::Guid {
rss::Guid { value, permalink: false }
@ -140,7 +140,7 @@ mod tests {
let rss_channel = create_channel([rss_item, rss_item2].to_vec());
let date: DateTime<Utc> = Utc.with_ymd_and_hms(2020,1,1,0,0,0).unwrap();
let date: DateTime<Utc> = get_datetime();
let channel = FetchedRSSChannel::parse(rss_channel, date).unwrap();

View file

@ -54,9 +54,19 @@ pub enum Score {
impl Score {
pub fn new() -> DecayingScore {
Self::new_with_initial(default::INITIAL_SCORE)
}
pub fn new_with_initial(initial_score: i64) -> DecayingScore {
Self::new_with_initial_and_time(initial_score, Utc::now())
}
pub fn new_with_initial_and_time(
initial: i64, time: DateTime<Utc>
) -> DecayingScore {
DecayingScore {
value: default::INITIAL_SCORE,
last_updated: Utc::now(),
value: initial,
last_updated: time,
}
}
@ -68,12 +78,20 @@ impl Score {
}
pub fn update_score(self, gravity: Gravity) -> Self {
self.update_score_at_time(gravity, Utc::now())
}
fn update_score_at_time(self, gravity: Gravity, time: DateTime<Utc>) -> Self {
match self {
Self::Decaying(d) => Score::Decaying(d.update_score(gravity)),
Self::Decaying(d) => Score::Decaying(
d.apply_gravity_to_time(gravity, time)
),
Self::Boosted(b) => {
let try_unfrozen = b.try_unfreeze();
let try_unfrozen = b.try_unfreeze_at_time(time);
match try_unfrozen {
Self::Decaying(s) => Score::Decaying(s.update_score(gravity)),
Self::Decaying(s) => Score::Decaying(
s.apply_gravity_to_time(gravity, time)
),
Self::Boosted(b) => Score::Boosted(b),
}
}
@ -110,16 +128,12 @@ impl DecayingScore {
self.value
}
fn update_score(self, gravity: Gravity) -> Self {
self.update_score_at_time(gravity, Utc::now())
}
fn update_score_at_time(
fn apply_gravity_to_time(
self, gravity: Gravity, update_time: DateTime<Utc>
) -> Self {
let elapsed_time: TimeDelta = update_time.signed_duration_since(self.last_updated);
let new_value = if elapsed_time <= TimeDelta::zero() { self.value } else {
i64::from(gravity) * (elapsed_time.num_seconds() / SECONDS_IN_A_DAY)
self.value + i64::from(gravity) * (elapsed_time.num_seconds() / SECONDS_IN_A_DAY)
};
Self {
last_updated: update_time,
@ -151,13 +165,9 @@ impl BoostedScore {
}
}
fn try_unfreeze(self) -> Score {
self.try_unfreeze_at_time(Utc::now())
}
fn try_unfreeze_at_time(self, update_time: DateTime<Utc>) -> Score {
let boost_end = self.boosted_at + TimeDelta::seconds(default::BOOST_FREEZE_IN_SECONDS);
if boost_end > update_time {
if boost_end < update_time {
Score::Decaying(DecayingScore {
value: self.value,
last_updated: boost_end,
@ -167,3 +177,210 @@ 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 parse_decaying() {
let ups = UnparsedScore {
value: 10,
last_updated: get_datetime(),
last_boosted: None,
};
ups.parse().get_decaying().unwrap();
}
#[test]
fn parse_boosted() {
let dt = get_datetime();
let ups = UnparsedScore {
value: 10,
last_updated: dt,
last_boosted: Some(dt),
};
ups.parse().get_boosted().unwrap();
}
#[test]
fn new() {
let score = Score::new();
assert_eq!(score.value, default::INITIAL_SCORE);
}
#[test]
fn new_with_values() {
let dt = get_datetime();
let score = Score::new_with_initial_and_time(10, dt);
assert_eq!(score.value, 10);
assert_eq!(score.last_updated, dt);
}
#[test]
fn update_score_stays_decaying() {
let dt = get_datetime();
let score = Score::Decaying(Score::new_with_initial_and_time(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 = Score::Boosted(BoostedScore { value: 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 = Score::Boosted(BoostedScore { value: 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 < 10)
}
#[test]
fn get_decaying_success() {
let dt = get_datetime();
let score = Score::Decaying(Score::new_with_initial_and_time(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 = Score::Decaying(Score::new_with_initial_and_time(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 = Score::Boosted(
Score::new_with_initial_and_time(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 = Score::Boosted(
Score::new_with_initial_and_time(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: 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_eq!(updated.value, 10 + default::GRAVITY);
assert_eq!(updated.last_updated, future);
}
#[test]
fn apply_gravity_to_past() {
let dt = get_datetime();
let score = DecayingScore { value: 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_eq!(updated.value, 10);
assert_eq!(updated.last_updated, past);
}
#[test]
fn boost() {
let dt = get_datetime();
let score = DecayingScore { value: 10, last_updated: dt };
let boost = Boost::new(None);
let boosted = score.boost_at_time(boost, dt);
assert_eq!(boosted.value, 10 + default::BOOST);
assert_eq!(boosted.boosted_at, dt);
}
// "BoostedScore" tests
#[test]
fn unboost() {
let dt = get_datetime();
let score = DecayingScore { value: 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, 10);
assert_eq!(unboosted.last_updated, dt);
}
#[test]
fn boosted_stays_frozen() {
let dt = get_datetime();
let score = BoostedScore { value: 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: 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();
}
}

View file

@ -11,6 +11,11 @@ use crate::{
}
};
use reqwest::Url;
use chrono::{
Utc,
TimeZone,
DateTime
};
pub const FEED1: &str = "https://example.com/feed";
pub const FEED2: &str = "https://example2.com/feed";
@ -26,6 +31,11 @@ pub const ITEM_TITLE: &str = "My Item!";
pub const ITEM_DESC: &str = "My Item's description";
pub const ITEM_CONT: &str = "The content of my Item";
pub fn get_datetime() -> DateTime<Utc> {
Utc.with_ymd_and_hms(2020,1,1,0,0,0).unwrap()
}
pub async fn setup_adapter() -> Adapter {
AdapterBuilder::new()
.database_url("sqlite::memory:")