matrix_sdk_base/
latest_event.rs

1//! Utilities for working with events to decide whether they are suitable for
2//! use as a [crate::Room::latest_event].
3
4use 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/// A latest event value!
28#[derive(Debug, Default, Clone, Serialize, Deserialize)]
29pub enum LatestEventValue {
30    /// No value has been computed yet, or no candidate value was found.
31    #[default]
32    None,
33
34    /// The latest event represents a remote event.
35    Remote(RemoteLatestEventValue),
36
37    /// The latest event represents a local event that is sending.
38    LocalIsSending(LocalLatestEventValue),
39
40    /// The latest event represents a local event that cannot be sent, either
41    /// because a previous local event, or this local event cannot be sent.
42    LocalCannotBeSent(LocalLatestEventValue),
43}
44
45impl LatestEventValue {
46    /// Get the timestamp of the [`LatestEventValue`].
47    ///
48    /// If it's [`None`], it returns `None`. If it's [`Remote`], it returns the
49    /// [`TimelineEvent::timestamp`]. If it's [`LocalIsSending`] or
50    /// [`LocalCannotBeSent`], it returns the
51    /// [`LocalLatestEventValue::timestamp`] value.
52    ///
53    /// [`None`]: LatestEventValue::None
54    /// [`Remote`]: LatestEventValue::Remote
55    /// [`LocalIsSending`]: LatestEventValue::LocalIsSending
56    /// [`LocalCannotBeSent`]: LatestEventValue::LocalCannotBeSent
57    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    /// Check whether the [`LatestEventValue`] represents a local value or not,
67    /// i.e. it is [`LocalIsSending`] or [`LocalCannotBeSent`].
68    ///
69    /// [`LocalIsSending`]: LatestEventValue::LocalIsSending
70    /// [`LocalCannotBeSent`]: LatestEventValue::LocalCannotBeSent
71    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
79/// Represents the value for [`LatestEventValue::Remote`].
80pub type RemoteLatestEventValue = TimelineEvent;
81
82/// Represents the value for [`LatestEventValue::LocalIsSending`] and
83/// [`LatestEventValue::LocalCannotBeSent`].
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct LocalLatestEventValue {
86    /// The time where the event has been created (by this module).
87    pub timestamp: MilliSecondsSinceUnixEpoch,
88
89    /// The content of the local event.
90    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::new(&AnyMessageLikeEventContent::RoomMessage(
138                RoomMessageEventContent::text_plain("raclette"),
139            ))
140            .unwrap(),
141        });
142
143        assert_eq!(value.timestamp(), Some(MilliSecondsSinceUnixEpoch(uint!(42))));
144    }
145
146    #[test]
147    fn test_timestamp_with_local_cannot_be_sent() {
148        let value = LatestEventValue::LocalCannotBeSent(LocalLatestEventValue {
149            timestamp: MilliSecondsSinceUnixEpoch(uint!(42)),
150            content: SerializableEventContent::new(&AnyMessageLikeEventContent::RoomMessage(
151                RoomMessageEventContent::text_plain("raclette"),
152            ))
153            .unwrap(),
154        });
155
156        assert_eq!(value.timestamp(), Some(MilliSecondsSinceUnixEpoch(uint!(42))));
157    }
158}
159
160/// Represents a decision about whether an event could be stored as the latest
161/// event in a room. Variants starting with Yes indicate that this message could
162/// be stored, and provide the inner event information, and those starting with
163/// a No indicate that it could not, and give a reason.
164#[cfg(feature = "e2e-encryption")]
165#[derive(Debug)]
166pub enum PossibleLatestEvent<'a> {
167    /// This message is suitable - it is an m.room.message
168    YesRoomMessage(&'a SyncRoomMessageEvent),
169    /// This message is suitable - it is a sticker
170    YesSticker(&'a SyncStickerEvent),
171    /// This message is suitable - it is a poll
172    YesPoll(&'a SyncUnstablePollStartEvent),
173
174    /// This message is suitable - it is a call invite
175    YesCallInvite(&'a SyncCallInviteEvent),
176
177    /// This message is suitable - it's a call notification
178    YesRtcNotification(&'a SyncRtcNotificationEvent),
179
180    /// This state event is suitable - it's a knock membership change
181    /// that can be handled by the current user.
182    YesKnockedStateEvent(&'a SyncRoomMemberEvent),
183
184    // Later: YesState(),
185    // Later: YesReaction(),
186    /// Not suitable - it's a state event
187    NoUnsupportedEventType,
188    /// Not suitable - it's not a m.room.message or an edit/replacement
189    NoUnsupportedMessageLikeType,
190    /// Not suitable - it's encrypted
191    NoEncrypted,
192}
193
194/// Decide whether an event could be stored as the latest event in a room.
195/// Returns a LatestEvent representing our decision.
196#[cfg(feature = "e2e-encryption")]
197pub fn is_suitable_for_latest_event<'a>(
198    event: &'a AnySyncTimelineEvent,
199    power_levels_info: Option<(&'a UserId, &'a RoomPowerLevels)>,
200) -> PossibleLatestEvent<'a> {
201    match event {
202        // Suitable - we have an m.room.message that was not redacted or edited
203        AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(message)) => {
204            if let Some(original_message) = message.as_original() {
205                // Don't show incoming verification requests
206                if let MessageType::VerificationRequest(_) = original_message.content.msgtype {
207                    return PossibleLatestEvent::NoUnsupportedMessageLikeType;
208                }
209
210                // Check if this is a replacement for another message. If it is, ignore it
211                let is_replacement =
212                    original_message.content.relates_to.as_ref().is_some_and(|relates_to| {
213                        if let Some(relation_type) = relates_to.rel_type() {
214                            relation_type == RelationType::Replacement
215                        } else {
216                            false
217                        }
218                    });
219
220                if is_replacement {
221                    PossibleLatestEvent::NoUnsupportedMessageLikeType
222                } else {
223                    PossibleLatestEvent::YesRoomMessage(message)
224                }
225            } else {
226                PossibleLatestEvent::YesRoomMessage(message)
227            }
228        }
229
230        AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::UnstablePollStart(poll)) => {
231            PossibleLatestEvent::YesPoll(poll)
232        }
233
234        AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::CallInvite(invite)) => {
235            PossibleLatestEvent::YesCallInvite(invite)
236        }
237
238        AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RtcNotification(notify)) => {
239            PossibleLatestEvent::YesRtcNotification(notify)
240        }
241
242        AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::Sticker(sticker)) => {
243            PossibleLatestEvent::YesSticker(sticker)
244        }
245
246        // Encrypted events are not suitable
247        AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted(_)) => {
248            PossibleLatestEvent::NoEncrypted
249        }
250
251        // Later, if we support reactions:
252        // AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::Reaction(_))
253
254        // MessageLike, but not one of the types we want to show in message previews, so not
255        // suitable
256        AnySyncTimelineEvent::MessageLike(_) => PossibleLatestEvent::NoUnsupportedMessageLikeType,
257
258        // We don't currently support most state events
259        AnySyncTimelineEvent::State(state) => {
260            // But we make an exception for knocked state events *if* the current user
261            // can either accept or decline them
262            if let AnySyncStateEvent::RoomMember(member) = state
263                && matches!(member.membership(), MembershipState::Knock)
264            {
265                let can_accept_or_decline_knocks = match power_levels_info {
266                    Some((own_user_id, room_power_levels)) => {
267                        room_power_levels.user_can_invite(own_user_id)
268                            || room_power_levels.user_can_kick(own_user_id)
269                    }
270                    _ => false,
271                };
272
273                // The current user can act on the knock changes, so they should be
274                // displayed
275                if can_accept_or_decline_knocks {
276                    return PossibleLatestEvent::YesKnockedStateEvent(member);
277                }
278            }
279            PossibleLatestEvent::NoUnsupportedEventType
280        }
281    }
282}
283
284/// Represent all information required to represent a latest event in an
285/// efficient way.
286///
287/// ## Implementation details
288///
289/// Serialization and deserialization should be a breeze, but we introduced a
290/// change in the format without realizing, and without a migration. Ideally,
291/// this would be handled with a `serde(untagged)` enum that would be used to
292/// deserialize in either the older format, or to the new format. Unfortunately,
293/// untagged enums don't play nicely with `serde_json::value::RawValue`,
294/// so we did have to implement a custom `Deserialize` for `LatestEvent`, that
295/// first deserializes the thing as a raw JSON value, and then deserializes the
296/// JSON string as one variant or the other.
297///
298/// Because of that, `LatestEvent` should only be (de)serialized using
299/// serde_json.
300///
301/// Whenever you introduce new fields to `LatestEvent` make sure to add them to
302/// `SerializedLatestEvent` too.
303#[derive(Clone, Debug, Serialize)]
304pub struct LatestEvent {
305    /// The actual event.
306    event: TimelineEvent,
307
308    /// The member profile of the event' sender.
309    #[serde(skip_serializing_if = "Option::is_none")]
310    sender_profile: Option<MinimalRoomMemberEvent>,
311
312    /// The name of the event' sender is ambiguous.
313    #[serde(skip_serializing_if = "Option::is_none")]
314    sender_name_is_ambiguous: Option<bool>,
315}
316
317#[derive(Deserialize)]
318struct SerializedLatestEvent {
319    /// The actual event.
320    event: TimelineEvent,
321
322    /// The member profile of the event' sender.
323    #[serde(skip_serializing_if = "Option::is_none")]
324    sender_profile: Option<MinimalRoomMemberEvent>,
325
326    /// The name of the event' sender is ambiguous.
327    #[serde(skip_serializing_if = "Option::is_none")]
328    sender_name_is_ambiguous: Option<bool>,
329}
330
331// Note: this deserialize implementation for LatestEvent will *only* work with
332// serde_json.
333impl<'de> Deserialize<'de> for LatestEvent {
334    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
335    where
336        D: serde::Deserializer<'de>,
337    {
338        let raw: Box<serde_json::value::RawValue> = Box::deserialize(deserializer)?;
339
340        let mut variant_errors = Vec::new();
341
342        match serde_json::from_str::<SerializedLatestEvent>(raw.get()) {
343            Ok(value) => {
344                return Ok(LatestEvent {
345                    event: value.event,
346                    sender_profile: value.sender_profile,
347                    sender_name_is_ambiguous: value.sender_name_is_ambiguous,
348                });
349            }
350            Err(err) => variant_errors.push(err),
351        }
352
353        match serde_json::from_str::<TimelineEvent>(raw.get()) {
354            Ok(value) => {
355                return Ok(LatestEvent {
356                    event: value,
357                    sender_profile: None,
358                    sender_name_is_ambiguous: None,
359                });
360            }
361            Err(err) => variant_errors.push(err),
362        }
363
364        Err(serde::de::Error::custom(format!(
365            "data did not match any variant of serialized LatestEvent (using serde_json). \
366             Observed errors: {variant_errors:?}"
367        )))
368    }
369}
370
371impl LatestEvent {
372    /// Create a new [`LatestEvent`] without the sender's profile.
373    pub fn new(event: TimelineEvent) -> Self {
374        Self { event, sender_profile: None, sender_name_is_ambiguous: None }
375    }
376
377    /// Create a new [`LatestEvent`] with maybe the sender's profile.
378    pub fn new_with_sender_details(
379        event: TimelineEvent,
380        sender_profile: Option<MinimalRoomMemberEvent>,
381        sender_name_is_ambiguous: Option<bool>,
382    ) -> Self {
383        Self { event, sender_profile, sender_name_is_ambiguous }
384    }
385
386    /// Transform [`Self`] into an event.
387    pub fn into_event(self) -> TimelineEvent {
388        self.event
389    }
390
391    /// Get a reference to the event.
392    pub fn event(&self) -> &TimelineEvent {
393        &self.event
394    }
395
396    /// Get a mutable reference to the event.
397    pub fn event_mut(&mut self) -> &mut TimelineEvent {
398        &mut self.event
399    }
400
401    /// Get the event ID.
402    pub fn event_id(&self) -> Option<OwnedEventId> {
403        self.event.event_id()
404    }
405
406    /// Check whether [`Self`] has a sender profile.
407    pub fn has_sender_profile(&self) -> bool {
408        self.sender_profile.is_some()
409    }
410
411    /// Return the sender's display name if it was known at the time [`Self`]
412    /// was built.
413    pub fn sender_display_name(&self) -> Option<&str> {
414        self.sender_profile.as_ref().and_then(|profile| {
415            profile.as_original().and_then(|event| event.content.displayname.as_deref())
416        })
417    }
418
419    /// Return `Some(true)` if the sender's name is ambiguous, `Some(false)` if
420    /// it isn't, `None` if ambiguity detection wasn't possible at the time
421    /// [`Self`] was built.
422    pub fn sender_name_ambiguous(&self) -> Option<bool> {
423        self.sender_name_is_ambiguous
424    }
425
426    /// Return the sender's avatar URL if it was known at the time [`Self`] was
427    /// built.
428    pub fn sender_avatar_url(&self) -> Option<&MxcUri> {
429        self.sender_profile.as_ref().and_then(|profile| {
430            profile.as_original().and_then(|event| event.content.avatar_url.as_deref())
431        })
432    }
433}
434
435#[cfg(test)]
436mod tests {
437    #[cfg(feature = "e2e-encryption")]
438    use std::collections::BTreeMap;
439    use std::time::Duration;
440
441    #[cfg(feature = "e2e-encryption")]
442    use assert_matches::assert_matches;
443    #[cfg(feature = "e2e-encryption")]
444    use assert_matches2::assert_let;
445    use matrix_sdk_common::deserialized_responses::TimelineEvent;
446    #[cfg(feature = "e2e-encryption")]
447    use matrix_sdk_test::event_factory::EventFactory;
448    #[cfg(feature = "e2e-encryption")]
449    use ruma::{
450        MilliSecondsSinceUnixEpoch, UInt, VoipVersionId,
451        events::{
452            SyncMessageLikeEvent,
453            call::{SessionDescription, invite::CallInviteEventContent},
454            poll::{
455                unstable_response::UnstablePollResponseEventContent,
456                unstable_start::{
457                    NewUnstablePollStartEventContent, UnstablePollAnswer,
458                    UnstablePollStartContentBlock,
459                },
460            },
461            relation::Replacement,
462            room::{
463                ImageInfo, MediaSource,
464                encrypted::{
465                    EncryptedEventScheme, OlmV1Curve25519AesSha2Content, RoomEncryptedEventContent,
466                },
467                message::{
468                    ImageMessageEventContent, MessageType, RedactedRoomMessageEventContent,
469                    Relation, RoomMessageEventContent,
470                },
471                topic::RoomTopicEventContent,
472            },
473            sticker::{StickerEventContent, SyncStickerEvent},
474        },
475        owned_event_id, owned_mxc_uri, user_id,
476    };
477    use ruma::{
478        events::rtc::notification::{NotificationType, RtcNotificationEventContent},
479        serde::Raw,
480    };
481    use serde_json::json;
482
483    use super::LatestEvent;
484    #[cfg(feature = "e2e-encryption")]
485    use super::{PossibleLatestEvent, is_suitable_for_latest_event};
486
487    #[cfg(feature = "e2e-encryption")]
488    #[test]
489    fn test_room_messages_are_suitable() {
490        let event = EventFactory::new()
491            .sender(user_id!("@a:b.c"))
492            .event(RoomMessageEventContent::new(MessageType::Image(ImageMessageEventContent::new(
493                "".to_owned(),
494                MediaSource::Plain(owned_mxc_uri!("mxc://example.com/1")),
495            ))))
496            .into();
497        assert_let!(
498            PossibleLatestEvent::YesRoomMessage(SyncMessageLikeEvent::Original(m)) =
499                is_suitable_for_latest_event(&event, None)
500        );
501
502        assert_eq!(m.content.msgtype.msgtype(), "m.image");
503    }
504
505    #[cfg(feature = "e2e-encryption")]
506    #[test]
507    fn test_polls_are_suitable() {
508        let event = EventFactory::new()
509            .sender(user_id!("@a:b.c"))
510            .event(NewUnstablePollStartEventContent::new(UnstablePollStartContentBlock::new(
511                "do you like rust?",
512                vec![UnstablePollAnswer::new("id", "yes")].try_into().unwrap(),
513            )))
514            .into();
515        assert_let!(
516            PossibleLatestEvent::YesPoll(SyncMessageLikeEvent::Original(m)) =
517                is_suitable_for_latest_event(&event, None)
518        );
519
520        assert_eq!(m.content.poll_start().question.text, "do you like rust?");
521    }
522
523    #[cfg(feature = "e2e-encryption")]
524    #[test]
525    fn test_call_invites_are_suitable() {
526        let event = EventFactory::new()
527            .sender(user_id!("@a:b.c"))
528            .event(CallInviteEventContent::new(
529                "call_id".into(),
530                UInt::new(123).unwrap(),
531                SessionDescription::new("".into(), "".into()),
532                VoipVersionId::V1,
533            ))
534            .into();
535        assert_let!(
536            PossibleLatestEvent::YesCallInvite(SyncMessageLikeEvent::Original(_)) =
537                is_suitable_for_latest_event(&event, None)
538        );
539    }
540
541    #[cfg(feature = "e2e-encryption")]
542    #[test]
543    fn test_call_notifications_are_suitable() {
544        let event = EventFactory::new()
545            .sender(user_id!("@a:b.c"))
546            .event(RtcNotificationEventContent::new(
547                MilliSecondsSinceUnixEpoch::now(),
548                Duration::new(30, 0),
549                NotificationType::Ring,
550            ))
551            .into();
552        assert_let!(
553            PossibleLatestEvent::YesRtcNotification(SyncMessageLikeEvent::Original(_)) =
554                is_suitable_for_latest_event(&event, None)
555        );
556    }
557
558    #[cfg(feature = "e2e-encryption")]
559    #[test]
560    fn test_stickers_are_suitable() {
561        let event = EventFactory::new()
562            .sender(user_id!("@a:b.c"))
563            .event(StickerEventContent::new(
564                "sticker!".to_owned(),
565                ImageInfo::new(),
566                owned_mxc_uri!("mxc://example.com/1"),
567            ))
568            .into();
569
570        assert_matches!(
571            is_suitable_for_latest_event(&event, None),
572            PossibleLatestEvent::YesSticker(SyncStickerEvent::Original(_))
573        );
574    }
575
576    #[cfg(feature = "e2e-encryption")]
577    #[test]
578    fn test_different_types_of_messagelike_are_unsuitable() {
579        let event = EventFactory::new()
580            .sender(user_id!("@a:b.c"))
581            .event(UnstablePollResponseEventContent::new(
582                vec![String::from("option1")],
583                owned_event_id!("$1"),
584            ))
585            .into();
586
587        assert_matches!(
588            is_suitable_for_latest_event(&event, None),
589            PossibleLatestEvent::NoUnsupportedMessageLikeType
590        );
591    }
592
593    #[cfg(feature = "e2e-encryption")]
594    #[test]
595    fn test_redacted_messages_are_suitable() {
596        let event = EventFactory::new()
597            .sender(user_id!("@a:b.c"))
598            .redacted(user_id!("@x:y.za"), RedactedRoomMessageEventContent::new())
599            .into();
600
601        assert_matches!(
602            is_suitable_for_latest_event(&event, None),
603            PossibleLatestEvent::YesRoomMessage(SyncMessageLikeEvent::Redacted(_))
604        );
605    }
606
607    #[cfg(feature = "e2e-encryption")]
608    #[test]
609    fn test_encrypted_messages_are_unsuitable() {
610        let event = EventFactory::new()
611            .sender(user_id!("@a:b.c"))
612            .event(RoomEncryptedEventContent::new(
613                EncryptedEventScheme::OlmV1Curve25519AesSha2(OlmV1Curve25519AesSha2Content::new(
614                    BTreeMap::new(),
615                    "".to_owned(),
616                )),
617                None,
618            ))
619            .into();
620
621        assert_matches!(
622            is_suitable_for_latest_event(&event, None),
623            PossibleLatestEvent::NoEncrypted
624        );
625    }
626
627    #[cfg(feature = "e2e-encryption")]
628    #[test]
629    fn test_state_events_are_unsuitable() {
630        let event = EventFactory::new()
631            .sender(user_id!("@a:b.c"))
632            .event(RoomTopicEventContent::new("".to_owned()))
633            .state_key("")
634            .into();
635
636        assert_matches!(
637            is_suitable_for_latest_event(&event, None),
638            PossibleLatestEvent::NoUnsupportedEventType
639        );
640    }
641
642    #[cfg(feature = "e2e-encryption")]
643    #[test]
644    fn test_replacement_events_are_unsuitable() {
645        let mut event_content = RoomMessageEventContent::text_plain("Bye bye, world!");
646        event_content.relates_to = Some(Relation::Replacement(Replacement::new(
647            owned_event_id!("$1"),
648            RoomMessageEventContent::text_plain("Hello, world!").into(),
649        )));
650
651        let event = EventFactory::new().sender(user_id!("@a:b.c")).event(event_content).into();
652
653        assert_matches!(
654            is_suitable_for_latest_event(&event, None),
655            PossibleLatestEvent::NoUnsupportedMessageLikeType
656        );
657    }
658
659    #[cfg(feature = "e2e-encryption")]
660    #[test]
661    fn test_verification_requests_are_unsuitable() {
662        use ruma::{device_id, events::room::message::KeyVerificationRequestEventContent, user_id};
663
664        let event = EventFactory::new()
665            .sender(user_id!("@a:b.c"))
666            .event(RoomMessageEventContent::new(MessageType::VerificationRequest(
667                KeyVerificationRequestEventContent::new(
668                    "body".to_owned(),
669                    vec![],
670                    device_id!("device_id").to_owned(),
671                    user_id!("@user_id:example.com").to_owned(),
672                ),
673            )))
674            .into();
675
676        assert_let!(
677            PossibleLatestEvent::NoUnsupportedMessageLikeType =
678                is_suitable_for_latest_event(&event, None)
679        );
680    }
681
682    #[test]
683    fn test_deserialize_latest_event() {
684        #[derive(Debug, serde::Serialize, serde::Deserialize)]
685        struct TestStruct {
686            latest_event: LatestEvent,
687        }
688
689        let event = TimelineEvent::from_plaintext(
690            Raw::from_json_string(json!({ "event_id": "$1" }).to_string()).unwrap(),
691        );
692
693        let initial = TestStruct {
694            latest_event: LatestEvent {
695                event: event.clone(),
696                sender_profile: None,
697                sender_name_is_ambiguous: None,
698            },
699        };
700
701        // When serialized, LatestEvent always uses the new format.
702        let serialized = serde_json::to_value(&initial).unwrap();
703        assert_eq!(
704            serialized,
705            json!({
706                "latest_event": {
707                    "event": {
708                        "kind": {
709                            "PlainText": {
710                                "event": {
711                                    "event_id": "$1"
712                                }
713                            }
714                        },
715                        "thread_summary": "None",
716                        "timestamp": null,
717                    }
718                }
719            })
720        );
721
722        // And it can be properly deserialized from the new format.
723        let deserialized: TestStruct = serde_json::from_value(serialized).unwrap();
724        assert_eq!(deserialized.latest_event.event().event_id().unwrap(), "$1");
725        assert!(deserialized.latest_event.sender_profile.is_none());
726        assert!(deserialized.latest_event.sender_name_is_ambiguous.is_none());
727
728        // The previous format can also be deserialized.
729        let serialized = json!({
730                "latest_event": {
731                    "event": {
732                        "encryption_info": null,
733                        "event": {
734                            "event_id": "$1"
735                        }
736                    },
737                }
738        });
739
740        let deserialized: TestStruct = serde_json::from_value(serialized).unwrap();
741        assert_eq!(deserialized.latest_event.event().event_id().unwrap(), "$1");
742        assert!(deserialized.latest_event.sender_profile.is_none());
743        assert!(deserialized.latest_event.sender_name_is_ambiguous.is_none());
744
745        // The even older format can also be deserialized.
746        let serialized = json!({
747            "latest_event": event
748        });
749
750        let deserialized: TestStruct = serde_json::from_value(serialized).unwrap();
751        assert_eq!(deserialized.latest_event.event().event_id().unwrap(), "$1");
752        assert!(deserialized.latest_event.sender_profile.is_none());
753        assert!(deserialized.latest_event.sender_name_is_ambiguous.is_none());
754    }
755}