use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::{Arc, Mutex};
use super::backend::{
DecisionBackend, DecisionRequest, DecisionTransportError, RawDecisionResponse,
};
pub type MockOutcome = Result<RawDecisionResponse, DecisionTransportError>;
#[derive(Default)]
pub struct MockDecisionBackend {
scripted: Mutex<Vec<MockOutcome>>,
requests: Arc<AtomicU32>,
seen: Mutex<Vec<MockSeenRequest>>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct MockSeenRequest {
pub provider: String,
pub model: String,
pub question_ids: Vec<String>,
pub effort: Option<String>,
pub temperature: Option<f64>,
pub state: serde_json::Value,
}
impl MockDecisionBackend {
pub fn scripted(outcomes: Vec<MockOutcome>) -> Self {
Self {
scripted: Mutex::new(outcomes),
requests: Arc::new(AtomicU32::new(0)),
seen: Mutex::new(Vec::new()),
}
}
pub fn request_count(&self) -> u32 {
self.requests.load(Ordering::SeqCst)
}
pub fn requests(&self) -> Vec<MockSeenRequest> {
self.seen.lock().expect("mock backend log").clone()
}
}
#[async_trait::async_trait]
impl DecisionBackend for MockDecisionBackend {
async fn evaluate(
&self,
request: DecisionRequest<'_>,
) -> Result<RawDecisionResponse, DecisionTransportError> {
self.requests.fetch_add(1, Ordering::SeqCst);
self.seen
.lock()
.expect("mock backend log")
.push(MockSeenRequest {
provider: request.provider.to_string(),
model: request.model.to_string(),
question_ids: request.questions.ids(),
effort: request.effort.map(str::to_string),
temperature: request.temperature,
state: request.state.clone(),
});
let mut scripted = self.scripted.lock().expect("mock backend script");
if scripted.is_empty() {
return Err(DecisionTransportError::TransportFailed {
diagnostic: "mock decision backend was called more times than it was scripted for"
.into(),
});
}
scripted.remove(0)
}
}