Skip to main content

bsv_wallet_toolbox/monitor/tasks/
task_clock.rs

1//! TaskClock -- periodic heartbeat at minute boundaries.
2//!
3//! Translated from wallet-toolbox/src/monitor/tasks/TaskClock.ts.
4
5use async_trait::async_trait;
6
7use crate::error::WalletError;
8use crate::monitor::helpers::now_msecs;
9use crate::monitor::task_trait::WalletMonitorTask;
10use crate::monitor::ONE_MINUTE;
11
12/// A simple heartbeat task that fires at minute boundaries.
13///
14/// Returns the current timestamp as a log string each time it runs.
15pub struct TaskClock {
16    /// Next minute boundary (epoch ms).
17    next_minute: u64,
18}
19
20impl TaskClock {
21    /// Create a new clock task.
22    pub fn new() -> Self {
23        Self {
24            next_minute: Self::get_next_minute(),
25        }
26    }
27
28    fn get_next_minute() -> u64 {
29        let now = now_msecs();
30        // Ceiling division to next minute boundary
31        ((now / ONE_MINUTE) + 1) * ONE_MINUTE
32    }
33}
34
35#[async_trait]
36impl WalletMonitorTask for TaskClock {
37    fn name(&self) -> &str {
38        "Clock"
39    }
40
41    fn trigger(&mut self, _now_msecs: u64) -> bool {
42        now_msecs() > self.next_minute
43    }
44
45    async fn run_task(&mut self) -> Result<String, WalletError> {
46        let dt =
47            chrono::DateTime::from_timestamp_millis(self.next_minute as i64).unwrap_or_default();
48        let log = dt.to_rfc3339();
49        self.next_minute = Self::get_next_minute();
50        Ok(log)
51    }
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    #[test]
59    fn test_clock_trigger_before_next_minute() {
60        let mut clock = TaskClock::new();
61        // next_minute is in the future, so trigger should be false now
62        assert!(
63            clock.next_minute > now_msecs(),
64            "next_minute should be in the future"
65        );
66        assert!(!clock.trigger(now_msecs()));
67    }
68
69    #[tokio::test]
70    async fn test_clock_run_task_returns_timestamp() {
71        let mut clock = TaskClock::new();
72        let result = clock.run_task().await.unwrap();
73        // Should be an ISO timestamp string
74        assert!(!result.is_empty());
75        assert!(
76            result.contains("T"),
77            "Expected ISO timestamp, got: {}",
78            result
79        );
80    }
81
82    #[test]
83    fn test_clock_name() {
84        let clock = TaskClock::new();
85        assert_eq!(clock.name(), "Clock");
86    }
87}