diff --git a/solitaire_sync/src/achievements.rs b/solitaire_sync/src/achievements.rs index 1c310ce..a610e15 100644 --- a/solitaire_sync/src/achievements.rs +++ b/solitaire_sync/src/achievements.rs @@ -46,3 +46,36 @@ impl AchievementRecord { self.unlock_date = Some(at); } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn locked_creates_an_unlocked_record() { + let r = AchievementRecord::locked("first_win"); + assert_eq!(r.id, "first_win"); + assert!(!r.unlocked); + assert!(r.unlock_date.is_none()); + assert!(!r.reward_granted); + } + + #[test] + fn unlock_sets_unlocked_and_stores_timestamp() { + let mut r = AchievementRecord::locked("first_win"); + let ts = Utc::now(); + r.unlock(ts); + assert!(r.unlocked); + assert_eq!(r.unlock_date, Some(ts)); + } + + #[test] + fn unlock_is_idempotent_and_preserves_earliest_date() { + let mut r = AchievementRecord::locked("first_win"); + let early = DateTime::UNIX_EPOCH; + let later = Utc::now(); + r.unlock(early); + r.unlock(later); // should be a no-op + assert_eq!(r.unlock_date, Some(early), "earliest unlock date must be preserved"); + } +} diff --git a/solitaire_sync/src/stats.rs b/solitaire_sync/src/stats.rs index 9d322f3..9f0285e 100644 --- a/solitaire_sync/src/stats.rs +++ b/solitaire_sync/src/stats.rs @@ -74,3 +74,53 @@ impl StatsSnapshot { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn win_rate_is_none_before_any_game() { + let s = StatsSnapshot::default(); + assert!(s.win_rate().is_none()); + } + + #[test] + fn win_rate_100_when_all_games_won() { + let s = StatsSnapshot { + games_played: 5, + games_won: 5, + ..StatsSnapshot::default() + }; + let rate = s.win_rate().expect("should have a rate"); + assert!((rate - 100.0).abs() < 0.01, "expected 100.0, got {rate}"); + } + + #[test] + fn win_rate_50_when_half_won() { + let s = StatsSnapshot { + games_played: 10, + games_won: 5, + ..StatsSnapshot::default() + }; + let rate = s.win_rate().expect("should have a rate"); + assert!((rate - 50.0).abs() < 0.01, "expected 50.0, got {rate}"); + } + + #[test] + fn win_rate_0_when_no_wins() { + let s = StatsSnapshot { + games_played: 3, + games_won: 0, + ..StatsSnapshot::default() + }; + let rate = s.win_rate().expect("should have a rate"); + assert!((rate - 0.0).abs() < 0.01, "expected 0.0, got {rate}"); + } + + #[test] + fn fastest_win_seconds_defaults_to_max() { + let s = StatsSnapshot::default(); + assert_eq!(s.fastest_win_seconds, u64::MAX); + } +}