use crate::types::milestone::MilestoneContract;
pub struct MilestoneTracker {
pub(crate) contract: Option<MilestoneContract>,
pub(crate) current_phase: usize,
pub(crate) blocked_count: usize,
}
impl MilestoneTracker {
pub fn new() -> Self {
Self {
contract: None,
current_phase: 0,
blocked_count: 0,
}
}
pub fn load_contract(&mut self, contract: MilestoneContract) {
self.contract = Some(contract);
self.current_phase = 0;
self.blocked_count = 0;
}
pub fn current_phase_id(&self) -> Option<&str> {
self.contract
.as_ref()
.and_then(|c| c.phases.get(self.current_phase))
.map(|p| p.id.as_str())
}
pub fn current_criteria(&self) -> &[String] {
self.contract
.as_ref()
.and_then(|c| c.phases.get(self.current_phase))
.map(|p| p.criteria.as_slice())
.unwrap_or(&[])
}
pub fn is_complete(&self) -> bool {
match &self.contract {
None => true,
Some(c) => self.current_phase >= c.phases.len(),
}
}
}
impl Default for MilestoneTracker {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::milestone::{MilestonePhase, MilestoneRollbackPolicy};
#[test]
fn test_tracker_no_contract_is_complete() {
let tracker = MilestoneTracker::new();
assert!(tracker.is_complete());
assert_eq!(tracker.current_phase_id(), None);
assert!(tracker.current_criteria().is_empty());
}
#[test]
fn test_tracker_single_phase_is_incomplete_until_passed() {
use crate::types::milestone::MilestoneUnlockPolicy;
let contract = MilestoneContract {
phases: vec![MilestonePhase {
id: "phase1".to_string(),
criteria: vec!["c1".to_string()],
unlocks: vec![],
retry_policy: None,
verifier: None,
required_evidence: vec![],
unlock_policy: MilestoneUnlockPolicy::Immediate,
rollback_policy: MilestoneRollbackPolicy::Terminate,
}],
};
let mut tracker = MilestoneTracker::new();
tracker.load_contract(contract);
assert!(!tracker.is_complete());
assert_eq!(tracker.current_phase_id(), Some("phase1"));
assert_eq!(tracker.current_criteria(), &["c1".to_string()]);
}
#[test]
fn test_tracker_multi_phase_advances_on_pass() {
use crate::types::milestone::MilestoneUnlockPolicy;
let contract = MilestoneContract {
phases: vec![
MilestonePhase {
id: "phase1".to_string(),
criteria: vec!["c1".to_string()],
unlocks: vec![],
retry_policy: None,
verifier: None,
required_evidence: vec![],
unlock_policy: MilestoneUnlockPolicy::Immediate,
rollback_policy: MilestoneRollbackPolicy::Terminate,
},
MilestonePhase {
id: "phase2".to_string(),
criteria: vec!["c2".to_string()],
unlocks: vec![],
retry_policy: None,
verifier: None,
required_evidence: vec![],
unlock_policy: MilestoneUnlockPolicy::Immediate,
rollback_policy: MilestoneRollbackPolicy::Terminate,
},
],
};
let mut tracker = MilestoneTracker::new();
tracker.load_contract(contract);
assert_eq!(tracker.current_phase_id(), Some("phase1"));
tracker.current_phase += 1; assert_eq!(tracker.current_phase_id(), Some("phase2"));
tracker.current_phase += 1;
assert!(tracker.is_complete());
assert_eq!(tracker.current_phase_id(), None);
}
}