use spate_core::config::PipelineConfig;
use spate_core::ops::chain_owned;
use spate_core::pipeline::{Pipeline, RuntimeOptions};
use spate_core::sink::KeyHashRouter;
use spate_test::{BytesPassthrough, LogCapture, TestEncoder, capture_sink, memory_source};
use spate_test_support::http;
use std::net::SocketAddr;
use std::time::Duration;
const DEADLINE: Duration = Duration::from_secs(10);
const CONFIG: &str = r#"
pipeline: { name: admin-addr-test, threads: 1, io_threads: 1 }
admin: { listen: "127.0.0.1:0" }
metrics: { exporter: none }
source: { memory: {} }
sink: { capture: {} }
"#;
fn logged_addr(capture: &LogCapture) -> Option<SocketAddr> {
capture
.lines()
.iter()
.find(|l| l.contains("admin server listening"))
.map(|l| {
l.split_whitespace()
.find_map(|f| f.strip_prefix("addr="))
.unwrap_or_else(|| panic!("no `addr` field to read on: {l}"))
.parse()
.expect("addr is a socket address")
})
}
#[test]
#[expect(
clippy::disallowed_methods,
reason = "the test joins the pipeline thread"
)]
fn the_bound_admin_address_is_logged_and_serves() {
let capture = LogCapture::new();
tracing_subscriber::fmt()
.with_writer(capture.clone())
.with_max_level(tracing_subscriber::filter::LevelFilter::INFO)
.without_time()
.init();
let (source, _handle) = memory_source();
let (sink, _script) = capture_sink(1, 1);
let runtime = Pipeline::from_config(PipelineConfig::from_str(CONFIG).expect("config"))
.expect("builder")
.sink(sink)
.expect("sink")
.chains(|ctx| {
let chunk_cfg = ctx.chunk();
chain_owned::<Vec<u8>, _>(BytesPassthrough)
.with_metrics(ctx.pipeline, "main")
.sink(
TestEncoder,
KeyHashRouter,
chunk_cfg,
ctx.queues,
ctx.budget,
)
.build()
})
.runtime_options(RuntimeOptions {
handle_signals: false,
..RuntimeOptions::default()
})
.into_runtime(source)
.expect("into_runtime");
let shutdown = runtime.shutdown_handle();
let join = std::thread::spawn(move || runtime.run());
let addr = capture.wait_for(DEADLINE, "the admin server to announce its address", || {
logged_addr(&capture)
});
assert_ne!(
addr.port(),
0,
"the line must carry the port the kernel picked, not the one asked for"
);
let (status, body) =
capture.wait_for(DEADLINE, "/healthz to answer at the logged address", || {
http(addr, "GET", "/healthz").ok()
});
assert_eq!(status, 200, "the logged address serves the probes");
assert_eq!(body, "ok");
shutdown.trigger();
let report = join.join().expect("join").expect("run");
assert_eq!(report.exit_code(), 0, "clean drain");
}