use crate::models::{Client, ClientType};
use std::collections::HashMap;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct DeviceClientStats {
pub device_id: String,
pub total_clients: usize,
pub wired_clients: usize,
pub wireless_clients: usize,
pub guest_clients: usize,
}
impl DeviceClientStats {
pub fn new(device_id: impl Into<String>) -> Self {
Self {
device_id: device_id.into(),
..Default::default()
}
}
pub fn add_client(&mut self, client: &Client) {
self.total_clients += 1;
match client.client_type {
ClientType::Wired => self.wired_clients += 1,
ClientType::Wireless => self.wireless_clients += 1,
ClientType::Unknown => {}
}
if client.is_guest() {
self.guest_clients += 1;
}
}
pub fn has_clients(&self) -> bool {
self.total_clients > 0
}
pub fn non_guest_clients(&self) -> usize {
self.total_clients.saturating_sub(self.guest_clients)
}
}
pub fn aggregate_clients_by_device(clients: &[Client]) -> HashMap<String, DeviceClientStats> {
let mut stats: HashMap<String, DeviceClientStats> = HashMap::new();
for client in clients {
if let Some(device_id) = &client.uplink_device_id {
stats
.entry(device_id.clone())
.or_insert_with(|| DeviceClientStats::new(device_id))
.add_client(client);
}
}
stats
}
pub fn get_device_client_stats(clients: &[Client], device_id: &str) -> DeviceClientStats {
let mut stats = DeviceClientStats::new(device_id);
for client in clients {
if client.uplink_device_id.as_deref() == Some(device_id) {
stats.add_client(client);
}
}
stats
}
#[cfg(test)]
mod tests {
use super::*;
use crate::models::{AccessType, ClientAccess};
fn make_client(
id: &str,
client_type: ClientType,
device_id: Option<&str>,
is_guest: bool,
) -> Client {
Client {
id: id.to_string(),
client_type,
name: None,
connected_at: None,
ip_address: None,
access: if is_guest {
Some(ClientAccess {
access_type: AccessType::Guest,
})
} else {
None
},
uplink_device_id: device_id.map(String::from),
}
}
#[test]
fn test_device_client_stats_new() {
let stats = DeviceClientStats::new("device-123");
assert_eq!(stats.device_id, "device-123");
assert_eq!(stats.total_clients, 0);
assert_eq!(stats.wired_clients, 0);
assert_eq!(stats.wireless_clients, 0);
assert_eq!(stats.guest_clients, 0);
}
#[test]
fn test_device_client_stats_add_client() {
let mut stats = DeviceClientStats::new("device-1");
stats.add_client(&make_client(
"c1",
ClientType::Wireless,
Some("device-1"),
false,
));
assert_eq!(stats.total_clients, 1);
assert_eq!(stats.wireless_clients, 1);
assert_eq!(stats.wired_clients, 0);
assert_eq!(stats.guest_clients, 0);
stats.add_client(&make_client(
"c2",
ClientType::Wired,
Some("device-1"),
false,
));
assert_eq!(stats.total_clients, 2);
assert_eq!(stats.wireless_clients, 1);
assert_eq!(stats.wired_clients, 1);
stats.add_client(&make_client(
"c3",
ClientType::Wireless,
Some("device-1"),
true,
));
assert_eq!(stats.total_clients, 3);
assert_eq!(stats.wireless_clients, 2);
assert_eq!(stats.guest_clients, 1);
}
#[test]
fn test_device_client_stats_non_guest_clients() {
let mut stats = DeviceClientStats::new("device-1");
stats.total_clients = 10;
stats.guest_clients = 3;
assert_eq!(stats.non_guest_clients(), 7);
}
#[test]
fn test_aggregate_clients_by_device() {
let clients = vec![
make_client("c1", ClientType::Wireless, Some("device-1"), false),
make_client("c2", ClientType::Wired, Some("device-1"), false),
make_client("c3", ClientType::Wireless, Some("device-1"), true),
make_client("c4", ClientType::Wireless, Some("device-2"), false),
make_client("c5", ClientType::Wireless, Some("device-2"), true),
make_client("c6", ClientType::Wireless, None, false),
];
let stats = aggregate_clients_by_device(&clients);
assert_eq!(stats.len(), 2);
let device1_stats = stats.get("device-1").unwrap();
assert_eq!(device1_stats.total_clients, 3);
assert_eq!(device1_stats.wireless_clients, 2);
assert_eq!(device1_stats.wired_clients, 1);
assert_eq!(device1_stats.guest_clients, 1);
let device2_stats = stats.get("device-2").unwrap();
assert_eq!(device2_stats.total_clients, 2);
assert_eq!(device2_stats.wireless_clients, 2);
assert_eq!(device2_stats.wired_clients, 0);
assert_eq!(device2_stats.guest_clients, 1);
}
#[test]
fn test_aggregate_clients_empty_list() {
let clients: Vec<Client> = vec![];
let stats = aggregate_clients_by_device(&clients);
assert!(stats.is_empty());
}
#[test]
fn test_aggregate_clients_no_device_ids() {
let clients = vec![
make_client("c1", ClientType::Wireless, None, false),
make_client("c2", ClientType::Wired, None, false),
];
let stats = aggregate_clients_by_device(&clients);
assert!(stats.is_empty());
}
#[test]
fn test_get_device_client_stats() {
let clients = vec![
make_client("c1", ClientType::Wireless, Some("device-1"), false),
make_client("c2", ClientType::Wired, Some("device-1"), false),
make_client("c3", ClientType::Wireless, Some("device-2"), false),
];
let stats = get_device_client_stats(&clients, "device-1");
assert_eq!(stats.device_id, "device-1");
assert_eq!(stats.total_clients, 2);
assert_eq!(stats.wireless_clients, 1);
assert_eq!(stats.wired_clients, 1);
}
#[test]
fn test_get_device_client_stats_no_matches() {
let clients = vec![make_client(
"c1",
ClientType::Wireless,
Some("device-1"),
false,
)];
let stats = get_device_client_stats(&clients, "device-999");
assert_eq!(stats.device_id, "device-999");
assert_eq!(stats.total_clients, 0);
assert!(!stats.has_clients());
}
#[test]
fn test_has_clients() {
let mut stats = DeviceClientStats::new("device-1");
assert!(!stats.has_clients());
stats.total_clients = 1;
assert!(stats.has_clients());
}
}