windows_users 0.3.0

A crate for managing Windows local users and groups.
Documentation
use windows_users::{UserAccountFlags, UserManager, UserUpdate};

use crate::helpers::{
    auto_remove_user::AutoRemoveUser,
    build::{build_full_user, build_full_user_alt},
    constants::USER_NAME,
    utils::assert_user_eq,
};

mod helpers;

#[test]
fn test_add_and_get_user_with_full_payload() {
    let user_manager = UserManager::local();
    let user_name = format!("{USER_NAME}_2");
    let user = build_full_user(&user_name);
    let _guard = AutoRemoveUser::add(&user_manager, &user).expect("Failed to add user");

    let fetched = user_manager
        .get_user(&user_name)
        .expect("Failed to fetch user");

    assert_user_eq(&fetched, &user);
}

#[test]
fn test_add_user_if_not_exists() {
    let user_manager = UserManager::local();
    let user_name = format!("{USER_NAME}_3");
    let user = build_full_user(&user_name);

    let auto_remove_user_result = AutoRemoveUser::add_if_not_exists(&user_manager, &user).unwrap();
    assert!(auto_remove_user_result.added_or_changed);
    let auto_remove_user_result = AutoRemoveUser::add_if_not_exists(&user_manager, &user).unwrap();
    assert!(!auto_remove_user_result.added_or_changed);
}

#[test]
fn test_add_user_or_update() {
    let user_manager = UserManager::local();
    let user_name = format!("{USER_NAME}_4");
    let user = build_full_user(&user_name);

    let auto_remove_user_result =
        AutoRemoveUser::add_or_update(&user_manager, &user).expect("Failed to add or update user");
    assert!(
        auto_remove_user_result.added_or_changed,
        "User should be added"
    );

    let updated_user = build_full_user_alt(&user_name);
    let auto_remove_user_result =
        AutoRemoveUser::add_or_update(&user_manager, &updated_user).expect("Failed to update user");
    assert!(
        !auto_remove_user_result.added_or_changed,
        "Rule should be updated"
    );

    let updated_user = user_manager
        .get_user(&user_name)
        .expect("Failed to fetch updated user");
    assert_user_eq(&updated_user, &updated_user);
}

#[test]
fn test_user_exists_returns_true_for_created_user() {
    let user_manager = UserManager::local();
    let user_name = format!("{USER_NAME}_5");
    let user = build_full_user(&user_name);
    let _guard = AutoRemoveUser::add(&user_manager, &user).expect("Failed to add user");

    assert!(
        user_manager.user_exists(&user_name),
        "Expected user_exists to return true for an existing user"
    );
}

#[test]
fn test_user_exists_returns_false_for_missing_user() {
    let user_manager = UserManager::local();
    let user_name = format!("{USER_NAME}_6");

    assert!(
        !user_manager.user_exists(&user_name),
        "Expected user_exists to return false for a missing user"
    );
}

#[test]
fn test_update_user_changes_only_requested_fields() {
    let user_manager = UserManager::local();
    let user_name = format!("{USER_NAME}_7");
    let mut user = build_full_user(&user_name);
    let _guard = AutoRemoveUser::add(&user_manager, &user).expect("Failed to add user");

    let new_full_name = "Updated Full Name";
    let new_comment = "Updated comment";
    let settings = UserUpdate::builder()
        .full_name(new_full_name)
        .comment(new_comment)
        .build();

    user.update(&user_manager, &settings)
        .expect("Failed to update user with new API");

    let fetched = user_manager
        .get_user(&user_name)
        .expect("Failed to fetch updated user");
    user.set_full_name(Some(new_full_name.to_string()));
    user.set_comment(Some(new_comment.to_string()));

    assert_user_eq(&fetched, &user);
}

