ssh-test-server 0.1.2

In memoery ssh server for integration testing.
Documentation
use cucumber::{given, then, when, World};
use ssh_test_server::{SshServer, SshServerBuilder, User};

mod common;

const SSH_USER: &str = "user1";
const SSH_PASS: &str = "pass123";

#[derive(Debug, Default, World)]
struct SshWorld {
    serer: Option<SshServer>,
    remote_addr: String,

    stdout: String,
    stderr: String,
    status_code: Option<i32>,
}

#[given("Running ssh server")]
async fn running_ssh_server(world: &mut SshWorld) {
    let server = SshServerBuilder::default()
        .add_user(User::new(SSH_USER, SSH_PASS))
        .run()
        .await
        .unwrap();

    world.remote_addr = server.addr();
    world.serer = Some(server);
}

#[when(expr = "Executed command {string} on remote ssh server")]
async fn execute_command(world: &mut SshWorld, command: String) {
    let r = common::run_ssh_command(&world.remote_addr, SSH_USER, SSH_PASS, move |channel| {
        channel.exec(&command).unwrap();
    })
    .await;
    world.stdout = r.0;
    world.stderr = r.1;
    world.status_code = Some(r.2);
}

#[when("Change user password via passwd command")]
async fn execute_passwd(world: &mut SshWorld) {
    let new_pass = format!("{SSH_PASS}_changed");
    common::change_password(&world.remote_addr, SSH_USER, SSH_PASS, &new_pass)
        .await
        .unwrap();
}

#[then(expr = "Got exit code {int} and response {string} and error containing {string}")]
fn got_command_result(world: &mut SshWorld, status_code: i32, stdout: String, stderr: String) {
    assert_eq!(world.stdout.trim(), stdout);
    assert!(
        world.stderr.contains(&stderr),
        "stderr should contain text '{stderr}' but got '{}'",
        world.stderr
    );
    assert_eq!(world.status_code, Some(status_code));
}

#[then("Password has been changed")]
async fn password_has_been_changed(world: &mut SshWorld) {
    let new_pass = format!("{SSH_PASS}_changed");
    let (_, _, status_code) =
        common::run_ssh_command(&world.remote_addr, SSH_USER, &new_pass, |channel| {
            channel.exec("echo abc").unwrap();
        })
        .await;
    assert_eq!(status_code, 0);
}

#[tokio::main(flavor = "current_thread")]
async fn main() {
    let summary = SshWorld::cucumber()
        .max_concurrent_scenarios(1)
        .init_tracing()
        .run("tests/features/ssh.feature")
        .await;

    assert_eq!(summary.scenarios_stats().failed, 0);
    assert_eq!(summary.scenarios_stats().skipped, 0);
}