use crate::ports::message_broker::{Message, MessageBroker};
use crate::ports::session_store::{SessionEvent, SessionStore};
use crate::ports::user_output::UserOutput;
pub fn assert_message_broker_contract(broker: impl MessageBroker + Clone) {
let mut rx = broker.subscribe("t1");
let msg = Message::new("src", "t1", 1, serde_json::Value::Null);
let n = broker.publish(msg);
assert!(n >= 1, "expected at least 1 receiver, got {n}");
let received = rx.try_recv().expect("subscriber should receive message");
assert_eq!(received.topic, "t1");
assert_eq!(received.source, "src");
let broker2 = broker.clone();
let n = broker2.publish(Message::new("src", "no-sub", 1, serde_json::Value::Null));
assert_eq!(n, 0, "publish with no subscribers should return 0");
let mut rx_a = broker.subscribe("fan");
let mut rx_b = broker.subscribe("fan");
let n = broker.publish(Message::new("src", "fan", 1, serde_json::Value::Null));
assert_eq!(n, 2, "expected fan-out to 2 subscribers");
assert!(rx_a.try_recv().is_ok());
assert!(rx_b.try_recv().is_ok());
let mut rx_x = broker.subscribe("x");
let mut rx_y = broker.subscribe("y");
broker.publish(Message::new("src", "x", 1, serde_json::Value::Null));
assert!(rx_x.try_recv().is_ok(), "x subscriber should get x message");
assert!(
rx_y.try_recv().is_err(),
"y subscriber should NOT get x message"
);
broker.close();
let n = broker.publish(Message::new("src", "t1", 1, serde_json::Value::Null));
assert_eq!(n, 0, "publish after close should return 0");
}
pub fn assert_session_store_contract(store: &mut dyn SessionStore) {
let id = store.session_id().to_string();
assert!(!id.is_empty(), "session_id must not be empty");
assert_eq!(
store.session_id(),
id,
"session_id must be stable across calls"
);
let events = vec![
SessionEvent::UserMessage {
content: "hello".into(),
provider: "test".into(),
},
SessionEvent::Inference {
content: "response".into(),
provider: "test".into(),
},
SessionEvent::ToolUse {
tool_name: "bash".into(),
input: serde_json::json!({"cmd": "echo"}),
tool_use_id: "tu-1".into(),
provider: "test".into(),
},
SessionEvent::ToolResult {
tool_use_id: "tu-1".into(),
output: "ok".into(),
is_error: false,
provider: "test".into(),
},
SessionEvent::SessionEnd,
];
for (i, event) in events.into_iter().enumerate() {
store
.log(event)
.unwrap_or_else(|e| panic!("log() failed on event variant {i}: {e}"));
}
let p1 = store.path().map(|p| p.to_path_buf());
let p2 = store.path().map(|p| p.to_path_buf());
assert_eq!(p1, p2, "path() must return consistent value");
}
pub fn assert_inference_provider_contract(provider: &dyn crate::ports::InferenceProvider) {
let name = provider.name();
assert!(!name.is_empty(), "name() must return a non-empty string");
let model = provider.model();
assert!(
!model.as_str().is_empty(),
"model() must return a non-empty ModelId"
);
let _ = provider.supports_tool_use();
let _ = provider.validate_config();
}
pub fn assert_user_output_contract(output: &dyn UserOutput) {
output.info("info message");
output.info("");
output.warn("warning message");
output.warn("");
output.error("error message");
output.error("");
output.assistant_text("assistant text");
output.assistant_text("");
output.tool_call("bash", "echo hello");
output.tool_call("", "");
output.tool_ok();
output.tool_err("something failed");
output.tool_err("");
output.write_chunk("chunk");
output.write_chunk("");
}