#![cfg(unix)]
use assert_cmd::Command;
use predicates::prelude::*;
use std::io::{Read, Write};
use std::time::{Duration, Instant};
const INIT_LINE: &str = "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{}}\n";
fn worker(home: &tempfile::TempDir) -> Command {
let mut cmd = Command::cargo_bin("newt").unwrap();
cmd.env("HOME", home.path())
.env("OLLAMA_HOST", "http://127.0.0.1:1")
.env("RUST_LOG", "info")
.env_remove("NEWT_OPERATOR_KEY")
.env_remove("NEWT_METRICS_PORT")
.env_remove("NEWT_CODER")
.arg("worker")
.write_stdin(INIT_LINE)
.timeout(Duration::from_secs(30));
cmd
}
fn blocked_key_path(home: &tempfile::TempDir) -> std::path::PathBuf {
let blocker = home.path().join("blocker");
std::fs::write(&blocker, b"not a directory").unwrap();
blocker.join("identity.pem")
}
#[test]
fn worker_refuses_to_start_when_key_unavailable() {
let home = tempfile::tempdir().unwrap();
let key = blocked_key_path(&home);
worker(&home)
.arg("--operator-key-path")
.arg(&key)
.assert()
.failure()
.stderr(predicate::str::contains("headless worker refused to start"))
.stderr(predicate::str::contains("--allow-no-key"));
}
#[test]
fn worker_generates_key_and_answers_initialize() {
let home = tempfile::tempdir().unwrap();
let key = home.path().join("keys").join("identity.pem");
assert!(!key.exists());
worker(&home)
.arg("--operator-key-path")
.arg(&key)
.assert()
.success()
.stdout(predicate::str::contains("\"jsonrpc\":\"2.0\""))
.stderr(predicate::str::contains("operator-rooted identity"));
assert!(key.exists(), "first run must persist the operator key");
}
#[test]
fn worker_allow_no_key_falls_back_to_debug_identity() {
let home = tempfile::tempdir().unwrap();
let key = blocked_key_path(&home);
worker(&home)
.arg("--operator-key-path")
.arg(&key)
.arg("--allow-no-key")
.assert()
.success()
.stdout(predicate::str::contains("\"jsonrpc\":\"2.0\""))
.stderr(predicate::str::contains("unbounded debug authority"));
}
#[test]
fn worker_ignores_invalid_metrics_port() {
let home = tempfile::tempdir().unwrap();
let key = home.path().join("identity.pem");
worker(&home)
.arg("--operator-key-path")
.arg(&key)
.env("NEWT_METRICS_PORT", "0")
.assert()
.success()
.stdout(predicate::str::contains("\"jsonrpc\":\"2.0\""))
.stderr(predicate::str::contains("Prometheus metrics server started").not());
}
fn http_get(port: u16, path: &str) -> std::io::Result<String> {
let mut stream = std::net::TcpStream::connect(("127.0.0.1", port))?;
stream.set_read_timeout(Some(Duration::from_secs(5)))?;
let req = format!("GET {path} HTTP/1.0\r\nHost: localhost\r\n\r\n");
stream.write_all(req.as_bytes())?;
let mut resp = String::new();
stream.read_to_string(&mut resp)?;
Ok(resp)
}
fn http_get_retry(port: u16, path: &str, secs: u64) -> std::io::Result<String> {
let deadline = Instant::now() + Duration::from_secs(secs);
loop {
match http_get(port, path) {
Ok(resp) => return Ok(resp),
Err(_) if Instant::now() < deadline => {
std::thread::sleep(Duration::from_millis(100));
}
Err(e) => return Err(e),
}
}
}
#[test]
fn worker_metrics_server_serves_healthz_and_metrics() {
let home = tempfile::tempdir().unwrap();
let key = home.path().join("identity.pem");
let port = {
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
listener.local_addr().unwrap().port()
};
let newt_bin = assert_cmd::cargo::cargo_bin("newt");
let mut child = std::process::Command::new(&newt_bin)
.env("HOME", home.path())
.env("OLLAMA_HOST", "http://127.0.0.1:1")
.env("RUST_LOG", "info")
.env("NEWT_METRICS_PORT", port.to_string())
.env_remove("NEWT_OPERATOR_KEY")
.args(["worker", "--operator-key-path"])
.arg(&key)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.expect("spawn newt worker");
let deadline = Instant::now() + Duration::from_secs(15);
let healthz = loop {
match http_get(port, "/healthz") {
Ok(resp) if resp.contains("200 OK") => break resp,
Ok(_) | Err(_) if Instant::now() < deadline => {
std::thread::sleep(Duration::from_millis(100));
}
Ok(resp) => {
let _ = child.kill();
panic!("healthz never returned 200 on port {port}; last: {resp}");
}
Err(e) => {
let _ = child.kill();
panic!("metrics server never came up on port {port}: {e}");
}
}
};
assert!(healthz.contains("200 OK"), "healthz response: {healthz}");
assert!(healthz.contains("ok"), "healthz body: {healthz}");
let metrics = http_get_retry(port, "/metrics", 15).expect("GET /metrics");
assert!(metrics.contains("200 OK"), "metrics response: {metrics}");
let not_found = http_get_retry(port, "/nope", 15).expect("GET /nope");
assert!(not_found.contains("404 Not Found"), "response: {not_found}");
drop(child.stdin.take());
let deadline = Instant::now() + Duration::from_secs(15);
let status = loop {
match child.try_wait().expect("try_wait") {
Some(status) => break status,
None if Instant::now() < deadline => {
std::thread::sleep(Duration::from_millis(100));
}
None => {
let _ = child.kill();
panic!("worker did not exit after stdin EOF");
}
}
};
assert!(status.success(), "worker exit status: {status}");
let mut stderr = String::new();
child
.stderr
.take()
.expect("stderr piped")
.read_to_string(&mut stderr)
.unwrap();
assert!(
stderr.contains("Prometheus metrics server started"),
"stderr: {stderr}"
);
}