use crate::{
bonus_track::{ALL_TRACKS, Clock, PushContext, Reward},
git::Push,
state::State,
storage::PushHistory,
};
#[derive(Debug, Clone)]
pub enum AppliedBonus {
Multiplier {
name: &'static str,
value: u32,
},
FlatBonus {
name: &'static str,
points: u64,
count: u32,
},
}
#[derive(Debug, Clone)]
pub struct PointsBreakdown {
pub commits: u64,
pub points_per_commit: u64,
pub total: u64,
pub applied: Vec<AppliedBonus>,
}
pub fn calculate_points(
push: &Push,
state: &State,
history: &PushHistory,
clock: &Clock,
) -> PointsBreakdown {
let points_per_commit = state.points_per_commit();
let base_points = push.commits().len() as u64 * points_per_commit;
let mut total_multiplier: u64 = 1;
let mut flat_bonus_total: u64 = 0;
let mut applied = Vec::new();
let ctx = PushContext {
push,
history,
clock,
};
for track in ALL_TRACKS.iter() {
if track.id() == "commit_value" {
continue;
}
let level = state.bonus_level(track.id());
if level == 0 {
continue;
}
let count = track.applies(&ctx);
if count == 0 {
continue;
}
match track.reward_at_level(level) {
Some(Reward::Multiplier(m)) => {
total_multiplier *= m as u64;
applied.push(AppliedBonus::Multiplier {
name: track.name(),
value: m,
});
}
Some(Reward::FlatPoints(pts)) => {
let bonus = pts * count as u64;
flat_bonus_total += bonus;
applied.push(AppliedBonus::FlatBonus {
name: track.name(),
points: bonus,
count,
});
}
None => {}
}
}
let total = (base_points + flat_bonus_total) * total_multiplier;
PointsBreakdown {
commits: push.commits().len() as u64,
points_per_commit,
total,
applied,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{git::Commit, storage::DbConnection};
fn get_multiplier(id: &str, level: u32) -> u64 {
ALL_TRACKS
.iter()
.find(|t| t.id() == id)
.and_then(|t| t.reward_at_level(level))
.map(|r| match r {
Reward::Multiplier(m) => m as u64,
_ => 1,
})
.unwrap_or(1)
}
fn get_flat(id: &str, level: u32) -> u64 {
ALL_TRACKS
.iter()
.find(|t| t.id() == id)
.and_then(|t| t.reward_at_level(level))
.map(|r| match r {
Reward::FlatPoints(p) => p,
_ => 0,
})
.unwrap_or(0)
}
#[test]
fn base_points_without_bonuses() {
let conn = DbConnection::create_in_memory().unwrap();
let history = PushHistory::new(&conn);
let state = State::default();
let push = Push::new(vec![
Commit::with_lines(10),
Commit::with_lines(20),
Commit::with_lines(30),
]);
let result = calculate_points(&push, &state, &history, &Clock::at(1000));
assert_eq!(result.total, 3); }
#[test]
fn formula_applies_flat_before_multiplier() {
let conn = DbConnection::create_in_memory().unwrap();
let history = PushHistory::new(&conn);
let mut state = State::default();
state.set_bonus_level("first_push", 1);
state.set_bonus_level("one_line_change", 1);
let push = Push::new(vec![Commit::with_lines(1), Commit::with_lines(10)]);
let result = calculate_points(&push, &state, &history, &Clock::at(1000));
let mult = get_multiplier("first_push", 1);
let flat = get_flat("one_line_change", 1);
assert_eq!(result.total, (2 + flat) * mult);
}
#[test]
fn flat_bonus_scales_with_qualifying_commits() {
let conn = DbConnection::create_in_memory().unwrap();
let history = PushHistory::new(&conn);
let mut state = State::default();
state.set_bonus_level("one_line_change", 1);
let push = Push::new(vec![
Commit::with_lines(1),
Commit::with_lines(1),
Commit::with_lines(1),
Commit::with_lines(50),
]);
let result = calculate_points(&push, &state, &history, &Clock::at(1000));
let flat_per = get_flat("one_line_change", 1);
assert_eq!(result.total, 4 + 3 * flat_per);
}
}