#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use super::*;
use crate::daemon::models::{CommandMode, ControlSource, GimbalCommand, PrimaryControl};
#[test]
fn primary_control_default_allows_all() {
let m = StateManager::new();
assert!(m.is_primary_allowed(1, 1));
assert!(m.is_primary_allowed(42, 200));
}
#[test]
fn primary_control_exact_match_only() {
let m = StateManager::new();
m.set_primary_control(PrimaryControl {
primary_sysid: 7,
primary_compid: 190,
secondary_sysid: 0,
secondary_compid: 0,
});
assert!(m.is_primary_allowed(7, 190));
assert!(!m.is_primary_allowed(7, 191));
assert!(!m.is_primary_allowed(8, 190));
}
#[test]
fn test_state_manager_init() {
let manager = StateManager::new();
let state = manager.get_state();
assert_eq!(state.yaw, 0.0);
assert_eq!(state.pitch, 0.0);
assert_eq!(state.roll, 0.0);
}
#[test]
fn test_state_update() {
let manager = StateManager::new();
assert!(manager.get_state().measured_at.is_none());
manager.update_measured_angles(10.0, 20.0, 30.0);
let state = manager.get_state();
assert_eq!(state.yaw, 10.0);
assert_eq!(state.pitch, 20.0);
assert_eq!(state.roll, 30.0);
assert!(state.measured_at.is_some());
}
#[test]
fn vehicle_yaw_returns_none_until_set() {
let m = StateManager::new();
assert!(m.vehicle_yaw_deg(Duration::from_secs(1)).is_none());
}
#[test]
fn vehicle_yaw_returns_value_within_window() {
let m = StateManager::new();
m.update_vehicle_yaw(42.5);
let yaw = m.vehicle_yaw_deg(Duration::from_secs(1));
assert!(yaw.is_some());
assert!((yaw.unwrap() - 42.5).abs() < 1e-4);
}
#[test]
fn vehicle_yaw_returns_none_when_stale() {
let m = StateManager::new();
m.update_vehicle_yaw(42.5);
assert!(m.vehicle_yaw_deg(Duration::ZERO).is_none());
}
#[test]
fn evict_for_insert_drops_entries_past_ttl_keeps_fresh() {
let mut map: HashMap<ControlSource, Instant> = HashMap::new();
let now = Instant::now();
let stale = now - Duration::from_secs(600);
let fresh = now - Duration::from_millis(100);
map.insert(ControlSource::Mavlink(1), stale);
map.insert(ControlSource::UnixSocket(99), fresh);
assert_eq!(map.len(), 2);
evict_for_insert(&mut map, now);
assert_eq!(map.len(), 1);
assert!(map.contains_key(&ControlSource::UnixSocket(99)));
assert!(!map.contains_key(&ControlSource::Mavlink(1)));
}
#[test]
fn evict_for_insert_enforces_hard_cap_under_burst() {
let mut map: HashMap<ControlSource, Instant> = HashMap::new();
let now = Instant::now();
for i in 0..MAX_TRACKED_SOURCES as u32 {
map.insert(
ControlSource::UnixSocket(i),
now - Duration::from_millis(u64::from(i)),
);
}
assert_eq!(map.len(), MAX_TRACKED_SOURCES);
evict_for_insert(&mut map, now);
assert_eq!(map.len(), MAX_TRACKED_SOURCES - 1);
let evicted = ControlSource::UnixSocket(MAX_TRACKED_SOURCES as u32 - 1);
assert!(!map.contains_key(&evicted));
}
#[test]
fn rate_tick_burst_stays_bounded_at_cap() {
let manager = StateManager::new();
let now = Instant::now();
for i in 0..(MAX_TRACKED_SOURCES as u32) * 4 {
manager.record_rate_tick(
ControlSource::UnixSocket(i),
now + Duration::from_nanos(u64::from(i)),
);
}
let map = manager.last_rate_at.read();
assert!(
map.len() <= MAX_TRACKED_SOURCES,
"map.len() = {} exceeded cap {}",
map.len(),
MAX_TRACKED_SOURCES
);
}
#[test]
fn rate_limit_evicts_stale_source_entries() {
let manager = StateManager::with_min_command_interval(std::time::Duration::from_millis(20));
{
let mut map = manager.last_command_at.write();
map.insert(
ControlSource::UnixSocket(1234),
Instant::now() - Duration::from_secs(600),
);
assert_eq!(map.len(), 1);
}
let cmd = GimbalCommand::new(
ControlSource::Mavlink(2),
CommandMode::Position,
Some(0.0),
Some(0.0),
Some(0.0),
);
assert!(manager.set_command(cmd));
let map = manager.last_command_at.read();
assert_eq!(map.len(), 1);
assert!(map.contains_key(&ControlSource::Mavlink(2)));
}
#[test]
fn rate_tick_evicts_stale_source_entries() {
let manager = StateManager::new();
{
let mut map = manager.last_rate_at.write();
map.insert(
ControlSource::UnixSocket(7777),
Instant::now() - Duration::from_secs(600),
);
}
manager.record_rate_tick(ControlSource::Mavlink(3), Instant::now());
let map = manager.last_rate_at.read();
assert_eq!(map.len(), 1);
assert!(map.contains_key(&ControlSource::Mavlink(3)));
}
#[test]
fn rate_limit_disabled_by_default() {
let manager = StateManager::new();
let cmd = || {
GimbalCommand::new(
ControlSource::Cli,
CommandMode::Position,
Some(0.0),
Some(0.0),
Some(0.0),
)
};
assert!(manager.set_command(cmd()));
std::thread::sleep(std::time::Duration::from_millis(1));
assert!(manager.set_command(cmd()));
}
#[test]
fn rate_limit_blocks_back_to_back_same_source() {
let manager = StateManager::with_min_command_interval(std::time::Duration::from_millis(50));
let cmd = || {
GimbalCommand::new(
ControlSource::Mavlink(1),
CommandMode::Position,
Some(0.0),
Some(0.0),
Some(0.0),
)
};
assert!(manager.set_command(cmd()));
assert!(!manager.set_command(cmd()));
}
#[test]
fn rate_limit_isolates_per_source() {
let manager = StateManager::with_min_command_interval(std::time::Duration::from_millis(500));
let cmd_from = |sysid: u8| {
GimbalCommand::new(
ControlSource::Mavlink(sysid),
CommandMode::Position,
Some(0.0),
Some(0.0),
Some(0.0),
)
};
assert!(manager.set_command(cmd_from(2)));
assert!(!manager.set_command(cmd_from(2)));
assert!(manager.set_command(cmd_from(3)));
}
#[test]
fn rate_limit_clears_after_interval() {
let interval = std::time::Duration::from_millis(20);
let manager = StateManager::with_min_command_interval(interval);
let cmd = || {
GimbalCommand::new(
ControlSource::Cli,
CommandMode::Position,
Some(0.0),
Some(0.0),
Some(0.0),
)
};
assert!(manager.set_command(cmd()));
std::thread::sleep(std::time::Duration::from_millis(60));
assert!(manager.set_command(cmd()));
}
#[test]
fn test_command_priority() {
let manager = StateManager::new();
let cmd1 = GimbalCommand::new(
ControlSource::Cli,
CommandMode::Position,
Some(0.0),
Some(0.0),
Some(0.0),
);
assert!(manager.set_command(cmd1.clone()));
let cmd2 = GimbalCommand::new(
ControlSource::Mavlink(1),
CommandMode::Position,
Some(10.0),
Some(10.0),
Some(10.0),
);
assert!(manager.set_command(cmd2.clone()));
let cmd3 = GimbalCommand::new(
ControlSource::UnixSocket(1),
CommandMode::Position,
Some(20.0),
Some(20.0),
Some(20.0),
);
assert!(!manager.set_command(cmd3));
let last = manager.get_last_command().unwrap();
assert_eq!(last.yaw, Some(10.0));
}