summaryrefslogtreecommitdiff
path: root/tests/server_shell.rs
blob: ae77a6acbbdf410fc153920526d6b4fd7cb5f81a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
use anyhow::Result;
use assert_cmd::cargo::cargo_bin;
use rexpect::session::{spawn_command, PtySession};
use std::{io, path, sync::Once};
use tempfile::TempDir;
use thiserror::Error;

static BUILD_DOCKER: Once = Once::new();
static mut BUILD_DOCKER_RESULT: Result<(), DockerBuildError> = Ok(());

struct TestContext {
    workdir: TempDir,
    ssh_port: u32,
    docker_process: PtySession,
}

#[derive(Error, Debug, Clone)]
pub enum DockerBuildError {
    #[error(transparent)]
    StripPrefixError(#[from] path::StripPrefixError),
    #[error("IO Error: `{0}`")]
    IoError(String),
    #[error("Failed to build dockerfile")]
    CliError,
}

impl From<io::Error> for DockerBuildError {
    fn from(e: io::Error) -> Self {
        DockerBuildError::IoError(e.to_string())
    }
}

fn build_docker_image() -> Result<(), DockerBuildError> {
    let build_docker = || {
        let mut command = std::process::Command::new("docker");

        let absolute_shell_path = cargo_bin(env!("CARGO_PKG_NAME"));
        let relative_shell_path = absolute_shell_path.strip_prefix(std::fs::canonicalize(".")?)?;
        command.args([
            "build",
            "--quiet",
            "-t",
            "shackle-server",
            "--build-arg",
            &format!("SHELL={}", relative_shell_path.display()),
            "./",
        ]);

        let status = command.status()?;
        if status.success() {
            Ok(())
        } else {
            Err(DockerBuildError::CliError)
        }
    };

    // Based on this example: https://doc.rust-lang.org/std/sync/struct.Once.html#examples-1
    unsafe {
        BUILD_DOCKER.call_once(|| {
            BUILD_DOCKER_RESULT = build_docker();
        });
        BUILD_DOCKER_RESULT.clone()
    }
}

fn spawn_ssh_server() -> Result<TestContext> {
    build_docker_image()?;

    let workdir = tempfile::tempdir()?;

    let mut command = std::process::Command::new("docker");
    // TODO: randomize port?
    command.args(["run", "-it", "-p", "2022:22", "shackle-server"]);
    command.current_dir(&workdir);
    let docker_process = spawn_command(command, Some(3000))?;
    Ok(TestContext {
        workdir,
        ssh_port: 2022,
        docker_process,
    })
}

fn connect_to_ssh_server_interactively(c: &TestContext) -> Result<PtySession> {
    let mut command = std::process::Command::new("ssh");
    command.args(["-p", &c.ssh_port.to_string(), "shukkie@localhost"]);
    command.current_dir(&c.workdir);
    let mut p = spawn_command(command, Some(3000))?;
    expect_prompt(&mut p)?;
    Ok(p)
}

fn expect_prompt(p: &mut PtySession) -> Result<()> {
    p.exp_string("> ")?;
    Ok(())
}

#[test]
fn shows_a_prompt() -> Result<()> {
    let c = spawn_ssh_server()?;
    connect_to_ssh_server_interactively(&c)?;
    Ok(())
}