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