lawn 0.1.1

utility to interact with trusted remote development systems
use crate::config::Config;
use crate::server;
use std::collections::btree_map::IntoIter as BTreeMapIter;
use std::collections::BTreeMap;
use std::ffi::OsString;
use std::fs;
use std::io;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Duration;

pub struct FakeEnvironmentIter {
    map: BTreeMapIter<OsString, OsString>,
}

impl Iterator for FakeEnvironmentIter {
    type Item = (OsString, OsString);

    fn next(&mut self) -> Option<(OsString, OsString)> {
        let (k, v) = self.map.next()?;
        Some((k.into(), v.into()))
    }
}

#[derive(Clone)]
pub struct FakeEnvironment {
    root: PathBuf,
}

impl FakeEnvironment {
    fn new(root: &Path) -> FakeEnvironment {
        Self { root: root.into() }
    }

    fn env(&self, s: &str) -> Option<OsString> {
        let subpath = match s {
            "HOME" => Some("home"),
            "XDG_RUNTIME_DIR" => Some("runtime"),
            "PATH" => return Some("/bin:/usr/bin:/sbin:/usr/sbin".into()),
            _ => None,
        }?;
        let mut root = self.root.clone();
        root.push(subpath);
        Some(root.into())
    }

    fn iter(&self) -> FakeEnvironmentIter {
        let keys = &["HOME", "PATH", "XDG_RUNTIME_DIR"];
        let map: BTreeMap<OsString, OsString> = keys
            .iter()
            .filter_map(|k| {
                let e = self.env(k)?;
                Some((k.into(), e))
            })
            .collect();
        FakeEnvironmentIter {
            map: map.into_iter(),
        }
    }
}

pub struct TestInstance {
    dir: tempfile::TempDir,
    config_file: PathBuf,
}

impl TestInstance {
    pub fn new() -> Self {
        let dir = tempfile::tempdir().unwrap();
        let mut server = dir.path().to_owned();
        server.push("server");
        fs::create_dir(&server).unwrap();
        let paths = &[
            "home/.local/run/lawn",
            "path",
            "run/user",
            "runtime/lawn",
            "client",
            "server",
        ];
        for p in paths {
            let mut to_create: PathBuf = dir.path().into();
            to_create.push(p);
            fs::create_dir_all(&to_create).unwrap();
        }
        let config_file = Self::write_config_file(&server);
        Self { dir, config_file }
    }

    fn write_config_file(dir: &Path) -> PathBuf {
        let mut dest = dir.to_owned();
        dest.push("config.yaml");
        let mut fp = fs::File::create(&dest).unwrap();
        write!(
            fp,
            "---
v0:
    root: true
    commands:
        printf:
            if: '!/bin/true'
            command: '!f() {{ printf \"$@\"; }};f'
        echo:
            if: true
            command: '!f() {{ printf \"$@\"; }};f'
"
        )
        .unwrap();
        dest
    }

    pub fn config(&self) -> Arc<Config> {
        let env = FakeEnvironment::new(self.dir.path());
        let iterenv = env.clone();
        let cfg = Arc::new(
            Config::new(
                move |s| env.env(s),
                move || iterenv.iter(),
                false,
                5,
                Box::new(io::Cursor::new(Vec::new())),
                Box::new(io::stderr()),
                Some(&self.config_file),
            )
            .unwrap(),
        );
        cfg.set_detach(false);
        cfg
    }

    pub fn server(&mut self) -> Arc<server::Server> {
        Arc::new(server::Server::new(self.config()))
    }
}

fn runtime() -> tokio::runtime::Runtime {
    tokio::runtime::Builder::new_multi_thread()
        .enable_all()
        .build()
        .unwrap()
}

#[test]
fn starts_server() {
    let mut ti = TestInstance::new();
    let rt = runtime();
    rt.block_on(async {
        let s = ti.server();
        let s2 = s.clone();
        let h = tokio::spawn(async move {
            tokio::time::sleep(Duration::from_secs(2)).await;
            s2.shutdown().await;
        });
        s.run_async().await.unwrap();
        h.await
    })
    .unwrap();
}