use std::borrow::Cow; use assert_matches::assert_matches; use js_int::uint; use ruma_common::{ event_id, events::{ key::verification::VerificationMethod, room::{ message::{ AudioMessageEventContent, ForwardThread, KeyVerificationRequestEventContent, MessageType, OriginalRoomMessageEvent, RoomMessageEventContent, TextMessageEventContent, }, MediaSource, }, MessageLikeUnsigned, }, mxc_uri, room_id, user_id, MilliSecondsSinceUnixEpoch, OwnedDeviceId, }; use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; macro_rules! json_object { ( $($key:expr => $value:expr),* $(,)? ) => { { let mut _map = serde_json::Map::::new(); $( let _ = _map.insert($key, $value); )* _map } }; } #[test] fn serialization() { let content = RoomMessageEventContent::new(MessageType::Audio(AudioMessageEventContent::plain( "test".into(), mxc_uri!("mxc://example.org/ffed755USFFxlgbQYZGtryd").to_owned(), None, ))); #[cfg(not(feature = "unstable-msc3246"))] assert_eq!( to_json_value(content).unwrap(), json!({ "body": "test", "msgtype": "m.audio", "url": "mxc://example.org/ffed755USFFxlgbQYZGtryd", }) ); #[cfg(feature = "unstable-msc3246")] assert_eq!( to_json_value(content).unwrap(), json!({ "body": "test", "msgtype": "m.audio", "url": "mxc://example.org/ffed755USFFxlgbQYZGtryd", "org.matrix.msc1767.text": "test", "org.matrix.msc1767.file": { "url": "mxc://example.org/ffed755USFFxlgbQYZGtryd", }, "org.matrix.msc1767.audio": {}, }) ); } #[test] fn content_serialization() { let message_event_content = RoomMessageEventContent::new(MessageType::Audio(AudioMessageEventContent::plain( "test".into(), mxc_uri!("mxc://example.org/ffed755USFFxlgbQYZGtryd").to_owned(), None, ))); #[cfg(not(feature = "unstable-msc3246"))] assert_eq!( to_json_value(&message_event_content).unwrap(), json!({ "body": "test", "msgtype": "m.audio", "url": "mxc://example.org/ffed755USFFxlgbQYZGtryd" }) ); #[cfg(feature = "unstable-msc3246")] assert_eq!( to_json_value(&message_event_content).unwrap(), json!({ "body": "test", "msgtype": "m.audio", "url": "mxc://example.org/ffed755USFFxlgbQYZGtryd", "org.matrix.msc1767.text": "test", "org.matrix.msc1767.file": { "url": "mxc://example.org/ffed755USFFxlgbQYZGtryd", }, "org.matrix.msc1767.audio": {}, }) ); } #[test] fn custom_msgtype_serialization() { let json_data = json_object! { "custom_field".into() => json!("baba"), "another_one".into() => json!("abab"), }; let custom_msgtype = MessageType::new("my_custom_msgtype", "my message body".into(), json_data).unwrap(); assert_eq!( to_json_value(&custom_msgtype).unwrap(), json!({ "msgtype": "my_custom_msgtype", "body": "my message body", "custom_field": "baba", "another_one": "abab", }) ); } #[test] fn custom_content_deserialization() { let json_data = json!({ "msgtype": "my_custom_msgtype", "body": "my custom message", "custom_field": "baba", "another_one": "abab", }); let expected_json_data = json_object! { "custom_field".into() => json!("baba"), "another_one".into() => json!("abab"), }; let custom_event: MessageType = from_json_value(json_data).unwrap(); assert_eq!(custom_event.msgtype(), "my_custom_msgtype"); assert_eq!(custom_event.body(), "my custom message"); assert_eq!(custom_event.data(), Cow::Owned(expected_json_data)); } #[test] fn formatted_body_serialization() { let message_event_content = RoomMessageEventContent::text_html("Hello, World!", "Hello, World!"); #[cfg(not(feature = "unstable-msc1767"))] assert_eq!( to_json_value(&message_event_content).unwrap(), json!({ "body": "Hello, World!", "msgtype": "m.text", "format": "org.matrix.custom.html", "formatted_body": "Hello, World!", }) ); #[cfg(feature = "unstable-msc1767")] assert_eq!( to_json_value(&message_event_content).unwrap(), json!({ "body": "Hello, World!", "msgtype": "m.text", "format": "org.matrix.custom.html", "formatted_body": "Hello, World!", "org.matrix.msc1767.html": "Hello, World!", "org.matrix.msc1767.text": "Hello, World!", }) ); } #[test] fn plain_text_content_serialization() { let message_event_content = RoomMessageEventContent::text_plain("> <@test:example.com> test\n\ntest reply"); #[cfg(not(feature = "unstable-msc1767"))] assert_eq!( to_json_value(&message_event_content).unwrap(), json!({ "body": "> <@test:example.com> test\n\ntest reply", "msgtype": "m.text" }) ); #[cfg(feature = "unstable-msc1767")] assert_eq!( to_json_value(&message_event_content).unwrap(), json!({ "body": "> <@test:example.com> test\n\ntest reply", "msgtype": "m.text", "org.matrix.msc1767.text": "> <@test:example.com> test\n\ntest reply", }) ); } #[test] #[cfg(feature = "markdown")] fn markdown_content_serialization() { use ruma_common::events::room::message::TextMessageEventContent; let formatted_message = RoomMessageEventContent::new(MessageType::Text( TextMessageEventContent::markdown("Testing **bold** and _italic_!"), )); #[cfg(not(feature = "unstable-msc1767"))] assert_eq!( to_json_value(&formatted_message).unwrap(), json!({ "body": "Testing **bold** and _italic_!", "formatted_body": "

