use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::path::{Path, PathBuf};
use std::thread;
use std::time::Duration;
use crate::features::client::{embedded_driver, DriverConfig};
use super::{serve, ServiceConfig, ServiceTlsMode};
fn temp_data_dir(prefix: &str) -> PathBuf {
let mut dir = std::env::temp_dir();
dir.push(format!(
"{}_{}_{}",
prefix,
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("time")
.as_nanos()
));
std::fs::create_dir_all(&dir).expect("create temp dir");
dir
}
fn seed_query_fixture(data_dir: &Path) {
let driver =
embedded_driver(DriverConfig::embedded_data_dir(data_dir.to_path_buf())).expect("driver");
driver.ingest_node(1, 1, &[2, 3]).expect("seed node");
}
fn read_response(stream: &mut TcpStream) -> String {
let mut out = String::new();
stream.read_to_string(&mut out).expect("read response");
out
}
#[test]
fn service_config_validation_reports_warnings_for_weak_security() {
let report = ServiceConfig {
listen_address: "127.0.0.1:7001".to_string(),
data_dir: PathBuf::from("/tmp/iridium-service"),
telemetry_endpoint: "stdout".to_string(),
tls_mode: ServiceTlsMode::Disabled,
admin_token: None,
max_requests: 1,
}
.validate();
assert!(report.compatible);
assert_eq!(report.profile_id, "single-node-service-v0-1");
assert!(report
.warnings
.iter()
.any(|warning| warning.contains("tls_mode=disabled")));
assert!(report
.warnings
.iter()
.any(|warning| warning.contains("admin_token is unset")));
}
#[test]
fn service_serve_exposes_health_query_and_admin_routes() {
let data_dir = temp_data_dir("iridium-service");
seed_query_fixture(&data_dir);
let listener = TcpListener::bind("127.0.0.1:0").expect("bind probe");
let addr = listener.local_addr().expect("addr");
drop(listener);
let config = ServiceConfig {
listen_address: addr.to_string(),
data_dir: data_dir.clone(),
telemetry_endpoint: "stdout".to_string(),
tls_mode: ServiceTlsMode::OperatorOptional,
admin_token: Some("secret-token".to_string()),
max_requests: 4,
};
let thread_config = config.clone();
let handle = thread::spawn(move || serve(&thread_config));
std::thread::sleep(Duration::from_millis(50));
let mut live = TcpStream::connect(addr).expect("connect live");
live.write_all(b"GET /livez HTTP/1.1\r\nHost: localhost\r\n\r\n")
.expect("write live");
let live_text = read_response(&mut live);
assert!(live_text.contains("200 OK"));
assert!(live_text.contains("live"));
let mut query = TcpStream::connect(addr).expect("connect query");
query
.write_all(
b"GET /v1/query?q=MATCH+%28n%29+RETURN+n+LIMIT+1 HTTP/1.1\r\nHost: localhost\r\n\r\n",
)
.expect("write query");
let query_text = read_response(&mut query);
assert!(query_text.contains("200 OK"));
assert!(query_text.contains("\"rows\""));
let mut unauthorized = TcpStream::connect(addr).expect("connect unauthorized");
unauthorized
.write_all(b"GET /admin/status HTTP/1.1\r\nHost: localhost\r\n\r\n")
.expect("write unauthorized");
let unauthorized_text = read_response(&mut unauthorized);
assert!(unauthorized_text.contains("401 Unauthorized"));
let mut stop = TcpStream::connect(addr).expect("connect stop");
stop.write_all(
b"GET /admin/lifecycle?action=stop HTTP/1.1\r\nHost: localhost\r\nAuthorization: Bearer secret-token\r\n\r\n",
)
.expect("write stop");
let stop_text = read_response(&mut stop);
assert!(stop_text.contains("200 OK"));
assert!(stop_text.contains("\"action\": \"stop\""));
handle.join().expect("join").expect("serve");
}