Score tests, and get_datetime test-util
This commit is contained in:
parent
e353977da9
commit
91229287a8
3 changed files with 246 additions and 19 deletions
|
|
@ -93,9 +93,9 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::test_utils::{
|
use crate::test_utils::{
|
||||||
ITEM_TITLE, ITEM_GUID, ITEM_GUID2, ITEM_DESC, ITEM_CONT,
|
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 {
|
fn create_guid(value: String) -> rss::Guid {
|
||||||
rss::Guid { value, permalink: false }
|
rss::Guid { value, permalink: false }
|
||||||
|
|
@ -140,7 +140,7 @@ mod tests {
|
||||||
|
|
||||||
let rss_channel = create_channel([rss_item, rss_item2].to_vec());
|
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();
|
let channel = FetchedRSSChannel::parse(rss_channel, date).unwrap();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,9 +54,19 @@ pub enum Score {
|
||||||
|
|
||||||
impl Score {
|
impl Score {
|
||||||
pub fn new() -> DecayingScore {
|
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 {
|
DecayingScore {
|
||||||
value: default::INITIAL_SCORE,
|
value: initial,
|
||||||
last_updated: Utc::now(),
|
last_updated: time,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,12 +78,20 @@ impl Score {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_score(self, gravity: Gravity) -> Self {
|
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 {
|
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) => {
|
Self::Boosted(b) => {
|
||||||
let try_unfrozen = b.try_unfreeze();
|
let try_unfrozen = b.try_unfreeze_at_time(time);
|
||||||
match try_unfrozen {
|
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),
|
Self::Boosted(b) => Score::Boosted(b),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -110,16 +128,12 @@ impl DecayingScore {
|
||||||
self.value
|
self.value
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_score(self, gravity: Gravity) -> Self {
|
fn apply_gravity_to_time(
|
||||||
self.update_score_at_time(gravity, Utc::now())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_score_at_time(
|
|
||||||
self, gravity: Gravity, update_time: DateTime<Utc>
|
self, gravity: Gravity, update_time: DateTime<Utc>
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let elapsed_time: TimeDelta = update_time.signed_duration_since(self.last_updated);
|
let elapsed_time: TimeDelta = update_time.signed_duration_since(self.last_updated);
|
||||||
let new_value = if elapsed_time <= TimeDelta::zero() { self.value } else {
|
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 {
|
Self {
|
||||||
last_updated: update_time,
|
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 {
|
fn try_unfreeze_at_time(self, update_time: DateTime<Utc>) -> Score {
|
||||||
let boost_end = self.boosted_at + TimeDelta::seconds(default::BOOST_FREEZE_IN_SECONDS);
|
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 {
|
Score::Decaying(DecayingScore {
|
||||||
value: self.value,
|
value: self.value,
|
||||||
last_updated: boost_end,
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,11 @@ use crate::{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
|
use chrono::{
|
||||||
|
Utc,
|
||||||
|
TimeZone,
|
||||||
|
DateTime
|
||||||
|
};
|
||||||
|
|
||||||
pub const FEED1: &str = "https://example.com/feed";
|
pub const FEED1: &str = "https://example.com/feed";
|
||||||
pub const FEED2: &str = "https://example2.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_DESC: &str = "My Item's description";
|
||||||
pub const ITEM_CONT: &str = "The content of my Item";
|
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 {
|
pub async fn setup_adapter() -> Adapter {
|
||||||
AdapterBuilder::new()
|
AdapterBuilder::new()
|
||||||
.database_url("sqlite::memory:")
|
.database_url("sqlite::memory:")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue