use std::collections::HashMap;
use std::io::Write;
use std::sync::Arc;
use tempfile::TempDir;
use tokio::runtime::Runtime;
use aethermapd::config::ConfigManager;
use aethermapd::remap_engine::RemapEngine;
fn create_test_config_manager(temp_dir: &TempDir) -> ConfigManager {
ConfigManager {
config_path: temp_dir.path().join("config.yaml"),
macros_path: temp_dir.path().join("macros.yaml"),
cache_path: temp_dir.path().join("macros.bin"),
profiles_dir: temp_dir.path().join("profiles"),
remaps_path: temp_dir.path().join("remaps.yaml"),
device_profiles_path: temp_dir.path().join("device_profiles.yaml"),
layer_state_path: temp_dir.path().join("layer_state.yaml"),
config: Arc::new(tokio::sync::RwLock::new(aethermapd::config::DaemonConfig::default())),
macros: Arc::new(tokio::sync::RwLock::new(HashMap::new())),
profiles: Arc::new(tokio::sync::RwLock::new(HashMap::new())),
remaps: Arc::new(tokio::sync::RwLock::new(HashMap::new())),
device_profiles: Arc::new(tokio::sync::RwLock::new(HashMap::new())),
}
}
fn write_remaps_file(path: &std::path::Path, content: &str) -> std::io::Result<()> {
let mut file = std::fs::File::create(path)?;
file.write_all(content.as_bytes())
}
#[test]
fn test_valid_remap_reload() {
let rt = Runtime::new().unwrap();
let temp_dir = TempDir::new().unwrap();
let manager = create_test_config_manager(&temp_dir);
let remaps_path = temp_dir.path().join("remaps.yaml");
let initial_config = r#"
# Initial remapping configuration
capslock: leftctrl
"#;
write_remaps_file(&remaps_path, initial_config).unwrap();
rt.block_on(async {
let result = manager.load_remaps().await;
assert!(result.is_ok(), "Initial config load should succeed");
let remaps = manager.remaps.read().await;
assert_eq!(remaps.len(), 1);
assert_eq!(remaps.get("capslock"), Some(&"leftctrl".to_string()));
});
let updated_config = r#"
# Updated remapping configuration
capslock: leftctrl
a: b
"#;
write_remaps_file(&remaps_path, updated_config).unwrap();
rt.block_on(async {
let result = manager.load_remaps().await;
assert!(result.is_ok(), "Updated config load should succeed");
let remaps = manager.remaps.read().await;
assert_eq!(remaps.len(), 2);
assert_eq!(remaps.get("capslock"), Some(&"leftctrl".to_string()));
assert_eq!(remaps.get("a"), Some(&"b".to_string()));
});
}
#[test]
fn test_invalid_remap_rejection() {
let rt = Runtime::new().unwrap();
let temp_dir = TempDir::new().unwrap();
let manager = create_test_config_manager(&temp_dir);
let remaps_path = temp_dir.path().join("remaps.yaml");
let initial_config = r#"
capslock: leftctrl
a: b
"#;
write_remaps_file(&remaps_path, initial_config).unwrap();
rt.block_on(async {
let result = manager.load_remaps().await;
assert!(result.is_ok());
let remaps = manager.remaps.read().await;
assert_eq!(remaps.len(), 2);
drop(remaps);
drop(write_remaps_file(
&remaps_path,
"invalid_key_xyz: leftctrl\n",
));
let result = manager.load_remaps().await;
assert!(result.is_err(), "Invalid config should be rejected");
let remaps = manager.remaps.read().await;
assert_eq!(remaps.len(), 2, "Original remaps should remain active");
assert_eq!(
remaps.get("capslock"),
Some(&"leftctrl".to_string()),
"Original remaps should be unchanged"
);
});
}
#[test]
fn test_reload_remaps_with_engine() {
let rt = Runtime::new().unwrap();
let temp_dir = TempDir::new().unwrap();
let manager = create_test_config_manager(&temp_dir);
let remaps_path = temp_dir.path().join("remaps.yaml");
write_remaps_file(&remaps_path, "capslock: leftctrl\n").unwrap();
rt.block_on(async {
let engine = Arc::new(RemapEngine::new());
let result = manager.reload_remaps(engine.clone()).await;
assert!(result.is_ok(), "Initial reload should succeed");
assert_eq!(engine.remap_count().await, 1);
write_remaps_file(&remaps_path, "capslock: leftctrl\na: b\n").unwrap();
let result = manager.reload_remaps(engine.clone()).await;
assert!(result.is_ok(), "Updated reload should succeed");
assert_eq!(engine.remap_count().await, 2);
});
}
#[test]
fn test_reload_remaps_validates_keys() {
let rt = Runtime::new().unwrap();
let temp_dir = TempDir::new().unwrap();
let manager = create_test_config_manager(&temp_dir);
let remaps_path = temp_dir.path().join("remaps.yaml");
rt.block_on(async {
let engine = Arc::new(RemapEngine::new());
write_remaps_file(&remaps_path, "capslock: leftctrl\n").unwrap();
let result = manager.reload_remaps(engine.clone()).await;
assert!(result.is_ok());
let initial_count = engine.remap_count().await;
assert_eq!(initial_count, 1);
write_remaps_file(&remaps_path, "not_a_real_key: leftctrl\n").unwrap();
let result = manager.reload_remaps(engine.clone()).await;
assert!(result.is_err(), "Should reject invalid key name");
let final_count = engine.remap_count().await;
assert_eq!(
final_count, initial_count,
"Engine should keep original config on validation failure"
);
});
}
fn write_device_profiles_file(path: &std::path::Path, content: &str) -> std::io::Result<()> {
let mut file = std::fs::File::create(path)?;
file.write_all(content.as_bytes())
}
#[test]
fn test_valid_device_profile_reload() {
let rt = Runtime::new().unwrap();
let temp_dir = TempDir::new().unwrap();
let manager = create_test_config_manager(&temp_dir);
let profiles_path = temp_dir.path().join("device_profiles.yaml");
let config = r#"
devices:
"1532:0220":
device_id: "1532:0220"
profiles:
gaming:
name: "gaming"
remaps:
- from: capslock
to: leftctrl
work:
name: "work"
remaps:
- from: a
to: b
"#;
write_device_profiles_file(&profiles_path, config).unwrap();
rt.block_on(async {
let result = manager.reload_device_profiles().await;
assert!(result.is_ok(), "Valid device profiles should load successfully");
let devices = manager.list_profile_devices().await;
assert_eq!(devices.len(), 1);
assert!(devices.contains(&"1532:0220".to_string()));
let profiles = manager.list_device_profiles("1532:0220").await;
assert_eq!(profiles.len(), 2);
assert!(profiles.contains(&"gaming".to_string()));
assert!(profiles.contains(&"work".to_string()));
});
}
#[test]
fn test_invalid_device_profile_rejection() {
let rt = Runtime::new().unwrap();
let temp_dir = TempDir::new().unwrap();
let manager = create_test_config_manager(&temp_dir);
let profiles_path = temp_dir.path().join("device_profiles.yaml");
rt.block_on(async {
let valid_config = r#"
devices:
"1532:0220":
device_id: "1532:0220"
profiles:
gaming:
name: "gaming"
remaps:
- from: capslock
to: leftctrl
"#;
write_device_profiles_file(&profiles_path, valid_config).unwrap();
let result = manager.reload_device_profiles().await;
assert!(result.is_ok());
let devices = manager.list_profile_devices().await;
assert_eq!(devices.len(), 1);
let profiles = manager.list_device_profiles("1532:0220").await;
assert_eq!(profiles.len(), 1);
let invalid_config = r#"
devices:
"1532:0220":
device_id: "1532:0220"
profiles:
bad_profile:
name: "bad_profile"
remaps:
- from: invalid_key_name_xyz
to: leftctrl
"#;
write_device_profiles_file(&profiles_path, invalid_config).unwrap();
let result = manager.reload_device_profiles().await;
assert!(result.is_err(), "Should reject invalid key name");
let devices = manager.list_profile_devices().await;
assert_eq!(
devices.len(),
1,
"Original device list should remain"
);
let profiles = manager.list_device_profiles("1532:0220").await;
assert_eq!(
profiles.len(),
1,
"Original profiles should remain"
);
});
}
#[test]
fn test_device_profile_atomic_swap() {
let rt = Runtime::new().unwrap();
let temp_dir = TempDir::new().unwrap();
let manager = create_test_config_manager(&temp_dir);
let profiles_path = temp_dir.path().join("device_profiles.yaml");
rt.block_on(async {
let multi_device_config = r#"
devices:
"1532:0220":
device_id: "1532:0220"
profiles:
gaming:
name: "gaming"
remaps:
- from: capslock
to: leftctrl
"046d:c52b":
device_id: "046d:c52b"
profiles:
default:
name: "default"
remaps:
- from: a
to: b
"#;
write_device_profiles_file(&profiles_path, multi_device_config).unwrap();
let result = manager.reload_device_profiles().await;
assert!(result.is_ok());
let devices = manager.list_profile_devices().await;
assert_eq!(devices.len(), 2);
let partial_invalid_config = r#"
devices:
"1532:0220":
device_id: "1532:0220"
profiles:
gaming:
name: "gaming"
remaps:
- from: capslock
to: leftctrl
"046d:c52b":
device_id: "046d:c52b"
profiles:
bad_profile:
name: "bad_profile"
remaps:
- from: invalid_key_xyz
to: leftctrl
"#;
write_device_profiles_file(&profiles_path, partial_invalid_config).unwrap();
let result = manager.reload_device_profiles().await;
assert!(result.is_err(), "Should reject config with invalid keys");
let devices = manager.list_profile_devices().await;
assert_eq!(
devices.len(),
2,
"Should keep both original devices"
);
assert!(devices.contains(&"1532:0220".to_string()));
assert!(devices.contains(&"046d:c52b".to_string()));
});
}
#[test]
fn test_empty_remap_reload() {
let rt = Runtime::new().unwrap();
let temp_dir = TempDir::new().unwrap();
let manager = create_test_config_manager(&temp_dir);
let remaps_path = temp_dir.path().join("remaps.yaml");
rt.block_on(async {
write_remaps_file(&remaps_path, "capslock: leftctrl\n").unwrap();
let result = manager.load_remaps().await;
assert!(result.is_ok());
let remaps = manager.remaps.read().await;
assert_eq!(remaps.len(), 1);
drop(remaps);
write_remaps_file(&remaps_path, "{}\n").unwrap();
let result = manager.load_remaps().await;
assert!(result.is_ok(), "Empty config should be valid");
let remaps = manager.remaps.read().await;
assert_eq!(remaps.len(), 0, "Empty config should clear remaps");
});
}
#[test]
fn test_concurrent_reload_safety() {
let rt = Runtime::new().unwrap();
let temp_dir = TempDir::new().unwrap();
let manager = Arc::new(create_test_config_manager(&temp_dir));
let remaps_path = temp_dir.path().join("remaps.yaml");
rt.block_on(async {
write_remaps_file(&remaps_path, "capslock: leftctrl\na: b\n").unwrap();
let manager1 = manager.clone();
let manager2 = manager.clone();
let manager3 = manager.clone();
let task1 = tokio::spawn(async move {
manager1.load_remaps().await
});
let task2 = tokio::spawn(async move {
manager2.load_remaps().await
});
let task3 = tokio::spawn(async move {
manager3.load_remaps().await
});
let results = vec![
task1.await.unwrap(),
task2.await.unwrap(),
task3.await.unwrap(),
];
for result in results {
assert!(result.is_ok(), "Concurrent reloads should succeed");
}
let remaps = manager.remaps.read().await;
assert_eq!(remaps.len(), 2);
});
}