use super::*;
use std::cell::Cell;
use std::io::Cursor;
fn connection() -> rusqlite::Connection {
rusqlite::Connection::open_in_memory().unwrap()
}
fn create_settings(conn: &rusqlite::Connection) {
conn.execute(
"CREATE TABLE settings (key TEXT PRIMARY KEY, value TEXT NOT NULL)",
[],
)
.unwrap();
}
fn insert_setting(conn: &rusqlite::Connection, key: &str, value: &str) {
conn.execute(
"INSERT INTO settings (key, value) VALUES (?1, ?2)",
params![key, value],
)
.unwrap();
}
#[test]
fn default_builder_values_load_default_table_ordered_by_key() {
let conn = connection();
create_settings(&conn);
insert_setting(&conn, "log_level", "info");
insert_setting(&conn, "graph_client_id", "abc123");
let rows = ConfigMenu::new(&conn).load_rows().unwrap();
assert_eq!(rows[0].key, "graph_client_id");
assert_eq!(rows[1].key, "log_level");
}
#[test]
fn masks_non_empty_secret_values_only() {
let conn = connection();
let menu = ConfigMenu::new(&conn).secret_keys(["secret"]);
assert_eq!(
menu.display_value(&SettingRow {
key: "secret".to_string(),
value: "value".to_string(),
}),
"********"
);
assert_eq!(
menu.display_value(&SettingRow {
key: "secret".to_string(),
value: String::new(),
}),
""
);
assert_eq!(
menu.display_value(&SettingRow {
key: "public".to_string(),
value: "value".to_string(),
}),
"value"
);
}
#[test]
fn updates_selected_setting() {
let conn = connection();
create_settings(&conn);
insert_setting(&conn, "log_level", "info");
ConfigMenu::new(&conn)
.update_row("log_level", "debug")
.unwrap();
let value: String = conn
.query_row(
"SELECT value FROM settings WHERE key = 'log_level'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(value, "debug");
}
#[test]
fn validation_failure_prevents_update() {
let conn = connection();
create_settings(&conn);
insert_setting(&conn, "log_level", "info");
let menu = ConfigMenu::new(&conn).validator(|_, _| Err("bad value".into()));
let error = menu.validate_value("log_level", "debug").unwrap_err();
assert!(matches!(error, ConfigEasyError::ValidationFailed { .. }));
let value: String = conn
.query_row(
"SELECT value FROM settings WHERE key = 'log_level'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(value, "info");
}
#[test]
fn supports_custom_table_and_columns() {
let conn = connection();
conn.execute(
"CREATE TABLE app_config (name TEXT PRIMARY KEY, setting TEXT NOT NULL)",
[],
)
.unwrap();
conn.execute(
"INSERT INTO app_config (name, setting) VALUES ('log_level', 'info')",
[],
)
.unwrap();
let rows = ConfigMenu::new(&conn)
.table("app_config")
.key_column("name")
.value_column("setting")
.order_by("name")
.load_rows()
.unwrap();
assert_eq!(rows[0].key, "log_level");
assert_eq!(rows[0].value, "info");
}
#[test]
fn supports_custom_ordering_column() {
let conn = connection();
conn.execute(
"CREATE TABLE settings (key TEXT PRIMARY KEY, value TEXT NOT NULL, display_order INTEGER NOT NULL)",
[],
)
.unwrap();
conn.execute(
"INSERT INTO settings (key, value, display_order) VALUES ('second', '2', 2), ('first', '1', 1)",
[],
)
.unwrap();
let rows = ConfigMenu::new(&conn)
.order_by("display_order")
.load_rows()
.unwrap();
assert_eq!(rows[0].key, "first");
assert_eq!(rows[1].key, "second");
}
#[test]
fn custom_action_callback_runs() {
let conn = connection();
let called = Cell::new(false);
let menu = ConfigMenu::new(&conn).action("r", "reset", |_| {
called.set(true);
Ok(())
});
menu.run_action(&menu.actions[0]).unwrap();
assert!(called.get());
}
#[test]
fn custom_action_errors_are_wrapped() {
let conn = connection();
let menu = ConfigMenu::new(&conn).action("r", "reset", |_| Err("failed".into()));
let error = menu.run_action(&menu.actions[0]).unwrap_err();
assert!(matches!(error, ConfigEasyError::ActionFailed { .. }));
}
#[test]
fn run_with_updates_then_exits() {
let conn = connection();
create_settings(&conn);
insert_setting(&conn, "log_level", "info");
let input = b"1\ndebug\n\n";
let mut input = Cursor::new(input);
let mut output = Vec::new();
ConfigMenu::new(&conn)
.run_with(&mut input, &mut output)
.unwrap();
let value: String = conn
.query_row(
"SELECT value FROM settings WHERE key = 'log_level'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(value, "debug");
}
#[test]
fn invalid_action_key_fails_before_menu_loop() {
let conn = connection();
create_settings(&conn);
let input = b"\n";
let mut input = Cursor::new(input);
let mut output = Vec::new();
let error = ConfigMenu::new(&conn)
.action("1", "bad", |_| Ok(()))
.run_with(&mut input, &mut output)
.unwrap_err();
assert!(matches!(error, ConfigEasyError::InvalidActionKey(_)));
}
#[test]
fn duplicate_action_key_fails_before_menu_loop() {
let conn = connection();
create_settings(&conn);
let input = b"\n";
let mut input = Cursor::new(input);
let mut output = Vec::new();
let error = ConfigMenu::new(&conn)
.action("r", "reset", |_| Ok(()))
.action("r", "refresh", |_| Ok(()))
.run_with(&mut input, &mut output)
.unwrap_err();
assert!(matches!(error, ConfigEasyError::DuplicateActionKey(_)));
}