use super::tests_helpers::{make_namespace_config, make_namespace_env_config};
use super::*;
use crate::config::{ConfigEntry, Environment, Group, Server};
fn create_test_config() -> Config {
Config {
defaults: None,
includes: vec![],
groups: vec![ConfigEntry::Group(Group {
name: "G1".to_string(),
user: None,
ssh_key: None,
mode: None,
ssh_port: None,
ssh_options: None,
wallix: None,
wallix_group: None,
jump: None,
probe_filesystems: None,
environments: Some(vec![Environment {
name: "E1".to_string(),
user: None,
ssh_key: None,
mode: None,
ssh_port: None,
ssh_options: None,
wallix: None,
wallix_group: None,
jump: None,
probe_filesystems: None,
tunnels: None,
tags: None,
servers: vec![Server {
name: "S1".to_string(),
host: "198.51.100.1".to_string(),
user: None,
ssh_key: None,
ssh_port: None,
ssh_options: None,
mode: None,
wallix: None,
jump: None,
probe_filesystems: None,
tunnels: None,
tags: None,
..Default::default()
}],
}]),
servers: Some(vec![Server {
name: "S2".to_string(),
host: "198.51.100.2".to_string(),
user: None,
ssh_key: None,
ssh_port: None,
ssh_options: None,
mode: None,
wallix: None,
jump: None,
probe_filesystems: None,
tunnels: None,
tags: None,
..Default::default()
}]),
tunnels: None,
tags: None,
})],
vars: Default::default(),
}
}
#[test]
fn test_initial_visibility() {
let config = create_test_config();
let mut app = App::new(config, vec![], std::path::PathBuf::new(), vec![]).unwrap();
let items = app.get_visible_items();
assert_eq!(items.len(), 1);
match &items[0] {
ConfigItem::Group(name, _ns) => assert_eq!(name, "G1"),
_ => panic!("Expected Group G1"),
}
}
#[test]
fn test_expansion() {
let config = create_test_config();
let mut app = App::new(config, vec![], std::path::PathBuf::new(), vec![]).unwrap();
app.toggle_expansion();
let items = app.get_visible_items();
assert_eq!(items.len(), 3);
match &items[1] {
ConfigItem::Environment(g, e, _ns) => {
assert_eq!(g, "G1");
assert_eq!(e, "E1");
}
_ => panic!("Expected Environment E1"),
}
match &items[2] {
ConfigItem::Server(s) => assert_eq!(s.name, "S2"),
_ => panic!("Expected Server S2"),
}
}
#[test]
fn test_collapse_all() {
let config = create_test_config();
let mut app = App::new(config, vec![], std::path::PathBuf::new(), vec![]).unwrap();
app.toggle_expansion();
app.selected_index = 1;
app.items_dirty = true;
app.toggle_expansion();
assert!(!app.expanded_items.is_empty());
app.collapse_all();
assert!(app.expanded_items.is_empty());
assert_eq!(app.selected_index, 0);
let items = app.get_visible_items();
assert_eq!(items.len(), 1);
}
#[test]
fn test_search_filtering() {
let config = create_test_config();
let mut app = App::new(config, vec![], std::path::PathBuf::new(), vec![]).unwrap();
app.search_query = "S1".to_string();
app.invalidate_cache();
let items = app.get_visible_items();
assert!(!items.is_empty());
assert!(
items.iter().all(|i| matches!(i, ConfigItem::Server(_))),
"En mode recherche, seuls des Server doivent apparaître"
);
let has_s1 = items.iter().any(|i| match i {
ConfigItem::Server(s) => s.name == "S1",
_ => false,
});
assert!(has_s1, "Should contain S1");
let has_s2 = items.iter().any(|i| match i {
ConfigItem::Server(s) => s.name == "S2",
_ => false,
});
assert!(!has_s2, "Should NOT contain S2");
}
#[test]
fn test_namespace_visibility_collapsed() {
let config = make_namespace_config();
let mut app = App::new(config, vec![], std::path::PathBuf::new(), vec![]).unwrap();
app.expanded_items.clear();
app.invalidate_cache();
let items = app.get_visible_items();
assert_eq!(items.len(), 2);
assert!(matches!(
&items[0],
ConfigItem::Group(name, ns) if name == "RootGroup" && ns.is_empty()
));
assert!(matches!(&items[1], ConfigItem::Namespace(label) if label == "CES"));
}
#[test]
fn test_namespace_expansion() {
let config = make_namespace_config();
let mut app = App::new(config, vec![], std::path::PathBuf::new(), vec![]).unwrap();
app.expanded_items.clear();
app.invalidate_cache();
app.select(1);
app.toggle_expansion();
let items = app.get_visible_items();
assert_eq!(items.len(), 3);
assert!(matches!(
&items[2],
ConfigItem::Group(name, ns) if name == "CES_Group" && ns == "CES"
));
}
#[test]
fn test_search_crosses_namespaces() {
let config = make_namespace_config();
let mut app = App::new(config, vec![], std::path::PathBuf::new(), vec![]).unwrap();
app.search_query = "ces_srv".to_string();
app.invalidate_cache();
let items = app.get_visible_items();
let has_ces = items.iter().any(|i| match i {
ConfigItem::Server(s) => s.name == "ces_srv",
_ => false,
});
assert!(has_ces, "Search should find ces_srv in namespace CES");
let has_root = items.iter().any(|i| match i {
ConfigItem::Server(s) => s.name == "root_srv",
_ => false,
});
assert!(!has_root, "root_srv should be filtered out");
}
#[test]
fn namespace_hidden_when_no_favorites_inside() {
let config = make_namespace_config();
let mut app = App::new(config, vec![], std::path::PathBuf::new(), vec![]).unwrap();
app.favorites.clear();
app.favorites_only = true;
app.items_dirty = true;
let items = app.get_visible_items();
assert!(
items.iter().all(|i| !matches!(i, ConfigItem::Namespace(_))),
"Namespace without favorites should not appear"
);
}
#[test]
fn namespace_shown_when_has_favorite() {
let config = make_namespace_config();
let mut app = App::new(config, vec![], std::path::PathBuf::new(), vec![]).unwrap();
app.favorites.clear();
let ces_server = app
.resolved_servers
.iter()
.find(|s| s.namespace == "CES")
.cloned()
.unwrap();
app.favorites.insert(App::server_key(&ces_server));
app.favorites_only = true;
app.items_dirty = true;
let items = app.get_visible_items();
let has_ns = items
.iter()
.any(|i| matches!(i, ConfigItem::Namespace(n) if n == "CES"));
assert!(
has_ns,
"CES namespace should appear when it contains a favorite"
);
}
#[test]
fn group_hidden_when_no_favorites_inside_namespace() {
let config = make_namespace_config();
let mut app = App::new(config, vec![], std::path::PathBuf::new(), vec![]).unwrap();
app.favorites.clear();
let root_server = app
.resolved_servers
.iter()
.find(|s| s.namespace.is_empty())
.cloned()
.unwrap();
app.favorites.insert(App::server_key(&root_server));
app.favorites_only = true;
app.expanded_items.insert("NS:CES".to_string());
app.items_dirty = true;
let items = app.get_visible_items();
let has_ces_group = items
.iter()
.any(|i| matches!(i, ConfigItem::Group(n, ns) if n == "CES_Group" && ns == "CES"));
assert!(
!has_ces_group,
"CES_Group should be hidden since it has no favorites"
);
}
#[test]
fn namespace_env_favorite_shows_env_and_server() {
let config = make_namespace_env_config();
let mut app = App::new(config, vec![], std::path::PathBuf::new(), vec![]).unwrap();
app.favorites.clear();
let ns_server = app.resolved_servers[0].clone();
app.favorites.insert(App::server_key(&ns_server));
app.favorites_only = true;
app.expanded_items.insert("NS:NS1".to_string());
app.expanded_items.insert("NS:NS1:Group:GrpA".to_string());
app.expanded_items
.insert("NS:NS1:Env:GrpA:EnvA".to_string());
app.items_dirty = true;
let items = app.get_visible_items();
let has_server = items
.iter()
.any(|i| matches!(i, ConfigItem::Server(s) if s.name == "ns_srv"));
assert!(has_server, "ns_srv should be visible when favorited");
}
#[test]
fn namespace_env_expand_all_covers_ns_env_key() {
let config = make_namespace_env_config();
let mut app = App::new(config, vec![], std::path::PathBuf::new(), vec![]).unwrap();
app.expanded_items.clear();
app.expand_all();
assert!(app.expanded_items.contains("NS:NS1"));
assert!(app.expanded_items.contains("NS:NS1:Group:GrpA"));
assert!(app.expanded_items.contains("NS:NS1:Env:GrpA:EnvA"));
}
#[test]
fn toggle_expansion_on_namespaced_env() {
let config = make_namespace_env_config();
let mut app = App::new(config, vec![], std::path::PathBuf::new(), vec![]).unwrap();
app.expanded_items.insert("NS:NS1".to_string());
app.expanded_items.insert("NS:NS1:Group:GrpA".to_string());
app.items_dirty = true;
app.get_visible_items();
let items = app.get_visible_items();
let env_idx = items
.iter()
.position(|i| matches!(i, ConfigItem::Environment(g, e, ns) if g == "GrpA" && e == "EnvA" && ns == "NS1"))
.unwrap();
app.select(env_idx);
app.toggle_expansion();
assert!(app.expanded_items.contains("NS:NS1:Env:GrpA:EnvA"));
}
#[test]
fn toggle_expansion_on_server_is_noop() {
let config = create_test_config();
let mut app = App::new(config, vec![], std::path::PathBuf::new(), vec![]).unwrap();
app.expanded_items.insert("Group:G1".to_string());
app.expanded_items.insert("Env:G1:E1".to_string());
app.items_dirty = true;
app.get_visible_items();
app.select(3);
let items = app.get_visible_items();
assert!(matches!(items.get(3), Some(ConfigItem::Server(_))));
let expanded_before = app.expanded_items.clone();
app.toggle_expansion();
assert_eq!(app.expanded_items, expanded_before);
}
#[test]
fn toggle_expansion_on_namespace_group_uses_namespaced_key() {
let config = make_namespace_config();
let mut app = App::new(config, vec![], std::path::PathBuf::new(), vec![]).unwrap();
app.expanded_items.clear();
app.items_dirty = true;
app.get_visible_items();
app.select(1);
app.toggle_expansion();
let items = app.get_visible_items();
assert!(
items
.iter()
.any(|i| matches!(i, ConfigItem::Group(n, ns) if n == "CES_Group" && ns == "CES"))
);
let group_idx = items
.iter()
.position(|i| matches!(i, ConfigItem::Group(n, ns) if n == "CES_Group" && ns == "CES"))
.unwrap();
app.select(group_idx);
app.toggle_expansion();
assert!(app.expanded_items.contains("NS:CES:Group:CES_Group"));
}
#[test]
fn favorites_only_hides_non_favorite_servers() {
let config = create_test_config();
let mut app = App::new(config, vec![], std::path::PathBuf::new(), vec![]).unwrap();
app.expanded_items.insert("Group:G1".to_string());
app.expanded_items.insert("Env:G1:E1".to_string());
app.items_dirty = true;
app.favorites.clear();
app.favorites_only = true;
app.items_dirty = true;
let items = app.get_visible_items();
assert!(items.is_empty());
}
#[test]
fn favorites_only_shows_only_favorited_server() {
let config = create_test_config();
let mut app = App::new(config, vec![], std::path::PathBuf::new(), vec![]).unwrap();
app.expanded_items.insert("Group:G1".to_string());
app.expanded_items.insert("Env:G1:E1".to_string());
app.items_dirty = true;
app.get_visible_items();
let server_s2 = app
.resolved_servers
.iter()
.find(|s| s.name == "S2")
.cloned()
.unwrap();
app.favorites.insert(App::server_key(&server_s2));
app.favorites_only = true;
app.items_dirty = true;
let items = app.get_visible_items();
let names: Vec<&str> = items
.iter()
.filter_map(|i| {
if let ConfigItem::Server(s) = i {
Some(s.name.as_str())
} else {
None
}
})
.collect();
assert!(names.contains(&"S2"), "S2 should be visible");
assert!(!names.contains(&"S1"), "S1 should be hidden");
}
#[test]
fn sort_by_recent_returns_flat_list_of_servers() {
let config = create_test_config();
let mut app = App::new(config, vec![], std::path::PathBuf::new(), vec![]).unwrap();
app.sort_by_recent = true;
app.items_dirty = true;
let items = app.get_visible_items();
assert!(!items.is_empty());
assert!(items.iter().all(|i| matches!(i, ConfigItem::Server(_))));
}
#[test]
fn sort_by_recent_orders_by_timestamp_descending() {
let config = create_test_config();
let mut app = App::new(config, vec![], std::path::PathBuf::new(), vec![]).unwrap();
let s1 = app
.resolved_servers
.iter()
.find(|s| s.name == "S1")
.cloned()
.unwrap();
let s2 = app
.resolved_servers
.iter()
.find(|s| s.name == "S2")
.cloned()
.unwrap();
app.last_seen.insert(App::server_key(&s1), 2000);
app.last_seen.insert(App::server_key(&s2), 1000);
app.sort_by_recent = true;
app.items_dirty = true;
let items = app.get_visible_items();
let names: Vec<&str> = items
.iter()
.filter_map(|i| {
if let ConfigItem::Server(s) = i {
Some(s.name.as_str())
} else {
None
}
})
.collect();
assert_eq!(names[0], "S1", "S1 (newer) should come first");
assert_eq!(names[1], "S2");
}
#[test]
fn sort_by_recent_respects_favorites_only() {
let config = create_test_config();
let mut app = App::new(config, vec![], std::path::PathBuf::new(), vec![]).unwrap();
app.favorites.clear();
let s2 = app
.resolved_servers
.iter()
.find(|s| s.name == "S2")
.cloned()
.unwrap();
app.favorites.insert(App::server_key(&s2));
app.sort_by_recent = true;
app.favorites_only = true;
app.items_dirty = true;
let items = app.get_visible_items();
assert_eq!(items.len(), 1);
if let ConfigItem::Server(s) = &items[0] {
assert_eq!(s.name, "S2");
}
}