Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/monetize

crates/monetize-server/tests/no_bearer_no_server.rs

//! **No bearer, no server** (stinky #9) — the binary's own start path.
//!
//! An empty bearer is what `nornir_service::auth_interceptor` reads as OPEN:
//! every gRPC call let in unauthenticated. So a `monetize-server` whose
//! `--token`, `$MONETIZE_SERVER_TOKEN` and token file are all missing or empty
//! refuses to start, with ONE alert line that names the token file (and never
//! a bearer). Open mode is asked for by name, `--insecure-open-mode-for-tests`,
//! and only by tests.

use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Stdio};
use std::sync::mpsc;
use std::time::Duration;

fn scratch(name: &str) -> PathBuf {
    let dir = std::env::temp_dir().join(format!("monetize-{name}-{}", std::process::id()));
    let _ = std::fs::remove_dir_all(&dir);
    std::fs::create_dir_all(&dir).unwrap();
    dir
}

fn config(dir: &Path) -> PathBuf {
    let path = dir.join("monetize.toml");
    let text = format!("grpc_addr = \"127.0.0.1:0\"\ndata_dir = \"{}\"\ntoken_file = \"{}\"\n", dir.join("data").display(), dir.join("token").display());
    std::fs::write(&path, text).unwrap();
    path
}

fn server(config: &Path, extra: &[&str]) -> Child {
    Command::new(env!("CARGO_BIN_EXE_monetize-server"))
        .arg("--config")
        .arg(config)
        .args(extra)
        .env_remove("MONETIZE_SERVER_TOKEN")
        .stdin(Stdio::null())
        .stdout(Stdio::null())
        .stderr(Stdio::piped())
        .spawn()
        .unwrap()
}

/// Every stderr line, as it comes, on a channel.
fn lines(child: &mut Child) -> mpsc::Receiver<String> {
    let (tx, rx) = mpsc::channel();
    let err = child.stderr.take().unwrap();
    std::thread::spawn(move || {
        for l in BufReader::new(err).lines().map_while(Result::ok) {
            if tx.send(l).is_err() {
                break;
            }
        }
    });
    rx
}

/// The process's exit and all it said — or `None` (and it is killed) if it is
/// still up after `limit`, i.e. it is SERVING.
fn exits_within(mut child: Child, limit: Duration) -> Option<(std::process::ExitStatus, Vec<String>)> {
    let rx = lines(&mut child);
    let until = std::time::Instant::now() + limit;
    while std::time::Instant::now() < until {
        if let Some(status) = child.try_wait().unwrap() {
            std::thread::sleep(Duration::from_millis(100));
            return Some((status, rx.try_iter().collect()));
        }
        std::thread::sleep(Duration::from_millis(50));
    }
    let _ = child.kill();
    let _ = child.wait();
    None
}

#[test]
fn a_missing_or_empty_bearer_refuses_to_start_with_one_alert_that_names_the_token_file() {
    for (i, content) in [None, Some(""), Some("  \n\t")].into_iter().enumerate() {
        let dir = scratch(&format!("no-bearer-{i}"));
        let token = dir.join("token");
        if let Some(c) = content {
            std::fs::write(&token, c).unwrap();
        }
        let (status, said) = exits_within(server(&config(&dir), &[]), Duration::from_secs(30)).unwrap_or_else(|| panic!("{content:?}: no bearer, and the server is SERVING (open mode)"));
        assert!(!status.success(), "{content:?}: refused is a failed start: {status:?}");
        let alerts: Vec<&String> = said.iter().filter(|l| l.contains("ALERT")).collect();
        assert_eq!(alerts.len(), 1, "{content:?}: ONE alert line: {said:#?}");
        assert!(alerts[0].contains(&token.display().to_string()), "{content:?}: the alert names the token file: {}", alerts[0]);
        assert!(!said.iter().any(|l| l.contains("OPEN (dev mode)")), "{content:?}: never served open: {said:#?}");
        let _ = std::fs::remove_dir_all(&dir);
    }
}

#[test]
fn open_mode_is_only_by_its_test_flag_and_never_over_a_configured_bearer() {
    let dir = scratch("open-by-name");
    let cfg = config(&dir);
    let mut child = server(&cfg, &["--insecure-open-mode-for-tests"]);
    let rx = lines(&mut child);
    let mut said = Vec::new();
    let saw_open = loop {
        match rx.recv_timeout(Duration::from_secs(30)) {
            Ok(l) => {
                let open = l.contains("--insecure-open-mode-for-tests") && l.contains("OPEN");
                said.push(l);
                if open {
                    break true;
                }
            }
            Err(_) => break false,
        }
    };
    let still_up = child.try_wait().unwrap().is_none();
    let _ = child.kill();
    let _ = child.wait();
    assert!(saw_open && still_up, "the test flag serves OPEN and says so by name: {said:#?}");

    std::fs::write(dir.join("token"), "a-configured-bearer-of-enough-length-0123456789").unwrap();
    let (status, said) = exits_within(server(&cfg, &["--insecure-open-mode-for-tests"]), Duration::from_secs(30)).expect("a configured bearer is never served open");
    assert!(!status.success(), "{status:?}");
    assert!(said.iter().any(|l| l.contains("--insecure-open-mode-for-tests")), "the refusal names the flag: {said:#?}");
    assert!(!said.iter().any(|l| l.contains("a-configured-bearer")), "never the bearer: {said:#?}");
    let _ = std::fs::remove_dir_all(&dir);
}