1use matrix_sdk_common::deserialized_responses::TimelineEvent;
5use ruma::{MilliSecondsSinceUnixEpoch, MxcUri, OwnedEventId};
6#[cfg(feature = "e2e-encryption")]
7use ruma::{
8 UserId,
9 events::{
10 AnySyncMessageLikeEvent, AnySyncStateEvent, AnySyncTimelineEvent,
11 call::invite::SyncCallInviteEvent,
12 poll::unstable_start::SyncUnstablePollStartEvent,
13 relation::RelationType,
14 room::{
15 member::{MembershipState, SyncRoomMemberEvent},
16 message::{MessageType, SyncRoomMessageEvent},
17 power_levels::RoomPowerLevels,
18 },
19 rtc::notification::SyncRtcNotificationEvent,
20 sticker::SyncStickerEvent,
21 },
22};
23use serde::{Deserialize, Serialize};
24
25use crate::{MinimalRoomMemberEvent, store::SerializableEventContent};
26
27#[derive(Debug, Default, Clone, Serialize, Deserialize)]
29pub enum LatestEventValue {
30 #[default]
32 None,
33
34 Remote(RemoteLatestEventValue),
36
37 LocalIsSending(LocalLatestEventValue),
39
40 LocalCannotBeSent(LocalLatestEventValue),
43}
44
45impl LatestEventValue {
46 pub fn timestamp(&self) -> Option<MilliSecondsSinceUnixEpoch> {
58 match self {
59 Self::None => None,
60 Self::Remote(remote_latest_event_value) => remote_latest_event_value.timestamp(),
61 Self::LocalIsSending(LocalLatestEventValue { timestamp, .. })
62 | Self::LocalCannotBeSent(LocalLatestEventValue { timestamp, .. }) => Some(*timestamp),
63 }
64 }
65
66 pub fn is_local(&self) -> bool {
72 match self {
73 Self::LocalIsSending(_) | Self::LocalCannotBeSent(_) => true,
74 Self::None | Self::Remote(_) => false,
75 }
76 }
77}
78
79pub type RemoteLatestEventValue = TimelineEvent;
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct LocalLatestEventValue {
86 pub timestamp: MilliSecondsSinceUnixEpoch,
88
89 pub content: SerializableEventContent,
91}
92
93#[cfg(test)]
94mod tests_latest_event_value {
95 use ruma::{
96 MilliSecondsSinceUnixEpoch,
97 events::{AnyMessageLikeEventContent, room::message::RoomMessageEventContent},
98 serde::Raw,
99 uint,
100 };
101 use serde_json::json;
102
103 use super::{LatestEventValue, LocalLatestEventValue, RemoteLatestEventValue};
104 use crate::store::SerializableEventContent;
105
106 #[test]
107 fn test_timestamp_with_none() {
108 let value = LatestEventValue::None;
109
110 assert_eq!(value.timestamp(), None);
111 }
112
113 #[test]
114 fn test_timestamp_with_remote() {
115 let value = LatestEventValue::Remote(RemoteLatestEventValue::from_plaintext(
116 Raw::from_json_string(
117 json!({
118 "content": RoomMessageEventContent::text_plain("raclette"),
119 "type": "m.room.message",
120 "event_id": "$ev0",
121 "room_id": "!r0",
122 "origin_server_ts": 42,
123 "sender": "@mnt_io:matrix.org",
124 })
125 .to_string(),
126 )
127 .unwrap(),
128 ));
129
130 assert_eq!(value.timestamp(), Some(MilliSecondsSinceUnixEpoch(uint!(42))));
131 }
132
133 #[test]
134 fn test_timestamp_with_local_is_sending() {
135 let value = LatestEventValue::LocalIsSending(LocalLatestEventValue {
136 timestamp: MilliSecondsSinceUnixEpoch(uint!(42)),
137 content: SerializableEventContent::from_raw(
138 Raw::new(&AnyMessageLikeEventContent::RoomMessage(
139 RoomMessageEventContent::text_plain("raclette"),
140 ))
141 .unwrap(),
142 "m.room.message".to_owned(),
143 ),
144 });
145
146 assert_eq!(value.timestamp(), Some(MilliSecondsSinceUnixEpoch(uint!(42))));
147 }
148
149 #[test]
150 fn test_timestamp_with_local_cannot_be_sent() {
151 let value = LatestEventValue::LocalCannotBeSent(LocalLatestEventValue {
152 timestamp: MilliSecondsSinceUnixEpoch(uint!(42)),
153 content: SerializableEventContent::from_raw(
154 Raw::new(&AnyMessageLikeEventContent::RoomMessage(
155 RoomMessageEventContent::text_plain("raclette"),
156 ))
157 .unwrap(),
158 "m.room.message".to_owned(),
159 ),
160 });
161
162 assert_eq!(value.timestamp(), Some(MilliSecondsSinceUnixEpoch(uint!(42))));
163 }
164}
165
166#[cfg(feature = "e2e-encryption")]
171#[derive(Debug)]
172pub enum PossibleLatestEvent<'a> {
173 YesRoomMessage(&'a SyncRoomMessageEvent),
175 YesSticker(&'a SyncStickerEvent),
177 YesPoll(&'a SyncUnstablePollStartEvent),
179
180 YesCallInvite(&'a SyncCallInviteEvent),
182
183 YesRtcNotification(&'a SyncRtcNotificationEvent),
185
186 YesKnockedStateEvent(&'a SyncRoomMemberEvent),
189
190 NoUnsupportedEventType,
194 NoUnsupportedMessageLikeType,
196 NoEncrypted,
198}
199
200#[cfg(feature = "e2e-encryption")]
203pub fn is_suitable_for_latest_event<'a>(
204 event: &'a AnySyncTimelineEvent,
205 power_levels_info: Option<(&'a UserId, &'a RoomPowerLevels)>,
206) -> PossibleLatestEvent<'a> {
207 match event {
208 AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(message)) => {
210 if let Some(original_message) = message.as_original() {
211 if let MessageType::VerificationRequest(_) = original_message.content.msgtype {
213 return PossibleLatestEvent::NoUnsupportedMessageLikeType;
214 }
215
216 let is_replacement =
218 original_message.content.relates_to.as_ref().is_some_and(|relates_to| {
219 if let Some(relation_type) = relates_to.rel_type() {
220 relation_type == RelationType::Replacement
221 } else {
222 false
223 }
224 });
225
226 if is_replacement {
227 PossibleLatestEvent::NoUnsupportedMessageLikeType
228 } else {
229 PossibleLatestEvent::YesRoomMessage(message)
230 }
231 } else {
232 PossibleLatestEvent::YesRoomMessage(message)
233 }
234 }
235
236 AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::UnstablePollStart(poll)) => {
237 PossibleLatestEvent::YesPoll(poll)
238 }
239
240 AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::CallInvite(invite)) => {
241 PossibleLatestEvent::YesCallInvite(invite)
242 }
243
244 AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RtcNotification(notify)) => {
245 PossibleLatestEvent::YesRtcNotification(notify)
246 }
247
248 AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::Sticker(sticker)) => {
249 PossibleLatestEvent::YesSticker(sticker)
250 }
251
252 AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted(_)) => {
254 PossibleLatestEvent::NoEncrypted
255 }
256
257 AnySyncTimelineEvent::MessageLike(_) => PossibleLatestEvent::NoUnsupportedMessageLikeType,
263
264 AnySyncTimelineEvent::State(state) => {
266 if let AnySyncStateEvent::RoomMember(member) = state
269 && matches!(member.membership(), MembershipState::Knock)
270 {
271 let can_accept_or_decline_knocks = match power_levels_info {
272 Some((own_user_id, room_power_levels)) => {
273 room_power_levels.user_can_invite(own_user_id)
274 || room_power_levels.user_can_kick(own_user_id)
275 }
276 _ => false,
277 };
278
279 if can_accept_or_decline_knocks {
282 return PossibleLatestEvent::YesKnockedStateEvent(member);
283 }
284 }
285 PossibleLatestEvent::NoUnsupportedEventType
286 }
287 }
288}
289
290#[derive(Clone, Debug, Serialize)]
310pub struct LatestEvent {
311 event: TimelineEvent,
313
314 #[serde(skip_serializing_if = "Option::is_none")]
316 sender_profile: Option<MinimalRoomMemberEvent>,
317
318 #[serde(skip_serializing_if = "Option::is_none")]
320 sender_name_is_ambiguous: Option<bool>,
321}
322
323#[derive(Deserialize)]
324struct SerializedLatestEvent {
325 event: TimelineEvent,
327
328 #[serde(skip_serializing_if = "Option::is_none")]
330 sender_profile: Option<MinimalRoomMemberEvent>,
331
332 #[serde(skip_serializing_if = "Option::is_none")]
334 sender_name_is_ambiguous: Option<bool>,
335}
336
337impl<'de> Deserialize<'de> for LatestEvent {
340 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
341 where
342 D: serde::Deserializer<'de>,
343 {
344 let raw: Box<serde_json::value::RawValue> = Box::deserialize(deserializer)?;
345
346 let mut variant_errors = Vec::new();
347
348 match serde_json::from_str::<SerializedLatestEvent>(raw.get()) {
349 Ok(value) => {
350 return Ok(LatestEvent {
351 event: value.event,
352 sender_profile: value.sender_profile,
353 sender_name_is_ambiguous: value.sender_name_is_ambiguous,
354 });
355 }
356 Err(err) => variant_errors.push(err),
357 }
358
359 match serde_json::from_str::<TimelineEvent>(raw.get()) {
360 Ok(value) => {
361 return Ok(LatestEvent {
362 event: value,
363 sender_profile: None,
364 sender_name_is_ambiguous: None,
365 });
366 }
367 Err(err) => variant_errors.push(err),
368 }
369
370 Err(serde::de::Error::custom(format!(
371 "data did not match any variant of serialized LatestEvent (using serde_json). \
372 Observed errors: {variant_errors:?}"
373 )))
374 }
375}
376
377impl LatestEvent {
378 pub fn new(event: TimelineEvent) -> Self {
380 Self { event, sender_profile: None, sender_name_is_ambiguous: None }
381 }
382
383 pub fn new_with_sender_details(
385 event: TimelineEvent,
386 sender_profile: Option<MinimalRoomMemberEvent>,
387 sender_name_is_ambiguous: Option<bool>,
388 ) -> Self {
389 Self { event, sender_profile, sender_name_is_ambiguous }
390 }
391
392 pub fn into_event(self) -> TimelineEvent {
394 self.event
395 }
396
397 pub fn event(&self) -> &TimelineEvent {
399 &self.event
400 }
401
402 pub fn event_mut(&mut self) -> &mut TimelineEvent {
404 &mut self.event
405 }
406
407 pub fn event_id(&self) -> Option<OwnedEventId> {
409 self.event.event_id()
410 }
411
412 pub fn has_sender_profile(&self) -> bool {
414 self.sender_profile.is_some()
415 }
416
417 pub fn sender_display_name(&self) -> Option<&str> {
420 self.sender_profile.as_ref().and_then(|profile| {
421 profile.as_original().and_then(|event| event.content.displayname.as_deref())
422 })
423 }
424
425 pub fn sender_name_ambiguous(&self) -> Option<bool> {
429 self.sender_name_is_ambiguous
430 }
431
432 pub fn sender_avatar_url(&self) -> Option<&MxcUri> {
435 self.sender_profile.as_ref().and_then(|profile| {
436 profile.as_original().and_then(|event| event.content.avatar_url.as_deref())
437 })
438 }
439}
440
441#[cfg(test)]
442mod tests {
443 #[cfg(feature = "e2e-encryption")]
444 use std::collections::BTreeMap;
445 use std::time::Duration;
446
447 #[cfg(feature = "e2e-encryption")]
448 use assert_matches::assert_matches;
449 #[cfg(feature = "e2e-encryption")]
450 use assert_matches2::assert_let;
451 use matrix_sdk_common::deserialized_responses::TimelineEvent;
452 #[cfg(feature = "e2e-encryption")]
453 use ruma::{
454 MilliSecondsSinceUnixEpoch, UInt, VoipVersionId,
455 events::{
456 AnySyncMessageLikeEvent, AnySyncStateEvent, AnySyncTimelineEvent, EmptyStateKey,
457 MessageLikeUnsigned, OriginalSyncMessageLikeEvent, OriginalSyncStateEvent,
458 RedactedSyncMessageLikeEvent, RedactedUnsigned, StateUnsigned, SyncMessageLikeEvent,
459 call::{
460 SessionDescription,
461 invite::{CallInviteEventContent, SyncCallInviteEvent},
462 },
463 poll::{
464 unstable_response::{
465 SyncUnstablePollResponseEvent, UnstablePollResponseEventContent,
466 },
467 unstable_start::{
468 NewUnstablePollStartEventContent, SyncUnstablePollStartEvent,
469 UnstablePollAnswer, UnstablePollStartContentBlock,
470 },
471 },
472 relation::Replacement,
473 room::{
474 ImageInfo, MediaSource,
475 encrypted::{
476 EncryptedEventScheme, OlmV1Curve25519AesSha2Content, RoomEncryptedEventContent,
477 SyncRoomEncryptedEvent,
478 },
479 message::{
480 ImageMessageEventContent, MessageType, RedactedRoomMessageEventContent,
481 Relation, RoomMessageEventContent, SyncRoomMessageEvent,
482 },
483 topic::{RoomTopicEventContent, SyncRoomTopicEvent},
484 },
485 sticker::{StickerEventContent, SyncStickerEvent},
486 },
487 owned_event_id, owned_mxc_uri, owned_user_id,
488 };
489 use ruma::{
490 events::rtc::notification::{
491 NotificationType, RtcNotificationEventContent, SyncRtcNotificationEvent,
492 },
493 serde::Raw,
494 };
495 use serde_json::json;
496
497 use super::LatestEvent;
498 #[cfg(feature = "e2e-encryption")]
499 use super::{PossibleLatestEvent, is_suitable_for_latest_event};
500
501 #[cfg(feature = "e2e-encryption")]
502 #[test]
503 fn test_room_messages_are_suitable() {
504 let event = AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
505 SyncRoomMessageEvent::Original(OriginalSyncMessageLikeEvent {
506 content: RoomMessageEventContent::new(MessageType::Image(
507 ImageMessageEventContent::new(
508 "".to_owned(),
509 MediaSource::Plain(owned_mxc_uri!("mxc://example.com/1")),
510 ),
511 )),
512 event_id: owned_event_id!("$1"),
513 sender: owned_user_id!("@a:b.c"),
514 origin_server_ts: MilliSecondsSinceUnixEpoch(UInt::new(2123).unwrap()),
515 unsigned: MessageLikeUnsigned::new(),
516 }),
517 ));
518 assert_let!(
519 PossibleLatestEvent::YesRoomMessage(SyncMessageLikeEvent::Original(m)) =
520 is_suitable_for_latest_event(&event, None)
521 );
522
523 assert_eq!(m.content.msgtype.msgtype(), "m.image");
524 }
525
526 #[cfg(feature = "e2e-encryption")]
527 #[test]
528 fn test_polls_are_suitable() {
529 let event = AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::UnstablePollStart(
530 SyncUnstablePollStartEvent::Original(OriginalSyncMessageLikeEvent {
531 content: NewUnstablePollStartEventContent::new(UnstablePollStartContentBlock::new(
532 "do you like rust?",
533 vec![UnstablePollAnswer::new("id", "yes")].try_into().unwrap(),
534 ))
535 .into(),
536 event_id: owned_event_id!("$1"),
537 sender: owned_user_id!("@a:b.c"),
538 origin_server_ts: MilliSecondsSinceUnixEpoch(UInt::new(2123).unwrap()),
539 unsigned: MessageLikeUnsigned::new(),
540 }),
541 ));
542 assert_let!(
543 PossibleLatestEvent::YesPoll(SyncMessageLikeEvent::Original(m)) =
544 is_suitable_for_latest_event(&event, None)
545 );
546
547 assert_eq!(m.content.poll_start().question.text, "do you like rust?");
548 }
549
550 #[cfg(feature = "e2e-encryption")]
551 #[test]
552 fn test_call_invites_are_suitable() {
553 let event = AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::CallInvite(
554 SyncCallInviteEvent::Original(OriginalSyncMessageLikeEvent {
555 content: CallInviteEventContent::new(
556 "call_id".into(),
557 UInt::new(123).unwrap(),
558 SessionDescription::new("".into(), "".into()),
559 VoipVersionId::V1,
560 ),
561 event_id: owned_event_id!("$1"),
562 sender: owned_user_id!("@a:b.c"),
563 origin_server_ts: MilliSecondsSinceUnixEpoch(UInt::new(2123).unwrap()),
564 unsigned: MessageLikeUnsigned::new(),
565 }),
566 ));
567 assert_let!(
568 PossibleLatestEvent::YesCallInvite(SyncMessageLikeEvent::Original(_)) =
569 is_suitable_for_latest_event(&event, None)
570 );
571 }
572
573 #[cfg(feature = "e2e-encryption")]
574 #[test]
575 fn test_call_notifications_are_suitable() {
576 let event = AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RtcNotification(
577 SyncRtcNotificationEvent::Original(OriginalSyncMessageLikeEvent {
578 content: RtcNotificationEventContent::new(
579 MilliSecondsSinceUnixEpoch::now(),
580 Duration::new(30, 0),
581 NotificationType::Ring,
582 ),
583 event_id: owned_event_id!("$1"),
584 sender: owned_user_id!("@a:b.c"),
585 origin_server_ts: MilliSecondsSinceUnixEpoch(UInt::new(2123).unwrap()),
586 unsigned: MessageLikeUnsigned::new(),
587 }),
588 ));
589 assert_let!(
590 PossibleLatestEvent::YesRtcNotification(SyncMessageLikeEvent::Original(_)) =
591 is_suitable_for_latest_event(&event, None)
592 );
593 }
594
595 #[cfg(feature = "e2e-encryption")]
596 #[test]
597 fn test_stickers_are_suitable() {
598 let event = AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::Sticker(
599 SyncStickerEvent::Original(OriginalSyncMessageLikeEvent {
600 content: StickerEventContent::new(
601 "sticker!".to_owned(),
602 ImageInfo::new(),
603 owned_mxc_uri!("mxc://example.com/1"),
604 ),
605 event_id: owned_event_id!("$1"),
606 sender: owned_user_id!("@a:b.c"),
607 origin_server_ts: MilliSecondsSinceUnixEpoch(UInt::new(2123).unwrap()),
608 unsigned: MessageLikeUnsigned::new(),
609 }),
610 ));
611
612 assert_matches!(
613 is_suitable_for_latest_event(&event, None),
614 PossibleLatestEvent::YesSticker(SyncStickerEvent::Original(_))
615 );
616 }
617
618 #[cfg(feature = "e2e-encryption")]
619 #[test]
620 fn test_different_types_of_messagelike_are_unsuitable() {
621 let event =
622 AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::UnstablePollResponse(
623 SyncUnstablePollResponseEvent::Original(OriginalSyncMessageLikeEvent {
624 content: UnstablePollResponseEventContent::new(
625 vec![String::from("option1")],
626 owned_event_id!("$1"),
627 ),
628 event_id: owned_event_id!("$2"),
629 sender: owned_user_id!("@a:b.c"),
630 origin_server_ts: MilliSecondsSinceUnixEpoch(UInt::new(2123).unwrap()),
631 unsigned: MessageLikeUnsigned::new(),
632 }),
633 ));
634
635 assert_matches!(
636 is_suitable_for_latest_event(&event, None),
637 PossibleLatestEvent::NoUnsupportedMessageLikeType
638 );
639 }
640
641 #[cfg(feature = "e2e-encryption")]
642 #[test]
643 fn test_redacted_messages_are_suitable() {
644 let room_redaction_event = serde_json::from_value(json!({
646 "content": {},
647 "event_id": "$redaction",
648 "sender": "@x:y.za",
649 "origin_server_ts": 223543,
650 "unsigned": { "reason": "foo" }
651 }))
652 .unwrap();
653
654 let event = AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
655 SyncRoomMessageEvent::Redacted(RedactedSyncMessageLikeEvent {
656 content: RedactedRoomMessageEventContent::new(),
657 event_id: owned_event_id!("$1"),
658 sender: owned_user_id!("@a:b.c"),
659 origin_server_ts: MilliSecondsSinceUnixEpoch(UInt::new(2123).unwrap()),
660 unsigned: RedactedUnsigned::new(room_redaction_event),
661 }),
662 ));
663
664 assert_matches!(
665 is_suitable_for_latest_event(&event, None),
666 PossibleLatestEvent::YesRoomMessage(SyncMessageLikeEvent::Redacted(_))
667 );
668 }
669
670 #[cfg(feature = "e2e-encryption")]
671 #[test]
672 fn test_encrypted_messages_are_unsuitable() {
673 let event = AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted(
674 SyncRoomEncryptedEvent::Original(OriginalSyncMessageLikeEvent {
675 content: RoomEncryptedEventContent::new(
676 EncryptedEventScheme::OlmV1Curve25519AesSha2(
677 OlmV1Curve25519AesSha2Content::new(BTreeMap::new(), "".to_owned()),
678 ),
679 None,
680 ),
681 event_id: owned_event_id!("$1"),
682 sender: owned_user_id!("@a:b.c"),
683 origin_server_ts: MilliSecondsSinceUnixEpoch(UInt::new(2123).unwrap()),
684 unsigned: MessageLikeUnsigned::new(),
685 }),
686 ));
687
688 assert_matches!(
689 is_suitable_for_latest_event(&event, None),
690 PossibleLatestEvent::NoEncrypted
691 );
692 }
693
694 #[cfg(feature = "e2e-encryption")]
695 #[test]
696 fn test_state_events_are_unsuitable() {
697 let event = AnySyncTimelineEvent::State(AnySyncStateEvent::RoomTopic(
698 SyncRoomTopicEvent::Original(OriginalSyncStateEvent {
699 content: RoomTopicEventContent::new("".to_owned()),
700 event_id: owned_event_id!("$1"),
701 sender: owned_user_id!("@a:b.c"),
702 origin_server_ts: MilliSecondsSinceUnixEpoch(UInt::new(2123).unwrap()),
703 unsigned: StateUnsigned::new(),
704 state_key: EmptyStateKey,
705 }),
706 ));
707
708 assert_matches!(
709 is_suitable_for_latest_event(&event, None),
710 PossibleLatestEvent::NoUnsupportedEventType
711 );
712 }
713
714 #[cfg(feature = "e2e-encryption")]
715 #[test]
716 fn test_replacement_events_are_unsuitable() {
717 let mut event_content = RoomMessageEventContent::text_plain("Bye bye, world!");
718 event_content.relates_to = Some(Relation::Replacement(Replacement::new(
719 owned_event_id!("$1"),
720 RoomMessageEventContent::text_plain("Hello, world!").into(),
721 )));
722
723 let event = AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
724 SyncRoomMessageEvent::Original(OriginalSyncMessageLikeEvent {
725 content: event_content,
726 event_id: owned_event_id!("$2"),
727 sender: owned_user_id!("@a:b.c"),
728 origin_server_ts: MilliSecondsSinceUnixEpoch(UInt::new(2123).unwrap()),
729 unsigned: MessageLikeUnsigned::new(),
730 }),
731 ));
732
733 assert_matches!(
734 is_suitable_for_latest_event(&event, None),
735 PossibleLatestEvent::NoUnsupportedMessageLikeType
736 );
737 }
738
739 #[cfg(feature = "e2e-encryption")]
740 #[test]
741 fn test_verification_requests_are_unsuitable() {
742 use ruma::{device_id, events::room::message::KeyVerificationRequestEventContent, user_id};
743
744 let event = AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
745 SyncRoomMessageEvent::Original(OriginalSyncMessageLikeEvent {
746 content: RoomMessageEventContent::new(MessageType::VerificationRequest(
747 KeyVerificationRequestEventContent::new(
748 "body".to_owned(),
749 vec![],
750 device_id!("device_id").to_owned(),
751 user_id!("@user_id:example.com").to_owned(),
752 ),
753 )),
754 event_id: owned_event_id!("$1"),
755 sender: owned_user_id!("@a:b.c"),
756 origin_server_ts: MilliSecondsSinceUnixEpoch(UInt::new(123).unwrap()),
757 unsigned: MessageLikeUnsigned::new(),
758 }),
759 ));
760
761 assert_let!(
762 PossibleLatestEvent::NoUnsupportedMessageLikeType =
763 is_suitable_for_latest_event(&event, None)
764 );
765 }
766
767 #[test]
768 fn test_deserialize_latest_event() {
769 #[derive(Debug, serde::Serialize, serde::Deserialize)]
770 struct TestStruct {
771 latest_event: LatestEvent,
772 }
773
774 let event = TimelineEvent::from_plaintext(
775 Raw::from_json_string(json!({ "event_id": "$1" }).to_string()).unwrap(),
776 );
777
778 let initial = TestStruct {
779 latest_event: LatestEvent {
780 event: event.clone(),
781 sender_profile: None,
782 sender_name_is_ambiguous: None,
783 },
784 };
785
786 let serialized = serde_json::to_value(&initial).unwrap();
788 assert_eq!(
789 serialized,
790 json!({
791 "latest_event": {
792 "event": {
793 "kind": {
794 "PlainText": {
795 "event": {
796 "event_id": "$1"
797 }
798 }
799 },
800 "thread_summary": "None",
801 "timestamp": null,
802 }
803 }
804 })
805 );
806
807 let deserialized: TestStruct = serde_json::from_value(serialized).unwrap();
809 assert_eq!(deserialized.latest_event.event().event_id().unwrap(), "$1");
810 assert!(deserialized.latest_event.sender_profile.is_none());
811 assert!(deserialized.latest_event.sender_name_is_ambiguous.is_none());
812
813 let serialized = json!({
815 "latest_event": {
816 "event": {
817 "encryption_info": null,
818 "event": {
819 "event_id": "$1"
820 }
821 },
822 }
823 });
824
825 let deserialized: TestStruct = serde_json::from_value(serialized).unwrap();
826 assert_eq!(deserialized.latest_event.event().event_id().unwrap(), "$1");
827 assert!(deserialized.latest_event.sender_profile.is_none());
828 assert!(deserialized.latest_event.sender_name_is_ambiguous.is_none());
829
830 let serialized = json!({
832 "latest_event": event
833 });
834
835 let deserialized: TestStruct = serde_json::from_value(serialized).unwrap();
836 assert_eq!(deserialized.latest_event.event().event_id().unwrap(), "$1");
837 assert!(deserialized.latest_event.sender_profile.is_none());
838 assert!(deserialized.latest_event.sender_name_is_ambiguous.is_none());
839 }
840}