change-user-run 0.1.2

Run commands as other users and create users
Documentation
//! Containerized integration tests for `change_user_run`.

#![cfg(feature = "_containerized-integration-test")]

use std::collections::HashMap;
use std::path::Path;

use change_user_run::{create_users, run_command_as_user};
use log::{LevelFilter, debug};
use nix::unistd::User;
use rstest::rstest;
use simplelog::{ColorChoice, Config, TermLogger, TerminalMode};
use testresult::TestResult;

/// Initializes a global [`TermLogger`].
fn init_logger() {
    if TermLogger::init(
        LevelFilter::Trace,
        Config::default(),
        TerminalMode::Stderr,
        ColorChoice::Auto,
    )
    .is_err()
    {
        debug!("Not initializing another logger, as one is initialized already.");
    }
}

mod user {
    use super::*;

    /// Input for tests.
    struct TestInput<'a> {
        pub users: &'a [&'a str],
        pub home_base_dir: Option<&'a Path>,
        pub shell: Option<&'a Path>,
    }

    /// Ensures that users and their homes can be created and that custom shells can be set as well.
    #[rstest]
    #[case(TestInput{
        users: &["testuser1", "testuser2"],
        home_base_dir: None,
        shell: None,
    })]
    #[case(TestInput{
        users: &["testuser1", "testuser2"],
        home_base_dir: Some(Path::new("/usr/share/test")),
        shell: Some(Path::new("/bin/bash")),
    })]
    fn create_users_succeeds(#[case] input: TestInput) -> TestResult {
        init_logger();
        create_users(input.users, input.home_base_dir, input.shell)?;

        for user in input.users {
            let home_dir = input.home_base_dir.unwrap_or(Path::new("/home")).join(user);
            if !home_dir.is_dir() {
                panic!("The directory {home_dir:?} should exist as home for user {user}");
            }
            let Some(user) = User::from_name(user)? else {
                panic!("The user {user} should exist!");
            };
            assert_eq!(
                user.shell.as_path(),
                if let Some(shell) = input.shell {
                    shell
                } else {
                    Path::new("/usr/bin/bash")
                }
            )
        }
        Ok(())
    }
}

mod command {
    use super::*;

    /// Input for tests.
    struct TestInput<'a> {
        pub command: &'a str,
        pub command_args: &'a [&'a str],
        pub command_input: Option<&'a [u8]>,
        pub env_list: &'a [&'a str],
        pub user: &'a str,
        pub envs: Option<HashMap<String, String>>,
    }

    /// Ensures that [`run_command_as_user`] can be run successfully.
    #[cfg(target_os = "linux")]
    #[rstest]
    #[case::whoami(
    TestInput{
    command: "whoami",
    command_args: &[],
    command_input: None,
    env_list: &[],
    user: "testuser",
    envs: None,
    },
    "testuser\n",
)]
    #[case::cut_with_input(
    TestInput{
    command: "cut",
    command_args: &["-f", "1", "-d", " "],
    command_input: Some("foo bar".as_bytes()),
    env_list: &[],
    user: "testuser",
    envs: None,
    },
    "foo\n",
)]
    #[case::echo_env_var(
    TestInput{
    command: "bash",
    command_args: &["-c", "echo $FOO"],
    command_input: None,
    env_list: &[
        "LLVM_PROFILE_FILE",
        "CARGO_LLVM_COV",
        "CARGO_LLVM_COV_SHOW_ENV",
        "CARGO_LLVM_COV_TARGET_DIR",
        "RUSTFLAGS",
        "RUSTDOCFLAGS",
    ],
    user: "testuser",
    envs: Some(HashMap::from([("FOO".to_string(), "BAR".to_string())])),
    },
    "BAR\n",
)]
    fn run_command_as_user_succeeds(#[case] input: TestInput, #[case] output: &str) -> TestResult {
        init_logger();

        // Create the required user.
        create_users(&[input.user], None, None)?;

        // Run the command as user.
        let cmd_output = run_command_as_user(
            input.command,
            input.command_args,
            input.command_input,
            input.env_list,
            input.envs,
            input.user,
        )?;

        debug!("{output}");
        assert_eq!(output, cmd_output.stdout);

        Ok(())
    }
}