use async_trait::async_trait;
use std::sync::Mutex;
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum EngineEvent {
EngineStarted {
model: String,
port: u16,
},
ModelLoaded {
model: String,
},
WarmUpComplete {
model: String,
},
RequestServed {
route: String,
model: String,
provider: String,
latency_ms: u64,
},
EngineError {
kind: String,
},
EngineStopping {
model: String,
},
}
#[async_trait]
pub trait EventSink: Send + Sync {
async fn emit(&self, event: EngineEvent);
}
#[derive(Default)]
pub struct InMemorySink {
events: Mutex<Vec<EngineEvent>>,
}
impl InMemorySink {
pub fn snapshot(&self) -> Vec<EngineEvent> {
self.events
.lock()
.expect("InMemorySink: lock poison — ne devrait pas arriver en test")
.clone()
}
}
#[async_trait]
impl EventSink for InMemorySink {
async fn emit(&self, event: EngineEvent) {
self.events
.lock()
.expect("InMemorySink: lock poison — ne devrait pas arriver en test")
.push(event);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn in_memory_sink_captures_events() {
let sink = InMemorySink::default();
sink.emit(EngineEvent::ModelLoaded {
model: "qwen3-4b".into(),
})
.await;
sink.emit(EngineEvent::RequestServed {
route: "/v1/chat/completions".into(),
model: "qwen3-4b".into(),
provider: "engine-curator".into(),
latency_ms: 42,
})
.await;
let got = sink.snapshot();
assert_eq!(got.len(), 2);
assert!(matches!(got[0], EngineEvent::ModelLoaded { .. }));
assert!(matches!(
got[1],
EngineEvent::RequestServed { latency_ms: 42, .. }
));
}
#[tokio::test]
async fn in_memory_sink_empty_by_default() {
let sink = InMemorySink::default();
assert!(sink.snapshot().is_empty());
}
#[tokio::test]
async fn engine_event_non_exhaustive_lifecycle() {
let sink = InMemorySink::default();
sink.emit(EngineEvent::EngineStarted {
model: "test".into(),
port: 11435,
})
.await;
sink.emit(EngineEvent::WarmUpComplete {
model: "test".into(),
})
.await;
sink.emit(EngineEvent::EngineStopping {
model: "test".into(),
})
.await;
let events = sink.snapshot();
assert_eq!(events.len(), 3);
assert!(events
.iter()
.all(|e| !matches!(e, EngineEvent::RequestServed { .. })));
}
}