Testing bold and italic!

\n", "format": "org.matrix.custom.html", "msgtype": "m.text" }) ); #[cfg(feature = "unstable-msc1767")] assert_eq!( to_json_value(&formatted_message).unwrap(), json!({ "body": "Testing **bold** and _italic_!", "formatted_body": "

Testing bold and italic!

\n", "format": "org.matrix.custom.html", "msgtype": "m.text", "org.matrix.msc1767.html": "

Testing bold and italic!

\n", "org.matrix.msc1767.text": "Testing **bold** and _italic_!", }) ); let plain_message_simple = RoomMessageEventContent::new(MessageType::Text( TextMessageEventContent::markdown("Testing a simple phrase…"), )); #[cfg(not(feature = "unstable-msc1767"))] assert_eq!( to_json_value(&plain_message_simple).unwrap(), json!({ "body": "Testing a simple phrase…", "msgtype": "m.text" }) ); #[cfg(feature = "unstable-msc1767")] assert_eq!( to_json_value(&plain_message_simple).unwrap(), json!({ "body": "Testing a simple phrase…", "msgtype": "m.text", "org.matrix.msc1767.text": "Testing a simple phrase…", }) ); let plain_message_paragraphs = RoomMessageEventContent::new(MessageType::Text( TextMessageEventContent::markdown("Testing\n\nSeveral\n\nParagraphs."), )); #[cfg(not(feature = "unstable-msc1767"))] assert_eq!( to_json_value(&plain_message_paragraphs).unwrap(), json!({ "body": "Testing\n\nSeveral\n\nParagraphs.", "formatted_body": "

Testing

\n

Several

\n

Paragraphs.

\n", "format": "org.matrix.custom.html", "msgtype": "m.text" }) ); #[cfg(feature = "unstable-msc1767")] assert_eq!( to_json_value(&plain_message_paragraphs).unwrap(), json!({ "body": "Testing\n\nSeveral\n\nParagraphs.", "formatted_body": "

Testing

\n

Several

\n

Paragraphs.

\n", "format": "org.matrix.custom.html", "msgtype": "m.text", "org.matrix.msc1767.html": "

Testing

\n

Several

\n

Paragraphs.

