#![allow(clippy::expect_used, clippy::unwrap_used, clippy::items_after_statements)]
use std::path::{Path, PathBuf};
use std::time::Duration;
use ferridriver_test::config::{GracefulShutdown, WebServerConfig};
use ferridriver_test::server::WebServerManager;
fn unique_marker(label: &str) -> PathBuf {
let dir = std::env::temp_dir();
let pid = std::process::id();
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |d| d.as_nanos());
dir.join(format!("ferridriver-{label}-{pid}-{nanos}.marker"))
}
fn unused_port() -> u16 {
let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("bind ephemeral");
let port = listener.local_addr().expect("local_addr").port();
drop(listener);
port
}
fn build_trap_script(marker: &Path, port: u16) -> String {
let marker_str = marker.display().to_string();
format!(
"
const fs = require('fs');
const http = require('http');
const server = http.createServer((_, res) => {{
res.writeHead(200, {{ 'content-type': 'text/plain' }});
res.end('ok');
}});
server.listen({port}, '127.0.0.1');
let shuttingDown = false;
function gracefulExit() {{
if (shuttingDown) return;
shuttingDown = true;
fs.writeFileSync({marker_str:?}, 'graceful');
setTimeout(() => {{
server.close(() => process.exit(0));
}}, 50);
}}
process.on('SIGTERM', gracefulExit);
process.on('SIGINT', gracefulExit);
"
)
}
#[tokio::test]
async fn stop_with_graceful_shutdown_writes_marker_and_exits_clean() {
let marker = unique_marker("graceful");
let _ = std::fs::remove_file(&marker);
let port = unused_port();
let url = format!("http://127.0.0.1:{port}");
let script = build_trap_script(&marker, port);
let script_path = std::env::temp_dir().join(format!("ferridriver-trap-{}.js", std::process::id()));
std::fs::write(&script_path, &script).expect("write trap script");
let cfg = WebServerConfig {
command: Some(format!("node {}", script_path.display())),
url: Some(url.clone()),
timeout: 10_000,
name: Some("graceful-fixture".into()),
graceful_shutdown: Some(GracefulShutdown {
signal: "SIGTERM".into(),
timeout: 1_000,
}),
..WebServerConfig::default()
};
let manager = WebServerManager::start(std::slice::from_ref(&cfg))
.await
.expect("start web server");
assert_eq!(manager.first_url().as_deref(), Some(url.as_str()));
manager.stop().await;
for _ in 0..20 {
if marker.exists() {
break;
}
tokio::time::sleep(Duration::from_millis(50)).await;
}
assert!(
marker.exists(),
"expected SIGTERM-trap marker at {} after graceful shutdown",
marker.display(),
);
let _ = std::fs::remove_file(&marker);
let _ = std::fs::remove_file(&script_path);
}
#[tokio::test]
async fn stop_without_graceful_shutdown_hard_kills() {
let marker = unique_marker("hardkill");
let _ = std::fs::remove_file(&marker);
let port = unused_port();
let url = format!("http://127.0.0.1:{port}");
let script = build_trap_script(&marker, port);
let script_path = std::env::temp_dir().join(format!("ferridriver-hardkill-{}.js", std::process::id()));
std::fs::write(&script_path, &script).expect("write trap script");
let cfg = WebServerConfig {
command: Some(format!("node {}", script_path.display())),
url: Some(url.clone()),
timeout: 10_000,
..WebServerConfig::default()
};
let manager = WebServerManager::start(std::slice::from_ref(&cfg))
.await
.expect("start web server");
manager.stop().await;
tokio::time::sleep(Duration::from_millis(200)).await;
assert!(
!marker.exists(),
"did not expect a marker after SIGKILL — found {}",
marker.display(),
);
let _ = std::fs::remove_file(&script_path);
}
#[tokio::test]
async fn probe_client_honours_ignore_https_errors_flag() {
let strict = ferridriver_test::server::build_probe_client(false);
let lenient = ferridriver_test::server::build_probe_client(true);
let _ = (strict, lenient);
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.expect("bind");
let addr = listener.local_addr().expect("local_addr");
let app = axum::Router::new().route("/", axum::routing::get(|| async { "ok" }));
let handle = tokio::spawn(async move {
let _ = axum::serve(listener, app).await;
});
let url = format!("http://{addr}");
assert!(ferridriver_test::server::http_probe(&url, false).await);
assert!(ferridriver_test::server::http_probe(&url, true).await);
handle.abort();
}