use std::sync::{Arc, OnceLock};
use mockforge_recorder::protocols::async_brokers;
use mockforge_recorder::{models::RecordedRequest, Recorder, RecorderDatabase};
static RECORDER: OnceLock<Option<Arc<Recorder>>> = OnceLock::new();
pub async fn ensure_init() {
if RECORDER.get().is_some() {
return;
}
let recorder = match std::env::var("MOCKFORGE_AMQP_RECORDING_DB") {
Ok(path) if !path.trim().is_empty() => match RecorderDatabase::new(path.as_str()).await {
Ok(db) => Some(Arc::new(Recorder::new(db))),
Err(e) => {
tracing::warn!(
error = %e,
"MOCKFORGE_AMQP_RECORDING_DB unusable; continuing without recording"
);
None
}
},
_ => None,
};
let _ = RECORDER.set(recorder);
}
pub fn set_recorder(recorder: Arc<Recorder>) {
let _ = RECORDER.set(Some(recorder));
}
fn recorder() -> Option<Arc<Recorder>> {
RECORDER.get().and_then(|o| o.clone())
}
fn spawn_record(event: RecordedRequest) {
if let Some(rec) = recorder() {
tokio::spawn(async move {
if let Err(e) = rec.record_request(event).await {
tracing::warn!(error = %e, "failed to record amqp exchange");
}
});
}
}
pub(crate) fn record_publish(exchange: &str, routing_key: &str, payload: &[u8]) {
spawn_record(async_brokers::amqp_event("publish", exchange, routing_key, payload));
}
pub(crate) fn record_deliver(queue_name: &str, payload: &[u8]) {
spawn_record(async_brokers::amqp_event("deliver", "", queue_name, payload));
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn recorder_roundtrip_stores_amqp_exchange() {
let db = RecorderDatabase::new_in_memory().await.expect("in-memory db");
let probe_db = db.clone();
set_recorder(Arc::new(Recorder::new(db)));
record_publish("amq.direct", "orders.created", br#"{"id":1}"#);
for _ in 0..20 {
let result = mockforge_recorder::query::execute_query(
&probe_db,
mockforge_recorder::query::QueryFilter {
protocol: Some(mockforge_recorder::models::Protocol::Amqp),
..Default::default()
},
)
.await
.expect("query");
if result.total >= 1 {
let exchange = &result.exchanges[0];
assert_eq!(exchange.request.method, "publish");
assert_eq!(exchange.request.path, "amq.direct/orders.created");
return;
}
tokio::time::sleep(std::time::Duration::from_millis(25)).await;
}
panic!("amqp exchange was never recorded");
}
}