Skip to main content

jugar_probar/
clock.rs

1//! Clock Manipulation for Deterministic Tests (Feature G.6)
2//!
3//! Provides fake clock implementation for controlling time in tests.
4//! Enables deterministic testing of time-dependent code.
5
6use serde::{Deserialize, Serialize};
7use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
8use std::sync::Arc;
9use std::time::{Duration, SystemTime, UNIX_EPOCH};
10
11/// Clock state for fake time
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13pub enum ClockState {
14    /// Clock is running normally
15    Running,
16    /// Clock is paused at a fixed time
17    Paused,
18    /// Clock is using system time
19    System,
20}
21
22/// Options for clock installation
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct ClockOptions {
25    /// Initial time to set (milliseconds since Unix epoch)
26    pub time_ms: u64,
27    /// Whether to pause immediately
28    pub paused: bool,
29}
30
31impl ClockOptions {
32    /// Create options with current system time
33    #[must_use]
34    pub fn now() -> Self {
35        let time_ms = SystemTime::now()
36            .duration_since(UNIX_EPOCH)
37            .map(|d| d.as_millis() as u64)
38            .unwrap_or(0);
39
40        Self {
41            time_ms,
42            paused: false,
43        }
44    }
45
46    /// Create options with fixed time
47    #[must_use]
48    pub fn fixed(time_ms: u64) -> Self {
49        Self {
50            time_ms,
51            paused: true,
52        }
53    }
54
55    /// Parse ISO 8601 datetime string
56    ///
57    /// # Errors
58    ///
59    /// Returns error if parsing fails
60    pub fn from_iso(iso: &str) -> Result<Self, ClockError> {
61        // Simple ISO 8601 parser for common formats
62        // Format: YYYY-MM-DDTHH:MM:SSZ or YYYY-MM-DDTHH:MM:SS
63        let time_ms = parse_iso_to_ms(iso)?;
64        Ok(Self {
65            time_ms,
66            paused: false,
67        })
68    }
69
70    /// Set whether to start paused
71    #[must_use]
72    pub fn paused(mut self, paused: bool) -> Self {
73        self.paused = paused;
74        self
75    }
76}
77
78impl Default for ClockOptions {
79    fn default() -> Self {
80        Self::now()
81    }
82}
83
84/// Errors that can occur with clock operations
85#[derive(Debug, Clone, PartialEq, Eq)]
86pub enum ClockError {
87    /// Invalid datetime format
88    InvalidFormat(String),
89    /// Clock not installed
90    NotInstalled,
91    /// Clock already installed
92    AlreadyInstalled,
93}
94
95impl std::fmt::Display for ClockError {
96    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97        match self {
98            Self::InvalidFormat(s) => write!(f, "Invalid datetime format: {s}"),
99            Self::NotInstalled => write!(f, "Clock not installed"),
100            Self::AlreadyInstalled => write!(f, "Clock already installed"),
101        }
102    }
103}
104
105impl std::error::Error for ClockError {}
106
107/// Fake clock for deterministic testing
108#[derive(Debug)]
109pub struct FakeClock {
110    /// Current time in milliseconds since Unix epoch
111    current_ms: AtomicU64,
112    /// Whether clock is paused
113    paused: AtomicBool,
114    /// Whether clock is installed
115    installed: AtomicBool,
116    /// Real time when clock was installed (for relative calculations)
117    install_real_ms: AtomicU64,
118    /// Fake time when clock was installed
119    install_fake_ms: AtomicU64,
120}
121
122impl FakeClock {
123    /// Create a new fake clock (not installed)
124    #[must_use]
125    pub fn new() -> Self {
126        Self {
127            current_ms: AtomicU64::new(0),
128            paused: AtomicBool::new(false),
129            installed: AtomicBool::new(false),
130            install_real_ms: AtomicU64::new(0),
131            install_fake_ms: AtomicU64::new(0),
132        }
133    }
134
135    /// Install the fake clock with options
136    ///
137    /// # Errors
138    ///
139    /// Returns error if clock is already installed
140    pub fn install(&self, options: ClockOptions) -> Result<(), ClockError> {
141        if self.installed.swap(true, Ordering::SeqCst) {
142            return Err(ClockError::AlreadyInstalled);
143        }
144
145        let real_ms = SystemTime::now()
146            .duration_since(UNIX_EPOCH)
147            .map(|d| d.as_millis() as u64)
148            .unwrap_or(0);
149
150        self.install_real_ms.store(real_ms, Ordering::SeqCst);
151        self.install_fake_ms
152            .store(options.time_ms, Ordering::SeqCst);
153        self.current_ms.store(options.time_ms, Ordering::SeqCst);
154        self.paused.store(options.paused, Ordering::SeqCst);
155
156        Ok(())
157    }
158
159    /// Uninstall the fake clock
160    pub fn uninstall(&self) {
161        self.installed.store(false, Ordering::SeqCst);
162        self.paused.store(false, Ordering::SeqCst);
163    }
164
165    /// Check if clock is installed
166    #[must_use]
167    pub fn is_installed(&self) -> bool {
168        self.installed.load(Ordering::SeqCst)
169    }
170
171    /// Check if clock is paused
172    #[must_use]
173    pub fn is_paused(&self) -> bool {
174        self.paused.load(Ordering::SeqCst)
175    }
176
177    /// Get current fake time in milliseconds
178    #[must_use]
179    pub fn now_ms(&self) -> u64 {
180        if !self.is_installed() {
181            return SystemTime::now()
182                .duration_since(UNIX_EPOCH)
183                .map(|d| d.as_millis() as u64)
184                .unwrap_or(0);
185        }
186
187        if self.is_paused() {
188            return self.current_ms.load(Ordering::SeqCst);
189        }
190
191        // Calculate elapsed time since install
192        let real_now = SystemTime::now()
193            .duration_since(UNIX_EPOCH)
194            .map(|d| d.as_millis() as u64)
195            .unwrap_or(0);
196        let real_elapsed = real_now.saturating_sub(self.install_real_ms.load(Ordering::SeqCst));
197        let current = self.current_ms.load(Ordering::SeqCst);
198
199        current + real_elapsed
200    }
201
202    /// Get current fake time as Duration since Unix epoch
203    #[must_use]
204    pub fn now(&self) -> Duration {
205        Duration::from_millis(self.now_ms())
206    }
207
208    /// Pause the clock at current time
209    pub fn pause(&self) {
210        if self.is_installed() && !self.is_paused() {
211            // Capture current time before pausing
212            let current = self.now_ms();
213            self.current_ms.store(current, Ordering::SeqCst);
214            self.paused.store(true, Ordering::SeqCst);
215        }
216    }
217
218    /// Resume the clock from paused state
219    pub fn resume(&self) {
220        if self.is_installed() && self.is_paused() {
221            // Update install time to now
222            let real_now = SystemTime::now()
223                .duration_since(UNIX_EPOCH)
224                .map(|d| d.as_millis() as u64)
225                .unwrap_or(0);
226            self.install_real_ms.store(real_now, Ordering::SeqCst);
227            self.paused.store(false, Ordering::SeqCst);
228        }
229    }
230
231    /// Set clock to a fixed time (pauses clock)
232    pub fn set_fixed_time(&self, time_ms: u64) {
233        self.current_ms.store(time_ms, Ordering::SeqCst);
234        self.paused.store(true, Ordering::SeqCst);
235    }
236
237    /// Set clock to a fixed time from ISO string
238    ///
239    /// # Errors
240    ///
241    /// Returns error if parsing fails
242    pub fn set_fixed_time_iso(&self, iso: &str) -> Result<(), ClockError> {
243        let time_ms = parse_iso_to_ms(iso)?;
244        self.set_fixed_time(time_ms);
245        Ok(())
246    }
247
248    /// Fast-forward time by duration
249    pub fn fast_forward(&self, duration: Duration) {
250        let current = self.current_ms.load(Ordering::SeqCst);
251        let new_time = current + duration.as_millis() as u64;
252        self.current_ms.store(new_time, Ordering::SeqCst);
253    }
254
255    /// Fast-forward time by milliseconds
256    pub fn fast_forward_ms(&self, ms: u64) {
257        self.fast_forward(Duration::from_millis(ms));
258    }
259
260    /// Pause at specific time
261    pub fn pause_at(&self, time_ms: u64) {
262        self.current_ms.store(time_ms, Ordering::SeqCst);
263        self.paused.store(true, Ordering::SeqCst);
264    }
265
266    /// Get current state
267    #[must_use]
268    pub fn state(&self) -> ClockState {
269        if !self.is_installed() {
270            ClockState::System
271        } else if self.is_paused() {
272            ClockState::Paused
273        } else {
274            ClockState::Running
275        }
276    }
277}
278
279impl Default for FakeClock {
280    fn default() -> Self {
281        Self::new()
282    }
283}
284
285impl Clone for FakeClock {
286    fn clone(&self) -> Self {
287        Self {
288            current_ms: AtomicU64::new(self.current_ms.load(Ordering::SeqCst)),
289            paused: AtomicBool::new(self.paused.load(Ordering::SeqCst)),
290            installed: AtomicBool::new(self.installed.load(Ordering::SeqCst)),
291            install_real_ms: AtomicU64::new(self.install_real_ms.load(Ordering::SeqCst)),
292            install_fake_ms: AtomicU64::new(self.install_fake_ms.load(Ordering::SeqCst)),
293        }
294    }
295}
296
297/// Thread-safe clock handle
298pub type Clock = Arc<FakeClock>;
299
300/// Create a new shared clock
301#[must_use]
302pub fn create_clock() -> Clock {
303    Arc::new(FakeClock::new())
304}
305
306/// Parse simple ISO 8601 datetime to milliseconds
307fn parse_iso_to_ms(iso: &str) -> Result<u64, ClockError> {
308    // Support formats:
309    // - YYYY-MM-DDTHH:MM:SSZ
310    // - YYYY-MM-DDTHH:MM:SS
311    // - YYYY-MM-DD
312
313    let iso = iso.trim().trim_end_matches('Z');
314
315    let parts: Vec<&str> = if iso.contains('T') {
316        iso.split('T').collect()
317    } else {
318        vec![iso, "00:00:00"]
319    };
320
321    if parts.is_empty() {
322        return Err(ClockError::InvalidFormat(iso.to_string()));
323    }
324
325    let date_parts: Vec<u32> = parts[0]
326        .split('-')
327        .map(|s| s.parse().unwrap_or(0))
328        .collect();
329
330    if date_parts.len() < 3 {
331        return Err(ClockError::InvalidFormat(iso.to_string()));
332    }
333
334    let year = date_parts[0];
335    let month = date_parts[1];
336    let day = date_parts[2];
337
338    let (hour, minute, second) = if parts.len() > 1 {
339        let time_parts: Vec<u32> = parts[1]
340            .split(':')
341            .map(|s| s.parse().unwrap_or(0))
342            .collect();
343        (
344            *time_parts.first().unwrap_or(&0),
345            *time_parts.get(1).unwrap_or(&0),
346            *time_parts.get(2).unwrap_or(&0),
347        )
348    } else {
349        (0, 0, 0)
350    };
351
352    // Simple days since epoch calculation (not accounting for leap seconds)
353    let days_since_epoch = days_since_unix_epoch(year, month, day);
354    let seconds = days_since_epoch * 86400
355        + u64::from(hour) * 3600
356        + u64::from(minute) * 60
357        + u64::from(second);
358
359    Ok(seconds * 1000)
360}
361
362/// Calculate days since Unix epoch (1970-01-01)
363fn days_since_unix_epoch(year: u32, month: u32, day: u32) -> u64 {
364    let mut days: i64 = 0;
365
366    // Years
367    for y in 1970..year {
368        days += if is_leap_year(y) { 366 } else { 365 };
369    }
370
371    // Months
372    let month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
373    for m in 1..month {
374        days += i64::from(month_days[(m - 1) as usize]);
375        if m == 2 && is_leap_year(year) {
376            days += 1;
377        }
378    }
379
380    // Days
381    days += i64::from(day - 1);
382
383    days.max(0) as u64
384}
385
386/// Check if year is a leap year
387fn is_leap_year(year: u32) -> bool {
388    (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
389}
390
391/// Clock controller for page/context
392#[derive(Debug, Clone)]
393pub struct ClockController {
394    clock: Clock,
395}
396
397impl ClockController {
398    /// Create a new clock controller
399    #[must_use]
400    pub fn new() -> Self {
401        Self {
402            clock: create_clock(),
403        }
404    }
405
406    /// Create with existing clock
407    #[must_use]
408    pub fn with_clock(clock: Clock) -> Self {
409        Self { clock }
410    }
411
412    /// Install fake clock
413    ///
414    /// # Errors
415    ///
416    /// Returns error if already installed
417    pub fn install(&self, options: ClockOptions) -> Result<(), ClockError> {
418        self.clock.install(options)
419    }
420
421    /// Uninstall fake clock
422    pub fn uninstall(&self) {
423        self.clock.uninstall();
424    }
425
426    /// Fast-forward time
427    pub fn fast_forward(&self, duration: Duration) {
428        self.clock.fast_forward(duration);
429    }
430
431    /// Set fixed time
432    pub fn set_fixed_time(&self, time_ms: u64) {
433        self.clock.set_fixed_time(time_ms);
434    }
435
436    /// Set fixed time from ISO string
437    ///
438    /// # Errors
439    ///
440    /// Returns error if parsing fails
441    pub fn set_fixed_time_iso(&self, iso: &str) -> Result<(), ClockError> {
442        self.clock.set_fixed_time_iso(iso)
443    }
444
445    /// Pause at specific time
446    pub fn pause_at(&self, time_ms: u64) {
447        self.clock.pause_at(time_ms);
448    }
449
450    /// Pause clock
451    pub fn pause(&self) {
452        self.clock.pause();
453    }
454
455    /// Resume clock
456    pub fn resume(&self) {
457        self.clock.resume();
458    }
459
460    /// Get current time in milliseconds
461    #[must_use]
462    pub fn now_ms(&self) -> u64 {
463        self.clock.now_ms()
464    }
465
466    /// Get current state
467    #[must_use]
468    pub fn state(&self) -> ClockState {
469        self.clock.state()
470    }
471
472    /// Get inner clock
473    #[must_use]
474    pub fn inner(&self) -> &Clock {
475        &self.clock
476    }
477}
478
479impl Default for ClockController {
480    fn default() -> Self {
481        Self::new()
482    }
483}
484
485#[cfg(test)]
486#[allow(clippy::unwrap_used, clippy::expect_used)]
487mod tests {
488    use super::*;
489
490    // =========================================================================
491    // H₀-CLOCK-01: FakeClock creation
492    // =========================================================================
493
494    #[test]
495    fn h0_clock_01_new() {
496        let clock = FakeClock::new();
497        assert!(!clock.is_installed());
498        assert!(!clock.is_paused());
499    }
500
501    #[test]
502    fn h0_clock_02_state_system_when_not_installed() {
503        let clock = FakeClock::new();
504        assert_eq!(clock.state(), ClockState::System);
505    }
506
507    // =========================================================================
508    // H₀-CLOCK-03: Clock installation
509    // =========================================================================
510
511    #[test]
512    fn h0_clock_03_install_success() {
513        let clock = FakeClock::new();
514        let options = ClockOptions::fixed(1_000_000);
515        clock.install(options).unwrap();
516
517        assert!(clock.is_installed());
518        assert!(clock.is_paused());
519    }
520
521    #[test]
522    fn h0_clock_04_install_already_installed() {
523        let clock = FakeClock::new();
524        clock.install(ClockOptions::now()).unwrap();
525
526        let result = clock.install(ClockOptions::now());
527        assert!(matches!(result, Err(ClockError::AlreadyInstalled)));
528    }
529
530    #[test]
531    fn h0_clock_05_uninstall() {
532        let clock = FakeClock::new();
533        clock.install(ClockOptions::now()).unwrap();
534        clock.uninstall();
535
536        assert!(!clock.is_installed());
537    }
538
539    // =========================================================================
540    // H₀-CLOCK-06: Time retrieval
541    // =========================================================================
542
543    #[test]
544    fn h0_clock_06_now_ms_when_paused() {
545        let clock = FakeClock::new();
546        clock
547            .install(ClockOptions::fixed(1_705_312_800_000))
548            .unwrap(); // 2024-01-15T10:00:00Z
549
550        let time = clock.now_ms();
551        assert_eq!(time, 1_705_312_800_000);
552    }
553
554    #[test]
555    fn h0_clock_07_now_returns_duration() {
556        let clock = FakeClock::new();
557        clock.install(ClockOptions::fixed(1000)).unwrap();
558
559        let duration = clock.now();
560        assert_eq!(duration.as_millis(), 1000);
561    }
562
563    // =========================================================================
564    // H₀-CLOCK-08: Fast forward
565    // =========================================================================
566
567    #[test]
568    fn h0_clock_08_fast_forward() {
569        let clock = FakeClock::new();
570        clock.install(ClockOptions::fixed(1000)).unwrap();
571
572        clock.fast_forward(Duration::from_secs(60));
573
574        assert_eq!(clock.now_ms(), 61_000);
575    }
576
577    #[test]
578    fn h0_clock_09_fast_forward_ms() {
579        let clock = FakeClock::new();
580        clock.install(ClockOptions::fixed(0)).unwrap();
581
582        clock.fast_forward_ms(5000);
583
584        assert_eq!(clock.now_ms(), 5000);
585    }
586
587    // =========================================================================
588    // H₀-CLOCK-10: Pause and resume
589    // =========================================================================
590
591    #[test]
592    fn h0_clock_10_pause() {
593        let clock = FakeClock::new();
594        clock
595            .install(ClockOptions {
596                time_ms: 1000,
597                paused: false,
598            })
599            .unwrap();
600
601        clock.pause();
602
603        assert!(clock.is_paused());
604        assert_eq!(clock.state(), ClockState::Paused);
605    }
606
607    #[test]
608    fn h0_clock_11_resume() {
609        let clock = FakeClock::new();
610        clock.install(ClockOptions::fixed(1000)).unwrap();
611
612        clock.resume();
613
614        assert!(!clock.is_paused());
615        assert_eq!(clock.state(), ClockState::Running);
616    }
617
618    #[test]
619    fn h0_clock_12_pause_at() {
620        let clock = FakeClock::new();
621        clock.install(ClockOptions::now()).unwrap();
622
623        clock.pause_at(5000);
624
625        assert!(clock.is_paused());
626        assert_eq!(clock.now_ms(), 5000);
627    }
628
629    // =========================================================================
630    // H₀-CLOCK-13: Set fixed time
631    // =========================================================================
632
633    #[test]
634    fn h0_clock_13_set_fixed_time() {
635        let clock = FakeClock::new();
636        clock.install(ClockOptions::now()).unwrap();
637
638        clock.set_fixed_time(9999);
639
640        assert!(clock.is_paused());
641        assert_eq!(clock.now_ms(), 9999);
642    }
643
644    #[test]
645    fn h0_clock_14_set_fixed_time_iso() {
646        let clock = FakeClock::new();
647        clock.install(ClockOptions::now()).unwrap();
648
649        clock.set_fixed_time_iso("2024-01-15T12:00:00Z").unwrap();
650
651        assert!(clock.is_paused());
652        // Should be around Jan 15, 2024 12:00:00 UTC
653        assert!(clock.now_ms() > 1_705_000_000_000);
654    }
655
656    // =========================================================================
657    // H₀-CLOCK-15: ClockOptions
658    // =========================================================================
659
660    #[test]
661    fn h0_clock_15_options_now() {
662        let options = ClockOptions::now();
663        assert!(!options.paused);
664        assert!(options.time_ms > 0);
665    }
666
667    #[test]
668    fn h0_clock_16_options_fixed() {
669        let options = ClockOptions::fixed(1234);
670        assert!(options.paused);
671        assert_eq!(options.time_ms, 1234);
672    }
673
674    #[test]
675    fn h0_clock_17_options_from_iso() {
676        let options = ClockOptions::from_iso("2024-01-01T00:00:00Z").unwrap();
677        assert!(options.time_ms > 1_704_000_000_000);
678    }
679
680    #[test]
681    fn h0_clock_18_options_paused_builder() {
682        let options = ClockOptions::now().paused(true);
683        assert!(options.paused);
684    }
685
686    // =========================================================================
687    // H₀-CLOCK-19: ISO parsing
688    // =========================================================================
689
690    #[test]
691    fn h0_clock_19_parse_iso_full() {
692        let ms = parse_iso_to_ms("2024-01-15T10:30:00Z").unwrap();
693        // Should be roughly Jan 15, 2024 10:30:00 UTC
694        assert!(ms > 1_705_000_000_000);
695    }
696
697    #[test]
698    fn h0_clock_20_parse_iso_date_only() {
699        let ms = parse_iso_to_ms("2024-01-15").unwrap();
700        assert!(ms > 1_705_000_000_000);
701    }
702
703    #[test]
704    fn h0_clock_21_parse_iso_invalid() {
705        let result = parse_iso_to_ms("invalid");
706        assert!(result.is_err());
707    }
708
709    // =========================================================================
710    // H₀-CLOCK-22: ClockController
711    // =========================================================================
712
713    #[test]
714    fn h0_clock_22_controller_new() {
715        let controller = ClockController::new();
716        assert_eq!(controller.state(), ClockState::System);
717    }
718
719    #[test]
720    fn h0_clock_23_controller_install() {
721        let controller = ClockController::new();
722        controller.install(ClockOptions::fixed(1000)).unwrap();
723
724        assert_eq!(controller.state(), ClockState::Paused);
725        assert_eq!(controller.now_ms(), 1000);
726    }
727
728    #[test]
729    fn h0_clock_24_controller_fast_forward() {
730        let controller = ClockController::new();
731        controller.install(ClockOptions::fixed(0)).unwrap();
732
733        controller.fast_forward(Duration::from_secs(30));
734
735        assert_eq!(controller.now_ms(), 30_000);
736    }
737
738    #[test]
739    fn h0_clock_25_controller_pause_resume() {
740        let controller = ClockController::new();
741        controller
742            .install(ClockOptions {
743                time_ms: 1000,
744                paused: false,
745            })
746            .unwrap();
747
748        controller.pause();
749        assert_eq!(controller.state(), ClockState::Paused);
750
751        controller.resume();
752        assert_eq!(controller.state(), ClockState::Running);
753    }
754
755    // =========================================================================
756    // H₀-CLOCK-26: Clone
757    // =========================================================================
758
759    #[test]
760    fn h0_clock_26_clone() {
761        let clock = FakeClock::new();
762        clock.install(ClockOptions::fixed(5000)).unwrap();
763
764        let cloned = clock;
765
766        assert!(cloned.is_installed());
767        assert_eq!(cloned.now_ms(), 5000);
768    }
769
770    // =========================================================================
771    // H₀-CLOCK-27: Leap year
772    // =========================================================================
773
774    #[test]
775    fn h0_clock_27_is_leap_year() {
776        assert!(is_leap_year(2000));
777        assert!(is_leap_year(2024));
778        assert!(!is_leap_year(2023));
779        assert!(!is_leap_year(1900));
780    }
781
782    // =========================================================================
783    // H₀-CLOCK-28: Error display
784    // =========================================================================
785
786    #[test]
787    fn h0_clock_28_error_display() {
788        let err = ClockError::InvalidFormat("bad".to_string());
789        assert!(err.to_string().contains("Invalid datetime"));
790
791        let err = ClockError::NotInstalled;
792        assert!(err.to_string().contains("not installed"));
793
794        let err = ClockError::AlreadyInstalled;
795        assert!(err.to_string().contains("already installed"));
796    }
797
798    // =========================================================================
799    // H₀-CLOCK-29: Shared clock
800    // =========================================================================
801
802    #[test]
803    fn h0_clock_29_create_clock() {
804        let clock = create_clock();
805        assert!(!clock.is_installed());
806    }
807
808    #[test]
809    fn h0_clock_30_controller_with_clock() {
810        let clock = create_clock();
811        clock.install(ClockOptions::fixed(1234)).unwrap();
812
813        let controller = ClockController::with_clock(clock);
814        assert_eq!(controller.now_ms(), 1234);
815    }
816
817    // =========================================================================
818    // H₀-CLOCK-31: Default options
819    // =========================================================================
820
821    #[test]
822    fn h0_clock_31_options_default() {
823        let options = ClockOptions::default();
824        // Default is ClockOptions::now() which should have a reasonable timestamp
825        // time_ms should be > 0 for now() or paused should be false
826        assert!(options.time_ms > 0 || !options.paused);
827    }
828
829    // =========================================================================
830    // Additional tests for 95% coverage
831    // =========================================================================
832
833    #[test]
834    fn test_clock_controller_inner() {
835        let controller = ClockController::new();
836        let inner = controller.inner();
837        assert!(!inner.is_installed());
838    }
839
840    #[test]
841    fn test_clock_controller_uninstall() {
842        let controller = ClockController::new();
843        controller.install(ClockOptions::fixed(1000)).unwrap();
844        assert_eq!(controller.state(), ClockState::Paused);
845
846        controller.uninstall();
847        assert_eq!(controller.state(), ClockState::System);
848    }
849
850    #[test]
851    fn test_clock_controller_pause_at() {
852        let controller = ClockController::new();
853        controller.install(ClockOptions::now()).unwrap();
854
855        controller.pause_at(5000);
856        assert_eq!(controller.state(), ClockState::Paused);
857        assert_eq!(controller.now_ms(), 5000);
858    }
859
860    #[test]
861    fn test_clock_controller_set_fixed_time() {
862        let controller = ClockController::new();
863        controller.install(ClockOptions::now()).unwrap();
864
865        controller.set_fixed_time(9999);
866        assert_eq!(controller.now_ms(), 9999);
867    }
868
869    #[test]
870    fn test_clock_controller_set_fixed_time_iso() {
871        let controller = ClockController::new();
872        controller.install(ClockOptions::now()).unwrap();
873
874        controller
875            .set_fixed_time_iso("2024-06-15T12:30:00Z")
876            .unwrap();
877        // Time should be around mid-2024
878        assert!(controller.now_ms() > 1_718_000_000_000);
879    }
880
881    #[test]
882    fn test_clock_controller_set_fixed_time_iso_error() {
883        let controller = ClockController::new();
884        controller.install(ClockOptions::now()).unwrap();
885
886        let result = controller.set_fixed_time_iso("invalid");
887        assert!(result.is_err());
888    }
889
890    #[test]
891    fn test_fake_clock_now_ms_not_installed() {
892        let clock = FakeClock::new();
893        // When not installed, should return system time
894        let time = clock.now_ms();
895        let system_time = SystemTime::now()
896            .duration_since(UNIX_EPOCH)
897            .map(|d| d.as_millis() as u64)
898            .unwrap_or(0);
899
900        // Should be within 1 second of system time
901        assert!((time as i64 - system_time as i64).abs() < 1000);
902    }
903
904    #[test]
905    fn test_fake_clock_now_ms_running() {
906        let clock = FakeClock::new();
907        clock
908            .install(ClockOptions {
909                time_ms: 1000,
910                paused: false, // Running, not paused
911            })
912            .unwrap();
913
914        // Running clock should advance with real time
915        std::thread::sleep(std::time::Duration::from_millis(50));
916        let time = clock.now_ms();
917        // Should be at least 1000 + ~50ms
918        assert!(time >= 1040);
919    }
920
921    #[test]
922    fn test_fake_clock_pause_not_installed() {
923        let clock = FakeClock::new();
924        // Pause on not installed clock should be no-op
925        clock.pause();
926        assert!(!clock.is_paused());
927    }
928
929    #[test]
930    fn test_fake_clock_resume_not_installed() {
931        let clock = FakeClock::new();
932        // Resume on not installed clock should be no-op
933        clock.resume();
934        assert!(!clock.is_paused());
935    }
936
937    #[test]
938    fn test_fake_clock_pause_already_paused() {
939        let clock = FakeClock::new();
940        clock.install(ClockOptions::fixed(1000)).unwrap(); // Already paused
941
942        clock.pause(); // Should be no-op
943        assert!(clock.is_paused());
944        assert_eq!(clock.now_ms(), 1000);
945    }
946
947    #[test]
948    fn test_fake_clock_resume_not_paused() {
949        let clock = FakeClock::new();
950        clock
951            .install(ClockOptions {
952                time_ms: 1000,
953                paused: false,
954            })
955            .unwrap();
956
957        clock.resume(); // Should be no-op since not paused
958        assert!(!clock.is_paused());
959    }
960
961    #[test]
962    fn test_fake_clock_clone() {
963        let clock = FakeClock::new();
964        clock.install(ClockOptions::fixed(5000)).unwrap();
965
966        let cloned = clock;
967        assert!(cloned.is_installed());
968        assert!(cloned.is_paused());
969        assert_eq!(cloned.now_ms(), 5000);
970    }
971
972    #[test]
973    fn test_clock_options_serialize_deserialize() {
974        let options = ClockOptions {
975            time_ms: 1234567890,
976            paused: true,
977        };
978
979        let json = serde_json::to_string(&options).unwrap();
980        let deserialized: ClockOptions = serde_json::from_str(&json).unwrap();
981
982        assert_eq!(deserialized.time_ms, 1234567890);
983        assert!(deserialized.paused);
984    }
985
986    #[test]
987    fn test_clock_state_serialize_deserialize() {
988        let state = ClockState::Paused;
989        let json = serde_json::to_string(&state).unwrap();
990        let deserialized: ClockState = serde_json::from_str(&json).unwrap();
991        assert_eq!(deserialized, ClockState::Paused);
992
993        let state2 = ClockState::Running;
994        let json2 = serde_json::to_string(&state2).unwrap();
995        let deserialized2: ClockState = serde_json::from_str(&json2).unwrap();
996        assert_eq!(deserialized2, ClockState::Running);
997    }
998
999    #[test]
1000    fn test_clock_error_display_invalid_format() {
1001        let err = ClockError::InvalidFormat("bad date".to_string());
1002        let display = err.to_string();
1003        assert!(display.contains("Invalid datetime format"));
1004        assert!(display.contains("bad date"));
1005    }
1006
1007    #[test]
1008    fn test_clock_error_is_error() {
1009        let err: &dyn std::error::Error = &ClockError::NotInstalled;
1010        assert!(err.to_string().contains("not installed"));
1011    }
1012
1013    #[test]
1014    fn test_parse_iso_date_only_without_t() {
1015        let ms = parse_iso_to_ms("2024-03-15").unwrap();
1016        // Should be March 15, 2024 00:00:00 UTC
1017        assert!(ms > 1_710_000_000_000);
1018    }
1019
1020    #[test]
1021    fn test_parse_iso_with_trailing_z() {
1022        let ms1 = parse_iso_to_ms("2024-01-15T10:30:00Z").unwrap();
1023        let ms2 = parse_iso_to_ms("2024-01-15T10:30:00").unwrap();
1024        assert_eq!(ms1, ms2);
1025    }
1026
1027    #[test]
1028    fn test_parse_iso_with_whitespace() {
1029        let ms = parse_iso_to_ms("  2024-01-15T10:30:00Z  ").unwrap();
1030        assert!(ms > 1_705_000_000_000);
1031    }
1032
1033    #[test]
1034    fn test_parse_iso_partial_time() {
1035        // Only hours
1036        let ms = parse_iso_to_ms("2024-01-15T10").unwrap();
1037        assert!(ms > 1_705_000_000_000);
1038    }
1039
1040    #[test]
1041    fn test_days_since_unix_epoch() {
1042        // 1970-01-01 should be day 0
1043        let days = days_since_unix_epoch(1970, 1, 1);
1044        assert_eq!(days, 0);
1045
1046        // 1970-01-02 should be day 1
1047        let days = days_since_unix_epoch(1970, 1, 2);
1048        assert_eq!(days, 1);
1049
1050        // 1971-01-01 should be day 365
1051        let days = days_since_unix_epoch(1971, 1, 1);
1052        assert_eq!(days, 365);
1053    }
1054
1055    #[test]
1056    fn test_days_since_unix_epoch_leap_year() {
1057        // 2000 is a leap year
1058        let days_2000 = days_since_unix_epoch(2000, 12, 31);
1059        let days_2001 = days_since_unix_epoch(2001, 1, 1);
1060        assert_eq!(days_2001 - days_2000, 1);
1061
1062        // Leap year should have 366 days
1063        let start_2000 = days_since_unix_epoch(2000, 1, 1);
1064        let start_2001 = days_since_unix_epoch(2001, 1, 1);
1065        assert_eq!(start_2001 - start_2000, 366);
1066    }
1067
1068    #[test]
1069    fn test_is_leap_year_comprehensive() {
1070        // Standard leap years
1071        assert!(is_leap_year(2020));
1072        assert!(is_leap_year(2024));
1073        assert!(is_leap_year(2028));
1074
1075        // Non-leap years
1076        assert!(!is_leap_year(2019));
1077        assert!(!is_leap_year(2021));
1078        assert!(!is_leap_year(2023));
1079
1080        // Century years
1081        assert!(!is_leap_year(1900)); // Divisible by 100 but not 400
1082        assert!(!is_leap_year(2100));
1083        assert!(is_leap_year(2000)); // Divisible by 400
1084    }
1085
1086    #[test]
1087    fn test_clock_controller_default() {
1088        let controller = ClockController::default();
1089        assert_eq!(controller.state(), ClockState::System);
1090    }
1091
1092    #[test]
1093    fn test_fake_clock_default() {
1094        let clock = FakeClock::default();
1095        assert!(!clock.is_installed());
1096        assert!(!clock.is_paused());
1097    }
1098
1099    #[test]
1100    fn test_fast_forward_preserves_paused_state() {
1101        let clock = FakeClock::new();
1102        clock.install(ClockOptions::fixed(1000)).unwrap();
1103        assert!(clock.is_paused());
1104
1105        clock.fast_forward_ms(500);
1106        assert_eq!(clock.now_ms(), 1500);
1107        // Still paused after fast forward
1108        assert!(clock.is_paused());
1109    }
1110
1111    #[test]
1112    fn test_set_fixed_time_pauses_clock() {
1113        let clock = FakeClock::new();
1114        clock
1115            .install(ClockOptions {
1116                time_ms: 1000,
1117                paused: false,
1118            })
1119            .unwrap();
1120        assert!(!clock.is_paused());
1121
1122        clock.set_fixed_time(5000);
1123        assert!(clock.is_paused());
1124        assert_eq!(clock.now_ms(), 5000);
1125    }
1126
1127    #[test]
1128    fn test_clock_state_equality() {
1129        assert_eq!(ClockState::Running, ClockState::Running);
1130        assert_eq!(ClockState::Paused, ClockState::Paused);
1131        assert_eq!(ClockState::System, ClockState::System);
1132        assert_ne!(ClockState::Running, ClockState::Paused);
1133    }
1134
1135    #[test]
1136    fn test_parse_iso_short_date() {
1137        // Short date without enough parts should error
1138        let result = parse_iso_to_ms("2024-01");
1139        assert!(result.is_err());
1140    }
1141}