1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
20pub struct SyncState {
21 pub local_head: u64,
23 pub remote_head: u64,
25 pub last_push: Option<DateTime<Utc>>,
27 pub last_pull: Option<DateTime<Utc>>,
29 pub pending_count: usize,
31}
32
33impl SyncState {
34 #[must_use]
36 pub const fn lag(&self) -> u64 {
37 self.remote_head.saturating_sub(self.local_head)
38 }
39
40 #[must_use]
42 pub const fn is_synced(&self) -> bool {
43 self.local_head >= self.remote_head && self.pending_count == 0
44 }
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct SyncStatus {
52 pub initialized: bool,
54 pub local_head: u64,
56 pub remote_head: u64,
58 pub pending: usize,
60 pub lag: u64,
62 pub last_push: Option<DateTime<Utc>>,
64 pub last_pull: Option<DateTime<Utc>>,
66 pub buffered_events: usize,
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn default_state() {
76 let state = SyncState::default();
77 assert_eq!(state.local_head, 0);
78 assert_eq!(state.remote_head, 0);
79 assert!(state.last_push.is_none());
80 assert!(state.last_pull.is_none());
81 assert_eq!(state.pending_count, 0);
82 }
83
84 #[test]
85 fn lag_calculation() {
86 let state = SyncState { local_head: 5, remote_head: 15, ..Default::default() };
87 assert_eq!(state.lag(), 10);
88 }
89
90 #[test]
91 fn lag_when_local_ahead() {
92 let state = SyncState { local_head: 20, remote_head: 15, ..Default::default() };
93 assert_eq!(state.lag(), 0);
94 }
95
96 #[test]
97 fn is_synced_when_equal() {
98 let state =
99 SyncState { local_head: 10, remote_head: 10, pending_count: 0, ..Default::default() };
100 assert!(state.is_synced());
101 }
102
103 #[test]
104 fn not_synced_with_pending() {
105 let state =
106 SyncState { local_head: 10, remote_head: 10, pending_count: 3, ..Default::default() };
107 assert!(!state.is_synced());
108 }
109
110 #[test]
111 fn not_synced_when_behind() {
112 let state =
113 SyncState { local_head: 5, remote_head: 10, pending_count: 0, ..Default::default() };
114 assert!(!state.is_synced());
115 }
116
117 #[test]
118 fn state_serde_roundtrip() {
119 let state = SyncState {
120 local_head: 42,
121 remote_head: 100,
122 last_push: Some(Utc::now()),
123 last_pull: Some(Utc::now()),
124 pending_count: 7,
125 };
126 let json = serde_json::to_string(&state).unwrap();
127 let deserialized: SyncState = serde_json::from_str(&json).unwrap();
128 assert_eq!(deserialized.local_head, state.local_head);
129 assert_eq!(deserialized.remote_head, state.remote_head);
130 assert_eq!(deserialized.pending_count, state.pending_count);
131 }
132
133 #[test]
134 fn state_clone_eq() {
135 let state =
136 SyncState { local_head: 10, remote_head: 20, pending_count: 5, ..Default::default() };
137 let cloned = state.clone();
138 assert_eq!(state, cloned);
139 }
140
141 #[test]
142 fn sync_status_debug() {
143 let status = SyncStatus {
144 initialized: true,
145 local_head: 10,
146 remote_head: 20,
147 pending: 5,
148 lag: 10,
149 last_push: None,
150 last_pull: None,
151 buffered_events: 3,
152 };
153 let debug = format!("{status:?}");
154 assert!(debug.contains("SyncStatus"));
155 }
156}