1pub mod error;
22pub mod types;
23pub mod uuid;
24
25pub use error::{ParseError, ParseResult};
26pub use types::{
27 CurrentReading, CurrentReadingBuilder, DeviceInfo, DeviceInfoBuilder, DeviceType,
28 HistoryRecord, HistoryRecordBuilder, MIN_CURRENT_READING_BYTES, Status,
29};
30
31pub use uuid as ble;
34#[doc(hidden)]
35pub use uuid as uuids;
36
37#[cfg(test)]
38mod tests {
39 use super::*;
40
41 #[test]
44 fn test_parse_current_reading_from_valid_bytes() {
45 let bytes: [u8; 13] = [
55 0x20, 0x03, 0xC2, 0x01, 0x94, 0x27, 45, 85, 1, 0x2C, 0x01, 0x78, 0x00, ];
64
65 let reading = CurrentReading::from_bytes(&bytes).unwrap();
66
67 assert_eq!(reading.co2, 800);
68 assert!((reading.temperature - 22.5).abs() < 0.01);
69 assert!((reading.pressure - 1013.2).abs() < 0.1);
70 assert_eq!(reading.humidity, 45);
71 assert_eq!(reading.battery, 85);
72 assert_eq!(reading.status, Status::Green);
73 assert_eq!(reading.interval, 300);
74 assert_eq!(reading.age, 120);
75 }
76
77 #[test]
78 fn test_parse_current_reading_from_insufficient_bytes() {
79 let bytes: [u8; 10] = [0; 10]; let result = CurrentReading::from_bytes(&bytes);
82
83 assert!(result.is_err());
84 let err = result.unwrap_err();
85 assert_eq!(
86 err,
87 ParseError::InsufficientBytes {
88 expected: 13,
89 actual: 10
90 }
91 );
92 assert!(err.to_string().contains("expected 13"));
93 assert!(err.to_string().contains("got 10"));
94 }
95
96 #[test]
97 fn test_parse_current_reading_zero_bytes() {
98 let bytes: [u8; 0] = [];
99
100 let result = CurrentReading::from_bytes(&bytes);
101 assert!(result.is_err());
102 }
103
104 #[test]
105 fn test_parse_current_reading_all_zeros() {
106 let bytes: [u8; 13] = [0; 13];
107
108 let reading = CurrentReading::from_bytes(&bytes).unwrap();
109 assert_eq!(reading.co2, 0);
110 assert!((reading.temperature - 0.0).abs() < 0.01);
111 assert!((reading.pressure - 0.0).abs() < 0.1);
112 assert_eq!(reading.humidity, 0);
113 assert_eq!(reading.battery, 0);
114 assert_eq!(reading.status, Status::Error);
115 assert_eq!(reading.interval, 0);
116 assert_eq!(reading.age, 0);
117 }
118
119 #[test]
120 fn test_parse_current_reading_max_values() {
121 let bytes: [u8; 13] = [
122 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 3, 0xFF, 0xFF, 0xFF, 0xFF, ];
131
132 let reading = CurrentReading::from_bytes(&bytes).unwrap();
133 assert_eq!(reading.co2, 65535);
134 assert!((reading.temperature - 3276.75).abs() < 0.01); assert!((reading.pressure - 6553.5).abs() < 0.1); assert_eq!(reading.humidity, 255);
137 assert_eq!(reading.battery, 255);
138 assert_eq!(reading.interval, 65535);
139 assert_eq!(reading.age, 65535);
140 }
141
142 #[test]
143 fn test_parse_current_reading_high_co2_red_status() {
144 let bytes: [u8; 13] = [
146 0xD0, 0x07, 0xC2, 0x01, 0x94, 0x27, 50, 80, 3, 0x2C, 0x01, 0x78, 0x00,
151 ];
152
153 let reading = CurrentReading::from_bytes(&bytes).unwrap();
154 assert_eq!(reading.co2, 2000);
155 assert_eq!(reading.status, Status::Red);
156 }
157
158 #[test]
159 fn test_parse_current_reading_moderate_co2_yellow_status() {
160 let bytes: [u8; 13] = [
162 0xB0, 0x04, 0xC2, 0x01, 0x94, 0x27, 50, 80, 2, 0x2C, 0x01, 0x78, 0x00,
165 ];
166
167 let reading = CurrentReading::from_bytes(&bytes).unwrap();
168 assert_eq!(reading.co2, 1200);
169 assert_eq!(reading.status, Status::Yellow);
170 }
171
172 #[test]
173 fn test_parse_current_reading_extra_bytes_ignored() {
174 let bytes: [u8; 16] = [
176 0x20, 0x03, 0xC2, 0x01, 0x94, 0x27, 45, 85, 1, 0x2C, 0x01, 0x78, 0x00, 0xAA, 0xBB, 0xCC,
177 ];
178
179 let reading = CurrentReading::from_bytes(&bytes).unwrap();
180 assert_eq!(reading.co2, 800);
181 }
182
183 #[test]
186 fn test_status_from_u8() {
187 assert_eq!(Status::from(0), Status::Error);
188 assert_eq!(Status::from(1), Status::Green);
189 assert_eq!(Status::from(2), Status::Yellow);
190 assert_eq!(Status::from(3), Status::Red);
191 assert_eq!(Status::from(4), Status::Error);
193 assert_eq!(Status::from(255), Status::Error);
194 }
195
196 #[test]
197 fn test_status_repr_values() {
198 assert_eq!(Status::Error as u8, 0);
199 assert_eq!(Status::Green as u8, 1);
200 assert_eq!(Status::Yellow as u8, 2);
201 assert_eq!(Status::Red as u8, 3);
202 }
203
204 #[test]
205 fn test_status_debug() {
206 assert_eq!(format!("{:?}", Status::Green), "Green");
207 assert_eq!(format!("{:?}", Status::Yellow), "Yellow");
208 assert_eq!(format!("{:?}", Status::Red), "Red");
209 assert_eq!(format!("{:?}", Status::Error), "Error");
210 }
211
212 #[test]
213 fn test_status_clone() {
214 let status = Status::Green;
215 let cloned = status.clone();
216 assert_eq!(status, cloned);
217 }
218
219 #[test]
220 fn test_status_copy() {
221 let status = Status::Red;
222 let copied = status; assert_eq!(status, copied); }
225
226 #[test]
229 fn test_device_type_values() {
230 assert_eq!(DeviceType::Aranet4 as u8, 0xF1);
231 assert_eq!(DeviceType::Aranet2 as u8, 0xF2);
232 assert_eq!(DeviceType::AranetRadon as u8, 0xF3);
233 assert_eq!(DeviceType::AranetRadiation as u8, 0xF4);
234 }
235
236 #[test]
237 fn test_device_type_debug() {
238 assert_eq!(format!("{:?}", DeviceType::Aranet4), "Aranet4");
239 assert_eq!(format!("{:?}", DeviceType::Aranet2), "Aranet2");
240 assert_eq!(format!("{:?}", DeviceType::AranetRadon), "AranetRadon");
241 assert_eq!(
242 format!("{:?}", DeviceType::AranetRadiation),
243 "AranetRadiation"
244 );
245 }
246
247 #[test]
248 fn test_device_type_clone() {
249 let device_type = DeviceType::Aranet4;
250 let cloned = device_type.clone();
251 assert_eq!(device_type, cloned);
252 }
253
254 #[test]
255 fn test_device_type_try_from_u8() {
256 assert_eq!(DeviceType::try_from(0xF1), Ok(DeviceType::Aranet4));
257 assert_eq!(DeviceType::try_from(0xF2), Ok(DeviceType::Aranet2));
258 assert_eq!(DeviceType::try_from(0xF3), Ok(DeviceType::AranetRadon));
259 assert_eq!(DeviceType::try_from(0xF4), Ok(DeviceType::AranetRadiation));
260 }
261
262 #[test]
263 fn test_device_type_try_from_u8_invalid() {
264 let result = DeviceType::try_from(0x00);
265 assert!(result.is_err());
266 assert_eq!(result.unwrap_err(), ParseError::UnknownDeviceType(0x00));
267
268 let result = DeviceType::try_from(0xFF);
269 assert!(result.is_err());
270 assert_eq!(result.unwrap_err(), ParseError::UnknownDeviceType(0xFF));
271 }
272
273 #[test]
274 fn test_device_type_display() {
275 assert_eq!(format!("{}", DeviceType::Aranet4), "Aranet4");
276 assert_eq!(format!("{}", DeviceType::Aranet2), "Aranet2");
277 assert_eq!(format!("{}", DeviceType::AranetRadon), "Aranet Radon");
278 assert_eq!(
279 format!("{}", DeviceType::AranetRadiation),
280 "Aranet Radiation"
281 );
282 }
283
284 #[test]
285 fn test_device_type_hash() {
286 use std::collections::HashSet;
287 let mut set = HashSet::new();
288 set.insert(DeviceType::Aranet4);
289 set.insert(DeviceType::Aranet2);
290 set.insert(DeviceType::Aranet4); assert_eq!(set.len(), 2);
292 assert!(set.contains(&DeviceType::Aranet4));
293 assert!(set.contains(&DeviceType::Aranet2));
294 }
295
296 #[test]
297 fn test_status_display() {
298 assert_eq!(format!("{}", Status::Error), "Error");
299 assert_eq!(format!("{}", Status::Green), "Good");
300 assert_eq!(format!("{}", Status::Yellow), "Moderate");
301 assert_eq!(format!("{}", Status::Red), "High");
302 }
303
304 #[test]
305 fn test_status_hash() {
306 use std::collections::HashSet;
307 let mut set = HashSet::new();
308 set.insert(Status::Green);
309 set.insert(Status::Yellow);
310 set.insert(Status::Green); assert_eq!(set.len(), 2);
312 assert!(set.contains(&Status::Green));
313 assert!(set.contains(&Status::Yellow));
314 }
315
316 #[test]
319 fn test_device_info_creation() {
320 let info = types::DeviceInfo {
321 name: "Aranet4 12345".to_string(),
322 model: "Aranet4".to_string(),
323 serial: "12345".to_string(),
324 firmware: "v1.2.0".to_string(),
325 hardware: "1.0".to_string(),
326 software: "1.2.0".to_string(),
327 manufacturer: "SAF Tehnika".to_string(),
328 };
329
330 assert_eq!(info.name, "Aranet4 12345");
331 assert_eq!(info.serial, "12345");
332 assert_eq!(info.manufacturer, "SAF Tehnika");
333 }
334
335 #[test]
336 fn test_device_info_clone() {
337 let info = types::DeviceInfo {
338 name: "Test".to_string(),
339 model: "Model".to_string(),
340 serial: "123".to_string(),
341 firmware: "1.0".to_string(),
342 hardware: "1.0".to_string(),
343 software: "1.0".to_string(),
344 manufacturer: "Mfg".to_string(),
345 };
346
347 let cloned = info.clone();
348 assert_eq!(cloned.name, info.name);
349 assert_eq!(cloned.serial, info.serial);
350 }
351
352 #[test]
353 fn test_device_info_debug() {
354 let info = types::DeviceInfo {
355 name: "Aranet4".to_string(),
356 model: "".to_string(),
357 serial: "".to_string(),
358 firmware: "".to_string(),
359 hardware: "".to_string(),
360 software: "".to_string(),
361 manufacturer: "".to_string(),
362 };
363
364 let debug_str = format!("{:?}", info);
365 assert!(debug_str.contains("Aranet4"));
366 }
367
368 #[test]
369 fn test_device_info_default() {
370 let info = types::DeviceInfo::default();
371 assert_eq!(info.name, "");
372 assert_eq!(info.model, "");
373 assert_eq!(info.serial, "");
374 assert_eq!(info.firmware, "");
375 assert_eq!(info.hardware, "");
376 assert_eq!(info.software, "");
377 assert_eq!(info.manufacturer, "");
378 }
379
380 #[test]
381 fn test_device_info_equality() {
382 let info1 = types::DeviceInfo {
383 name: "Test".to_string(),
384 model: "Model".to_string(),
385 serial: "123".to_string(),
386 firmware: "1.0".to_string(),
387 hardware: "1.0".to_string(),
388 software: "1.0".to_string(),
389 manufacturer: "Mfg".to_string(),
390 };
391 let info2 = info1.clone();
392 let info3 = types::DeviceInfo {
393 name: "Different".to_string(),
394 ..info1.clone()
395 };
396 assert_eq!(info1, info2);
397 assert_ne!(info1, info3);
398 }
399
400 #[test]
403 fn test_history_record_creation() {
404 use time::OffsetDateTime;
405
406 let record = types::HistoryRecord {
407 timestamp: OffsetDateTime::UNIX_EPOCH,
408 co2: 800,
409 temperature: 22.5,
410 pressure: 1013.2,
411 humidity: 45,
412 radon: None,
413 radiation_rate: None,
414 radiation_total: None,
415 };
416
417 assert_eq!(record.co2, 800);
418 assert!((record.temperature - 22.5).abs() < 0.01);
419 assert!((record.pressure - 1013.2).abs() < 0.1);
420 assert_eq!(record.humidity, 45);
421 assert!(record.radon.is_none());
422 assert!(record.radiation_rate.is_none());
423 assert!(record.radiation_total.is_none());
424 }
425
426 #[test]
427 fn test_history_record_clone() {
428 use time::OffsetDateTime;
429
430 let record = types::HistoryRecord {
431 timestamp: OffsetDateTime::UNIX_EPOCH,
432 co2: 500,
433 temperature: 20.0,
434 pressure: 1000.0,
435 humidity: 50,
436 radon: Some(100),
437 radiation_rate: Some(0.15),
438 radiation_total: Some(1.5),
439 };
440
441 let cloned = record.clone();
442 assert_eq!(cloned.co2, record.co2);
443 assert_eq!(cloned.humidity, record.humidity);
444 assert_eq!(cloned.radon, Some(100));
445 assert_eq!(cloned.radiation_rate, Some(0.15));
446 assert_eq!(cloned.radiation_total, Some(1.5));
447 }
448
449 #[test]
450 fn test_history_record_equality() {
451 use time::OffsetDateTime;
452
453 let record1 = types::HistoryRecord {
454 timestamp: OffsetDateTime::UNIX_EPOCH,
455 co2: 800,
456 temperature: 22.5,
457 pressure: 1013.2,
458 humidity: 45,
459 radon: None,
460 radiation_rate: None,
461 radiation_total: None,
462 };
463 let record2 = record1.clone();
464 assert_eq!(record1, record2);
465 }
466
467 #[test]
468 fn test_current_reading_equality() {
469 let reading1 = CurrentReading {
470 co2: 800,
471 temperature: 22.5,
472 pressure: 1013.2,
473 humidity: 45,
474 battery: 85,
475 status: Status::Green,
476 interval: 300,
477 age: 120,
478 captured_at: None,
479 radon: None,
480 radiation_rate: None,
481 radiation_total: None,
482 };
483 let reading2 = reading1.clone();
484 assert_eq!(reading1, reading2);
485 }
486
487 #[test]
488 fn test_min_current_reading_bytes_const() {
489 assert_eq!(MIN_CURRENT_READING_BYTES, 13);
490 let bytes = [0u8; MIN_CURRENT_READING_BYTES];
492 assert!(CurrentReading::from_bytes(&bytes).is_ok());
493 let short_bytes = [0u8; MIN_CURRENT_READING_BYTES - 1];
495 assert!(CurrentReading::from_bytes(&short_bytes).is_err());
496 }
497
498 #[test]
501 fn test_parse_error_display() {
502 let err = ParseError::invalid_value("test message");
503 assert_eq!(err.to_string(), "Invalid value: test message");
504 }
505
506 #[test]
507 fn test_parse_error_insufficient_bytes() {
508 let err = ParseError::InsufficientBytes {
509 expected: 13,
510 actual: 5,
511 };
512 assert_eq!(err.to_string(), "Insufficient bytes: expected 13, got 5");
513 }
514
515 #[test]
516 fn test_parse_error_unknown_device_type() {
517 let err = ParseError::UnknownDeviceType(0xAB);
518 assert_eq!(err.to_string(), "Unknown device type: 0xAB");
519 }
520
521 #[test]
522 fn test_parse_error_invalid_value() {
523 let err = ParseError::InvalidValue("bad value".to_string());
524 assert_eq!(err.to_string(), "Invalid value: bad value");
525 }
526
527 #[test]
528 fn test_parse_error_debug() {
529 let err = ParseError::invalid_value("debug test");
530 let debug_str = format!("{:?}", err);
531 assert!(debug_str.contains("InvalidValue"));
532 assert!(debug_str.contains("debug test"));
533 }
534
535 #[test]
536 fn test_parse_error_equality() {
537 let err1 = ParseError::InsufficientBytes {
538 expected: 10,
539 actual: 5,
540 };
541 let err2 = ParseError::InsufficientBytes {
542 expected: 10,
543 actual: 5,
544 };
545 let err3 = ParseError::InsufficientBytes {
546 expected: 10,
547 actual: 6,
548 };
549 assert_eq!(err1, err2);
550 assert_ne!(err1, err3);
551 }
552
553 #[test]
556 fn test_current_reading_serialization() {
557 let reading = CurrentReading {
558 co2: 800,
559 temperature: 22.5,
560 pressure: 1013.2,
561 humidity: 45,
562 battery: 85,
563 status: Status::Green,
564 interval: 300,
565 age: 120,
566 captured_at: None,
567 radon: None,
568 radiation_rate: None,
569 radiation_total: None,
570 };
571
572 let json = serde_json::to_string(&reading).unwrap();
573 assert!(json.contains("\"co2\":800"));
574 assert!(json.contains("\"humidity\":45"));
575 }
576
577 #[test]
578 fn test_current_reading_deserialization() {
579 let json = r#"{"co2":800,"temperature":22.5,"pressure":1013.2,"humidity":45,"battery":85,"status":"Green","interval":300,"age":120,"radon":null,"radiation_rate":null,"radiation_total":null}"#;
580
581 let reading: CurrentReading = serde_json::from_str(json).unwrap();
582 assert_eq!(reading.co2, 800);
583 assert_eq!(reading.status, Status::Green);
584 }
585
586 #[test]
587 fn test_status_serialization() {
588 assert_eq!(serde_json::to_string(&Status::Green).unwrap(), "\"Green\"");
589 assert_eq!(
590 serde_json::to_string(&Status::Yellow).unwrap(),
591 "\"Yellow\""
592 );
593 assert_eq!(serde_json::to_string(&Status::Red).unwrap(), "\"Red\"");
594 assert_eq!(serde_json::to_string(&Status::Error).unwrap(), "\"Error\"");
595 }
596
597 #[test]
598 fn test_device_type_serialization() {
599 assert_eq!(
600 serde_json::to_string(&DeviceType::Aranet4).unwrap(),
601 "\"Aranet4\""
602 );
603 assert_eq!(
604 serde_json::to_string(&DeviceType::AranetRadon).unwrap(),
605 "\"AranetRadon\""
606 );
607 }
608
609 #[test]
610 fn test_device_info_serialization_roundtrip() {
611 let info = types::DeviceInfo {
612 name: "Test Device".to_string(),
613 model: "Model X".to_string(),
614 serial: "SN12345".to_string(),
615 firmware: "1.2.3".to_string(),
616 hardware: "2.0".to_string(),
617 software: "3.0".to_string(),
618 manufacturer: "Acme Corp".to_string(),
619 };
620
621 let json = serde_json::to_string(&info).unwrap();
622 let deserialized: types::DeviceInfo = serde_json::from_str(&json).unwrap();
623
624 assert_eq!(deserialized.name, info.name);
625 assert_eq!(deserialized.serial, info.serial);
626 assert_eq!(deserialized.manufacturer, info.manufacturer);
627 }
628
629 #[test]
632 fn test_status_ordering() {
633 assert!(Status::Error < Status::Green);
635 assert!(Status::Green < Status::Yellow);
636 assert!(Status::Yellow < Status::Red);
637
638 assert!(Status::Red > Status::Yellow);
640 assert!(Status::Yellow >= Status::Yellow);
641 assert!(Status::Green <= Status::Yellow);
642 }
643
644 #[test]
645 fn test_device_type_readings_characteristic() {
646 use crate::ble;
647
648 assert_eq!(
650 DeviceType::Aranet4.readings_characteristic(),
651 ble::CURRENT_READINGS_DETAIL
652 );
653
654 assert_eq!(
656 DeviceType::Aranet2.readings_characteristic(),
657 ble::CURRENT_READINGS_DETAIL_ALT
658 );
659 assert_eq!(
660 DeviceType::AranetRadon.readings_characteristic(),
661 ble::CURRENT_READINGS_DETAIL_ALT
662 );
663 assert_eq!(
664 DeviceType::AranetRadiation.readings_characteristic(),
665 ble::CURRENT_READINGS_DETAIL_ALT
666 );
667 }
668
669 #[test]
670 fn test_device_type_from_name_word_boundary() {
671 assert_eq!(
673 DeviceType::from_name("Aranet4 12345"),
674 Some(DeviceType::Aranet4)
675 );
676 assert_eq!(
677 DeviceType::from_name("My Aranet4"),
678 Some(DeviceType::Aranet4)
679 );
680
681 assert_eq!(DeviceType::from_name("ARANET4"), Some(DeviceType::Aranet4));
683 assert_eq!(DeviceType::from_name("aranet2"), Some(DeviceType::Aranet2));
684 }
685
686 #[test]
687 fn test_byte_size_constants() {
688 assert_eq!(MIN_CURRENT_READING_BYTES, 13);
689 assert_eq!(types::MIN_ARANET2_READING_BYTES, 7);
690 assert_eq!(types::MIN_RADON_READING_BYTES, 15);
691 assert_eq!(types::MIN_RADON_GATT_READING_BYTES, 18);
692 assert_eq!(types::MIN_RADIATION_READING_BYTES, 28);
693 }
694
695 #[test]
696 fn test_from_bytes_aranet2() {
697 let data = [
699 0x90, 0x01, 0x32, 0x55, 0x01, 0x2C, 0x01, ];
705
706 let reading = CurrentReading::from_bytes_aranet2(&data).unwrap();
707 assert_eq!(reading.co2, 0); assert!((reading.temperature - 20.0).abs() < 0.1);
709 assert_eq!(reading.humidity, 50);
710 assert_eq!(reading.battery, 85);
711 assert_eq!(reading.status, Status::Green);
712 assert_eq!(reading.interval, 300);
713 assert_eq!(reading.pressure, 0.0); }
715
716 #[test]
717 fn test_from_bytes_aranet2_insufficient() {
718 let data = [0u8; 6]; let result = CurrentReading::from_bytes_aranet2(&data);
720 assert!(result.is_err());
721 }
722
723 #[test]
724 fn test_from_bytes_for_device() {
725 let aranet4_data = [0u8; 13];
727 let result = CurrentReading::from_bytes_for_device(&aranet4_data, DeviceType::Aranet4);
728 assert!(result.is_ok());
729
730 let aranet2_data = [0u8; 7];
731 let result = CurrentReading::from_bytes_for_device(&aranet2_data, DeviceType::Aranet2);
732 assert!(result.is_ok());
733 }
734
735 #[test]
736 fn test_builder_with_captured_at() {
737 use time::OffsetDateTime;
738
739 let now = OffsetDateTime::now_utc();
740 let reading = CurrentReading::builder()
741 .co2(800)
742 .temperature(22.5)
743 .captured_at(now)
744 .build();
745
746 assert_eq!(reading.co2, 800);
747 assert_eq!(reading.captured_at, Some(now));
748 }
749
750 #[test]
751 fn test_builder_try_build_valid() {
752 let result = CurrentReading::builder()
753 .co2(800)
754 .temperature(22.5)
755 .pressure(1013.0)
756 .humidity(50)
757 .battery(85)
758 .try_build();
759
760 assert!(result.is_ok());
761 }
762
763 #[test]
764 fn test_builder_try_build_invalid_humidity() {
765 let result = CurrentReading::builder()
766 .humidity(150) .try_build();
768
769 assert!(result.is_err());
770 let err = result.unwrap_err();
771 assert!(err.to_string().contains("humidity"));
772 }
773
774 #[test]
775 fn test_builder_try_build_invalid_battery() {
776 let result = CurrentReading::builder()
777 .battery(120) .try_build();
779
780 assert!(result.is_err());
781 let err = result.unwrap_err();
782 assert!(err.to_string().contains("battery"));
783 }
784
785 #[test]
786 fn test_builder_try_build_invalid_temperature() {
787 let result = CurrentReading::builder()
788 .temperature(-50.0) .try_build();
790
791 assert!(result.is_err());
792 let err = result.unwrap_err();
793 assert!(err.to_string().contains("temperature"));
794 }
795
796 #[test]
797 fn test_builder_try_build_invalid_pressure() {
798 let result = CurrentReading::builder()
799 .temperature(22.0) .pressure(500.0) .try_build();
802
803 assert!(result.is_err());
804 let err = result.unwrap_err();
805 assert!(err.to_string().contains("pressure"));
806 }
807
808 #[test]
809 fn test_with_captured_at() {
810 use time::OffsetDateTime;
811
812 let reading = CurrentReading::builder().age(60).build();
813
814 let now = OffsetDateTime::now_utc();
815 let reading_with_time = reading.with_captured_at(now);
816
817 assert!(reading_with_time.captured_at.is_some());
818 let captured = reading_with_time.captured_at.unwrap();
820 let expected = now - time::Duration::seconds(60);
821 assert!((captured - expected).whole_seconds().abs() < 2);
822 }
823
824 #[test]
825 fn test_parse_error_invalid_value_helper() {
826 let err = ParseError::invalid_value("test error");
827 assert_eq!(err.to_string(), "Invalid value: test error");
828 }
829}