aquarium_control/utilities/
database_ping_trait.rs1use crate::database::pingable::Pingable;
2use crate::food::feed::Feed;
3use crate::mineral::balling::Balling;
4use crate::permission::schedule_check::ScheduleCheck;
5use crate::recorder::data_logger::DataLogger;
6use crate::thermal::heating::Heating;
7use crate::water::refill::Refill;
8use std::time::{Duration, Instant};
9
10pub trait DatabasePingTrait {
11 fn get_ping_interval(&self) -> Duration;
13
14 fn get_last_ping_instant(&self) -> Instant;
16
17 fn update_last_ping_instant(&mut self);
19
20 fn check_timing_and_ping_database(
22 &mut self,
23 database_interface: &mut (impl Pingable + ?Sized),
24 ) {
25 if self.get_last_ping_instant().elapsed() > self.get_ping_interval() {
26 database_interface.ping();
27 self.update_last_ping_instant();
28 }
29 }
30}
31
32impl DatabasePingTrait for DataLogger {
33 fn get_ping_interval(&self) -> Duration {
34 self.database_ping_interval
35 }
36
37 fn get_last_ping_instant(&self) -> Instant {
38 self.last_ping_instant
39 }
40
41 fn update_last_ping_instant(&mut self) {
42 self.last_ping_instant = Instant::now();
43 }
44}
45
46impl DatabasePingTrait for Feed {
47 fn get_ping_interval(&self) -> Duration {
48 self.database_ping_interval
49 }
50
51 fn get_last_ping_instant(&self) -> Instant {
52 self.last_ping_instant
53 }
54
55 fn update_last_ping_instant(&mut self) {
56 self.last_ping_instant = Instant::now();
57 }
58}
59
60impl DatabasePingTrait for Refill {
61 fn get_ping_interval(&self) -> Duration {
62 self.database_ping_interval
63 }
64
65 fn get_last_ping_instant(&self) -> Instant {
66 self.last_ping_instant
67 }
68
69 fn update_last_ping_instant(&mut self) {
70 self.last_ping_instant = Instant::now();
71 }
72}
73
74impl DatabasePingTrait for Heating {
75 fn get_ping_interval(&self) -> Duration {
76 self.database_ping_interval
77 }
78
79 fn get_last_ping_instant(&self) -> Instant {
80 self.last_ping_instant
81 }
82
83 fn update_last_ping_instant(&mut self) {
84 self.last_ping_instant = Instant::now();
85 }
86}
87
88impl DatabasePingTrait for Balling {
89 fn get_ping_interval(&self) -> Duration {
90 self.database_ping_interval
91 }
92
93 fn get_last_ping_instant(&self) -> Instant {
94 self.last_ping_instant
95 }
96
97 fn update_last_ping_instant(&mut self) {
98 self.last_ping_instant = Instant::now();
99 }
100}
101
102impl DatabasePingTrait for ScheduleCheck {
103 fn get_ping_interval(&self) -> Duration {
104 self.database_ping_interval
105 }
106
107 fn get_last_ping_instant(&self) -> Instant {
108 self.last_ping_instant
109 }
110
111 fn update_last_ping_instant(&mut self) {
112 self.last_ping_instant = Instant::now();
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 struct MockPingable {
123 ping_was_called: bool,
124 }
125
126 impl MockPingable {
127 fn new() -> Self {
128 Self {
129 ping_was_called: false,
130 }
131 }
132 }
133
134 impl Pingable for MockPingable {
135 fn ping(&mut self) {
136 self.ping_was_called = true;
137 }
138 }
139
140 struct MockPinger {
143 database_ping_interval: Duration,
144 last_ping_instant: Instant,
145 }
146
147 impl DatabasePingTrait for MockPinger {
148 fn get_ping_interval(&self) -> Duration {
149 self.database_ping_interval
150 }
151
152 fn get_last_ping_instant(&self) -> Instant {
153 self.last_ping_instant
154 }
155
156 fn update_last_ping_instant(&mut self) {
157 self.last_ping_instant = Instant::now();
158 }
159 }
160
161 #[test]
162 fn test_ping_not_needed_when_interval_not_elapsed() {
163 let mut pinger = MockPinger {
165 database_ping_interval: Duration::from_secs(10),
166 last_ping_instant: Instant::now(),
167 };
168 let mut pingable = MockPingable::new();
169
170 pinger.check_timing_and_ping_database(&mut pingable);
172
173 assert!(
175 !pingable.ping_was_called,
176 "Ping should not have been called as the interval has not elapsed."
177 );
178 }
179
180 #[test]
181 fn test_ping_is_needed_when_interval_has_elapsed() {
182 let interval = Duration::from_millis(10);
184 let mut pinger = MockPinger {
185 database_ping_interval: interval,
186 last_ping_instant: Instant::now() - (interval + Duration::from_millis(5)),
187 };
188 let mut pingable = MockPingable::new();
189
190 pinger.check_timing_and_ping_database(&mut pingable);
192
193 assert!(
195 pingable.ping_was_called,
196 "Ping should have been called because the interval has elapsed."
197 );
198 }
199
200 #[test]
201 fn test_ping_timer_is_reset_after_pinging() {
202 let interval = Duration::from_millis(10);
204 let mut pinger = MockPinger {
205 database_ping_interval: interval,
206 last_ping_instant: Instant::now() - (interval + Duration::from_millis(5)),
207 };
208 let mut pingable = MockPingable::new();
209
210 pinger.check_timing_and_ping_database(&mut pingable);
212
213 assert!(
215 pingable.ping_was_called,
216 "Ping should have been called the first time."
217 );
218
219 pingable.ping_was_called = false;
221
222 pinger.check_timing_and_ping_database(&mut pingable);
224
225 assert!(
227 !pingable.ping_was_called,
228 "Ping should not be called again immediately, as the timer should have been reset."
229 );
230 }
231}