aranet_core/
mock.rs

1//! Mock device implementation for testing.
2//!
3//! This module provides a mock device that can be used for unit testing
4//! without requiring actual BLE hardware.
5//!
6//! The [`MockDevice`] implements the [`AranetDevice`] trait, allowing it to be
7//! used interchangeably with real devices in generic code.
8//!
9//! # Features
10//!
11//! - **Failure injection**: Set the device to fail on specific operations
12//! - **Latency simulation**: Add artificial delays to simulate slow BLE responses
13//! - **Custom behavior**: Inject custom reading generators for dynamic test scenarios
14
15use 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
28/// A mock Aranet device for testing.
29///
30/// Implements [`AranetDevice`] trait for use in generic code and testing.
31///
32/// # Example
33///
34/// ```
35/// use aranet_core::{MockDevice, AranetDevice};
36/// use aranet_types::DeviceType;
37///
38/// #[tokio::main]
39/// async fn main() {
40///     let device = MockDevice::new("Test", DeviceType::Aranet4);
41///     device.connect().await.unwrap();
42///
43///     // Can use through trait
44///     async fn read_via_trait<D: AranetDevice>(d: &D) {
45///         let _ = d.read_current().await;
46///     }
47///     read_via_trait(&device).await;
48/// }
49/// ```
50pub 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    /// Simulated read latency in milliseconds (0 = no delay).
66    read_latency_ms: AtomicU64,
67    /// Simulated connect latency in milliseconds (0 = no delay).
68    connect_latency_ms: AtomicU64,
69    /// Number of operations to fail before succeeding (0 = always succeed/fail based on should_fail).
70    fail_count: AtomicU32,
71    /// Current count of failures (decremented on each failure).
72    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    /// Create a new mock device with default values.
88    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    /// Connect to the mock device.
144    pub async fn connect(&self) -> Result<()> {
145        use crate::error::DeviceNotFoundReason;
146
147        // Simulate connect latency
148        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        // Check for transient failures first
154        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    /// Disconnect from the mock device.
171    pub async fn disconnect(&self) -> Result<()> {
172        self.connected.store(false, Ordering::Relaxed);
173        Ok(())
174    }
175
176    /// Check if connected (sync method for internal use).
177    pub fn is_connected_sync(&self) -> bool {
178        self.connected.load(Ordering::Relaxed)
179    }
180
181    /// Get the device name.
182    pub fn name(&self) -> &str {
183        &self.name
184    }
185
186    /// Get the device address.
187    pub fn address(&self) -> &str {
188        &self.address
189    }
190
191    /// Get the device type.
192    pub fn device_type(&self) -> DeviceType {
193        self.device_type
194    }
195
196    /// Read current sensor values.
197    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    /// Read battery level.
206    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    /// Read RSSI (signal strength).
213    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    /// Read device info.
220    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    /// Get history info.
227    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    /// Download history.
242    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    /// Download history with options.
249    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        // Report progress if callback provided
264        if let Some(ref _callback) = options.progress_callback {
265            // For mock, we report progress immediately
266            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    /// Get the measurement interval.
284    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    /// Set the measurement interval.
291    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    /// Get calibration data.
299    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        // Simulate read latency
315        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        // Check for transient failures first
321        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    // --- Test control methods ---
334
335    /// Set the current reading for testing.
336    pub async fn set_reading(&self, reading: CurrentReading) {
337        *self.current_reading.write().await = reading;
338    }
339
340    /// Set CO2 level directly.
341    pub async fn set_co2(&self, co2: u16) {
342        self.current_reading.write().await.co2 = co2;
343    }
344
345    /// Set temperature directly.
346    pub async fn set_temperature(&self, temp: f32) {
347        self.current_reading.write().await.temperature = temp;
348    }
349
350    /// Set battery level.
351    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    /// Set RSSI (signal strength) for testing.
357    pub fn set_rssi(&self, rssi: i16) {
358        self.rssi.store(rssi, Ordering::Relaxed);
359    }
360
361    /// Add history records.
362    pub async fn add_history(&self, records: Vec<HistoryRecord>) {
363        self.history.write().await.extend(records);
364    }
365
366    /// Make the device fail on next operation.
367    pub async fn set_should_fail(&self, fail: bool, message: Option<&str>) {
368        self.should_fail.store(fail, Ordering::Relaxed);
369        if let Some(msg) = message {
370            *self.fail_message.write().await = msg.to_string();
371        }
372    }
373
374    /// Get the number of read operations performed.
375    pub fn read_count(&self) -> u32 {
376        self.read_count.load(Ordering::Relaxed)
377    }
378
379    /// Reset read count.
380    pub fn reset_read_count(&self) {
381        self.read_count.store(0, Ordering::Relaxed);
382    }
383
384    /// Set simulated read latency.
385    ///
386    /// Each read operation will be delayed by this duration.
387    /// Set to `Duration::ZERO` to disable latency simulation.
388    pub fn set_read_latency(&self, latency: Duration) {
389        self.read_latency_ms
390            .store(latency.as_millis() as u64, Ordering::Relaxed);
391    }
392
393    /// Set simulated connect latency.
394    ///
395    /// Connect operations will be delayed by this duration.
396    /// Set to `Duration::ZERO` to disable latency simulation.
397    pub fn set_connect_latency(&self, latency: Duration) {
398        self.connect_latency_ms
399            .store(latency.as_millis() as u64, Ordering::Relaxed);
400    }
401
402    /// Configure transient failures.
403    ///
404    /// The device will fail the next `count` operations, then succeed.
405    /// This is useful for testing retry logic.
406    ///
407    /// # Example
408    ///
409    /// ```
410    /// use aranet_core::MockDevice;
411    /// use aranet_types::DeviceType;
412    ///
413    /// let device = MockDevice::new("Test", DeviceType::Aranet4);
414    /// // First 3 connect attempts will fail, 4th will succeed
415    /// device.set_transient_failures(3);
416    /// ```
417    pub fn set_transient_failures(&self, count: u32) {
418        self.fail_count.store(count, Ordering::Relaxed);
419        self.remaining_failures.store(count, Ordering::Relaxed);
420    }
421
422    /// Reset transient failure counter.
423    pub fn reset_transient_failures(&self) {
424        self.remaining_failures
425            .store(self.fail_count.load(Ordering::Relaxed), Ordering::Relaxed);
426    }
427
428    /// Get the number of remaining transient failures.
429    pub fn remaining_failures(&self) -> u32 {
430        self.remaining_failures.load(Ordering::Relaxed)
431    }
432}
433
434// Implement the AranetDevice trait for MockDevice
435#[async_trait]
436impl AranetDevice for MockDevice {
437    // --- Connection Management ---
438
439    async fn is_connected(&self) -> bool {
440        self.is_connected_sync()
441    }
442
443    async fn disconnect(&self) -> Result<()> {
444        MockDevice::disconnect(self).await
445    }
446
447    // --- Device Identity ---
448
449    fn name(&self) -> Option<&str> {
450        Some(MockDevice::name(self))
451    }
452
453    fn address(&self) -> &str {
454        MockDevice::address(self)
455    }
456
457    fn device_type(&self) -> Option<DeviceType> {
458        Some(MockDevice::device_type(self))
459    }
460
461    // --- Current Readings ---
462
463    async fn read_current(&self) -> Result<CurrentReading> {
464        MockDevice::read_current(self).await
465    }
466
467    async fn read_device_info(&self) -> Result<DeviceInfo> {
468        MockDevice::read_device_info(self).await
469    }
470
471    async fn read_rssi(&self) -> Result<i16> {
472        MockDevice::read_rssi(self).await
473    }
474
475    // --- Battery ---
476
477    async fn read_battery(&self) -> Result<u8> {
478        MockDevice::read_battery(self).await
479    }
480
481    // --- History ---
482
483    async fn get_history_info(&self) -> Result<crate::history::HistoryInfo> {
484        MockDevice::get_history_info(self).await
485    }
486
487    async fn download_history(&self) -> Result<Vec<HistoryRecord>> {
488        MockDevice::download_history(self).await
489    }
490
491    async fn download_history_with_options(
492        &self,
493        options: HistoryOptions,
494    ) -> Result<Vec<HistoryRecord>> {
495        MockDevice::download_history_with_options(self, options).await
496    }
497
498    // --- Settings ---
499
500    async fn get_interval(&self) -> Result<MeasurementInterval> {
501        MockDevice::get_interval(self).await
502    }
503
504    async fn set_interval(&self, interval: MeasurementInterval) -> Result<()> {
505        MockDevice::set_interval(self, interval).await
506    }
507
508    async fn get_calibration(&self) -> Result<CalibrationData> {
509        MockDevice::get_calibration(self).await
510    }
511}
512
513/// Builder for creating mock devices with custom settings.
514#[derive(Debug)]
515pub struct MockDeviceBuilder {
516    name: String,
517    device_type: DeviceType,
518    co2: u16,
519    temperature: f32,
520    pressure: f32,
521    humidity: u8,
522    battery: u8,
523    status: Status,
524    auto_connect: bool,
525}
526
527impl Default for MockDeviceBuilder {
528    fn default() -> Self {
529        Self {
530            name: "Mock Aranet4".to_string(),
531            device_type: DeviceType::Aranet4,
532            co2: 800,
533            temperature: 22.5,
534            pressure: 1013.2,
535            humidity: 50,
536            battery: 85,
537            status: Status::Green,
538            auto_connect: true,
539        }
540    }
541}
542
543impl MockDeviceBuilder {
544    /// Create a new builder.
545    #[must_use]
546    pub fn new() -> Self {
547        Self::default()
548    }
549
550    /// Set the device name.
551    #[must_use]
552    pub fn name(mut self, name: &str) -> Self {
553        self.name = name.to_string();
554        self
555    }
556
557    /// Set the device type.
558    #[must_use]
559    pub fn device_type(mut self, device_type: DeviceType) -> Self {
560        self.device_type = device_type;
561        self
562    }
563
564    /// Set the CO2 level.
565    #[must_use]
566    pub fn co2(mut self, co2: u16) -> Self {
567        self.co2 = co2;
568        self
569    }
570
571    /// Set the temperature.
572    #[must_use]
573    pub fn temperature(mut self, temp: f32) -> Self {
574        self.temperature = temp;
575        self
576    }
577
578    /// Set the pressure.
579    #[must_use]
580    pub fn pressure(mut self, pressure: f32) -> Self {
581        self.pressure = pressure;
582        self
583    }
584
585    /// Set the humidity.
586    #[must_use]
587    pub fn humidity(mut self, humidity: u8) -> Self {
588        self.humidity = humidity;
589        self
590    }
591
592    /// Set the battery level.
593    #[must_use]
594    pub fn battery(mut self, battery: u8) -> Self {
595        self.battery = battery;
596        self
597    }
598
599    /// Set the status.
600    #[must_use]
601    pub fn status(mut self, status: Status) -> Self {
602        self.status = status;
603        self
604    }
605
606    /// Set whether to auto-connect.
607    #[must_use]
608    pub fn auto_connect(mut self, auto: bool) -> Self {
609        self.auto_connect = auto;
610        self
611    }
612
613    /// Build the mock device.
614    ///
615    /// Note: This is a sync method that sets initial state directly.
616    /// The device is created with the specified reading already set.
617    #[must_use]
618    pub fn build(self) -> MockDevice {
619        let reading = CurrentReading {
620            co2: self.co2,
621            temperature: self.temperature,
622            pressure: self.pressure,
623            humidity: self.humidity,
624            battery: self.battery,
625            status: self.status,
626            interval: 300,
627            age: 60,
628            captured_at: None,
629            radon: None,
630            radiation_rate: None,
631            radiation_total: None,
632            radon_avg_24h: None,
633            radon_avg_7d: None,
634            radon_avg_30d: None,
635        };
636
637        MockDevice {
638            name: self.name.clone(),
639            address: format!("MOCK-{:06X}", rand::random::<u32>() % 0xFFFFFF),
640            device_type: self.device_type,
641            connected: AtomicBool::new(self.auto_connect),
642            current_reading: RwLock::new(reading),
643            device_info: RwLock::new(MockDevice::default_info(&self.name)),
644            history: RwLock::new(Vec::new()),
645            interval: RwLock::new(MeasurementInterval::FiveMinutes),
646            calibration: RwLock::new(CalibrationData::default()),
647            battery: RwLock::new(self.battery),
648            rssi: AtomicI16::new(-50),
649            read_count: AtomicU32::new(0),
650            should_fail: AtomicBool::new(false),
651            fail_message: RwLock::new("Mock failure".to_string()),
652            read_latency_ms: AtomicU64::new(0),
653            connect_latency_ms: AtomicU64::new(0),
654            fail_count: AtomicU32::new(0),
655            remaining_failures: AtomicU32::new(0),
656        }
657    }
658}
659
660/// Unit tests for MockDevice and MockDeviceBuilder.
661///
662/// These tests verify the mock device implementation used for testing
663/// without requiring actual BLE hardware.
664///
665/// # Test Categories
666///
667/// ## Connection Tests
668/// - `test_mock_device_connect`: Connect/disconnect lifecycle
669/// - `test_mock_device_not_connected`: Error when reading without connection
670///
671/// ## Reading Tests
672/// - `test_mock_device_read`: Basic reading retrieval
673/// - `test_mock_device_read_battery`: Battery level reading
674/// - `test_mock_device_read_rssi`: Signal strength reading
675/// - `test_mock_device_read_device_info`: Device information
676/// - `test_mock_device_set_values`: Dynamic value updates
677///
678/// ## History Tests
679/// - `test_mock_device_history`: History download
680/// - `test_mock_device_history_with_options`: Filtered history download
681/// - `test_mock_device_history_info`: History metadata
682///
683/// ## Settings Tests
684/// - `test_mock_device_interval`: Measurement interval get/set
685/// - `test_mock_device_calibration`: Calibration data
686///
687/// ## Failure Injection Tests
688/// - `test_mock_device_fail`: Permanent failure mode
689/// - `test_mock_device_transient_failures`: Temporary failures for retry testing
690///
691/// ## Builder Tests
692/// - `test_builder_defaults`: Default builder values
693/// - `test_builder_all_options`: Full builder customization
694///
695/// ## Trait Tests
696/// - `test_aranet_device_trait`: Using MockDevice through AranetDevice trait
697/// - `test_trait_methods_match_direct_methods`: Trait/direct method consistency
698///
699/// # Running Tests
700///
701/// ```bash
702/// cargo test -p aranet-core mock::tests
703/// ```
704#[cfg(test)]
705mod tests {
706    use super::*;
707    use crate::traits::AranetDevice;
708
709    #[tokio::test]
710    async fn test_mock_device_connect() {
711        let device = MockDevice::new("Test", DeviceType::Aranet4);
712        assert!(!device.is_connected_sync());
713
714        device.connect().await.unwrap();
715        assert!(device.is_connected_sync());
716
717        device.disconnect().await.unwrap();
718        assert!(!device.is_connected_sync());
719    }
720
721    #[tokio::test]
722    async fn test_mock_device_read() {
723        let device = MockDeviceBuilder::new().co2(1200).temperature(25.0).build();
724
725        let reading = device.read_current().await.unwrap();
726        assert_eq!(reading.co2, 1200);
727        assert!((reading.temperature - 25.0).abs() < 0.01);
728    }
729
730    #[tokio::test]
731    async fn test_mock_device_fail() {
732        let device = MockDeviceBuilder::new().build();
733        device.set_should_fail(true, Some("Test error")).await;
734
735        let result = device.read_current().await;
736        assert!(result.is_err());
737        assert!(result.unwrap_err().to_string().contains("Test error"));
738    }
739
740    #[tokio::test]
741    async fn test_mock_device_not_connected() {
742        let device = MockDeviceBuilder::new().auto_connect(false).build();
743
744        let result = device.read_current().await;
745        assert!(matches!(result, Err(Error::NotConnected)));
746    }
747
748    #[test]
749    fn test_builder_defaults() {
750        let device = MockDeviceBuilder::new().build();
751        assert!(device.is_connected_sync());
752        assert_eq!(device.device_type(), DeviceType::Aranet4);
753    }
754
755    #[tokio::test]
756    async fn test_aranet_device_trait() {
757        let device = MockDeviceBuilder::new().co2(1000).build();
758
759        // Use via trait
760        async fn check_via_trait<D: AranetDevice>(d: &D) -> u16 {
761            d.read_current().await.unwrap().co2
762        }
763
764        assert_eq!(check_via_trait(&device).await, 1000);
765    }
766
767    #[tokio::test]
768    async fn test_mock_device_read_battery() {
769        let device = MockDeviceBuilder::new().battery(75).build();
770        let battery = device.read_battery().await.unwrap();
771        assert_eq!(battery, 75);
772    }
773
774    #[tokio::test]
775    async fn test_mock_device_read_rssi() {
776        let device = MockDeviceBuilder::new().build();
777        device.set_rssi(-65);
778        let rssi = device.read_rssi().await.unwrap();
779        assert_eq!(rssi, -65);
780    }
781
782    #[tokio::test]
783    async fn test_mock_device_read_device_info() {
784        let device = MockDeviceBuilder::new().name("Test Device").build();
785        let info = device.read_device_info().await.unwrap();
786        assert_eq!(info.name, "Test Device");
787        assert_eq!(info.manufacturer, "SAF Tehnika");
788    }
789
790    #[tokio::test]
791    async fn test_mock_device_history() {
792        let device = MockDeviceBuilder::new().build();
793
794        // Initially empty
795        let history = device.download_history().await.unwrap();
796        assert!(history.is_empty());
797
798        // Add some records
799        let records = vec![
800            HistoryRecord {
801                timestamp: time::OffsetDateTime::now_utc(),
802                co2: 800,
803                temperature: 22.5,
804                pressure: 1013.2,
805                humidity: 50,
806                radon: None,
807                radiation_rate: None,
808                radiation_total: None,
809            },
810            HistoryRecord {
811                timestamp: time::OffsetDateTime::now_utc(),
812                co2: 850,
813                temperature: 23.0,
814                pressure: 1013.5,
815                humidity: 48,
816                radon: None,
817                radiation_rate: None,
818                radiation_total: None,
819            },
820        ];
821        device.add_history(records).await;
822
823        let history = device.download_history().await.unwrap();
824        assert_eq!(history.len(), 2);
825        assert_eq!(history[0].co2, 800);
826        assert_eq!(history[1].co2, 850);
827    }
828
829    #[tokio::test]
830    async fn test_mock_device_history_with_options() {
831        let device = MockDeviceBuilder::new().build();
832
833        // Add 5 records
834        let records: Vec<HistoryRecord> = (0..5)
835            .map(|i| HistoryRecord {
836                timestamp: time::OffsetDateTime::now_utc(),
837                co2: 800 + i as u16 * 10,
838                temperature: 22.0,
839                pressure: 1013.0,
840                humidity: 50,
841                radon: None,
842                radiation_rate: None,
843                radiation_total: None,
844            })
845            .collect();
846        device.add_history(records).await;
847
848        // Download with range
849        let options = HistoryOptions {
850            start_index: Some(1),
851            end_index: Some(4),
852            ..Default::default()
853        };
854        let history = device.download_history_with_options(options).await.unwrap();
855        assert_eq!(history.len(), 3);
856        assert_eq!(history[0].co2, 810); // Second record (index 1)
857        assert_eq!(history[2].co2, 830); // Fourth record (index 3)
858    }
859
860    #[tokio::test]
861    async fn test_mock_device_interval() {
862        let device = MockDeviceBuilder::new().build();
863
864        let interval = device.get_interval().await.unwrap();
865        assert_eq!(interval, MeasurementInterval::FiveMinutes);
866
867        device
868            .set_interval(MeasurementInterval::TenMinutes)
869            .await
870            .unwrap();
871        let interval = device.get_interval().await.unwrap();
872        assert_eq!(interval, MeasurementInterval::TenMinutes);
873    }
874
875    #[tokio::test]
876    async fn test_mock_device_calibration() {
877        let device = MockDeviceBuilder::new().build();
878        let calibration = device.get_calibration().await.unwrap();
879        // Default calibration should exist
880        assert!(calibration.co2_offset.is_some() || calibration.co2_offset.is_none());
881    }
882
883    #[tokio::test]
884    async fn test_mock_device_read_count() {
885        let device = MockDeviceBuilder::new().build();
886        assert_eq!(device.read_count(), 0);
887
888        device.read_current().await.unwrap();
889        assert_eq!(device.read_count(), 1);
890
891        device.read_current().await.unwrap();
892        device.read_current().await.unwrap();
893        assert_eq!(device.read_count(), 3);
894
895        device.reset_read_count();
896        assert_eq!(device.read_count(), 0);
897    }
898
899    #[tokio::test]
900    async fn test_mock_device_transient_failures() {
901        let device = MockDeviceBuilder::new().build();
902        device.set_transient_failures(2);
903
904        // First two reads should fail
905        assert!(device.read_current().await.is_err());
906        assert!(device.read_current().await.is_err());
907
908        // Third read should succeed
909        assert!(device.read_current().await.is_ok());
910    }
911
912    #[tokio::test]
913    async fn test_mock_device_set_values() {
914        let device = MockDeviceBuilder::new().build();
915
916        device.set_co2(1500).await;
917        device.set_temperature(30.0).await;
918        device.set_battery(50).await;
919
920        let reading = device.read_current().await.unwrap();
921        assert_eq!(reading.co2, 1500);
922        assert!((reading.temperature - 30.0).abs() < 0.01);
923        assert_eq!(reading.battery, 50);
924    }
925
926    #[tokio::test]
927    async fn test_mock_device_history_info() {
928        let device = MockDeviceBuilder::new().build();
929
930        // Add some records
931        let records: Vec<HistoryRecord> = (0..10)
932            .map(|_| HistoryRecord {
933                timestamp: time::OffsetDateTime::now_utc(),
934                co2: 800,
935                temperature: 22.0,
936                pressure: 1013.0,
937                humidity: 50,
938                radon: None,
939                radiation_rate: None,
940                radiation_total: None,
941            })
942            .collect();
943        device.add_history(records).await;
944
945        let info = device.get_history_info().await.unwrap();
946        assert_eq!(info.total_readings, 10);
947        assert_eq!(info.interval_seconds, 300); // 5 minutes default
948    }
949
950    #[tokio::test]
951    async fn test_mock_device_debug() {
952        let device = MockDevice::new("Debug Test", DeviceType::Aranet4);
953        let debug_str = format!("{:?}", device);
954        assert!(debug_str.contains("MockDevice"));
955        assert!(debug_str.contains("Debug Test"));
956        assert!(debug_str.contains("Aranet4"));
957    }
958
959    #[test]
960    fn test_builder_all_options() {
961        let device = MockDeviceBuilder::new()
962            .name("Custom Device")
963            .device_type(DeviceType::Aranet2)
964            .co2(0)
965            .temperature(18.5)
966            .pressure(1020.0)
967            .humidity(65)
968            .battery(90)
969            .status(Status::Yellow)
970            .auto_connect(false)
971            .build();
972
973        assert_eq!(device.name(), "Custom Device");
974        assert_eq!(device.device_type(), DeviceType::Aranet2);
975        assert!(!device.is_connected_sync());
976    }
977
978    #[tokio::test]
979    async fn test_trait_methods_match_direct_methods() {
980        let device = MockDeviceBuilder::new()
981            .name("Trait Test")
982            .co2(999)
983            .battery(77)
984            .build();
985        device.set_rssi(-55);
986
987        // Test that trait methods return same values as direct methods
988        let trait_device: &dyn AranetDevice = &device;
989
990        assert_eq!(trait_device.name(), Some("Trait Test"));
991        assert_eq!(trait_device.device_type(), Some(DeviceType::Aranet4));
992        assert!(trait_device.is_connected().await);
993
994        let reading = trait_device.read_current().await.unwrap();
995        assert_eq!(reading.co2, 999);
996
997        let battery = trait_device.read_battery().await.unwrap();
998        assert_eq!(battery, 77);
999
1000        let rssi = trait_device.read_rssi().await.unwrap();
1001        assert_eq!(rssi, -55);
1002    }
1003}