#[test]
fn test_update_full_user_to_new_full_user() {
    let user_manager = UserManager::local();
    let user_name = format!("{USER_NAME}_8");

    let mut user = build_full_user(&user_name);
    let _guard = AutoRemoveUser::add(&user_manager, &user).expect("Failed to add user");

    let new_user = build_full_user_alt(&user_name);

    user.update(&user_manager, &new_user.clone().into())
        .expect("Failed to fully update user");

    let fetched = user_manager
        .get_user(&user_name)
        .expect("Failed to fetch updated user");

    assert_user_eq(&fetched, &new_user);
}

#[test]
fn test_update_user_fails_for_missing_user() {
    let user_manager = UserManager::local();
    let user_name = format!("{USER_NAME}_9");
    let settings = UserUpdate::builder().full_name("Should Fail").build();

    let result = user_manager.update_user(&user_name, &settings);

    assert!(
        result.is_err(),
        "Expected update_user to fail for missing user"
    );
}

#[test]
fn test_change_user_password_accepts_current_password() {
    let user_manager = UserManager::local();
    let user_name = format!("{USER_NAME}_10");
    let user = build_full_user(&user_name);
    let _guard = AutoRemoveUser::add(&user_manager, &user).expect("Failed to add user");

    user_manager
        .change_user_password(&user_name, "P@ssw0rd123!", "N3wP@ssw0rd!")
        .expect("Failed to change user password");
}

#[test]
fn test_current_user() {
    let user_manager = UserManager::local();

    let current = user_manager
        .current_user()
        .expect("Failed to get current user");
    assert!(
        !current.name().is_empty(),
        "Current user should have a non-empty name"
    );

    let mut buf = [0u16; 256];
    let mut size = buf.len() as u32;

    unsafe {
        windows::Win32::System::WindowsProgramming::GetUserNameW(
            Some(windows::core::PWSTR(buf.as_mut_ptr())),
            &mut size,
        )
        .expect("GetUserNameW failed");
    }

    let expected_name = String::from_utf16_lossy(&buf[..size.saturating_sub(1) as usize]);
    assert_eq!(
        current.name().to_lowercase(),
        expected_name.to_lowercase(),
        "current_user should match Windows session user"
    );
}

#[test]
fn test_validate_user_logon() {
    let user_manager = UserManager::local();
    let user_name = format!("{USER_NAME}_11");
    let password = "P@ssw0rd123!";

    let mut user = build_full_user(&user_name);
    user.set_password(Some(password.to_string()));
    user.set_workstations(None);

    let _guard = AutoRemoveUser::add(&user_manager, &user).expect("Failed to add user");

    let result = user_manager.validate_user_logon(&user_name, password);
    assert!(result.is_ok(), "Expected valid password to return true");

    let result = user_manager.validate_user_logon(&user_name, "WrongPassword!");
    assert!(result.is_err(), "Expected invalid password to return false");
}

#[test]
fn test_enable_and_disable_user_roundtrip() {
    let user_manager = UserManager::local();

    let user_name = format!("{USER_NAME}_12");
    let mut user = build_full_user(&user_name);
    let _guard = AutoRemoveUser::add(&user_manager, &user).expect("Failed to create user");

    user.enable(&user_manager, false)
        .expect("Failed to disable user");
    let disabled_user = user_manager
        .get_user(&user_name)
        .expect("Failed to fetch user after disable");
    assert!(
        disabled_user
            .flags()
            .contains(UserAccountFlags::ACCOUNTDISABLE),
        "User should be disabled after enable_user(false)"
    );
    assert!(user.flags().contains(UserAccountFlags::ACCOUNTDISABLE));

    user.enable(&user_manager, true)
        .expect("Failed to enable user");
    let enabled_user = user_manager
        .get_user(&user_name)
        .expect("Failed to fetch user after enable");
    assert!(
        !enabled_user
            .flags()
            .contains(UserAccountFlags::ACCOUNTDISABLE),
        "User should be enabled after enable_user(true)"
    );
    assert!(!user.flags().contains(UserAccountFlags::ACCOUNTDISABLE));
}