1use std::sync::atomic::{AtomicBool, AtomicI16, AtomicU32, AtomicU64, Ordering};
16use std::time::Duration;
17
18use async_trait::async_trait;
19use tokio::sync::RwLock;
20
21use aranet_types::{CurrentReading, DeviceInfo, DeviceType, HistoryRecord, Status};
22
23use crate::error::{Error, Result};
24use crate::history::{HistoryInfo, HistoryOptions};
25use crate::settings::{CalibrationData, MeasurementInterval};
26use crate::traits::AranetDevice;
27
28pub struct MockDevice {
51 name: String,
52 address: String,
53 device_type: DeviceType,
54 connected: AtomicBool,
55 current_reading: RwLock<CurrentReading>,
56 device_info: RwLock<DeviceInfo>,
57 history: RwLock<Vec<HistoryRecord>>,
58 interval: RwLock<MeasurementInterval>,
59 calibration: RwLock<CalibrationData>,
60 battery: RwLock<u8>,
61 rssi: AtomicI16,
62 read_count: AtomicU32,
63 should_fail: AtomicBool,
64 fail_message: RwLock<String>,
65 read_latency_ms: AtomicU64,
67 connect_latency_ms: AtomicU64,
69 fail_count: AtomicU32,
71 remaining_failures: AtomicU32,
73}
74
75impl std::fmt::Debug for MockDevice {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 f.debug_struct("MockDevice")
78 .field("name", &self.name)
79 .field("address", &self.address)
80 .field("device_type", &self.device_type)
81 .field("connected", &self.connected.load(Ordering::Relaxed))
82 .finish()
83 }
84}
85
86impl MockDevice {
87 pub fn new(name: &str, device_type: DeviceType) -> Self {
89 Self {
90 name: name.to_string(),
91 address: format!("MOCK-{:06X}", rand::random::<u32>() % 0xFFFFFF),
92 device_type,
93 connected: AtomicBool::new(false),
94 current_reading: RwLock::new(Self::default_reading()),
95 device_info: RwLock::new(Self::default_info(name)),
96 history: RwLock::new(Vec::new()),
97 interval: RwLock::new(MeasurementInterval::FiveMinutes),
98 calibration: RwLock::new(CalibrationData::default()),
99 battery: RwLock::new(85),
100 rssi: AtomicI16::new(-50),
101 read_count: AtomicU32::new(0),
102 should_fail: AtomicBool::new(false),
103 fail_message: RwLock::new("Mock failure".to_string()),
104 read_latency_ms: AtomicU64::new(0),
105 connect_latency_ms: AtomicU64::new(0),
106 fail_count: AtomicU32::new(0),
107 remaining_failures: AtomicU32::new(0),
108 }
109 }
110
111 fn default_reading() -> CurrentReading {
112 CurrentReading {
113 co2: 800,
114 temperature: 22.5,
115 pressure: 1013.2,
116 humidity: 50,
117 battery: 85,
118 status: Status::Green,
119 interval: 300,
120 age: 60,
121 captured_at: None,
122 radon: None,
123 radiation_rate: None,
124 radiation_total: None,
125 radon_avg_24h: None,
126 radon_avg_7d: None,
127 radon_avg_30d: None,
128 }
129 }
130
131 fn default_info(name: &str) -> DeviceInfo {
132 DeviceInfo {
133 name: name.to_string(),
134 model: "Aranet4".to_string(),
135 serial: "MOCK-12345".to_string(),
136 firmware: "v1.5.0".to_string(),
137 hardware: "1.0".to_string(),
138 software: "1.5.0".to_string(),
139 manufacturer: "SAF Tehnika".to_string(),
140 }
141 }
142
143 pub async fn connect(&self) -> Result<()> {
145 use crate::error::DeviceNotFoundReason;
146
147 let latency = self.connect_latency_ms.load(Ordering::Relaxed);
149 if latency > 0 {
150 tokio::time::sleep(Duration::from_millis(latency)).await;
151 }
152
153 if self.remaining_failures.load(Ordering::Relaxed) > 0 {
155 self.remaining_failures.fetch_sub(1, Ordering::Relaxed);
156 return Err(Error::DeviceNotFound(DeviceNotFoundReason::NotFound {
157 identifier: self.name.clone(),
158 }));
159 }
160
161 if self.should_fail.load(Ordering::Relaxed) {
162 return Err(Error::DeviceNotFound(DeviceNotFoundReason::NotFound {
163 identifier: self.name.clone(),
164 }));
165 }
166 self.connected.store(true, Ordering::Relaxed);
167 Ok(())
168 }
169
170 pub async fn disconnect(&self) -> Result<()> {
172 self.connected.store(false, Ordering::Relaxed);
173 Ok(())
174 }
175
176 pub fn is_connected_sync(&self) -> bool {
178 self.connected.load(Ordering::Relaxed)
179 }
180
181 pub fn name(&self) -> &str {
183 &self.name
184 }
185
186 pub fn address(&self) -> &str {
188 &self.address
189 }
190
191 pub fn device_type(&self) -> DeviceType {
193 self.device_type
194 }
195
196 pub async fn read_current(&self) -> Result<CurrentReading> {
198 self.check_connected()?;
199 self.check_should_fail().await?;
200
201 self.read_count.fetch_add(1, Ordering::Relaxed);
202 Ok(*self.current_reading.read().await)
203 }
204
205 pub async fn read_battery(&self) -> Result<u8> {
207 self.check_connected()?;
208 self.check_should_fail().await?;
209 Ok(*self.battery.read().await)
210 }
211
212 pub async fn read_rssi(&self) -> Result<i16> {
214 self.check_connected()?;
215 self.check_should_fail().await?;
216 Ok(self.rssi.load(Ordering::Relaxed))
217 }
218
219 pub async fn read_device_info(&self) -> Result<DeviceInfo> {
221 self.check_connected()?;
222 self.check_should_fail().await?;
223 Ok(self.device_info.read().await.clone())
224 }
225
226 pub async fn get_history_info(&self) -> Result<HistoryInfo> {
228 self.check_connected()?;
229 self.check_should_fail().await?;
230
231 let history = self.history.read().await;
232 let interval = self.interval.read().await;
233
234 Ok(HistoryInfo {
235 total_readings: history.len() as u16,
236 interval_seconds: interval.as_seconds(),
237 seconds_since_update: 60,
238 })
239 }
240
241 pub async fn download_history(&self) -> Result<Vec<HistoryRecord>> {
243 self.check_connected()?;
244 self.check_should_fail().await?;
245 Ok(self.history.read().await.clone())
246 }
247
248 pub async fn download_history_with_options(
250 &self,
251 options: HistoryOptions,
252 ) -> Result<Vec<HistoryRecord>> {
253 self.check_connected()?;
254 self.check_should_fail().await?;
255
256 let history = self.history.read().await;
257 let start = options.start_index.unwrap_or(0) as usize;
258 let end = options
259 .end_index
260 .map(|e| e as usize)
261 .unwrap_or(history.len());
262
263 if let Some(ref _callback) = options.progress_callback {
265 let progress = crate::history::HistoryProgress::new(
267 crate::history::HistoryParam::Co2,
268 1,
269 1,
270 history.len().min(end).saturating_sub(start),
271 );
272 options.report_progress(&progress);
273 }
274
275 Ok(history
276 .iter()
277 .skip(start)
278 .take(end.saturating_sub(start))
279 .cloned()
280 .collect())
281 }
282
283 pub async fn get_interval(&self) -> Result<MeasurementInterval> {
285 self.check_connected()?;
286 self.check_should_fail().await?;
287 Ok(*self.interval.read().await)
288 }
289
290 pub async fn set_interval(&self, interval: MeasurementInterval) -> Result<()> {
292 self.check_connected()?;
293 self.check_should_fail().await?;
294 *self.interval.write().await = interval;
295 Ok(())
296 }
297
298 pub async fn get_calibration(&self) -> Result<CalibrationData> {
300 self.check_connected()?;
301 self.check_should_fail().await?;
302 Ok(self.calibration.read().await.clone())
303 }
304
305 fn check_connected(&self) -> Result<()> {
306 if !self.connected.load(Ordering::Relaxed) {
307 Err(Error::NotConnected)
308 } else {
309 Ok(())
310 }
311 }
312
313 async fn check_should_fail(&self) -> Result<()> {
314 let latency = self.read_latency_ms.load(Ordering::Relaxed);
316 if latency > 0 {
317 tokio::time::sleep(Duration::from_millis(latency)).await;
318 }
319
320 if self.remaining_failures.load(Ordering::Relaxed) > 0 {
322 self.remaining_failures.fetch_sub(1, Ordering::Relaxed);
323 return Err(Error::InvalidData(self.fail_message.read().await.clone()));
324 }
325
326 if self.should_fail.load(Ordering::Relaxed) {
327 Err(Error::InvalidData(self.fail_message.read().await.clone()))
328 } else {
329 Ok(())
330 }
331 }
332
333 pub async fn set_reading(&self, reading: CurrentReading) {
337 *self.current_reading.write().await = reading;
338 }
339
340 pub async fn set_co2(&self, co2: u16) {
342 self.current_reading.write().await.co2 = co2;
343 }
344
345 pub async fn set_temperature(&self, temp: f32) {
347 self.current_reading.write().await.temperature = temp;
348 }
349
350 pub async fn set_battery(&self, level: u8) {
352 *self.battery.write().await = level;
353 self.current_reading.write().await.battery = level;
354 }
355
356 pub async fn set_radon(&self, radon: u32) {
358 self.current_reading.write().await.radon = Some(radon);
359 }
360
361 pub async fn set_radon_averages(&self, avg_24h: u32, avg_7d: u32, avg_30d: u32) {
363 let mut reading = self.current_reading.write().await;
364 reading.radon_avg_24h = Some(avg_24h);
365 reading.radon_avg_7d = Some(avg_7d);
366 reading.radon_avg_30d = Some(avg_30d);
367 }
368
369 pub async fn set_radiation(&self, rate: f32, total: f64) {
371 let mut reading = self.current_reading.write().await;
372 reading.radiation_rate = Some(rate);
373 reading.radiation_total = Some(total);
374 }
375
376 pub fn set_rssi(&self, rssi: i16) {
378 self.rssi.store(rssi, Ordering::Relaxed);
379 }
380
381 pub async fn add_history(&self, records: Vec<HistoryRecord>) {
383 self.history.write().await.extend(records);
384 }
385
386 pub async fn set_should_fail(&self, fail: bool, message: Option<&str>) {
388 self.should_fail.store(fail, Ordering::Relaxed);
389 if let Some(msg) = message {
390 *self.fail_message.write().await = msg.to_string();
391 }
392 }
393
394 pub fn read_count(&self) -> u32 {
396 self.read_count.load(Ordering::Relaxed)
397 }
398
399 pub fn reset_read_count(&self) {
401 self.read_count.store(0, Ordering::Relaxed);
402 }
403
404 pub fn set_read_latency(&self, latency: Duration) {
409 self.read_latency_ms
410 .store(latency.as_millis() as u64, Ordering::Relaxed);
411 }
412
413 pub fn set_connect_latency(&self, latency: Duration) {
418 self.connect_latency_ms
419 .store(latency.as_millis() as u64, Ordering::Relaxed);
420 }
421
422 pub fn set_transient_failures(&self, count: u32) {
438 self.fail_count.store(count, Ordering::Relaxed);
439 self.remaining_failures.store(count, Ordering::Relaxed);
440 }
441
442 pub fn reset_transient_failures(&self) {
444 self.remaining_failures
445 .store(self.fail_count.load(Ordering::Relaxed), Ordering::Relaxed);
446 }
447
448 pub fn remaining_failures(&self) -> u32 {
450 self.remaining_failures.load(Ordering::Relaxed)
451 }
452}
453
454#[async_trait]
456impl AranetDevice for MockDevice {
457 async fn is_connected(&self) -> bool {
460 self.is_connected_sync()
461 }
462
463 async fn disconnect(&self) -> Result<()> {
464 MockDevice::disconnect(self).await
465 }
466
467 fn name(&self) -> Option<&str> {
470 Some(MockDevice::name(self))
471 }
472
473 fn address(&self) -> &str {
474 MockDevice::address(self)
475 }
476
477 fn device_type(&self) -> Option<DeviceType> {
478 Some(MockDevice::device_type(self))
479 }
480
481 async fn read_current(&self) -> Result<CurrentReading> {
484 MockDevice::read_current(self).await
485 }
486
487 async fn read_device_info(&self) -> Result<DeviceInfo> {
488 MockDevice::read_device_info(self).await
489 }
490
491 async fn read_rssi(&self) -> Result<i16> {
492 MockDevice::read_rssi(self).await
493 }
494
495 async fn read_battery(&self) -> Result<u8> {
498 MockDevice::read_battery(self).await
499 }
500
501 async fn get_history_info(&self) -> Result<crate::history::HistoryInfo> {
504 MockDevice::get_history_info(self).await
505 }
506
507 async fn download_history(&self) -> Result<Vec<HistoryRecord>> {
508 MockDevice::download_history(self).await
509 }
510
511 async fn download_history_with_options(
512 &self,
513 options: HistoryOptions,
514 ) -> Result<Vec<HistoryRecord>> {
515 MockDevice::download_history_with_options(self, options).await
516 }
517
518 async fn get_interval(&self) -> Result<MeasurementInterval> {
521 MockDevice::get_interval(self).await
522 }
523
524 async fn set_interval(&self, interval: MeasurementInterval) -> Result<()> {
525 MockDevice::set_interval(self, interval).await
526 }
527
528 async fn get_calibration(&self) -> Result<CalibrationData> {
529 MockDevice::get_calibration(self).await
530 }
531}
532
533#[derive(Debug)]
535pub struct MockDeviceBuilder {
536 name: String,
537 device_type: DeviceType,
538 co2: u16,
539 temperature: f32,
540 pressure: f32,
541 humidity: u8,
542 battery: u8,
543 status: Status,
544 auto_connect: bool,
545 radon: Option<u32>,
546 radon_avg_24h: Option<u32>,
547 radon_avg_7d: Option<u32>,
548 radon_avg_30d: Option<u32>,
549 radiation_rate: Option<f32>,
550 radiation_total: Option<f64>,
551}
552
553impl Default for MockDeviceBuilder {
554 fn default() -> Self {
555 Self {
556 name: "Mock Aranet4".to_string(),
557 device_type: DeviceType::Aranet4,
558 co2: 800,
559 temperature: 22.5,
560 pressure: 1013.2,
561 humidity: 50,
562 battery: 85,
563 status: Status::Green,
564 auto_connect: true,
565 radon: None,
566 radon_avg_24h: None,
567 radon_avg_7d: None,
568 radon_avg_30d: None,
569 radiation_rate: None,
570 radiation_total: None,
571 }
572 }
573}
574
575impl MockDeviceBuilder {
576 #[must_use]
578 pub fn new() -> Self {
579 Self::default()
580 }
581
582 #[must_use]
584 pub fn name(mut self, name: &str) -> Self {
585 self.name = name.to_string();
586 self
587 }
588
589 #[must_use]
591 pub fn device_type(mut self, device_type: DeviceType) -> Self {
592 self.device_type = device_type;
593 self
594 }
595
596 #[must_use]
598 pub fn co2(mut self, co2: u16) -> Self {
599 self.co2 = co2;
600 self
601 }
602
603 #[must_use]
605 pub fn temperature(mut self, temp: f32) -> Self {
606 self.temperature = temp;
607 self
608 }
609
610 #[must_use]
612 pub fn pressure(mut self, pressure: f32) -> Self {
613 self.pressure = pressure;
614 self
615 }
616
617 #[must_use]
619 pub fn humidity(mut self, humidity: u8) -> Self {
620 self.humidity = humidity;
621 self
622 }
623
624 #[must_use]
626 pub fn battery(mut self, battery: u8) -> Self {
627 self.battery = battery;
628 self
629 }
630
631 #[must_use]
633 pub fn status(mut self, status: Status) -> Self {
634 self.status = status;
635 self
636 }
637
638 #[must_use]
640 pub fn auto_connect(mut self, auto: bool) -> Self {
641 self.auto_connect = auto;
642 self
643 }
644
645 #[must_use]
647 pub fn radon(mut self, radon: u32) -> Self {
648 self.radon = Some(radon);
649 self
650 }
651
652 #[must_use]
654 pub fn radon_avg_24h(mut self, avg: u32) -> Self {
655 self.radon_avg_24h = Some(avg);
656 self
657 }
658
659 #[must_use]
661 pub fn radon_avg_7d(mut self, avg: u32) -> Self {
662 self.radon_avg_7d = Some(avg);
663 self
664 }
665
666 #[must_use]
668 pub fn radon_avg_30d(mut self, avg: u32) -> Self {
669 self.radon_avg_30d = Some(avg);
670 self
671 }
672
673 #[must_use]
675 pub fn radiation_rate(mut self, rate: f32) -> Self {
676 self.radiation_rate = Some(rate);
677 self
678 }
679
680 #[must_use]
682 pub fn radiation_total(mut self, total: f64) -> Self {
683 self.radiation_total = Some(total);
684 self
685 }
686
687 #[must_use]
692 pub fn build(self) -> MockDevice {
693 let reading = CurrentReading {
694 co2: self.co2,
695 temperature: self.temperature,
696 pressure: self.pressure,
697 humidity: self.humidity,
698 battery: self.battery,
699 status: self.status,
700 interval: 300,
701 age: 60,
702 captured_at: None,
703 radon: self.radon,
704 radiation_rate: self.radiation_rate,
705 radiation_total: self.radiation_total,
706 radon_avg_24h: self.radon_avg_24h,
707 radon_avg_7d: self.radon_avg_7d,
708 radon_avg_30d: self.radon_avg_30d,
709 };
710
711 MockDevice {
712 name: self.name.clone(),
713 address: format!("MOCK-{:06X}", rand::random::<u32>() % 0xFFFFFF),
714 device_type: self.device_type,
715 connected: AtomicBool::new(self.auto_connect),
716 current_reading: RwLock::new(reading),
717 device_info: RwLock::new(MockDevice::default_info(&self.name)),
718 history: RwLock::new(Vec::new()),
719 interval: RwLock::new(MeasurementInterval::FiveMinutes),
720 calibration: RwLock::new(CalibrationData::default()),
721 battery: RwLock::new(self.battery),
722 rssi: AtomicI16::new(-50),
723 read_count: AtomicU32::new(0),
724 should_fail: AtomicBool::new(false),
725 fail_message: RwLock::new("Mock failure".to_string()),
726 read_latency_ms: AtomicU64::new(0),
727 connect_latency_ms: AtomicU64::new(0),
728 fail_count: AtomicU32::new(0),
729 remaining_failures: AtomicU32::new(0),
730 }
731 }
732}
733
734#[cfg(test)]
779mod tests {
780 use super::*;
781 use crate::traits::AranetDevice;
782
783 #[tokio::test]
784 async fn test_mock_device_connect() {
785 let device = MockDevice::new("Test", DeviceType::Aranet4);
786 assert!(!device.is_connected_sync());
787
788 device.connect().await.unwrap();
789 assert!(device.is_connected_sync());
790
791 device.disconnect().await.unwrap();
792 assert!(!device.is_connected_sync());
793 }
794
795 #[tokio::test]
796 async fn test_mock_device_read() {
797 let device = MockDeviceBuilder::new().co2(1200).temperature(25.0).build();
798
799 let reading = device.read_current().await.unwrap();
800 assert_eq!(reading.co2, 1200);
801 assert!((reading.temperature - 25.0).abs() < 0.01);
802 }
803
804 #[tokio::test]
805 async fn test_mock_device_fail() {
806 let device = MockDeviceBuilder::new().build();
807 device.set_should_fail(true, Some("Test error")).await;
808
809 let result = device.read_current().await;
810 assert!(result.is_err());
811 assert!(result.unwrap_err().to_string().contains("Test error"));
812 }
813
814 #[tokio::test]
815 async fn test_mock_device_not_connected() {
816 let device = MockDeviceBuilder::new().auto_connect(false).build();
817
818 let result = device.read_current().await;
819 assert!(matches!(result, Err(Error::NotConnected)));
820 }
821
822 #[test]
823 fn test_builder_defaults() {
824 let device = MockDeviceBuilder::new().build();
825 assert!(device.is_connected_sync());
826 assert_eq!(device.device_type(), DeviceType::Aranet4);
827 }
828
829 #[tokio::test]
830 async fn test_aranet_device_trait() {
831 let device = MockDeviceBuilder::new().co2(1000).build();
832
833 async fn check_via_trait<D: AranetDevice>(d: &D) -> u16 {
835 d.read_current().await.unwrap().co2
836 }
837
838 assert_eq!(check_via_trait(&device).await, 1000);
839 }
840
841 #[tokio::test]
842 async fn test_mock_device_read_battery() {
843 let device = MockDeviceBuilder::new().battery(75).build();
844 let battery = device.read_battery().await.unwrap();
845 assert_eq!(battery, 75);
846 }
847
848 #[tokio::test]
849 async fn test_mock_device_read_rssi() {
850 let device = MockDeviceBuilder::new().build();
851 device.set_rssi(-65);
852 let rssi = device.read_rssi().await.unwrap();
853 assert_eq!(rssi, -65);
854 }
855
856 #[tokio::test]
857 async fn test_mock_device_read_device_info() {
858 let device = MockDeviceBuilder::new().name("Test Device").build();
859 let info = device.read_device_info().await.unwrap();
860 assert_eq!(info.name, "Test Device");
861 assert_eq!(info.manufacturer, "SAF Tehnika");
862 }
863
864 #[tokio::test]
865 async fn test_mock_device_history() {
866 let device = MockDeviceBuilder::new().build();
867
868 let history = device.download_history().await.unwrap();
870 assert!(history.is_empty());
871
872 let records = vec![
874 HistoryRecord {
875 timestamp: time::OffsetDateTime::now_utc(),
876 co2: 800,
877 temperature: 22.5,
878 pressure: 1013.2,
879 humidity: 50,
880 radon: None,
881 radiation_rate: None,
882 radiation_total: None,
883 },
884 HistoryRecord {
885 timestamp: time::OffsetDateTime::now_utc(),
886 co2: 850,
887 temperature: 23.0,
888 pressure: 1013.5,
889 humidity: 48,
890 radon: None,
891 radiation_rate: None,
892 radiation_total: None,
893 },
894 ];
895 device.add_history(records).await;
896
897 let history = device.download_history().await.unwrap();
898 assert_eq!(history.len(), 2);
899 assert_eq!(history[0].co2, 800);
900 assert_eq!(history[1].co2, 850);
901 }
902
903 #[tokio::test]
904 async fn test_mock_device_history_with_options() {
905 let device = MockDeviceBuilder::new().build();
906
907 let records: Vec<HistoryRecord> = (0..5)
909 .map(|i| HistoryRecord {
910 timestamp: time::OffsetDateTime::now_utc(),
911 co2: 800 + i as u16 * 10,
912 temperature: 22.0,
913 pressure: 1013.0,
914 humidity: 50,
915 radon: None,
916 radiation_rate: None,
917 radiation_total: None,
918 })
919 .collect();
920 device.add_history(records).await;
921
922 let options = HistoryOptions {
924 start_index: Some(1),
925 end_index: Some(4),
926 ..Default::default()
927 };
928 let history = device.download_history_with_options(options).await.unwrap();
929 assert_eq!(history.len(), 3);
930 assert_eq!(history[0].co2, 810); assert_eq!(history[2].co2, 830); }
933
934 #[tokio::test]
935 async fn test_mock_device_interval() {
936 let device = MockDeviceBuilder::new().build();
937
938 let interval = device.get_interval().await.unwrap();
939 assert_eq!(interval, MeasurementInterval::FiveMinutes);
940
941 device
942 .set_interval(MeasurementInterval::TenMinutes)
943 .await
944 .unwrap();
945 let interval = device.get_interval().await.unwrap();
946 assert_eq!(interval, MeasurementInterval::TenMinutes);
947 }
948
949 #[tokio::test]
950 async fn test_mock_device_calibration() {
951 let device = MockDeviceBuilder::new().build();
952 let calibration = device.get_calibration().await.unwrap();
953 assert!(calibration.co2_offset.is_some() || calibration.co2_offset.is_none());
955 }
956
957 #[tokio::test]
958 async fn test_mock_device_read_count() {
959 let device = MockDeviceBuilder::new().build();
960 assert_eq!(device.read_count(), 0);
961
962 device.read_current().await.unwrap();
963 assert_eq!(device.read_count(), 1);
964
965 device.read_current().await.unwrap();
966 device.read_current().await.unwrap();
967 assert_eq!(device.read_count(), 3);
968
969 device.reset_read_count();
970 assert_eq!(device.read_count(), 0);
971 }
972
973 #[tokio::test]
974 async fn test_mock_device_transient_failures() {
975 let device = MockDeviceBuilder::new().build();
976 device.set_transient_failures(2);
977
978 assert!(device.read_current().await.is_err());
980 assert!(device.read_current().await.is_err());
981
982 assert!(device.read_current().await.is_ok());
984 }
985
986 #[tokio::test]
987 async fn test_mock_device_set_values() {
988 let device = MockDeviceBuilder::new().build();
989
990 device.set_co2(1500).await;
991 device.set_temperature(30.0).await;
992 device.set_battery(50).await;
993
994 let reading = device.read_current().await.unwrap();
995 assert_eq!(reading.co2, 1500);
996 assert!((reading.temperature - 30.0).abs() < 0.01);
997 assert_eq!(reading.battery, 50);
998 }
999
1000 #[tokio::test]
1001 async fn test_mock_device_history_info() {
1002 let device = MockDeviceBuilder::new().build();
1003
1004 let records: Vec<HistoryRecord> = (0..10)
1006 .map(|_| HistoryRecord {
1007 timestamp: time::OffsetDateTime::now_utc(),
1008 co2: 800,
1009 temperature: 22.0,
1010 pressure: 1013.0,
1011 humidity: 50,
1012 radon: None,
1013 radiation_rate: None,
1014 radiation_total: None,
1015 })
1016 .collect();
1017 device.add_history(records).await;
1018
1019 let info = device.get_history_info().await.unwrap();
1020 assert_eq!(info.total_readings, 10);
1021 assert_eq!(info.interval_seconds, 300); }
1023
1024 #[tokio::test]
1025 async fn test_mock_device_debug() {
1026 let device = MockDevice::new("Debug Test", DeviceType::Aranet4);
1027 let debug_str = format!("{:?}", device);
1028 assert!(debug_str.contains("MockDevice"));
1029 assert!(debug_str.contains("Debug Test"));
1030 assert!(debug_str.contains("Aranet4"));
1031 }
1032
1033 #[test]
1034 fn test_builder_all_options() {
1035 let device = MockDeviceBuilder::new()
1036 .name("Custom Device")
1037 .device_type(DeviceType::Aranet2)
1038 .co2(0)
1039 .temperature(18.5)
1040 .pressure(1020.0)
1041 .humidity(65)
1042 .battery(90)
1043 .status(Status::Yellow)
1044 .auto_connect(false)
1045 .build();
1046
1047 assert_eq!(device.name(), "Custom Device");
1048 assert_eq!(device.device_type(), DeviceType::Aranet2);
1049 assert!(!device.is_connected_sync());
1050 }
1051
1052 #[tokio::test]
1053 async fn test_trait_methods_match_direct_methods() {
1054 let device = MockDeviceBuilder::new()
1055 .name("Trait Test")
1056 .co2(999)
1057 .battery(77)
1058 .build();
1059 device.set_rssi(-55);
1060
1061 let trait_device: &dyn AranetDevice = &device;
1063
1064 assert_eq!(trait_device.name(), Some("Trait Test"));
1065 assert_eq!(trait_device.device_type(), Some(DeviceType::Aranet4));
1066 assert!(trait_device.is_connected().await);
1067
1068 let reading = trait_device.read_current().await.unwrap();
1069 assert_eq!(reading.co2, 999);
1070
1071 let battery = trait_device.read_battery().await.unwrap();
1072 assert_eq!(battery, 77);
1073
1074 let rssi = trait_device.read_rssi().await.unwrap();
1075 assert_eq!(rssi, -55);
1076 }
1077}