\n", "org.matrix.msc1767.text": "Testing\n\nSeveral\n\nParagraphs.", }) ); } #[test] #[cfg(feature = "markdown")] fn markdown_detection() { use ruma_common::events::room::message::FormattedBody; // No markdown let formatted_body = FormattedBody::markdown("A simple message."); assert_matches!(formatted_body, None); // Multiple paragraphs trigger markdown let formatted_body = FormattedBody::markdown("A message\nwith\n\nmultiple\n\nparagraphs"); formatted_body.unwrap(); // HTML entities don't trigger markdown. let formatted_body = FormattedBody::markdown("A message with & HTML < entities"); assert_matches!(formatted_body, None); // HTML triggers markdown. let formatted_body = FormattedBody::markdown("An HTML message"); formatted_body.unwrap(); } #[test] #[cfg(feature = "markdown")] fn markdown_options() { use ruma_common::events::room::message::FormattedBody; // Tables let formatted_body = FormattedBody::markdown( "|head1|head2|\n\ |---|---|\n\ |body1|body2|\ ", ); assert_eq!( formatted_body.unwrap().body, "\ \ \n\n\
head1head2
body1body2
\n" ); // Strikethrough let formatted_body = FormattedBody::markdown("A message with a ~~strike~~"); assert_eq!(formatted_body.unwrap().body, "

A message with a strike

\n"); } #[test] fn verification_request_deserialization() { let user_id = user_id!("@example2:localhost"); let device_id: OwnedDeviceId = "XOWLHHFSWM".into(); let json_data = json!({ "body": "@example:localhost is requesting to verify your key, ...", "msgtype": "m.key.verification.request", "to": user_id, "from_device": device_id, "methods": [ "m.sas.v1", "m.qr_code.show.v1", "m.reciprocate.v1" ] }); let content = from_json_value::(json_data).unwrap(); let verification = assert_matches!( content.msgtype, MessageType::VerificationRequest(verification) => verification ); assert_eq!(verification.body, "@example:localhost is requesting to verify your key, ..."); assert_eq!(verification.to, user_id); assert_eq!(verification.from_device, device_id); assert_eq!(verification.methods.len(), 3); assert!(verification.methods.contains(&VerificationMethod::SasV1)); assert_matches!(content.relates_to, None); } #[test] fn verification_request_serialization() { let user_id = user_id!("@example2:localhost").to_owned(); let device_id: OwnedDeviceId = "XOWLHHFSWM".into(); let body = "@example:localhost is requesting to verify your key, ...".to_owned(); let methods = vec![VerificationMethod::SasV1, "m.qr_code.show.v1".into(), "m.reciprocate.v1".into()]; let json_data = json!({ "body": body, "msgtype": "m.key.verification.request", "to": user_id, "from_device": device_id, "methods": methods }); let content = MessageType::VerificationRequest(KeyVerificationRequestEventContent::new( body, methods, device_id, user_id, )); assert_eq!(to_json_value(&content).unwrap(), json_data,); } #[test] fn content_deserialization() { let json_data = json!({ "body": "test", "msgtype": "m.audio", "url": "mxc://example.org/ffed755USFFxlgbQYZGtryd" }); let content = from_json_value::(json_data).unwrap(); let audio = assert_matches!( content.msgtype, MessageType::Audio(audio) => audio ); assert_eq!(audio.body, "test"); assert_matches!(audio.info, None); let url = assert_matches!(audio.source, MediaSource::Plain(url) => url); assert_eq!(url, "mxc://example.org/ffed755USFFxlgbQYZGtryd"); assert_matches!(content.relates_to, None); } #[test] fn content_deserialization_failure() { let json_data = json!({ "body": "test","msgtype": "m.location", "url": "http://example.com/audio.mp3" }); assert_matches!(from_json_value::(json_data), Err(_)); } #[test] fn escape_tags_in_plain_reply_body() { let first_message = OriginalRoomMessageEvent { content: RoomMessageEventContent::text_plain("Usage: cp "), event_id: event_id!("$143273582443PhrSn:example.org").to_owned(), origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(10_000)), room_id: room_id!("!testroomid:example.org").to_owned(), sender: user_id!("@user:example.org").to_owned(), unsigned: MessageLikeUnsigned::default(), }; let second_message = RoomMessageEventContent::text_plain("Usage: rm ") .make_reply_to(&first_message, ForwardThread::Yes); let body = assert_matches!( first_message.content.msgtype, MessageType::Text(TextMessageEventContent { body, formatted: None, .. }) => body ); assert_eq!(body, "Usage: cp "); let (body, formatted) = assert_matches!( second_message.msgtype, MessageType::Text(TextMessageEventContent { body, formatted, .. }) => (body, formatted) ); assert_eq!( body, "\ > <@user:example.org> Usage: cp \n\ Usage: rm \ " ); let formatted = formatted.unwrap(); assert_eq!( formatted.body, "\ \
\ In reply to \ @user:example.org\
\ Usage: cp <source> <destination>\
\
\ Usage: rm <path>\ " ); } #[test] #[cfg(feature = "unstable-sanitize")] fn reply_sanitize() { use ruma_common::events::room::message::ForwardThread; let first_message = OriginalRoomMessageEvent { content: RoomMessageEventContent::text_html( "# This is the first message", "

