use crate::storage::PushHistory;
use super::{BonusTrack, Clock, PushContext, Reward, Tier};
pub struct Streak;
fn consecutive_push_days(history: &PushHistory, clock: &Clock) -> u32 {
let mut day_id = clock.today_id();
let mut count = history.count_since(clock.today_start()).unwrap_or_default();
let mut consec_days = 1;
loop {
day_id -= 1;
let day_start = clock.day_start(day_id);
let new_count = history.count_since(day_start).unwrap_or_default();
if new_count > count {
consec_days += 1;
count = new_count;
} else {
break;
}
}
consec_days
}
const MIN_STREAK_DAYS: u32 = 3;
static TIERS: &[Tier] = &[
Tier {
cost: 50,
reward: Reward::Multiplier(2),
},
Tier {
cost: 500,
reward: Reward::Multiplier(3),
},
Tier {
cost: 3000,
reward: Reward::Multiplier(4),
},
Tier {
cost: 20000,
reward: Reward::Multiplier(5),
},
Tier {
cost: 120000,
reward: Reward::Multiplier(6),
},
];
impl BonusTrack for Streak {
fn id(&self) -> &'static str {
"streak"
}
fn name(&self) -> &'static str {
"Hot Streak"
}
fn description(&self) -> &'static str {
"Multiplier for pushing 3+ days in a row."
}
fn tiers(&self) -> &'static [Tier] {
TIERS
}
fn applies(&self, ctx: &PushContext) -> u32 {
if ctx.push.commits().is_empty() {
return 0;
}
if consecutive_push_days(ctx.history, ctx.clock) >= MIN_STREAK_DAYS {
1
} else {
0
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
git::{Commit, Push},
storage::{DbConnection, PushEntry},
};
const SECONDS_PER_DAY: u64 = 86400;
fn clock_at_day(day: u64) -> Clock {
Clock::at(day * SECONDS_PER_DAY + 3600) }
fn entry_on_day(day: u64) -> PushEntry {
PushEntry::at(day * SECONDS_PER_DAY + 3600)
}
#[test]
fn applies_with_3_day_streak() {
let conn = DbConnection::create_in_memory().unwrap();
let bonus = Streak;
let push = Push::new(vec![Commit::default()]);
let history = PushHistory::new(&conn).with_entries([
entry_on_day(100),
entry_on_day(101),
entry_on_day(102),
]);
let clock = clock_at_day(102);
let ctx = PushContext {
push: &push,
history: &history,
clock: &clock,
};
assert_eq!(bonus.applies(&ctx), 1);
}
#[test]
fn applies_with_longer_streak() {
let conn = DbConnection::create_in_memory().unwrap();
let bonus = Streak;
let push = Push::new(vec![Commit::default()]);
let history = PushHistory::new(&conn).with_entries((95..=102).map(entry_on_day));
let clock = clock_at_day(102);
let ctx = PushContext {
push: &push,
history: &history,
clock: &clock,
};
assert_eq!(bonus.applies(&ctx), 1);
}
#[test]
fn does_not_apply_with_2_day_streak() {
let conn = DbConnection::create_in_memory().unwrap();
let bonus = Streak;
let push = Push::new(vec![Commit::default()]);
let history = PushHistory::new(&conn).with_entries([entry_on_day(101), entry_on_day(102)]);
let clock = clock_at_day(102);
let ctx = PushContext {
push: &push,
history: &history,
clock: &clock,
};
assert_eq!(bonus.applies(&ctx), 0);
}
#[test]
fn does_not_apply_with_gap() {
let conn = DbConnection::create_in_memory().unwrap();
let bonus = Streak;
let push = Push::new(vec![Commit::default()]);
let history = PushHistory::new(&conn).with_entries([
entry_on_day(99),
entry_on_day(100),
entry_on_day(102),
]);
let clock = clock_at_day(102);
let ctx = PushContext {
push: &push,
history: &history,
clock: &clock,
};
assert_eq!(bonus.applies(&ctx), 0);
}
#[test]
fn applies_on_first_push_of_day() {
let conn = DbConnection::create_in_memory().unwrap();
let bonus = Streak;
let push = Push::new(vec![Commit::default()]);
let history = PushHistory::new(&conn).with_entries([
entry_on_day(99),
entry_on_day(100),
entry_on_day(101),
]);
let clock = clock_at_day(102);
let ctx = PushContext {
push: &push,
history: &history,
clock: &clock,
};
assert_eq!(bonus.applies(&ctx), 1);
}
#[test]
fn does_not_apply_to_empty_pushes() {
let conn = DbConnection::create_in_memory().unwrap();
let bonus = Streak;
let push = Push::new(vec![]);
let history = PushHistory::new(&conn).with_entries([
entry_on_day(100),
entry_on_day(101),
entry_on_day(102),
]);
let clock = clock_at_day(102);
let ctx = PushContext {
push: &push,
history: &history,
clock: &clock,
};
assert_eq!(bonus.applies(&ctx), 0);
}
}