use crate::testing::mock_transport::MockTransport;
use serde_json::Value;
pub struct Exchange {
events: Vec<Value>,
input_tokens: u64,
cached_tokens: u64,
output_tokens: u64,
}
impl Exchange {
pub fn new(events: Vec<Value>) -> Self {
Self {
events,
input_tokens: 100,
cached_tokens: 0,
output_tokens: 50,
}
}
pub fn with_usage(mut self, input: u64, cached: u64, output: u64) -> Self {
self.input_tokens = input;
self.cached_tokens = cached;
self.output_tokens = output;
self
}
}
pub struct ScenarioBuilder {
thread_id: String,
exchanges: Vec<Exchange>,
}
impl ScenarioBuilder {
#[must_use]
pub fn new(thread_id: impl Into<String>) -> Self {
Self {
thread_id: thread_id.into(),
exchanges: Vec::new(),
}
}
#[must_use]
pub fn exchange(self, events: Vec<Value>) -> Self {
self.exchange_typed(Exchange::new(events))
}
#[must_use]
pub fn exchange_typed(mut self, exchange: Exchange) -> Self {
self.exchanges.push(exchange);
self
}
#[must_use]
pub fn build(self) -> MockTransport {
use crate::testing::builders;
let transport = MockTransport::new();
transport.enqueue_event(builders::thread_started(&self.thread_id));
for exchange in &self.exchanges {
transport.enqueue_event(builders::turn_started());
for event in &exchange.events {
transport.enqueue_event(event.clone());
}
transport.enqueue_event(builders::turn_completed(
exchange.input_tokens,
exchange.cached_tokens,
exchange.output_tokens,
));
}
transport
}
#[must_use]
pub fn build_raw(self) -> MockTransport {
let transport = MockTransport::new();
for exchange in &self.exchanges {
for event in &exchange.events {
transport.enqueue_event(event.clone());
}
}
transport
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testing::builders;
#[test]
fn empty_build_has_thread_started_only() {
let transport = ScenarioBuilder::new("t1").build();
assert_eq!(transport.queued_count(), 1);
}
#[test]
fn single_exchange() {
let transport = ScenarioBuilder::new("t1")
.exchange(vec![builders::agent_message_completed("m1", "Hello!")])
.build();
assert_eq!(transport.queued_count(), 4);
}
#[test]
fn multiple_exchanges() {
let transport = ScenarioBuilder::new("t1")
.exchange(vec![builders::agent_message_completed("m1", "Turn 1")])
.exchange(vec![builders::agent_message_completed("m2", "Turn 2")])
.build();
assert_eq!(transport.queued_count(), 7);
}
#[test]
fn raw_mode_no_bookends() {
let transport = ScenarioBuilder::new("t1")
.exchange(vec![builders::agent_message_completed("m1", "raw")])
.build_raw();
assert_eq!(transport.queued_count(), 1);
}
#[test]
fn exchange_typed_custom_usage_accepted() {
let transport = ScenarioBuilder::new("t1")
.exchange_typed(
Exchange::new(vec![builders::agent_message_completed("m1", "big turn")])
.with_usage(500, 100, 1000),
)
.build();
assert_eq!(transport.queued_count(), 4);
}
}