use super::{BonusTrack, PushContext, Reward, Tier};
pub struct RapidFire;
const RAPID_FIRE_WINDOW_SECS: u64 = 15 * 60;
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 RapidFire {
fn id(&self) -> &'static str {
"rapid_fire"
}
fn name(&self) -> &'static str {
"Rapid Fire"
}
fn description(&self) -> &'static str {
"Multiplier for pushing twice within 15 minutes."
}
fn tiers(&self) -> &'static [Tier] {
TIERS
}
fn applies(&self, ctx: &PushContext) -> u32 {
if ctx.push.commits().is_empty() {
return 0;
}
let cutoff = ctx.clock.now().saturating_sub(RAPID_FIRE_WINDOW_SECS);
let has_recent_push = ctx.history.count_since(cutoff).unwrap_or_default() != 0;
if has_recent_push { 1 } else { 0 }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
bonus_track::Clock,
git::{Commit, Push},
storage::{DbConnection, PushEntry, PushHistory},
};
#[test]
fn applies_when_pushed_within_window() {
let conn = DbConnection::create_in_memory().unwrap();
let bonus = RapidFire;
let push = Push::new(vec![Commit::default()]);
let history = PushHistory::new(&conn).with_entries([PushEntry::at(1000)]);
let clock = Clock::at(1000 + 5 * 60);
let ctx = PushContext {
push: &push,
history: &history,
clock: &clock,
};
assert_eq!(bonus.applies(&ctx), 1);
}
#[test]
fn applies_at_exact_boundary() {
let conn = DbConnection::create_in_memory().unwrap();
let bonus = RapidFire;
let push = Push::new(vec![Commit::default()]);
let history = PushHistory::new(&conn).with_entries([PushEntry::at(1000)]);
let clock = Clock::at(1000 + RAPID_FIRE_WINDOW_SECS);
let ctx = PushContext {
push: &push,
history: &history,
clock: &clock,
};
assert_eq!(bonus.applies(&ctx), 1);
}
#[test]
fn does_not_apply_outside_window() {
let conn = DbConnection::create_in_memory().unwrap();
let bonus = RapidFire;
let push = Push::new(vec![Commit::default()]);
let history = PushHistory::new(&conn).with_entries([PushEntry::at(1000)]);
let clock = Clock::at(1000 + RAPID_FIRE_WINDOW_SECS + 60);
let ctx = PushContext {
push: &push,
history: &history,
clock: &clock,
};
assert_eq!(bonus.applies(&ctx), 0);
}
#[test]
fn does_not_apply_with_no_history() {
let conn = DbConnection::create_in_memory().unwrap();
let bonus = RapidFire;
let push = Push::new(vec![Commit::default()]);
let history = PushHistory::new(&conn);
let clock = Clock::at(1000);
let ctx = PushContext {
push: &push,
history: &history,
clock: &clock,
};
assert_eq!(bonus.applies(&ctx), 0);
}
#[test]
fn does_not_apply_to_empty_pushes() {
let conn = DbConnection::create_in_memory().unwrap();
let bonus = RapidFire;
let push = Push::new(vec![]);
let history = PushHistory::new(&conn).with_entries([PushEntry::at(1000)]);
let clock = Clock::at(1000 + 5 * 60);
let ctx = PushContext {
push: &push,
history: &history,
clock: &clock,
};
assert_eq!(bonus.applies(&ctx), 0);
}
}