use huddle_protocol::protocol::{
EncryptedFileMeta, RoomKind, RoomMessage, SignedRoomMessage, WireMessage,
};
use huddle_protocol::relay::{ClientMsg, ServerMsg};
use serde_json::{json, Value};
fn to_value<T: serde::Serialize>(v: &T) -> Value {
serde_json::to_value(v).unwrap()
}
#[test]
fn room_kind_is_snake_case_externally() {
assert_eq!(
serde_json::to_string(&RoomKind::Direct).unwrap(),
"\"direct\""
);
assert_eq!(
serde_json::to_string(&RoomKind::Group).unwrap(),
"\"group\""
);
assert_eq!(
serde_json::from_str::<RoomKind>("\"direct\"").unwrap(),
RoomKind::Direct
);
}
#[test]
fn wire_message_uses_type_content_tagging() {
let env = WireMessage::Plain(RoomMessage::Typing {
sender_fingerprint: "fp".into(),
});
let v = to_value(&env);
assert_eq!(v["type"], json!("plain"));
assert_eq!(v["data"]["Typing"]["sender_fingerprint"], json!("fp"));
}
#[test]
fn optional_room_id_is_omitted_when_none_but_present_when_some() {
let none = to_value(&WireMessage::Plain(RoomMessage::MemberLeave {
sender_fingerprint: "fp".into(),
room_id: None,
}));
let leave = &none["data"]["MemberLeave"];
assert_eq!(leave["sender_fingerprint"], json!("fp"));
assert!(
leave.get("room_id").is_none(),
"room_id MUST be absent when None, or old peers see different bytes"
);
let some = to_value(&WireMessage::Plain(RoomMessage::MemberLeave {
sender_fingerprint: "fp".into(),
room_id: Some("r1".into()),
}));
assert_eq!(some["data"]["MemberLeave"]["room_id"], json!("r1"));
}
#[test]
fn signed_envelope_shape_is_stable() {
let env = SignedRoomMessage {
fingerprint: "fp".into(),
ed25519_pubkey_b64: "pk".into(),
payload_b64: "pl".into(),
signature_b64: "sig".into(),
signed_at_ms: 1234,
mldsa_pubkey_b64: None,
mldsa_signature_b64: None,
};
let v = to_value(&env);
for k in [
"fingerprint",
"ed25519_pubkey_b64",
"payload_b64",
"signature_b64",
"signed_at_ms",
] {
assert!(v.get(k).is_some(), "SignedRoomMessage must keep field {k}");
}
assert_eq!(v["signed_at_ms"], json!(1234));
}
#[test]
fn relay_server_message_omits_mailbox_id_when_none() {
let live = to_value(&ServerMsg::Message {
room: "r".into(),
id: "i".into(),
payload_b64: "p".into(),
mailbox_id: None,
seq: None,
});
assert_eq!(live["type"], json!("message"));
assert!(live.get("mailbox_id").is_none());
assert!(live.get("seq").is_none());
let queued = to_value(&ServerMsg::Message {
room: "r".into(),
id: "i".into(),
payload_b64: "p".into(),
mailbox_id: Some(7),
seq: Some(42),
});
assert_eq!(queued["mailbox_id"], json!(7));
assert_eq!(queued["seq"], json!(42));
}
#[test]
fn relay_ready_and_hello_shapes_match_the_unified_definition() {
let ready = to_value(&ServerMsg::Ready {
fingerprint: "fp".into(),
});
assert_eq!(ready["type"], json!("ready"));
assert_eq!(ready["fingerprint"], json!("fp"));
let hello = to_value(&ClientMsg::Hello {
fingerprint: "fp".into(),
pubkey_b64: "pk".into(),
signature_b64: "sig".into(),
rooms: vec!["r1".into()],
acks: true,
});
assert_eq!(hello["type"], json!("hello"));
for k in [
"fingerprint",
"pubkey_b64",
"signature_b64",
"rooms",
"acks",
] {
assert!(hello.get(k).is_some(), "Hello must keep field {k}");
}
}
#[test]
fn relay_messages_round_trip() {
let msgs = [
ClientMsg::Subscribe { room: "r".into() },
ClientMsg::Publish {
room: "r".into(),
id: "i".into(),
payload_b64: "p".into(),
},
ClientMsg::Ack { mailbox_id: 9 },
ClientMsg::Ping,
];
for m in &msgs {
let s = serde_json::to_string(m).unwrap();
let back: ClientMsg = serde_json::from_str(&s).unwrap();
assert_eq!(serde_json::to_string(&back).unwrap(), s);
}
}
#[test]
fn encrypted_file_meta_round_trips() {
let meta = EncryptedFileMeta {
megolm_session_id: "sid".into(),
wrapped_key_b64: "wk".into(),
nonce_b64: "n".into(),
content_hash: "h".into(),
};
let s = serde_json::to_string(&meta).unwrap();
let back: EncryptedFileMeta = serde_json::from_str(&s).unwrap();
assert_eq!(meta, back);
}