use serde::Serialize;
use serde_json::Value;
use tokio::sync::broadcast;
#[derive(Debug, Clone, Serialize)]
pub struct MeshNodeInfo {
pub node_id: String,
pub uptime_secs: u64,
pub version: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct PeerSummary {
pub id: String,
pub connected: bool,
pub state: String,
pub rtt_ms: Option<u64>,
}
#[derive(Debug, Clone, Serialize)]
pub struct TopologySummary {
pub peer_count: usize,
pub role: String,
pub hierarchy_level: u32,
}
#[derive(Debug, Clone, Serialize)]
pub struct ReadinessResponse {
pub ready: bool,
pub node_id: String,
pub checks: Vec<ReadinessCheck>,
}
#[derive(Debug, Clone, Serialize)]
pub struct ReadinessCheck {
pub name: String,
pub ready: bool,
pub message: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type")]
pub enum MeshEvent {
PeerConnected {
peer_id: String,
},
PeerDisconnected {
peer_id: String,
reason: String,
},
TopologyChanged {
new_role: String,
peer_count: usize,
},
SyncEvent {
collection: String,
doc_id: String,
action: String,
},
}
#[async_trait::async_trait]
pub trait MeshBrokerState: Send + Sync + 'static {
fn node_info(&self) -> MeshNodeInfo;
async fn list_peers(&self) -> Vec<PeerSummary>;
async fn get_peer(&self, id: &str) -> Option<PeerSummary>;
fn topology(&self) -> TopologySummary;
fn subscribe_events(&self) -> broadcast::Receiver<MeshEvent>;
fn readiness(&self) -> ReadinessResponse {
ReadinessResponse {
ready: true,
node_id: self.node_info().node_id,
checks: vec![],
}
}
async fn list_documents(&self, _collection: &str) -> Option<Vec<Value>> {
None
}
async fn get_document(&self, _collection: &str, _id: &str) -> Option<Value> {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mesh_node_info_serialization() {
let info = MeshNodeInfo {
node_id: "node-1".into(),
uptime_secs: 3600,
version: "0.1.0".into(),
};
let json: serde_json::Value = serde_json::to_value(&info).unwrap();
assert_eq!(json["node_id"], "node-1");
assert_eq!(json["uptime_secs"], 3600);
assert_eq!(json["version"], "0.1.0");
}
#[test]
fn test_mesh_node_info_zero_uptime() {
let info = MeshNodeInfo {
node_id: "fresh".into(),
uptime_secs: 0,
version: "0.0.0".into(),
};
let json: serde_json::Value = serde_json::to_value(&info).unwrap();
assert_eq!(json["uptime_secs"], 0);
}
#[test]
fn test_peer_summary_with_rtt() {
let peer = PeerSummary {
id: "peer-1".into(),
connected: true,
state: "active".into(),
rtt_ms: Some(42),
};
let json: serde_json::Value = serde_json::to_value(&peer).unwrap();
assert_eq!(json["id"], "peer-1");
assert_eq!(json["connected"], true);
assert_eq!(json["state"], "active");
assert_eq!(json["rtt_ms"], 42);
}
#[test]
fn test_peer_summary_without_rtt() {
let peer = PeerSummary {
id: "peer-2".into(),
connected: false,
state: "disconnected".into(),
rtt_ms: None,
};
let json: serde_json::Value = serde_json::to_value(&peer).unwrap();
assert_eq!(json["id"], "peer-2");
assert_eq!(json["connected"], false);
assert!(json["rtt_ms"].is_null());
}
#[test]
fn test_topology_summary_serialization() {
let topo = TopologySummary {
peer_count: 5,
role: "leader".into(),
hierarchy_level: 1,
};
let json: serde_json::Value = serde_json::to_value(&topo).unwrap();
assert_eq!(json["peer_count"], 5);
assert_eq!(json["role"], "leader");
assert_eq!(json["hierarchy_level"], 1);
}
#[test]
fn test_topology_summary_standalone_node() {
let topo = TopologySummary {
peer_count: 0,
role: "standalone".into(),
hierarchy_level: 0,
};
let json: serde_json::Value = serde_json::to_value(&topo).unwrap();
assert_eq!(json["peer_count"], 0);
assert_eq!(json["role"], "standalone");
}
#[test]
fn test_mesh_event_peer_connected() {
let event = MeshEvent::PeerConnected {
peer_id: "peer-2".into(),
};
let json: serde_json::Value = serde_json::to_value(&event).unwrap();
assert_eq!(json["type"], "PeerConnected");
assert_eq!(json["peer_id"], "peer-2");
assert_eq!(json.as_object().unwrap().len(), 2);
}
#[test]
fn test_mesh_event_peer_disconnected() {
let event = MeshEvent::PeerDisconnected {
peer_id: "peer-3".into(),
reason: "timeout".into(),
};
let json: serde_json::Value = serde_json::to_value(&event).unwrap();
assert_eq!(json["type"], "PeerDisconnected");
assert_eq!(json["peer_id"], "peer-3");
assert_eq!(json["reason"], "timeout");
}
#[test]
fn test_mesh_event_topology_changed() {
let event = MeshEvent::TopologyChanged {
new_role: "follower".into(),
peer_count: 3,
};
let json: serde_json::Value = serde_json::to_value(&event).unwrap();
assert_eq!(json["type"], "TopologyChanged");
assert_eq!(json["new_role"], "follower");
assert_eq!(json["peer_count"], 3);
}
#[test]
fn test_mesh_event_sync_event() {
let event = MeshEvent::SyncEvent {
collection: "docs".into(),
doc_id: "d1".into(),
action: "insert".into(),
};
let json: serde_json::Value = serde_json::to_value(&event).unwrap();
assert_eq!(json["type"], "SyncEvent");
assert_eq!(json["collection"], "docs");
assert_eq!(json["doc_id"], "d1");
assert_eq!(json["action"], "insert");
}
#[test]
fn test_mesh_node_info_debug_clone() {
let info = MeshNodeInfo {
node_id: "n1".into(),
uptime_secs: 100,
version: "1.0".into(),
};
let cloned = info.clone();
assert_eq!(cloned.node_id, "n1");
assert_eq!(cloned.uptime_secs, 100);
let _ = format!("{:?}", info);
}
#[test]
fn test_peer_summary_debug_clone() {
let peer = PeerSummary {
id: "p1".into(),
connected: true,
state: "active".into(),
rtt_ms: Some(10),
};
let cloned = peer.clone();
assert_eq!(cloned.id, "p1");
assert!(cloned.connected);
let _ = format!("{:?}", peer);
}
#[test]
fn test_topology_summary_debug_clone() {
let topo = TopologySummary {
peer_count: 3,
role: "leader".into(),
hierarchy_level: 2,
};
let cloned = topo.clone();
assert_eq!(cloned.peer_count, 3);
assert_eq!(cloned.hierarchy_level, 2);
let _ = format!("{:?}", topo);
}
#[test]
fn test_mesh_event_debug_clone() {
let event = MeshEvent::PeerConnected {
peer_id: "p1".into(),
};
let cloned = event.clone();
let _ = format!("{:?}", cloned);
let event2 = MeshEvent::SyncEvent {
collection: "docs".into(),
doc_id: "d1".into(),
action: "update".into(),
};
let cloned2 = event2.clone();
let _ = format!("{:?}", cloned2);
}
#[test]
fn test_mesh_event_topology_changed_clone() {
let event = MeshEvent::TopologyChanged {
new_role: "follower".into(),
peer_count: 5,
};
let cloned = event.clone();
let _ = format!("{:?}", cloned);
}
#[test]
fn test_mesh_event_peer_disconnected_clone() {
let event = MeshEvent::PeerDisconnected {
peer_id: "p2".into(),
reason: "timeout".into(),
};
let cloned = event.clone();
let _ = format!("{:?}", cloned);
}
#[test]
fn test_readiness_response_serialization() {
let resp = ReadinessResponse {
ready: true,
node_id: "node-1".into(),
checks: vec![ReadinessCheck {
name: "transport".into(),
ready: true,
message: None,
}],
};
let json: serde_json::Value = serde_json::to_value(&resp).unwrap();
assert_eq!(json["ready"], true);
assert_eq!(json["node_id"], "node-1");
assert_eq!(json["checks"][0]["name"], "transport");
assert_eq!(json["checks"][0]["ready"], true);
assert!(json["checks"][0]["message"].is_null());
}
#[test]
fn test_readiness_check_with_message() {
let check = ReadinessCheck {
name: "sync".into(),
ready: false,
message: Some("catching up".into()),
};
let json: serde_json::Value = serde_json::to_value(&check).unwrap();
assert_eq!(json["name"], "sync");
assert_eq!(json["ready"], false);
assert_eq!(json["message"], "catching up");
}
#[test]
fn test_readiness_response_debug_clone() {
let resp = ReadinessResponse {
ready: false,
node_id: "n1".into(),
checks: vec![],
};
let cloned = resp.clone();
assert_eq!(cloned.ready, false);
assert_eq!(cloned.node_id, "n1");
let _ = format!("{:?}", resp);
}
#[tokio::test]
async fn test_default_trait_methods() {
struct MinimalState {
tx: broadcast::Sender<MeshEvent>,
}
#[async_trait::async_trait]
impl MeshBrokerState for MinimalState {
fn node_info(&self) -> MeshNodeInfo {
MeshNodeInfo {
node_id: "n".into(),
uptime_secs: 0,
version: "0".into(),
}
}
async fn list_peers(&self) -> Vec<PeerSummary> {
vec![]
}
async fn get_peer(&self, _id: &str) -> Option<PeerSummary> {
None
}
fn topology(&self) -> TopologySummary {
TopologySummary {
peer_count: 0,
role: "none".into(),
hierarchy_level: 0,
}
}
fn subscribe_events(&self) -> broadcast::Receiver<MeshEvent> {
self.tx.subscribe()
}
}
let (tx, _) = broadcast::channel(1);
let state = MinimalState { tx };
assert!(state.list_documents("any").await.is_none());
assert!(state.get_document("any", "id").await.is_none());
}
#[tokio::test]
async fn test_trait_required_methods() {
struct FullState {
tx: broadcast::Sender<MeshEvent>,
}
#[async_trait::async_trait]
impl MeshBrokerState for FullState {
fn node_info(&self) -> MeshNodeInfo {
MeshNodeInfo {
node_id: "full".into(),
uptime_secs: 42,
version: "1.0".into(),
}
}
async fn list_peers(&self) -> Vec<PeerSummary> {
vec![PeerSummary {
id: "p1".into(),
connected: true,
state: "active".into(),
rtt_ms: Some(10),
}]
}
async fn get_peer(&self, id: &str) -> Option<PeerSummary> {
if id == "p1" {
Some(PeerSummary {
id: "p1".into(),
connected: true,
state: "active".into(),
rtt_ms: Some(10),
})
} else {
None
}
}
fn topology(&self) -> TopologySummary {
TopologySummary {
peer_count: 1,
role: "leader".into(),
hierarchy_level: 2,
}
}
fn subscribe_events(&self) -> broadcast::Receiver<MeshEvent> {
self.tx.subscribe()
}
}
let (tx, _) = broadcast::channel(16);
let state = FullState { tx };
let info = state.node_info();
assert_eq!(info.node_id, "full");
assert_eq!(info.uptime_secs, 42);
let peers = state.list_peers().await;
assert_eq!(peers.len(), 1);
let peer = state.get_peer("p1").await;
assert!(peer.is_some());
let no_peer = state.get_peer("missing").await;
assert!(no_peer.is_none());
let topo = state.topology();
assert_eq!(topo.peer_count, 1);
let _rx = state.subscribe_events();
}
}