This is the first message

", ), event_id: event_id!("$143273582443PhrSn:example.org").to_owned(), origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(10_000)), room_id: room_id!("!testroomid:example.org").to_owned(), sender: user_id!("@user:example.org").to_owned(), unsigned: MessageLikeUnsigned::default(), }; let second_message = OriginalRoomMessageEvent { content: RoomMessageEventContent::text_html( "This is the _second_ message", "This is the second message", ) .make_reply_to(&first_message, ForwardThread::Yes), event_id: event_id!("$143273582443PhrSn:example.org").to_owned(), origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(10_000)), room_id: room_id!("!testroomid:example.org").to_owned(), sender: user_id!("@user:example.org").to_owned(), unsigned: MessageLikeUnsigned::default(), }; let final_reply = RoomMessageEventContent::text_html( "This is **my** reply", "This is my reply", ) .make_reply_to(&second_message, ForwardThread::Yes); let (body, formatted) = assert_matches!( first_message.content.msgtype, MessageType::Text(TextMessageEventContent { body, formatted, .. }) => (body, formatted) ); assert_eq!(body, "# This is the first message"); let formatted = formatted.unwrap(); assert_eq!(formatted.body, "

This is the first message

"); let (body, formatted) = assert_matches!( second_message.content.msgtype, MessageType::Text(TextMessageEventContent { body, formatted, .. }) => (body, formatted) ); assert_eq!( body, "\ > <@user:example.org> # This is the first message\n\ This is the _second_ message\ " ); let formatted = formatted.unwrap(); assert_eq!( formatted.body, "\ \
\ In reply to \ @user:example.org\
\

This is the first message

\
\
\ This is the second message\ " ); let (body, formatted) = assert_matches!( final_reply.msgtype, MessageType::Text(TextMessageEventContent { body, formatted, .. }) => (body, formatted) ); assert_eq!( body, "\ > <@user:example.org> This is the _second_ message\n\ This is **my** reply\ " ); let formatted = formatted.unwrap(); assert_eq!( formatted.body, "\ \
\ In reply to \ @user:example.org\
\ This is the second message\
\
\ This is my reply\ " ); } #[test] fn make_replacement_no_reply() { let content = RoomMessageEventContent::text_html( "This is _an edited_ message.", "This is an edited message.", ); let event_id = event_id!("$143273582443PhrSn:example.org").to_owned(); let content = content.make_replacement(event_id, None); let (body, formatted) = assert_matches!( content.msgtype, MessageType::Text(TextMessageEventContent { body, formatted, .. }) => (body, formatted) ); assert_eq!(body, "* This is _an edited_ message."); let formatted = formatted.unwrap(); assert_eq!(formatted.body, "* This is an edited message."); } #[test] fn make_replacement_with_reply() { let replied_to_message = OriginalRoomMessageEvent { content: RoomMessageEventContent::text_html( "# This is the first message", "

This is the first message

", ), event_id: event_id!("$143273582443PhrSn:example.org").to_owned(), origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(10_000)), room_id: room_id!("!testroomid:example.org").to_owned(), sender: user_id!("@user:example.org").to_owned(), unsigned: MessageLikeUnsigned::default(), }; let content = RoomMessageEventContent::text_html( "This is _an edited_ reply.", "This is an edited reply.", ); let event_id = event_id!("$143273582443PhrSn:example.org").to_owned(); let content = content.make_replacement(event_id, Some(&replied_to_message)); let (body, formatted) = assert_matches!( content.msgtype, MessageType::Text(TextMessageEventContent { body, formatted, .. }) => (body, formatted) ); assert_eq!( body, "\ > <@user:example.org> # This is the first message\n\ * This is _an edited_ reply.\ " ); let formatted = formatted.unwrap(); assert_eq!( formatted.body, "\ \
\ In reply to \ @user:example.org\
\

This is the first message

\
\
\ * This is an edited reply.\ " ); }