#![cfg(test)] use axum::extract::{Extension, Json}; use axum::routing::post; use axum::Router; use axum_server::Handle; use kumo_log_types::{JsonLogRecord, RecordType}; use std::collections::BTreeMap; use std::net::{SocketAddr, TcpListener}; use std::sync::{Arc, Mutex}; use std::time::Duration; pub struct WebHookServer { pub addr: SocketAddr, pub records: Arc>>, handle: Handle, } impl WebHookServer { pub async fn start() -> anyhow::Result { let records = Arc::new(Mutex::new(vec![])); let app = Router::new() .route("/log", post(log_record)) .route("/log-batch", post(log_batch)) .layer(Extension(Arc::clone(&records))); let handle = Handle::new(); let socket = TcpListener::bind("127.0.0.1:0")?; let addr = socket.local_addr()?; let server = axum_server::from_tcp(socket); let handle_copy = handle.clone(); tokio::spawn(async move { server .handle(handle_copy) .serve(app.into_make_service()) .await .unwrap(); }); Ok(Self { addr, records, handle, }) } pub fn shutdown(&self) { self.handle.shutdown(); } pub async fn wait_for_record_count(&self, count: usize, timeout: Duration) -> bool { eprintln!("waiting for webhook records to populate"); tokio::select! { _ = async { while self.records.lock().unwrap().len() != count { tokio::time::sleep(Duration::from_millis(100)).await; } } => true, _ = tokio::time::sleep(timeout) => false, } } pub fn dump_logs(&self) -> anyhow::Result> { let records = self.records.lock().unwrap(); let mut counts = BTreeMap::new(); for record in records.iter() { *counts.entry(record.kind).or_default() += 1; } Ok(counts) } pub fn return_logs(&self) -> Vec { (*self.records.lock().unwrap()).clone() } } async fn log_batch( Extension(records): Extension>>>, Json(mut batch): Json>, ) { records.lock().unwrap().append(&mut batch); } async fn log_record( Extension(records): Extension>>>, Json(record): Json, ) { records.lock().unwrap().push(record); }