use super::tests_helpers::make_namespace_config;
use super::*;
fn wallix_test_server(group: Option<&str>) -> ResolvedServer {
ResolvedServer {
namespace: String::new(),
group_name: "ALPHA-BD".to_string(),
env_name: String::new(),
name: "app-alpha".to_string(),
host: "APP-ALPHA-BD".to_string(),
user: "demo_user".to_string(),
port: 22,
ssh_key: String::new(),
ssh_options: vec![],
default_mode: ConnectionMode::Wallix,
jump_host: None,
bastion_host: Some("bastion.example.test".to_string()),
bastion_user: Some("demo_user".to_string()),
bastion_template: "{target_user}@%n:SSH:{bastion_user}".to_string(),
wallix_group: group.map(str::to_string),
wallix_account: "default".to_string(),
wallix_protocol: "SSH".to_string(),
wallix_auto_select: true,
wallix_fail_if_menu_match_error: true,
wallix_selection_timeout_secs: 8,
wallix_direct: false,
wallix_authorization: None,
wallix_header_columns: vec![],
use_system_ssh_config: false,
probe_filesystems: vec![],
tunnels: vec![],
tags: vec![],
control_master: false,
agent_forwarding: false,
control_path: String::new(),
control_persist: "10m".to_string(),
pre_connect_hook: None,
post_disconnect_hook: None,
hook_timeout_secs: 5,
ssh_cert: String::new(),
notes: String::new(),
ssh_agent_sock: String::new(),
}
}
#[test]
fn wallix_selector_required_when_auto_select_disabled() {
let mut app = App::new(
make_namespace_config(),
vec![],
std::path::PathBuf::new(),
vec![],
)
.unwrap();
app.connection_mode = ConnectionMode::Wallix;
let mut server = app.resolved_servers[0].clone();
server.wallix_auto_select = false;
server.wallix_fail_if_menu_match_error = true;
assert!(app.should_open_wallix_selector(&server));
}
#[test]
fn wallix_selector_required_when_auto_select_enabled() {
let mut app = App::new(
make_namespace_config(),
vec![],
std::path::PathBuf::new(),
vec![],
)
.unwrap();
app.connection_mode = ConnectionMode::Wallix;
let mut server = app.resolved_servers[0].clone();
server.wallix_auto_select = true;
server.wallix_fail_if_menu_match_error = false;
assert!(app.should_open_wallix_selector(&server));
}
#[test]
fn wallix_poll_auto_resolves_to_pending_connection() {
let mut app = App::new(
make_namespace_config(),
vec![],
std::path::PathBuf::new(),
vec![],
)
.unwrap();
let server = wallix_test_server(Some("dev-admins"));
let entries = vec![WallixMenuEntry {
id: "42".to_string(),
target: "demo_user@default@APP-ALPHA-BD:SSH".to_string(),
group: "APP-ALPHA_dev-admins".to_string(),
}];
let (tx, rx) = std::sync::mpsc::channel();
app.wallix_selector_rx = Some(rx);
tx.send((server.clone(), Ok(entries))).unwrap();
app.poll_wallix_selector();
assert!(app.wallix_selector.is_none());
let pending = app.take_pending_wallix_connection();
assert!(pending.is_some());
let (_, selected_id, _) = pending.unwrap();
assert_eq!(selected_id, "42");
assert_eq!(
app.wallix_selection_cache.get(&App::server_key(&server)),
Some(&"42".to_string())
);
}
#[test]
fn wallix_poll_ambiguous_resolution_opens_targeted_selector() {
let mut app = App::new(
make_namespace_config(),
vec![],
std::path::PathBuf::new(),
vec![],
)
.unwrap();
let server = wallix_test_server(Some("dev-admins"));
let entries = vec![
WallixMenuEntry {
id: "11".to_string(),
target: "demo_user@default@APP-ALPHA-BD:SSH".to_string(),
group: "APP-ALPHA_dev-admins".to_string(),
},
WallixMenuEntry {
id: "12".to_string(),
target: "demo_user@default@APP-ALPHA-BD:SSH".to_string(),
group: "APP-ALPHA_dev-admins".to_string(),
},
WallixMenuEntry {
id: "99".to_string(),
target: "demo_user@default@OTHER:SSH".to_string(),
group: "OTHER_dev-admins".to_string(),
},
];
let (tx, rx) = std::sync::mpsc::channel();
app.wallix_selector_rx = Some(rx);
tx.send((server, Ok(entries))).unwrap();
app.poll_wallix_selector();
assert!(app.take_pending_wallix_connection().is_none());
match &app.wallix_selector {
Some(WallixSelectorState::List { entries, .. }) => {
assert_eq!(entries.len(), 2);
assert!(
entries
.iter()
.all(|entry| entry.target == "demo_user@default@APP-ALPHA-BD:SSH")
);
}
_ => panic!("expected Wallix selector list"),
}
}
#[test]
fn wallix_poll_missing_group_opens_targeted_selector() {
let mut app = App::new(
make_namespace_config(),
vec![],
std::path::PathBuf::new(),
vec![],
)
.unwrap();
let server = wallix_test_server(None);
let entries = vec![
WallixMenuEntry {
id: "21".to_string(),
target: "demo_user@default@APP-ALPHA-BD:SSH".to_string(),
group: "APP-ALPHA_ops-admins".to_string(),
},
WallixMenuEntry {
id: "22".to_string(),
target: "demo_user@default@APP-ALPHA-BD:SSH".to_string(),
group: "APP-ALPHA_dev-admins".to_string(),
},
];
let (tx, rx) = std::sync::mpsc::channel();
app.wallix_selector_rx = Some(rx);
tx.send((server, Ok(entries))).unwrap();
app.poll_wallix_selector();
assert!(app.take_pending_wallix_connection().is_none());
assert!(matches!(
app.wallix_selector,
Some(WallixSelectorState::List { .. })
));
}
#[test]
fn wallix_poll_uses_cached_selection_when_available() {
let mut app = App::new(
make_namespace_config(),
vec![],
std::path::PathBuf::new(),
vec![],
)
.unwrap();
let server = wallix_test_server(Some("dev-admins"));
app.wallix_selection_cache
.insert(App::server_key(&server), "77".to_string());
let entries = vec![
WallixMenuEntry {
id: "77".to_string(),
target: "demo_user@default@APP-ALPHA-BD:SSH".to_string(),
group: "APP-ALPHA_ops-admins".to_string(),
},
WallixMenuEntry {
id: "78".to_string(),
target: "demo_user@default@APP-ALPHA-BD:SSH".to_string(),
group: "APP-ALPHA_dev-admins".to_string(),
},
];
let (tx, rx) = std::sync::mpsc::channel();
app.wallix_selector_rx = Some(rx);
tx.send((server, Ok(entries))).unwrap();
app.poll_wallix_selector();
assert!(app.wallix_selector.is_none());
let pending = app.take_pending_wallix_connection();
assert!(pending.is_some());
let (_, selected_id, _) = pending.unwrap();
assert_eq!(selected_id, "77");
}
#[test]
fn targeted_wallix_entries_keeps_matching_target_when_available() {
let server = wallix_test_server(Some("dev-admins"));
let entries = vec![
WallixMenuEntry {
id: "1".to_string(),
target: "demo_user@default@OTHER:SSH".to_string(),
group: "OTHER_dev-admins".to_string(),
},
WallixMenuEntry {
id: "2".to_string(),
target: "demo_user@default@APP-ALPHA-BD:SSH".to_string(),
group: "APP-ALPHA_dev-admins".to_string(),
},
];
let filtered = wallix_state::targeted_wallix_entries(&server, &entries);
assert_eq!(filtered.len(), 1);
assert_eq!(filtered[0].id, "2");
}
#[test]
fn open_wallix_selector_direct_sets_pending_connection() {
let mut app = App::new(
make_namespace_config(),
vec![],
std::path::PathBuf::new(),
vec![],
)
.unwrap();
let mut server = wallix_test_server(None);
server.wallix_direct = true;
app.open_wallix_selector(server.clone(), false);
assert!(app.wallix_selector.is_none());
let pending = app.take_pending_wallix_connection();
assert!(pending.is_some());
let (_, id, _) = pending.unwrap();
assert_eq!(id, "WALLIX_DIRECT");
}
#[test]
fn poll_wallix_selector_error_wallix_direct_sets_pending() {
let mut app = App::new(
make_namespace_config(),
vec![],
std::path::PathBuf::new(),
vec![],
)
.unwrap();
let server = wallix_test_server(None);
let (tx, rx) = std::sync::mpsc::channel();
app.wallix_selector = Some(WallixSelectorState::Loading {
server: Box::new(server.clone()),
verbose: false,
});
app.wallix_selector_rx = Some(rx);
tx.send((server, Err("WALLIX_DIRECT_CONNECTION".to_string())))
.unwrap();
app.poll_wallix_selector();
assert!(app.wallix_selector.is_none());
assert!(app.take_pending_wallix_connection().is_some());
}
#[test]
fn poll_wallix_selector_error_ssh_auth_required_sets_credential_input() {
let mut app = App::new(
make_namespace_config(),
vec![],
std::path::PathBuf::new(),
vec![],
)
.unwrap();
let server = wallix_test_server(None);
let (tx, rx) = std::sync::mpsc::channel();
app.wallix_selector = Some(WallixSelectorState::Loading {
server: Box::new(server.clone()),
verbose: false,
});
app.wallix_selector_rx = Some(rx);
tx.send((
server,
Err("SSH_AUTH_REQUIRED:Enter passphrase for key".to_string()),
))
.unwrap();
app.poll_wallix_selector();
assert!(app.wallix_selector.is_none());
assert!(matches!(
app.app_mode,
AppMode::CredentialInput {
is_passphrase: true,
..
}
));
}
#[test]
fn poll_wallix_selector_generic_error_sets_error_state() {
let mut app = App::new(
make_namespace_config(),
vec![],
std::path::PathBuf::new(),
vec![],
)
.unwrap();
let server = wallix_test_server(None);
let (tx, rx) = std::sync::mpsc::channel();
app.wallix_selector_rx = Some(rx);
tx.send((server, Err("network timeout".to_string())))
.unwrap();
app.poll_wallix_selector();
assert!(matches!(
app.wallix_selector,
Some(WallixSelectorState::Error { .. })
));
}
#[test]
fn poll_wallix_selector_no_rx_noop() {
let mut app = App::new(
make_namespace_config(),
vec![],
std::path::PathBuf::new(),
vec![],
)
.unwrap();
app.wallix_selector_rx = None;
app.poll_wallix_selector(); assert!(app.wallix_selector.is_none());
}
#[test]
fn close_wallix_selector_clears_state() {
let mut app = App::new(
make_namespace_config(),
vec![],
std::path::PathBuf::new(),
vec![],
)
.unwrap();
let server = wallix_test_server(None);
app.wallix_selector = Some(WallixSelectorState::List {
server: Box::new(server.clone()),
entries: vec![],
selected: 0,
});
let (_, rx) = std::sync::mpsc::channel::<WallixMenuLoadResult>();
app.wallix_selector_rx = Some(rx);
app.close_wallix_selector();
assert!(app.wallix_selector.is_none());
assert!(app.wallix_selector_rx.is_none());
}
#[test]
fn remember_wallix_selection_stores_id() {
let mut app = App::new(
make_namespace_config(),
vec![],
std::path::PathBuf::new(),
vec![],
)
.unwrap();
let server = wallix_test_server(None);
app.remember_wallix_selection(&server, "55");
let key = App::server_key(&server);
assert_eq!(
app.wallix_selection_cache.get(&key),
Some(&"55".to_string())
);
}
fn app_with_list_selector(entries: Vec<WallixMenuEntry>) -> App {
let mut app = App::new(
make_namespace_config(),
vec![],
std::path::PathBuf::new(),
vec![],
)
.unwrap();
let server = wallix_test_server(None);
app.wallix_selector = Some(WallixSelectorState::List {
server: Box::new(server),
entries,
selected: 0,
});
app
}
#[test]
fn wallix_selector_next_increments() {
let entries = vec![
WallixMenuEntry {
id: "1".into(),
target: "T1".into(),
group: "G1".into(),
},
WallixMenuEntry {
id: "2".into(),
target: "T2".into(),
group: "G2".into(),
},
];
let mut app = app_with_list_selector(entries);
app.wallix_selector_next();
if let Some(WallixSelectorState::List { selected, .. }) = &app.wallix_selector {
assert_eq!(*selected, 1);
}
}
#[test]
fn wallix_selector_next_clamps_at_last() {
let entries = vec![WallixMenuEntry {
id: "1".into(),
target: "T1".into(),
group: "G1".into(),
}];
let mut app = app_with_list_selector(entries);
app.wallix_selector_next();
app.wallix_selector_next();
if let Some(WallixSelectorState::List { selected, .. }) = &app.wallix_selector {
assert_eq!(*selected, 0); }
}
#[test]
fn wallix_selector_previous_decrements() {
let entries = vec![
WallixMenuEntry {
id: "1".into(),
target: "T1".into(),
group: "G1".into(),
},
WallixMenuEntry {
id: "2".into(),
target: "T2".into(),
group: "G2".into(),
},
];
let mut app = app_with_list_selector(entries);
app.wallix_selector_next(); app.wallix_selector_previous(); if let Some(WallixSelectorState::List { selected, .. }) = &app.wallix_selector {
assert_eq!(*selected, 0);
}
}
#[test]
fn wallix_selector_previous_saturates_at_zero() {
let entries = vec![WallixMenuEntry {
id: "1".into(),
target: "T1".into(),
group: "G1".into(),
}];
let mut app = app_with_list_selector(entries);
app.wallix_selector_previous();
if let Some(WallixSelectorState::List { selected, .. }) = &app.wallix_selector {
assert_eq!(*selected, 0);
}
}
#[test]
fn wallix_selector_selected_id_returns_entry() {
let entries = vec![WallixMenuEntry {
id: "42".into(),
target: "T".into(),
group: "G".into(),
}];
let app = app_with_list_selector(entries);
let result = app.wallix_selector_selected_id();
assert!(result.is_some());
let (_, id) = result.unwrap();
assert_eq!(id, "42");
}
#[test]
fn wallix_selector_selected_id_none_when_not_list() {
let app = App::new(
make_namespace_config(),
vec![],
std::path::PathBuf::new(),
vec![],
)
.unwrap();
assert!(app.wallix_selector_selected_id().is_none());
}
#[test]
fn poll_wallix_selector_auto_select_false_opens_list() {
let mut app = App::new(
make_namespace_config(),
vec![],
std::path::PathBuf::new(),
vec![],
)
.unwrap();
let mut server = wallix_test_server(None);
server.wallix_auto_select = false;
let entries = vec![WallixMenuEntry {
id: "10".to_string(),
target: "demo_user@default@APP-ALPHA-BD:SSH".to_string(),
group: "APP-ALPHA_ops".to_string(),
}];
let (tx, rx) = std::sync::mpsc::channel();
app.wallix_selector_rx = Some(rx);
tx.send((server, Ok(entries))).unwrap();
app.poll_wallix_selector();
assert!(matches!(
app.wallix_selector,
Some(WallixSelectorState::List { .. })
));
assert!(app.take_pending_wallix_connection().is_none());
}