Skip to content

Commit d080237

Browse files
committed
Implement user filtering enhancements and remove testing documentation
- Removed the outdated testing documentation from `docs/testing.md`. - Introduced a new `UsersFilterChips` struct to manage user filter states in `src/app/mod.rs`. - Updated the `apply_filters_and_search` function in `src/search.rs` to incorporate new filter options based on user states (inactive, no home, locked, no password, expired). - Enhanced the UI components to display filter options in the status bar and filter modal. - Added tests for the new filtering functionality and ensured proper handling of special characters in search queries.
1 parent 61c08fd commit d080237

File tree

8 files changed

+920
-207
lines changed

8 files changed

+920
-207
lines changed

docs/testing.md

Lines changed: 0 additions & 157 deletions
This file was deleted.

src/app/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,17 @@ pub enum UsersFilter {
315315
OnlySystemIds, // uid < 1000
316316
}
317317

318+
#[derive(Clone, Debug, Default)]
319+
pub struct UsersFilterChips {
320+
pub human_only: bool, // uid >= 1000
321+
pub system_only: bool, // uid < 1000
322+
pub inactive: bool, // shell is nologin/false
323+
pub no_home: bool, // home directory does not exist
324+
pub locked: bool, // requires system info (not yet implemented)
325+
pub no_password: bool, // requires system info (not yet implemented)
326+
pub expired: bool, // requires system info (not yet implemented)
327+
}
328+
318329
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
319330
pub enum GroupsFilter {
320331
OnlyUserGids, // gid >= 1000
@@ -410,6 +421,7 @@ pub struct AppState {
410421
pub sudo_password: Option<String>,
411422
pub users_filter: Option<UsersFilter>,
412423
pub groups_filter: Option<GroupsFilter>,
424+
pub users_filter_chips: UsersFilterChips,
413425
}
414426

415427
impl AppState {
@@ -439,6 +451,7 @@ impl AppState {
439451
sudo_password: None,
440452
users_filter: None,
441453
groups_filter: None,
454+
users_filter_chips: UsersFilterChips::default(),
442455
}
443456
}
444457
}

src/app/update.rs

Lines changed: 101 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -303,17 +303,23 @@ fn handle_modal_key(app: &mut AppState, key: KeyEvent) {
303303
KeyCode::Esc => close_modal(app),
304304
KeyCode::Backspace => close_modal(app),
305305
KeyCode::Up | KeyCode::Char('k') => {
306-
if *selected > 0 {
307-
*selected -= 1;
308-
} else {
309-
*selected = 2;
310-
}
306+
let max = if matches!(app.active_tab, ActiveTab::Users) { 7 } else { 2 };
307+
if *selected > 0 { *selected -= 1; } else { *selected = max; }
311308
}
312309
KeyCode::Down | KeyCode::Char('j') => {
313-
if *selected < 2 {
314-
*selected += 1;
315-
} else {
316-
*selected = 0;
310+
let max = if matches!(app.active_tab, ActiveTab::Users) { 7 } else { 2 };
311+
if *selected < max { *selected += 1; } else { *selected = 0; }
312+
}
313+
KeyCode::Char(' ') => {
314+
if let ActiveTab::Users = app.active_tab {
315+
match *selected {
316+
3 => app.users_filter_chips.inactive = !app.users_filter_chips.inactive,
317+
4 => app.users_filter_chips.no_home = !app.users_filter_chips.no_home,
318+
5 => app.users_filter_chips.locked = !app.users_filter_chips.locked,
319+
6 => app.users_filter_chips.no_password = !app.users_filter_chips.no_password,
320+
7 => app.users_filter_chips.expired = !app.users_filter_chips.expired,
321+
_ => {}
322+
}
317323
}
318324
}
319325
KeyCode::Enter => {
@@ -1871,3 +1877,89 @@ fn perform_pending_action(
18711877
}
18721878
Ok(())
18731879
}
1880+
1881+
#[cfg(test)]
1882+
mod tests {
1883+
use super::*;
1884+
use crossterm::event::{KeyEvent, KeyModifiers};
1885+
1886+
fn key(code: KeyCode) -> KeyEvent {
1887+
KeyEvent::new(code, KeyModifiers::NONE)
1888+
}
1889+
1890+
#[test]
1891+
fn filter_menu_sets_users_filter_and_closes() {
1892+
let mut app = AppState::default();
1893+
app.active_tab = ActiveTab::Users;
1894+
app.input_mode = InputMode::Modal;
1895+
app.modal = Some(ModalState::FilterMenu { selected: 1 });
1896+
1897+
handle_modal_key(&mut app, key(KeyCode::Enter));
1898+
1899+
assert!(matches!(app.users_filter, Some(UsersFilter::OnlyUserIds)));
1900+
assert!(app.modal.is_none());
1901+
assert!(matches!(app.input_mode, InputMode::Normal));
1902+
}
1903+
1904+
#[test]
1905+
fn actions_delete_opens_delete_confirm_with_allowed_flag() {
1906+
let mut app = AppState::default();
1907+
// Provide a deletable user (UID in 1000-1999)
1908+
app.users = vec![crate::sys::SystemUser {
1909+
uid: 1500,
1910+
name: "testuser".to_string(),
1911+
primary_gid: 1500,
1912+
full_name: None,
1913+
home_dir: "/home/testuser".to_string(),
1914+
shell: "/bin/bash".to_string(),
1915+
}];
1916+
app.input_mode = InputMode::Modal;
1917+
app.modal = Some(ModalState::Actions { selected: 1 });
1918+
1919+
handle_modal_key(&mut app, key(KeyCode::Enter));
1920+
1921+
match &app.modal {
1922+
Some(ModalState::DeleteConfirm { allowed, .. }) => assert!(*allowed),
1923+
other => panic!("unexpected modal state: {:?}", other),
1924+
}
1925+
}
1926+
1927+
#[test]
1928+
fn change_password_mismatch_shows_info() {
1929+
let mut app = AppState::default();
1930+
app.input_mode = InputMode::Modal;
1931+
app.modal = Some(ModalState::ChangePassword {
1932+
selected: 3, // Submit
1933+
password: "secret".to_string(),
1934+
confirm: "different".to_string(),
1935+
must_change: false,
1936+
});
1937+
1938+
handle_modal_key(&mut app, key(KeyCode::Enter));
1939+
1940+
match &app.modal {
1941+
Some(ModalState::Info { message }) => {
1942+
assert!(message.contains("Passwords do not match"))
1943+
}
1944+
other => panic!("expected Info modal, got {:?}", other),
1945+
}
1946+
}
1947+
1948+
#[test]
1949+
fn sudo_prompt_backspace_closes_when_empty() {
1950+
let mut app = AppState::default();
1951+
app.input_mode = InputMode::Modal;
1952+
app.modal = Some(ModalState::SudoPrompt {
1953+
next: PendingAction::ResetPassword {
1954+
username: "user".to_string(),
1955+
},
1956+
password: String::new(),
1957+
error: None,
1958+
});
1959+
1960+
handle_modal_key(&mut app, key(KeyCode::Backspace));
1961+
1962+
assert!(app.modal.is_none());
1963+
assert!(matches!(app.input_mode, InputMode::Normal));
1964+
}
1965+
}

0 commit comments

Comments
 (0)