use std::fmt::Write as _;
use std::net::TcpListener;
use std::process::{Command, Stdio};
use std::time::Duration;
struct ServerGuard(std::process::Child);
impl Drop for ServerGuard {
fn drop(&mut self) {
let _ = self.0.kill();
let _ = self.0.wait();
}
}
fn patch_generated_cargo_toml(project_dir: &std::path::Path) {
let cargo_toml_path = project_dir.join("Cargo.toml");
let mut content =
std::fs::read_to_string(&cargo_toml_path).expect("failed to read generated Cargo.toml");
let workspace_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("workspace root not found");
let autumn_web_crate = workspace_root.join("autumn");
write!(
content,
"\n[patch.crates-io]\nautumn-web = {{ path = \"{}\" }}\n",
autumn_web_crate.display().to_string().replace('\\', "/")
)
.expect("write to String is infallible");
std::fs::write(&cargo_toml_path, content).expect("failed to patch Cargo.toml");
}
#[test]
#[ignore = "slow: compiles a fresh Rust project — run with `cargo test -p autumn-cli -- --ignored`"]
fn generated_project_compiles_runs_and_serves() {
let temp_dir = tempfile::tempdir().expect("failed to create temp dir");
let autumn_bin = env!("CARGO_BIN_EXE_autumn");
let new_output = Command::new(autumn_bin)
.args(["new", "test-app"])
.current_dir(temp_dir.path())
.output()
.expect("failed to run `autumn new`");
assert!(
new_output.status.success(),
"autumn new failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&new_output.stdout),
String::from_utf8_lossy(&new_output.stderr),
);
let project_dir = temp_dir.path().join("test-app");
assert!(project_dir.join("Cargo.toml").is_file());
assert!(project_dir.join("src/main.rs").is_file());
patch_generated_cargo_toml(&project_dir);
let _ = std::fs::remove_file(project_dir.join("build.rs"));
let build_output = Command::new("cargo")
.args(["build"])
.current_dir(&project_dir)
.output()
.expect("failed to run cargo build");
assert!(
build_output.status.success(),
"cargo build failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&build_output.stdout),
String::from_utf8_lossy(&build_output.stderr),
);
let port = {
let listener = TcpListener::bind("127.0.0.1:0").expect("failed to bind ephemeral port");
listener.local_addr().unwrap().port()
};
let child = Command::new("cargo")
.args(["run"])
.current_dir(&project_dir)
.env("AUTUMN_SERVER__PORT", port.to_string())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("failed to spawn cargo run");
let _guard = ServerGuard(child);
let client = reqwest::blocking::Client::new();
let base = format!("http://127.0.0.1:{port}");
let mut ready = false;
for _ in 0..60 {
if client.get(format!("{base}/health")).send().is_ok() {
ready = true;
break;
}
std::thread::sleep(Duration::from_millis(500));
}
assert!(ready, "Server failed to become ready within 30 seconds");
let resp = client.get(format!("{base}/")).send().expect("GET / failed");
assert_eq!(resp.status(), 200, "GET / status");
let body = resp.text().unwrap();
assert!(
body.contains("Welcome to Autumn!"),
"GET / body missing welcome text, got: {body}",
);
let resp = client
.get(format!("{base}/hello/world"))
.send()
.expect("GET /hello/world failed");
assert_eq!(resp.status(), 200, "GET /hello/world status");
let body = resp.text().unwrap();
assert!(
body.contains("Hello, world!"),
"GET /hello/world body missing greeting, got: {body}",
);
let resp = client
.get(format!("{base}/health"))
.send()
.expect("GET /health failed");
assert_eq!(resp.status(), 200, "GET /health status");
let ct = resp
.headers()
.get("content-type")
.expect("missing content-type on /health")
.to_str()
.unwrap()
.to_owned();
assert!(
ct.contains("application/json"),
"GET /health content-type expected application/json, got: {ct}",
);
}