Skip to main content

rustifi/models/
device_details.rs

1use crate::models::common::{IpAddress, MacAddress};
2use serde::Deserialize;
3
4/// Port connector type.
5#[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/// Port interface state.
22#[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/// Power over Ethernet configuration and state.
32#[derive(Clone, Debug, Deserialize, PartialEq)]
33#[serde(rename_all = "camelCase")]
34pub struct PoE {
35    /// PoE standard (e.g., "802.3bt")
36    #[serde(default)]
37    pub standard: Option<String>,
38
39    /// PoE type identifier
40    #[serde(default, rename = "type")]
41    pub r#type: Option<i32>,
42
43    /// Whether PoE is enabled
44    #[serde(default)]
45    pub enabled: Option<bool>,
46
47    /// Current PoE state
48    #[serde(default)]
49    pub state: Option<String>,
50}
51
52impl PoE {
53    /// Check if PoE is enabled on this port.
54    pub fn is_enabled(&self) -> bool {
55        self.enabled.unwrap_or(false)
56    }
57
58    /// Check if PoE is actively delivering power (enabled and state is UP).
59    pub fn is_active(&self) -> bool {
60        self.is_enabled() && self.state.as_deref() == Some("UP")
61    }
62}
63
64/// Port information for physical interfaces.
65#[derive(Clone, Debug, Deserialize, PartialEq)]
66#[serde(rename_all = "camelCase")]
67pub struct Port {
68    /// Port index identifier (1-based)
69    pub idx: i32,
70
71    /// Current port state
72    pub state: InterfaceState,
73
74    /// Port connector type
75    pub connector: PortConnector,
76
77    /// Maximum speed in Mbps
78    pub max_speed_mbps: i32,
79
80    /// Current speed in Mbps
81    #[serde(default)]
82    pub speed_mbps: Option<i32>,
83
84    /// Power over Ethernet configuration
85    #[serde(default)]
86    pub poe: Option<PoE>,
87}
88
89impl Port {
90    /// Check if this port has PoE capability.
91    pub fn has_poe(&self) -> bool {
92        self.poe.is_some()
93    }
94
95    /// Check if this port is actively delivering PoE power.
96    pub fn is_poe_active(&self) -> bool {
97        self.poe.as_ref().map(|p| p.is_active()).unwrap_or(false)
98    }
99
100    /// Check if PoE is enabled on this port.
101    pub fn is_poe_enabled(&self) -> bool {
102        self.poe.as_ref().map(|p| p.is_enabled()).unwrap_or(false)
103    }
104
105    /// Check if the port link is up.
106    pub fn is_up(&self) -> bool {
107        self.state == InterfaceState::Up
108    }
109
110    /// Check if the port link is down.
111    pub fn is_down(&self) -> bool {
112        self.state == InterfaceState::Down
113    }
114
115    /// Get the current speed in Gbps, if available.
116    pub fn speed_gbps(&self) -> Option<f64> {
117        self.speed_mbps.map(|s| s as f64 / 1000.0)
118    }
119}
120
121/// Wireless radio standard.
122#[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/// Wireless radio information.
143#[derive(Clone, Debug, Deserialize, PartialEq)]
144#[serde(rename_all = "camelCase")]
145pub struct Radio {
146    /// Wireless standard (802.11a, 802.11ac, etc.)
147    pub wlan_standard: WirelessStandard,
148
149    /// Frequency in GHz (2.4, 5, 6, or 60)
150    #[serde(rename = "frequencyGHz")]
151    pub frequency_ghz: f64,
152
153    /// Channel width in MHz
154    #[serde(rename = "channelWidthMHz")]
155    pub channel_width_mhz: i32,
156
157    /// Current channel number
158    #[serde(default)]
159    pub channel: Option<i32>,
160}
161
162/// Device physical interfaces.
163#[derive(Clone, Debug, Deserialize, PartialEq, Default)]
164#[serde(rename_all = "camelCase")]
165pub struct PhysicalInterfaces {
166    /// List of physical ports
167    #[serde(default)]
168    pub ports: Vec<Port>,
169
170    /// List of wireless radios
171    #[serde(default)]
172    pub radios: Vec<Radio>,
173}
174
175/// Device uplink connection information.
176#[derive(Clone, Debug, Deserialize, PartialEq)]
177#[serde(rename_all = "camelCase")]
178pub struct DeviceUplink {
179    /// ID of the parent device in the network topology
180    pub device_id: String,
181}
182
183/// Device switching feature details.
184#[derive(Clone, Debug, Deserialize, PartialEq, Default)]
185#[serde(rename_all = "camelCase")]
186pub struct SwitchingFeature {
187    // Currently empty, but structured for future expansion
188}
189
190/// Device access point feature details.
191#[derive(Clone, Debug, Deserialize, PartialEq, Default)]
192#[serde(rename_all = "camelCase")]
193pub struct AccessPointFeature {
194    // Currently empty, but structured for future expansion
195}
196
197/// Device features and capabilities.
198#[derive(Clone, Debug, Deserialize, PartialEq, Default)]
199#[serde(rename_all = "camelCase")]
200pub struct DeviceFeatures {
201    /// Switching feature details (if supported)
202    #[serde(default)]
203    pub switching: Option<SwitchingFeature>,
204
205    /// Access point feature details (if supported)
206    #[serde(default)]
207    pub access_point: Option<AccessPointFeature>,
208}
209
210/// Detailed device information from the device details endpoint.
211/// Endpoint: GET /v1/sites/{siteId}/devices/{deviceId}
212#[derive(Clone, Debug, Deserialize, PartialEq)]
213#[serde(rename_all = "camelCase")]
214pub struct DeviceDetails {
215    /// Unique device identifier (UUID)
216    pub id: String,
217
218    /// MAC address of the device
219    pub mac_address: MacAddress,
220
221    /// IP address of the device
222    pub ip_address: IpAddress,
223
224    /// Display name of the device
225    pub name: String,
226
227    /// Device model identifier (e.g., "UHDIW", "U6-Pro")
228    pub model: String,
229
230    /// Whether the device is supported
231    pub supported: bool,
232
233    /// Current device state (ONLINE, OFFLINE, etc.)
234    pub state: String,
235
236    /// Current firmware version
237    #[serde(default)]
238    pub firmware_version: Option<String>,
239
240    /// Whether firmware can be updated
241    pub firmware_updatable: bool,
242
243    /// When the device was adopted (ISO 8601 timestamp)
244    #[serde(default)]
245    pub adopted_at: Option<String>,
246
247    /// When the device was provisioned (ISO 8601 timestamp)
248    #[serde(default)]
249    pub provisioned_at: Option<String>,
250
251    /// Configuration ID
252    pub configuration_id: String,
253
254    /// Device uplink connection info (optional)
255    #[serde(default)]
256    pub uplink: Option<DeviceUplink>,
257
258    /// Device features and capabilities
259    #[serde(default)]
260    pub features: DeviceFeatures,
261
262    /// Physical interfaces (ports and radios)
263    #[serde(default)]
264    pub interfaces: PhysicalInterfaces,
265}
266
267impl DeviceDetails {
268    /// Check if the device is currently online.
269    pub fn is_online(&self) -> bool {
270        self.state == "ONLINE"
271    }
272
273    /// Check if the device is currently offline.
274    pub fn is_offline(&self) -> bool {
275        self.state == "OFFLINE"
276    }
277
278    /// Get the number of available ports.
279    pub fn port_count(&self) -> usize {
280        self.interfaces.ports.len()
281    }
282
283    /// Get the number of available radios.
284    pub fn radio_count(&self) -> usize {
285        self.interfaces.radios.len()
286    }
287
288    /// Check if the device has switching capability.
289    pub fn has_switching(&self) -> bool {
290        self.features.switching.is_some()
291    }
292
293    /// Check if the device has access point capability.
294    pub fn has_access_point(&self) -> bool {
295        self.features.access_point.is_some()
296    }
297
298    /// Get all ports that are currently UP.
299    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    /// Get all ports that are currently DOWN.
308    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    /// Check if this device is a switch (has switching capability).
317    pub fn is_switch(&self) -> bool {
318        self.has_switching()
319    }
320
321    /// Get all ports with PoE capability.
322    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    /// Get all ports with PoE enabled.
331    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    /// Get all ports actively delivering PoE power.
340    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    /// Get the count of PoE-capable ports.
349    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        // Test with anonymized response based on real device structure
547        // Uses RFC 5737 test IP (198.51.100.x) and RFC 7042 example MAC (00:00:5e:00:53:xx)
548        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        // Verify ports
622        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        // Verify radios
635        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        // Test with minimal required fields (no adoptedAt, no uplink)
652        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        // Test with exact response from API docs
711        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        // Verify all fields deserialized correctly
762        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        // Verify ports and radios
774        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        // Test PoE with enabled and UP state
799        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        // Test PoE with enabled but DOWN state
809        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        // Test PoE disabled
819        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        // Test PoE with None values
829        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        // Port with active PoE
842        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        // Port with disabled PoE
860        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        // Port without PoE
878        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        // Test 10G port speed
918        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        // Create a switch device with multiple ports
932        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        // Test is_switch
1001        assert!(device.is_switch());
1002        assert!(device.has_switching());
1003        assert!(!device.has_access_point());
1004
1005        // Test port counts
1006        assert_eq!(device.port_count(), 4);
1007        assert_eq!(device.active_ports().len(), 3);
1008        assert_eq!(device.inactive_ports().len(), 1);
1009
1010        // Test PoE port methods
1011        assert_eq!(device.poe_port_count(), 3); // 3 ports have PoE capability
1012        assert_eq!(device.poe_ports().len(), 3);
1013        assert_eq!(device.poe_enabled_ports().len(), 2); // 2 ports have PoE enabled
1014        assert_eq!(device.poe_active_ports().len(), 1); // Only 1 port is actively delivering power
1015
1016        // Verify the active PoE port is port 1
1017        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}