use hypen_engine::ir::NodeId;
use hypen_engine::reconcile::Patch;
use hypen_engine::serialize::remote::{
deserialize_message, serialize_message, InitialTree, PatchStream, RemoteMessage,
};
use serde_json::json;
fn test_node_id() -> NodeId {
NodeId::default()
}
#[test]
fn test_initial_tree_new() {
let state = json!({"count": 0, "user": "Alice"});
let patches = vec![Patch::create(
test_node_id(),
"Column".to_string(),
std::sync::Arc::new(indexmap::indexmap! {}),
)];
let tree = InitialTree::new("Counter".to_string(), state.clone(), patches.clone());
assert_eq!(tree.module, "Counter");
assert_eq!(tree.state, state);
assert_eq!(tree.patches.len(), 1);
assert_eq!(tree.revision, 0);
assert_eq!(tree.hash, None);
}
#[test]
fn test_initial_tree_with_hash() {
let tree = InitialTree::new("Test".to_string(), json!({}), vec![]);
let tree_with_hash = tree.with_hash("abc123".to_string());
assert_eq!(tree_with_hash.hash, Some("abc123".to_string()));
}
#[test]
fn test_initial_tree_serialization() {
let tree = InitialTree::new(
"UserModule".to_string(),
json!({"name": "Alice", "email": "alice@example.com"}),
vec![Patch::create(
test_node_id(),
"Text".to_string(),
std::sync::Arc::new(indexmap::indexmap! {}),
)],
);
let json_str = serde_json::to_string(&tree).unwrap();
assert!(json_str.contains("UserModule"));
assert!(json_str.contains("Alice"));
assert!(json_str.contains("alice@example.com"));
assert!(json_str.contains("revision"));
}
#[test]
fn test_initial_tree_deserialization() {
let json_str = r#"{
"module": "Counter",
"state": {"count": 5},
"patches": [],
"revision": 0
}"#;
let tree: InitialTree = serde_json::from_str(json_str).unwrap();
assert_eq!(tree.module, "Counter");
assert_eq!(tree.state["count"], 5);
assert_eq!(tree.patches.len(), 0);
assert_eq!(tree.revision, 0);
assert_eq!(tree.hash, None);
}
#[test]
fn test_initial_tree_hash_omitted_when_none() {
let tree = InitialTree::new("Test".to_string(), json!({}), vec![]);
let json_str = serde_json::to_string(&tree).unwrap();
assert!(!json_str.contains("hash"));
}
#[test]
fn test_patch_stream_new() {
let patches = vec![Patch::set_prop(
test_node_id(),
"count".to_string(),
json!(10),
)];
let stream = PatchStream::new("Counter".to_string(), patches.clone(), 5);
assert_eq!(stream.module, "Counter");
assert_eq!(stream.patches.len(), 1);
assert_eq!(stream.revision, 5);
assert_eq!(stream.hash, None);
}
#[test]
fn test_patch_stream_with_hash() {
let stream = PatchStream::new("Test".to_string(), vec![], 1);
let stream_with_hash = stream.with_hash("xyz789".to_string());
assert_eq!(stream_with_hash.hash, Some("xyz789".to_string()));
}
#[test]
fn test_patch_stream_serialization() {
let stream = PatchStream::new(
"Counter".to_string(),
vec![Patch::set_prop(
test_node_id(),
"count".to_string(),
json!(15),
)],
3,
);
let json_str = serde_json::to_string(&stream).unwrap();
assert!(json_str.contains("Counter"));
assert!(json_str.contains("revision"));
assert!(json_str.contains("3"));
}
#[test]
fn test_patch_stream_deserialization() {
let json_str = r#"{
"module": "UserProfile",
"patches": [],
"revision": 10
}"#;
let stream: PatchStream = serde_json::from_str(json_str).unwrap();
assert_eq!(stream.module, "UserProfile");
assert_eq!(stream.patches.len(), 0);
assert_eq!(stream.revision, 10);
assert_eq!(stream.hash, None);
}
#[test]
fn test_patch_stream_revision_monotonically_increasing() {
let stream1 = PatchStream::new("Test".to_string(), vec![], 1);
let stream2 = PatchStream::new("Test".to_string(), vec![], 2);
let stream3 = PatchStream::new("Test".to_string(), vec![], 3);
assert!(stream2.revision > stream1.revision);
assert!(stream3.revision > stream2.revision);
}
#[test]
fn test_remote_message_initial_tree() {
let tree = InitialTree::new("Test".to_string(), json!({"count": 0}), vec![]);
let message = RemoteMessage::InitialTree(tree);
let json = serialize_message(&message).unwrap();
let deserialized = deserialize_message(&json).unwrap();
match deserialized {
RemoteMessage::InitialTree(t) => {
assert_eq!(t.module, "Test");
assert_eq!(t.state["count"], 0);
}
_ => panic!("Expected InitialTree variant"),
}
}
#[test]
fn test_remote_message_patch() {
let stream = PatchStream::new("Counter".to_string(), vec![], 5);
let message = RemoteMessage::Patch(stream);
let json = serialize_message(&message).unwrap();
let deserialized = deserialize_message(&json).unwrap();
match deserialized {
RemoteMessage::Patch(s) => {
assert_eq!(s.module, "Counter");
assert_eq!(s.revision, 5);
}
_ => panic!("Expected Patch variant"),
}
}
#[test]
fn test_remote_message_dispatch_action() {
let message = RemoteMessage::DispatchAction {
module: "Counter".to_string(),
action: "increment".to_string(),
payload: Some(json!({"amount": 5})),
};
let json = serialize_message(&message).unwrap();
let deserialized = deserialize_message(&json).unwrap();
match deserialized {
RemoteMessage::DispatchAction {
module,
action,
payload,
} => {
assert_eq!(module, "Counter");
assert_eq!(action, "increment");
assert_eq!(payload.unwrap()["amount"], 5);
}
_ => panic!("Expected DispatchAction variant"),
}
}
#[test]
fn test_remote_message_state_update() {
let message = RemoteMessage::StateUpdate {
module: "User".to_string(),
state: json!({"name": "Bob", "age": 25}),
};
let json = serialize_message(&message).unwrap();
let deserialized = deserialize_message(&json).unwrap();
match deserialized {
RemoteMessage::StateUpdate { module, state } => {
assert_eq!(module, "User");
assert_eq!(state["name"], "Bob");
assert_eq!(state["age"], 25);
}
_ => panic!("Expected StateUpdate variant"),
}
}
#[test]
fn test_remote_message_dispatch_action_without_payload() {
let message = RemoteMessage::DispatchAction {
module: "Auth".to_string(),
action: "logout".to_string(),
payload: None,
};
let json = serialize_message(&message).unwrap();
let deserialized = deserialize_message(&json).unwrap();
match deserialized {
RemoteMessage::DispatchAction {
module,
action,
payload,
} => {
assert_eq!(module, "Auth");
assert_eq!(action, "logout");
assert!(payload.is_none());
}
_ => panic!("Expected DispatchAction variant"),
}
}
#[test]
fn test_message_type_field_camelcase() {
let message = RemoteMessage::DispatchAction {
module: "Test".to_string(),
action: "test".to_string(),
payload: None,
};
let json = serialize_message(&message).unwrap();
assert!(json.contains("\"type\""));
assert!(json.contains("dispatchAction"));
}
#[test]
fn test_serialize_deserialize_roundtrip_all_variants() {
let messages = vec![
RemoteMessage::InitialTree(InitialTree::new("M1".to_string(), json!({}), vec![])),
RemoteMessage::Patch(PatchStream::new("M2".to_string(), vec![], 1)),
RemoteMessage::DispatchAction {
module: "M3".to_string(),
action: "act".to_string(),
payload: Some(json!({"x": 1})),
},
RemoteMessage::StateUpdate {
module: "M4".to_string(),
state: json!({"y": 2}),
},
];
for message in messages {
let json = serialize_message(&message).unwrap();
let deserialized = deserialize_message(&json).unwrap();
match (&message, &deserialized) {
(RemoteMessage::InitialTree(_), RemoteMessage::InitialTree(_)) => {}
(RemoteMessage::Patch(_), RemoteMessage::Patch(_)) => {}
(RemoteMessage::DispatchAction { .. }, RemoteMessage::DispatchAction { .. }) => {}
(RemoteMessage::StateUpdate { .. }, RemoteMessage::StateUpdate { .. }) => {}
_ => panic!("Variant mismatch after roundtrip"),
}
}
}
#[test]
fn test_deserialize_invalid_json_returns_error() {
let invalid_json = "{ not valid json }";
let result = deserialize_message(invalid_json);
assert!(result.is_err());
}
#[test]
fn test_initial_tree_with_multiple_patches() {
let patches = vec![
Patch::create(
test_node_id(),
"Column".to_string(),
std::sync::Arc::new(indexmap::indexmap! {}),
),
Patch::insert(test_node_id(), test_node_id(), None),
Patch::set_text(test_node_id(), "Hello".to_string()),
];
let tree = InitialTree::new("App".to_string(), json!({}), patches);
let json = serde_json::to_string(&tree).unwrap();
let deserialized: InitialTree = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.patches.len(), 3);
}