use assert_cmd::Command;
use predicates::prelude::*;
use std::io::Write;
use std::path::{Path, PathBuf};
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
fn write_config(contents: &str) -> tempfile::NamedTempFile {
let mut f = tempfile::NamedTempFile::new().unwrap();
f.write_all(contents.as_bytes()).unwrap();
f.flush().unwrap();
f
}
fn doctor(config: &tempfile::NamedTempFile, 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_remove("NEWT_DGX_OLLAMA_URL")
.env_remove("NEWT_DGX_OLLAMA_LB_URL")
.env_remove("NEWT_DGX_IN_CLUSTER_URL")
.env_remove("NEWT_DGX_VLLM_URL")
.env_remove("NEWT_DGX_HOST")
.env_remove("NEWT_DGX_MODEL")
.current_dir(home.path())
.args(["--config", config.path().to_str().unwrap(), "doctor"]);
cmd
}
fn path_with_front(front: &Path) -> std::ffi::OsString {
let mut paths = vec![front.to_path_buf()];
if let Some(existing) = std::env::var_os("PATH") {
paths.extend(std::env::split_paths(&existing));
}
std::env::join_paths(paths).unwrap()
}
fn write_provider_probe(dir: &Path, name: &str, exit_code: i32) -> String {
let command_name = provider_probe_name(name);
let path = dir.join(&command_name);
write_provider_probe_file(&path, exit_code);
command_name
}
#[cfg(windows)]
fn provider_probe_name(name: &str) -> String {
format!("{name}.cmd")
}
#[cfg(not(windows))]
fn provider_probe_name(name: &str) -> String {
name.to_string()
}
#[cfg(windows)]
fn write_provider_probe_file(path: &PathBuf, exit_code: i32) {
std::fs::write(path, format!("@echo off\r\nexit /b {exit_code}\r\n")).unwrap();
}
#[cfg(not(windows))]
fn write_provider_probe_file(path: &PathBuf, exit_code: i32) {
use std::os::unix::fs::PermissionsExt;
std::fs::write(path, format!("#!/bin/sh\nexit {exit_code}\n")).unwrap();
let mut permissions = std::fs::metadata(path).unwrap().permissions();
permissions.set_mode(0o755);
std::fs::set_permissions(path, permissions).unwrap();
}
#[tokio::test(flavor = "multi_thread")]
async fn doctor_reports_backend_and_provider_statuses() {
let ok_server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/api/tags"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({"models": []})))
.expect(1)
.mount(&ok_server)
.await;
let err_server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/api/tags"))
.respond_with(ResponseTemplate::new(500))
.expect(1)
.mount(&err_server)
.await;
let provider_dir = tempfile::tempdir().unwrap();
let present_command = write_provider_probe(provider_dir.path(), "newt-provider-present", 0);
let broken_command = write_provider_probe(provider_dir.path(), "newt-provider-broken", 1);
let config = write_config(&format!(
r#"
[[backends]]
name = "good"
endpoint = "{ok}"
model = "m"
tiers = ["FAST"]
[[backends]]
name = "bad-status"
endpoint = "{err}"
model = "m"
tiers = ["FAST"]
[[backends]]
name = "down"
endpoint = "http://127.0.0.1:1"
model = "m"
tiers = ["FAST"]
default_tier_order = ["FAST"]
[[providers]]
name = "present"
command = "{present_command}"
tiers = ["FAST"]
[[providers]]
name = "broken"
command = "{broken_command}"
tiers = ["FAST"]
[[providers]]
name = "absent"
command = "newt-test-no-such-binary"
tiers = ["FAST"]
"#,
ok = ok_server.uri(),
err = err_server.uri(),
present_command = present_command,
broken_command = broken_command,
));
let home = tempfile::tempdir().unwrap();
let mut cmd = doctor(&config, &home);
cmd.env("PATH", path_with_front(provider_dir.path()));
cmd.assert()
.success()
.stdout(predicate::str::contains(format!(
"good ({}) — OK",
ok_server.uri()
)))
.stdout(predicate::str::contains(format!(
"bad-status ({}) — HTTP 500",
err_server.uri()
)))
.stdout(predicate::str::contains(
"down (http://127.0.0.1:1) — unreachable",
))
.stdout(predicate::str::contains(format!(
"present (command: {present_command}) — found on PATH"
)))
.stdout(predicate::str::contains(format!(
"broken (command: {broken_command}) — found but exited with error"
)))
.stdout(predicate::str::contains(
"absent (command: newt-test-no-such-binary) — not found on PATH",
))
.stdout(predicate::str::contains("DGX nodes:"))
.stdout(predicate::str::contains("(none configured)"))
.stdout(predicate::str::contains("(none discovered)"));
}
#[tokio::test(flavor = "multi_thread")]
async fn doctor_probes_dgx_nodes_and_marks_active() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/api/tags"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({"models": []})))
.mount(&server)
.await;
Mock::given(method("GET"))
.and(path("/v1/models"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({"data": []})))
.expect(1)
.mount(&server)
.await;
let config = write_config(&format!(
r#"
[[backends]]
name = "x"
endpoint = "http://127.0.0.1:1"
model = "m"
tiers = ["FAST"]
default_tier_order = ["FAST"]
[dgx]
active_node = "spark"
active_model = "qwen3:32b"
[[dgx.nodes]]
name = "spark"
ollama = "{uri}"
vllm = "{uri}"
[[dgx.nodes]]
name = "bare"
"#,
uri = server.uri(),
));
let home = tempfile::tempdir().unwrap();
doctor(&config, &home)
.assert()
.success()
.stdout(predicate::str::contains("spark [active node]"))
.stdout(predicate::str::contains(format!(
"ollama ({}) * — OK",
server.uri()
)))
.stdout(predicate::str::contains(format!(
"vllm ({}) — OK",
server.uri()
)))
.stdout(predicate::str::contains("bare"))
.stdout(predicate::str::contains("bare [active node]").not())
.stdout(predicate::str::contains(format!(
"Active: qwen3:32b @ ollama → {}",
server.uri()
)));
}
#[test]
fn doctor_dgx_env_override_without_nodes() {
let config = write_config(
r#"
[[backends]]
name = "x"
endpoint = "http://127.0.0.1:1"
model = "m"
tiers = ["FAST"]
default_tier_order = ["FAST"]
[dgx]
"#,
);
let home = tempfile::tempdir().unwrap();
let mut cmd = doctor(&config, &home);
cmd.env("NEWT_DGX_OLLAMA_URL", "http://127.0.0.1:9999");
cmd.assert()
.success()
.stdout(predicate::str::contains(
"(no nodes — using env overrides only)",
))
.stdout(predicate::str::contains(
"Active: (none) @ ollama → http://127.0.0.1:9999",
));
}
#[test]
fn doctor_dgx_unresolved_when_unconfigured() {
let config = write_config(
r#"
[[backends]]
name = "x"
endpoint = "http://127.0.0.1:1"
model = "m"
tiers = ["FAST"]
default_tier_order = ["FAST"]
[dgx]
"#,
);
let home = tempfile::tempdir().unwrap();
doctor(&config, &home)
.assert()
.success()
.stdout(predicate::str::contains("Active endpoint: unresolved"))
.stdout(predicate::str::contains("no DGX nodes configured"));
}
#[test]
fn doctor_connects_lists_and_skips_mcp_servers() {
let newt_bin = assert_cmd::cargo::cargo_bin("newt");
let config = write_config(&format!(
r#"
[[backends]]
name = "x"
endpoint = "http://127.0.0.1:1"
model = "m"
tiers = ["FAST"]
default_tier_order = ["FAST"]
[[mcp_servers]]
name = "self"
# TOML literal string: the path is a Windows path on Windows CI and
# backslashes must not be treated as escapes.
command = '{newt}'
args = ["mcp"]
[[mcp_servers]]
name = "dead"
command = "true"
[[mcp_servers]]
name = "remote"
type = "http"
url = "http://127.0.0.1:1/mcp"
"#,
newt = newt_bin.display(),
));
let home = tempfile::tempdir().unwrap();
doctor(&config, &home)
.assert()
.success()
.stdout(predicate::str::contains("self [stdio] — OK"))
.stdout(predicate::str::contains("code_read"))
.stdout(predicate::str::contains("dead [stdio] — ERROR:"))
.stdout(predicate::str::contains(
"remote [http] — http://127.0.0.1:1/mcp (skipped: only stdio is supported in this build)",
));
}