1use crate::models::common::{IpAddress, MacAddress};
2use serde::Deserialize;
3
4#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
6pub enum PortConnector {
7 #[serde(rename = "RJ45")]
8 Rj45,
9 #[serde(rename = "SFP")]
10 Sfp,
11 #[serde(rename = "SFPPLUS")]
12 SfpPlus,
13 #[serde(rename = "SFP28")]
14 Sfp28,
15 #[serde(rename = "QSFP28")]
16 Qsfp28,
17 #[serde(other)]
18 Unknown,
19}
20
21#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
23#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
24pub enum InterfaceState {
25 Up,
26 Down,
27 #[serde(other)]
28 Unknown,
29}
30
31#[derive(Clone, Debug, Deserialize, PartialEq)]
33#[serde(rename_all = "camelCase")]
34pub struct PoE {
35 #[serde(default)]
37 pub standard: Option<String>,
38
39 #[serde(default, rename = "type")]
41 pub r#type: Option<i32>,
42
43 #[serde(default)]
45 pub enabled: Option<bool>,
46
47 #[serde(default)]
49 pub state: Option<String>,
50}
51
52impl PoE {
53 pub fn is_enabled(&self) -> bool {
55 self.enabled.unwrap_or(false)
56 }
57
58 pub fn is_active(&self) -> bool {
60 self.is_enabled() && self.state.as_deref() == Some("UP")
61 }
62}
63
64#[derive(Clone, Debug, Deserialize, PartialEq)]
66#[serde(rename_all = "camelCase")]
67pub struct Port {
68 pub idx: i32,
70
71 pub state: InterfaceState,
73
74 pub connector: PortConnector,
76
77 pub max_speed_mbps: i32,
79
80 #[serde(default)]
82 pub speed_mbps: Option<i32>,
83
84 #[serde(default)]
86 pub poe: Option<PoE>,
87}
88
89impl Port {
90 pub fn has_poe(&self) -> bool {
92 self.poe.is_some()
93 }
94
95 pub fn is_poe_active(&self) -> bool {
97 self.poe.as_ref().map(|p| p.is_active()).unwrap_or(false)
98 }
99
100 pub fn is_poe_enabled(&self) -> bool {
102 self.poe.as_ref().map(|p| p.is_enabled()).unwrap_or(false)
103 }
104
105 pub fn is_up(&self) -> bool {
107 self.state == InterfaceState::Up
108 }
109
110 pub fn is_down(&self) -> bool {
112 self.state == InterfaceState::Down
113 }
114
115 pub fn speed_gbps(&self) -> Option<f64> {
117 self.speed_mbps.map(|s| s as f64 / 1000.0)
118 }
119}
120
121#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
123pub enum WirelessStandard {
124 #[serde(rename = "802.11a")]
125 Standard802_11a,
126 #[serde(rename = "802.11b")]
127 Standard802_11b,
128 #[serde(rename = "802.11g")]
129 Standard802_11g,
130 #[serde(rename = "802.11n")]
131 Standard802_11n,
132 #[serde(rename = "802.11ac")]
133 Standard802_11ac,
134 #[serde(rename = "802.11ax")]
135 Standard802_11ax,
136 #[serde(rename = "802.11be")]
137 Standard802_11be,
138 #[serde(other)]
139 Unknown,
140}
141
142#[derive(Clone, Debug, Deserialize, PartialEq)]
144#[serde(rename_all = "camelCase")]
145pub struct Radio {
146 pub wlan_standard: WirelessStandard,
148
149 #[serde(rename = "frequencyGHz")]
151 pub frequency_ghz: f64,
152
153 #[serde(rename = "channelWidthMHz")]
155 pub channel_width_mhz: i32,
156
157 #[serde(default)]
159 pub channel: Option<i32>,
160}
161
162#[derive(Clone, Debug, Deserialize, PartialEq, Default)]
164#[serde(rename_all = "camelCase")]
165pub struct PhysicalInterfaces {
166 #[serde(default)]
168 pub ports: Vec<Port>,
169
170 #[serde(default)]
172 pub radios: Vec<Radio>,
173}
174
175#[derive(Clone, Debug, Deserialize, PartialEq)]
177#[serde(rename_all = "camelCase")]
178pub struct DeviceUplink {
179 pub device_id: String,
181}
182
183#[derive(Clone, Debug, Deserialize, PartialEq, Default)]
185#[serde(rename_all = "camelCase")]
186pub struct SwitchingFeature {
187 }
189
190#[derive(Clone, Debug, Deserialize, PartialEq, Default)]
192#[serde(rename_all = "camelCase")]
193pub struct AccessPointFeature {
194 }
196
197#[derive(Clone, Debug, Deserialize, PartialEq, Default)]
199#[serde(rename_all = "camelCase")]
200pub struct DeviceFeatures {
201 #[serde(default)]
203 pub switching: Option<SwitchingFeature>,
204
205 #[serde(default)]
207 pub access_point: Option<AccessPointFeature>,
208}
209
210#[derive(Clone, Debug, Deserialize, PartialEq)]
213#[serde(rename_all = "camelCase")]
214pub struct DeviceDetails {
215 pub id: String,
217
218 pub mac_address: MacAddress,
220
221 pub ip_address: IpAddress,
223
224 pub name: String,
226
227 pub model: String,
229
230 pub supported: bool,
232
233 pub state: String,
235
236 #[serde(default)]
238 pub firmware_version: Option<String>,
239
240 pub firmware_updatable: bool,
242
243 #[serde(default)]
245 pub adopted_at: Option<String>,
246
247 #[serde(default)]
249 pub provisioned_at: Option<String>,
250
251 pub configuration_id: String,
253
254 #[serde(default)]
256 pub uplink: Option<DeviceUplink>,
257
258 #[serde(default)]
260 pub features: DeviceFeatures,
261
262 #[serde(default)]
264 pub interfaces: PhysicalInterfaces,
265}
266
267impl DeviceDetails {
268 pub fn is_online(&self) -> bool {
270 self.state == "ONLINE"
271 }
272
273 pub fn is_offline(&self) -> bool {
275 self.state == "OFFLINE"
276 }
277
278 pub fn port_count(&self) -> usize {
280 self.interfaces.ports.len()
281 }
282
283 pub fn radio_count(&self) -> usize {
285 self.interfaces.radios.len()
286 }
287
288 pub fn has_switching(&self) -> bool {
290 self.features.switching.is_some()
291 }
292
293 pub fn has_access_point(&self) -> bool {
295 self.features.access_point.is_some()
296 }
297
298 pub fn active_ports(&self) -> Vec<&Port> {
300 self.interfaces
301 .ports
302 .iter()
303 .filter(|p| p.state == InterfaceState::Up)
304 .collect()
305 }
306
307 pub fn inactive_ports(&self) -> Vec<&Port> {
309 self.interfaces
310 .ports
311 .iter()
312 .filter(|p| p.state == InterfaceState::Down)
313 .collect()
314 }
315
316 pub fn is_switch(&self) -> bool {
318 self.has_switching()
319 }
320
321 pub fn poe_ports(&self) -> Vec<&Port> {
323 self.interfaces
324 .ports
325 .iter()
326 .filter(|p| p.has_poe())
327 .collect()
328 }
329
330 pub fn poe_enabled_ports(&self) -> Vec<&Port> {
332 self.interfaces
333 .ports
334 .iter()
335 .filter(|p| p.is_poe_enabled())
336 .collect()
337 }
338
339 pub fn poe_active_ports(&self) -> Vec<&Port> {
341 self.interfaces
342 .ports
343 .iter()
344 .filter(|p| p.is_poe_active())
345 .collect()
346 }
347
348 pub fn poe_port_count(&self) -> usize {
350 self.poe_ports().len()
351 }
352}
353
354#[cfg(test)]
355mod tests {
356 use super::*;
357 use serde_json::json;
358
359 #[test]
360 fn test_radio_deserialization() {
361 let radio_json = json!({
362 "wlanStandard": "802.11a",
363 "frequencyGHz": 2.4,
364 "channelWidthMHz": 40,
365 "channel": 36
366 });
367
368 match serde_json::from_value::<Radio>(radio_json) {
369 Ok(radio) => assert_eq!(radio.frequency_ghz, 2.4),
370 Err(e) => panic!("Failed to deserialize radio: {}", e),
371 }
372 }
373
374 #[test]
375 fn test_device_details_deserialization() {
376 let radio_json = json!({
377 "wlanStandard": "802.11a",
378 "frequencyGHz": 2.4,
379 "channelWidthMHz": 40,
380 "channel": 36
381 });
382
383 match serde_json::from_value::<Radio>(radio_json) {
384 Ok(radio) => assert_eq!(radio.frequency_ghz, 2.4),
385 Err(e) => panic!("Failed to deserialize radio in device test: {}", e),
386 }
387
388 let json_data = json!({
389 "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
390 "macAddress": "94:2a:6f:26:c6:ca",
391 "ipAddress": "192.168.1.55",
392 "name": "IW HD",
393 "model": "UHDIW",
394 "supported": true,
395 "state": "ONLINE",
396 "firmwareVersion": "6.6.55",
397 "firmwareUpdatable": true,
398 "adoptedAt": "2019-08-24T14:15:22Z",
399 "provisionedAt": "2019-08-24T14:15:22Z",
400 "configurationId": "7596498d2f367dc2",
401 "uplink": {
402 "deviceId": "4de4adb9-21ee-47e3-aeb4-8cf8ed6c109a"
403 },
404 "features": {
405 "switching": null,
406 "accessPoint": null
407 },
408 "interfaces": {
409 "ports": [
410 {
411 "idx": 1,
412 "state": "UP",
413 "connector": "RJ45",
414 "maxSpeedMbps": 10000,
415 "speedMbps": 1000,
416 "poe": {
417 "standard": "802.3bt",
418 "type": 3,
419 "enabled": true,
420 "state": "UP"
421 }
422 }
423 ],
424 "radios": [
425 {
426 "wlanStandard": "802.11a",
427 "frequencyGHz": 2.4,
428 "channelWidthMHz": 40,
429 "channel": 36
430 }
431 ]
432 }
433 });
434
435 let device: DeviceDetails = serde_json::from_value(json_data).unwrap();
436
437 assert_eq!(device.id, "497f6eca-6276-4993-bfeb-53cbbbba6f08");
438 assert_eq!(device.mac_address.as_str(), "94:2a:6f:26:c6:ca");
439 assert_eq!(device.ip_address.to_string(), "192.168.1.55");
440 assert_eq!(device.name, "IW HD");
441 assert_eq!(device.model, "UHDIW");
442 assert!(device.supported);
443 assert_eq!(device.state, "ONLINE");
444 assert_eq!(device.firmware_version, Some("6.6.55".to_string()));
445 assert!(device.firmware_updatable);
446 assert_eq!(device.port_count(), 1);
447 assert_eq!(device.radio_count(), 1);
448 assert!(device.is_online());
449 }
450
451 #[test]
452 fn test_device_details_ports() {
453 let json_data = json!({
454 "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
455 "macAddress": "94:2a:6f:26:c6:ca",
456 "ipAddress": "192.168.1.55",
457 "name": "Test Switch",
458 "model": "USW-24",
459 "supported": true,
460 "state": "ONLINE",
461 "firmwareUpdatable": false,
462 "configurationId": "config123",
463 "uplink": {
464 "deviceId": "uplink-device"
465 },
466 "features": {},
467 "interfaces": {
468 "ports": [
469 {
470 "idx": 1,
471 "state": "UP",
472 "connector": "RJ45",
473 "maxSpeedMbps": 1000,
474 "speedMbps": 1000
475 },
476 {
477 "idx": 2,
478 "state": "DOWN",
479 "connector": "RJ45",
480 "maxSpeedMbps": 1000
481 }
482 ],
483 "radios": []
484 }
485 });
486
487 let device: DeviceDetails = serde_json::from_value(json_data).unwrap();
488
489 assert_eq!(device.port_count(), 2);
490 assert_eq!(device.radio_count(), 0);
491 assert_eq!(device.active_ports().len(), 1);
492 assert_eq!(device.inactive_ports().len(), 1);
493 }
494
495 #[test]
496 fn test_device_details_features() {
497 let json_data = json!({
498 "id": "device-with-features",
499 "macAddress": "94:2a:6f:26:c6:ca",
500 "ipAddress": "192.168.1.55",
501 "name": "Multi-function Device",
502 "model": "UDM-Pro",
503 "supported": true,
504 "state": "ONLINE",
505 "firmwareUpdatable": false,
506 "configurationId": "config123",
507 "uplink": {
508 "deviceId": "uplink-device"
509 },
510 "features": {
511 "switching": {},
512 "accessPoint": {}
513 },
514 "interfaces": {
515 "ports": [],
516 "radios": []
517 }
518 });
519
520 let device: DeviceDetails = serde_json::from_value(json_data).unwrap();
521
522 assert!(device.has_switching());
523 assert!(device.has_access_point());
524 }
525
526 #[test]
527 fn test_wireless_standard_deserialization() {
528 let standards = vec![
529 ("\"802.11a\"", WirelessStandard::Standard802_11a),
530 ("\"802.11b\"", WirelessStandard::Standard802_11b),
531 ("\"802.11g\"", WirelessStandard::Standard802_11g),
532 ("\"802.11n\"", WirelessStandard::Standard802_11n),
533 ("\"802.11ac\"", WirelessStandard::Standard802_11ac),
534 ("\"802.11ax\"", WirelessStandard::Standard802_11ax),
535 ("\"802.11be\"", WirelessStandard::Standard802_11be),
536 ];
537
538 for (json_str, expected) in standards {
539 let standard: WirelessStandard = serde_json::from_str(json_str).unwrap();
540 assert_eq!(standard, expected);
541 }
542 }
543
544 #[test]
545 fn test_device_details_real_world_response() {
546 let json_data = json!({
549 "id": "device-0001-0000-0000-000000000001",
550 "macAddress": "00:00:5e:00:53:01",
551 "ipAddress": "198.51.100.1",
552 "name": "Test Device UX7",
553 "model": "Express 7",
554 "supported": true,
555 "state": "ONLINE",
556 "firmwareVersion": "5.0.10",
557 "firmwareUpdatable": false,
558 "provisionedAt": "2025-12-30T21:27:06Z",
559 "configurationId": "config-0001",
560 "uplink": {
561 "deviceId": "uplink-device-0001"
562 },
563 "features": {
564 "switching": {},
565 "accessPoint": {}
566 },
567 "interfaces": {
568 "ports": [
569 {
570 "idx": 1,
571 "state": "UP",
572 "connector": "RJ45",
573 "maxSpeedMbps": 2500,
574 "speedMbps": 1000
575 },
576 {
577 "idx": 2,
578 "state": "UP",
579 "connector": "RJ45",
580 "maxSpeedMbps": 10000,
581 "speedMbps": 1000
582 }
583 ],
584 "radios": [
585 {
586 "wlanStandard": "802.11be",
587 "frequencyGHz": 2.4,
588 "channelWidthMHz": 20,
589 "channel": 1
590 },
591 {
592 "wlanStandard": "802.11be",
593 "frequencyGHz": 5,
594 "channelWidthMHz": 80,
595 "channel": 48
596 },
597 {
598 "wlanStandard": "802.11be",
599 "frequencyGHz": 6,
600 "channelWidthMHz": 160,
601 "channel": 37
602 }
603 ]
604 }
605 });
606
607 let device: DeviceDetails =
608 serde_json::from_value(json_data).expect("Failed to deserialize real-world response");
609
610 assert_eq!(device.id, "device-0001-0000-0000-000000000001");
611 assert_eq!(device.name, "Test Device UX7");
612 assert_eq!(device.model, "Express 7");
613 assert!(device.supported);
614 assert_eq!(device.state, "ONLINE");
615 assert!(device.is_online());
616 assert_eq!(device.port_count(), 2);
617 assert_eq!(device.radio_count(), 3);
618 assert!(device.has_switching());
619 assert!(device.has_access_point());
620
621 assert_eq!(device.active_ports().len(), 2);
623 assert_eq!(device.inactive_ports().len(), 0);
624
625 let port1 = &device.interfaces.ports[0];
626 assert_eq!(port1.idx, 1);
627 assert_eq!(port1.max_speed_mbps, 2500);
628 assert_eq!(port1.speed_mbps, Some(1000));
629
630 let port2 = &device.interfaces.ports[1];
631 assert_eq!(port2.idx, 2);
632 assert_eq!(port2.max_speed_mbps, 10000);
633
634 let radio1 = &device.interfaces.radios[0];
636 assert_eq!(radio1.wlan_standard, WirelessStandard::Standard802_11be);
637 assert_eq!(radio1.frequency_ghz, 2.4);
638 assert_eq!(radio1.channel, Some(1));
639
640 let radio2 = &device.interfaces.radios[1];
641 assert_eq!(radio2.frequency_ghz, 5.0);
642 assert_eq!(radio2.channel, Some(48));
643
644 let radio3 = &device.interfaces.radios[2];
645 assert_eq!(radio3.frequency_ghz, 6.0);
646 assert_eq!(radio3.channel, Some(37));
647 }
648
649 #[test]
650 fn test_device_details_without_optional_fields() {
651 let json_data = json!({
653 "id": "minimal-device",
654 "macAddress": "1c:0b:8b:3e:5c:16",
655 "ipAddress": "192.168.1.100",
656 "name": "Minimal Device",
657 "model": "Test",
658 "supported": true,
659 "state": "ONLINE",
660 "firmwareUpdatable": false,
661 "configurationId": "config123",
662 "features": {},
663 "interfaces": {
664 "ports": [],
665 "radios": []
666 }
667 });
668
669 let device: DeviceDetails =
670 serde_json::from_value(json_data).expect("Failed to deserialize minimal response");
671
672 assert_eq!(device.id, "minimal-device");
673 assert_eq!(device.name, "Minimal Device");
674 assert!(device.is_online());
675 assert!(device.adopted_at.is_none());
676 assert!(device.uplink.is_none());
677 assert_eq!(device.port_count(), 0);
678 assert_eq!(device.radio_count(), 0);
679 }
680
681 #[test]
682 fn test_port_connector_deserialization() {
683 let connectors = vec![
684 ("\"RJ45\"", PortConnector::Rj45),
685 ("\"SFP\"", PortConnector::Sfp),
686 ("\"SFPPLUS\"", PortConnector::SfpPlus),
687 ("\"SFP28\"", PortConnector::Sfp28),
688 ("\"QSFP28\"", PortConnector::Qsfp28),
689 ];
690
691 for (json_str, expected) in connectors {
692 let connector: PortConnector = serde_json::from_str(json_str).unwrap();
693 assert_eq!(connector, expected);
694 }
695 }
696
697 #[test]
698 fn test_interface_state_deserialization() {
699 let up: InterfaceState = serde_json::from_str("\"UP\"").unwrap();
700 let down: InterfaceState = serde_json::from_str("\"DOWN\"").unwrap();
701 let unknown: InterfaceState = serde_json::from_str("\"UNKNOWN\"").unwrap();
702
703 assert_eq!(up, InterfaceState::Up);
704 assert_eq!(down, InterfaceState::Down);
705 assert_eq!(unknown, InterfaceState::Unknown);
706 }
707
708 #[test]
709 fn test_device_details_with_exact_api_response() {
710 let json_data = json!({
712 "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
713 "macAddress": "94:2a:6f:26:c6:ca",
714 "ipAddress": "192.168.1.55",
715 "name": "IW HD",
716 "model": "UHDIW",
717 "supported": true,
718 "state": "ONLINE",
719 "firmwareVersion": "6.6.55",
720 "firmwareUpdatable": true,
721 "adoptedAt": "2019-08-24T14:15:22Z",
722 "provisionedAt": "2019-08-24T14:15:22Z",
723 "configurationId": "7596498d2f367dc2",
724 "uplink": {
725 "deviceId": "4de4adb9-21ee-47e3-aeb4-8cf8ed6c109a"
726 },
727 "features": {
728 "switching": null,
729 "accessPoint": null
730 },
731 "interfaces": {
732 "ports": [
733 {
734 "idx": 1,
735 "state": "UP",
736 "connector": "RJ45",
737 "maxSpeedMbps": 10000,
738 "speedMbps": 1000,
739 "poe": {
740 "standard": "802.3bt",
741 "type": 3,
742 "enabled": true,
743 "state": "UP"
744 }
745 }
746 ],
747 "radios": [
748 {
749 "wlanStandard": "802.11a",
750 "frequencyGHz": 2.4,
751 "channelWidthMHz": 40,
752 "channel": 36
753 }
754 ]
755 }
756 });
757
758 let device: DeviceDetails =
759 serde_json::from_value(json_data).expect("Failed to deserialize exact API response");
760
761 assert_eq!(device.id, "497f6eca-6276-4993-bfeb-53cbbbba6f08");
763 assert_eq!(device.mac_address.as_str(), "94:2a:6f:26:c6:ca");
764 assert_eq!(device.ip_address.to_string(), "192.168.1.55");
765 assert_eq!(device.name, "IW HD");
766 assert_eq!(device.model, "UHDIW");
767 assert!(device.supported);
768 assert_eq!(device.state, "ONLINE");
769 assert_eq!(device.firmware_version, Some("6.6.55".to_string()));
770 assert!(device.firmware_updatable);
771 assert!(device.is_online());
772
773 assert_eq!(device.port_count(), 1);
775 assert_eq!(device.radio_count(), 1);
776
777 let port = &device.interfaces.ports[0];
778 assert_eq!(port.idx, 1);
779 assert_eq!(port.state, InterfaceState::Up);
780 assert_eq!(port.connector, PortConnector::Rj45);
781 assert_eq!(port.max_speed_mbps, 10000);
782 assert_eq!(port.speed_mbps, Some(1000));
783 assert!(port.poe.is_some());
784 let poe = port.poe.as_ref().unwrap();
785 assert_eq!(poe.standard, Some("802.3bt".to_string()));
786 assert_eq!(poe.r#type, Some(3));
787 assert_eq!(poe.enabled, Some(true));
788
789 let radio = &device.interfaces.radios[0];
790 assert_eq!(radio.wlan_standard, WirelessStandard::Standard802_11a);
791 assert_eq!(radio.frequency_ghz, 2.4);
792 assert_eq!(radio.channel_width_mhz, 40);
793 assert_eq!(radio.channel, Some(36));
794 }
795
796 #[test]
797 fn test_poe_methods() {
798 let poe_active = PoE {
800 standard: Some("802.3bt".to_string()),
801 r#type: Some(3),
802 enabled: Some(true),
803 state: Some("UP".to_string()),
804 };
805 assert!(poe_active.is_enabled());
806 assert!(poe_active.is_active());
807
808 let poe_enabled_down = PoE {
810 standard: Some("802.3at".to_string()),
811 r#type: Some(2),
812 enabled: Some(true),
813 state: Some("DOWN".to_string()),
814 };
815 assert!(poe_enabled_down.is_enabled());
816 assert!(!poe_enabled_down.is_active());
817
818 let poe_disabled = PoE {
820 standard: Some("802.3af".to_string()),
821 r#type: Some(1),
822 enabled: Some(false),
823 state: None,
824 };
825 assert!(!poe_disabled.is_enabled());
826 assert!(!poe_disabled.is_active());
827
828 let poe_none = PoE {
830 standard: None,
831 r#type: None,
832 enabled: None,
833 state: None,
834 };
835 assert!(!poe_none.is_enabled());
836 assert!(!poe_none.is_active());
837 }
838
839 #[test]
840 fn test_port_poe_methods() {
841 let port_with_poe = Port {
843 idx: 1,
844 state: InterfaceState::Up,
845 connector: PortConnector::Rj45,
846 max_speed_mbps: 1000,
847 speed_mbps: Some(1000),
848 poe: Some(PoE {
849 standard: Some("802.3bt".to_string()),
850 r#type: Some(3),
851 enabled: Some(true),
852 state: Some("UP".to_string()),
853 }),
854 };
855 assert!(port_with_poe.has_poe());
856 assert!(port_with_poe.is_poe_enabled());
857 assert!(port_with_poe.is_poe_active());
858
859 let port_poe_disabled = Port {
861 idx: 2,
862 state: InterfaceState::Up,
863 connector: PortConnector::Rj45,
864 max_speed_mbps: 1000,
865 speed_mbps: Some(1000),
866 poe: Some(PoE {
867 standard: Some("802.3at".to_string()),
868 r#type: Some(2),
869 enabled: Some(false),
870 state: None,
871 }),
872 };
873 assert!(port_poe_disabled.has_poe());
874 assert!(!port_poe_disabled.is_poe_enabled());
875 assert!(!port_poe_disabled.is_poe_active());
876
877 let port_no_poe = Port {
879 idx: 3,
880 state: InterfaceState::Up,
881 connector: PortConnector::Sfp,
882 max_speed_mbps: 10000,
883 speed_mbps: Some(10000),
884 poe: None,
885 };
886 assert!(!port_no_poe.has_poe());
887 assert!(!port_no_poe.is_poe_enabled());
888 assert!(!port_no_poe.is_poe_active());
889 }
890
891 #[test]
892 fn test_port_state_methods() {
893 let port_up = Port {
894 idx: 1,
895 state: InterfaceState::Up,
896 connector: PortConnector::Rj45,
897 max_speed_mbps: 1000,
898 speed_mbps: Some(1000),
899 poe: None,
900 };
901 assert!(port_up.is_up());
902 assert!(!port_up.is_down());
903 assert_eq!(port_up.speed_gbps(), Some(1.0));
904
905 let port_down = Port {
906 idx: 2,
907 state: InterfaceState::Down,
908 connector: PortConnector::Rj45,
909 max_speed_mbps: 1000,
910 speed_mbps: None,
911 poe: None,
912 };
913 assert!(!port_down.is_up());
914 assert!(port_down.is_down());
915 assert_eq!(port_down.speed_gbps(), None);
916
917 let port_10g = Port {
919 idx: 3,
920 state: InterfaceState::Up,
921 connector: PortConnector::SfpPlus,
922 max_speed_mbps: 10000,
923 speed_mbps: Some(10000),
924 poe: None,
925 };
926 assert_eq!(port_10g.speed_gbps(), Some(10.0));
927 }
928
929 #[test]
930 fn test_device_details_switch_methods() {
931 let json_data = json!({
933 "id": "switch-device-id",
934 "macAddress": "aa:bb:cc:dd:ee:ff",
935 "ipAddress": "192.168.1.100",
936 "name": "USW-24-PoE",
937 "model": "USW-24-PoE",
938 "supported": true,
939 "state": "ONLINE",
940 "firmwareUpdatable": false,
941 "configurationId": "config123",
942 "features": {
943 "switching": {},
944 "accessPoint": null
945 },
946 "interfaces": {
947 "ports": [
948 {
949 "idx": 1,
950 "state": "UP",
951 "connector": "RJ45",
952 "maxSpeedMbps": 1000,
953 "speedMbps": 1000,
954 "poe": {
955 "standard": "802.3at",
956 "type": 2,
957 "enabled": true,
958 "state": "UP"
959 }
960 },
961 {
962 "idx": 2,
963 "state": "UP",
964 "connector": "RJ45",
965 "maxSpeedMbps": 1000,
966 "speedMbps": 1000,
967 "poe": {
968 "standard": "802.3at",
969 "type": 2,
970 "enabled": true,
971 "state": "DOWN"
972 }
973 },
974 {
975 "idx": 3,
976 "state": "DOWN",
977 "connector": "RJ45",
978 "maxSpeedMbps": 1000,
979 "poe": {
980 "standard": "802.3at",
981 "type": 2,
982 "enabled": false,
983 "state": null
984 }
985 },
986 {
987 "idx": 25,
988 "state": "UP",
989 "connector": "SFP",
990 "maxSpeedMbps": 1000,
991 "speedMbps": 1000
992 }
993 ],
994 "radios": []
995 }
996 });
997
998 let device: DeviceDetails = serde_json::from_value(json_data).unwrap();
999
1000 assert!(device.is_switch());
1002 assert!(device.has_switching());
1003 assert!(!device.has_access_point());
1004
1005 assert_eq!(device.port_count(), 4);
1007 assert_eq!(device.active_ports().len(), 3);
1008 assert_eq!(device.inactive_ports().len(), 1);
1009
1010 assert_eq!(device.poe_port_count(), 3); assert_eq!(device.poe_ports().len(), 3);
1013 assert_eq!(device.poe_enabled_ports().len(), 2); assert_eq!(device.poe_active_ports().len(), 1); let active_poe = device.poe_active_ports();
1018 assert_eq!(active_poe[0].idx, 1);
1019 }
1020
1021 #[test]
1022 fn test_device_details_access_point_not_switch() {
1023 let json_data = json!({
1024 "id": "ap-device-id",
1025 "macAddress": "aa:bb:cc:dd:ee:ff",
1026 "ipAddress": "192.168.1.101",
1027 "name": "U6-Pro",
1028 "model": "U6-Pro",
1029 "supported": true,
1030 "state": "ONLINE",
1031 "firmwareUpdatable": false,
1032 "configurationId": "config123",
1033 "features": {
1034 "switching": null,
1035 "accessPoint": {}
1036 },
1037 "interfaces": {
1038 "ports": [
1039 {
1040 "idx": 1,
1041 "state": "UP",
1042 "connector": "RJ45",
1043 "maxSpeedMbps": 2500,
1044 "speedMbps": 1000
1045 }
1046 ],
1047 "radios": [
1048 {
1049 "wlanStandard": "802.11ax",
1050 "frequencyGHz": 5,
1051 "channelWidthMHz": 80,
1052 "channel": 36
1053 }
1054 ]
1055 }
1056 });
1057
1058 let device: DeviceDetails = serde_json::from_value(json_data).unwrap();
1059
1060 assert!(!device.is_switch());
1061 assert!(!device.has_switching());
1062 assert!(device.has_access_point());
1063 assert_eq!(device.poe_port_count(), 0);
1064 assert_eq!(device.radio_count(), 1);
1065 }
1066}