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::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/// Represents a decision about whether an event could be stored as the latest
167/// event in a room. Variants starting with Yes indicate that this message could
168/// be stored, and provide the inner event information, and those starting with
169/// a No indicate that it could not, and give a reason.
170#[cfg(feature = "e2e-encryption")]
171#[derive(Debug)]
172pub enum PossibleLatestEvent<'a> {
173    /// This message is suitable - it is an m.room.message
174    YesRoomMessage(&'a SyncRoomMessageEvent),
175    /// This message is suitable - it is a sticker
176    YesSticker(&'a SyncStickerEvent),
177    /// This message is suitable - it is a poll
178    YesPoll(&'a SyncUnstablePollStartEvent),
179
180    /// This message is suitable - it is a call invite
181    YesCallInvite(&'a SyncCallInviteEvent),
182
183    /// This message is suitable - it's a call notification
184    YesRtcNotification(&'a SyncRtcNotificationEvent),
185
186    /// This state event is suitable - it's a knock membership change
187    /// that can be handled by the current user.
188    YesKnockedStateEvent(&'a SyncRoomMemberEvent),
189
190    // Later: YesState(),
191    // Later: YesReaction(),
192    /// Not suitable - it's a state event
193    NoUnsupportedEventType,
194    /// Not suitable - it's not a m.room.message or an edit/replacement
195    NoUnsupportedMessageLikeType,
196    /// Not suitable - it's encrypted
197    NoEncrypted,
198}
199
200/// Decide whether an event could be stored as the latest event in a room.
201/// Returns a LatestEvent representing our decision.
202#[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        // Suitable - we have an m.room.message that was not redacted or edited
209        AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(message)) => {
210            if let Some(original_message) = message.as_original() {
211                // Don't show incoming verification requests
212                if let MessageType::VerificationRequest(_) = original_message.content.msgtype {
213                    return PossibleLatestEvent::NoUnsupportedMessageLikeType;
214                }
215
216                // Check if this is a replacement for another message. If it is, ignore it
217                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        // Encrypted events are not suitable
253        AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted(_)) => {
254            PossibleLatestEvent::NoEncrypted
255        }
256
257        // Later, if we support reactions:
258        // AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::Reaction(_))
259
260        // MessageLike, but not one of the types we want to show in message previews, so not
261        // suitable
262        AnySyncTimelineEvent::MessageLike(_) => PossibleLatestEvent::NoUnsupportedMessageLikeType,
263
264        // We don't currently support most state events
265        AnySyncTimelineEvent::State(state) => {
266            // But we make an exception for knocked state events *if* the current user
267            // can either accept or decline them
268            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                // The current user can act on the knock changes, so they should be
280                // displayed
281                if can_accept_or_decline_knocks {
282                    return PossibleLatestEvent::YesKnockedStateEvent(member);
283                }
284            }
285            PossibleLatestEvent::NoUnsupportedEventType
286        }
287    }
288}
289
290/// Represent all information required to represent a latest event in an
291/// efficient way.
292///
293/// ## Implementation details
294///
295/// Serialization and deserialization should be a breeze, but we introduced a
296/// change in the format without realizing, and without a migration. Ideally,
297/// this would be handled with a `serde(untagged)` enum that would be used to
298/// deserialize in either the older format, or to the new format. Unfortunately,
299/// untagged enums don't play nicely with `serde_json::value::RawValue`,
300/// so we did have to implement a custom `Deserialize` for `LatestEvent`, that
301/// first deserializes the thing as a raw JSON value, and then deserializes the
302/// JSON string as one variant or the other.
303///
304/// Because of that, `LatestEvent` should only be (de)serialized using
305/// serde_json.
306///
307/// Whenever you introduce new fields to `LatestEvent` make sure to add them to
308/// `SerializedLatestEvent` too.
309#[derive(Clone, Debug, Serialize)]
310pub struct LatestEvent {
311    /// The actual event.
312    event: TimelineEvent,
313
314    /// The member profile of the event' sender.
315    #[serde(skip_serializing_if = "Option::is_none")]
316    sender_profile: Option<MinimalRoomMemberEvent>,
317
318    /// The name of the event' sender is ambiguous.
319    #[serde(skip_serializing_if = "Option::is_none")]
320    sender_name_is_ambiguous: Option<bool>,
321}
322
323#[derive(Deserialize)]
324struct SerializedLatestEvent {
325    /// The actual event.
326    event: TimelineEvent,
327
328    /// The member profile of the event' sender.
329    #[serde(skip_serializing_if = "Option::is_none")]
330    sender_profile: Option<MinimalRoomMemberEvent>,
331
332    /// The name of the event' sender is ambiguous.
333    #[serde(skip_serializing_if = "Option::is_none")]
334    sender_name_is_ambiguous: Option<bool>,
335}
336
337// Note: this deserialize implementation for LatestEvent will *only* work with
338// serde_json.
339impl<'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    /// Create a new [`LatestEvent`] without the sender's profile.
379    pub fn new(event: TimelineEvent) -> Self {
380        Self { event, sender_profile: None, sender_name_is_ambiguous: None }
381    }
382
383    /// Create a new [`LatestEvent`] with maybe the sender's profile.
384    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    /// Transform [`Self`] into an event.
393    pub fn into_event(self) -> TimelineEvent {
394        self.event
395    }
396
397    /// Get a reference to the event.
398    pub fn event(&self) -> &TimelineEvent {
399        &self.event
400    }
401
402    /// Get a mutable reference to the event.
403    pub fn event_mut(&mut self) -> &mut TimelineEvent {
404        &mut self.event
405    }
406
407    /// Get the event ID.
408    pub fn event_id(&self) -> Option<OwnedEventId> {
409        self.event.event_id()
410    }
411
412    /// Check whether [`Self`] has a sender profile.
413    pub fn has_sender_profile(&self) -> bool {
414        self.sender_profile.is_some()
415    }
416
417    /// Return the sender's display name if it was known at the time [`Self`]
418    /// was built.
419    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    /// Return `Some(true)` if the sender's name is ambiguous, `Some(false)` if
426    /// it isn't, `None` if ambiguity detection wasn't possible at the time
427    /// [`Self`] was built.
428    pub fn sender_name_ambiguous(&self) -> Option<bool> {
429        self.sender_name_is_ambiguous
430    }
431
432    /// Return the sender's avatar URL if it was known at the time [`Self`] was
433    /// built.
434    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 matrix_sdk_test::event_factory::EventFactory;
454    #[cfg(feature = "e2e-encryption")]
455    use ruma::{
456        MilliSecondsSinceUnixEpoch, UInt, VoipVersionId,
457        events::{
458            SyncMessageLikeEvent,
459            call::{SessionDescription, invite::CallInviteEventContent},
460            poll::{
461                unstable_response::UnstablePollResponseEventContent,
462                unstable_start::{
463                    NewUnstablePollStartEventContent, UnstablePollAnswer,
464                    UnstablePollStartContentBlock,
465                },
466            },
467            relation::Replacement,
468            room::{
469                ImageInfo, MediaSource,
470                encrypted::{
471                    EncryptedEventScheme, OlmV1Curve25519AesSha2Content, RoomEncryptedEventContent,
472                },
473                message::{
474                    ImageMessageEventContent, MessageType, RedactedRoomMessageEventContent,
475                    Relation, RoomMessageEventContent,
476                },
477                topic::RoomTopicEventContent,
478            },
479            sticker::{StickerEventContent, SyncStickerEvent},
480        },
481        owned_event_id, owned_mxc_uri, user_id,
482    };
483    use ruma::{
484        events::rtc::notification::{NotificationType, RtcNotificationEventContent},
485        serde::Raw,
486    };
487    use serde_json::json;
488
489    use super::LatestEvent;
490    #[cfg(feature = "e2e-encryption")]
491    use super::{PossibleLatestEvent, is_suitable_for_latest_event};
492
493    #[cfg(feature = "e2e-encryption")]
494    #[test]
495    fn test_room_messages_are_suitable() {
496        let event = EventFactory::new()
497            .sender(user_id!("@a:b.c"))
498            .event(RoomMessageEventContent::new(MessageType::Image(ImageMessageEventContent::new(
499                "".to_owned(),
500                MediaSource::Plain(owned_mxc_uri!("mxc://example.com/1")),
501            ))))
502            .into();
503        assert_let!(
504            PossibleLatestEvent::YesRoomMessage(SyncMessageLikeEvent::Original(m)) =
505                is_suitable_for_latest_event(&event, None)
506        );
507
508        assert_eq!(m.content.msgtype.msgtype(), "m.image");
509    }
510
511    #[cfg(feature = "e2e-encryption")]
512    #[test]
513    fn test_polls_are_suitable() {
514        let event = EventFactory::new()
515            .sender(user_id!("@a:b.c"))
516            .event(NewUnstablePollStartEventContent::new(UnstablePollStartContentBlock::new(
517                "do you like rust?",
518                vec![UnstablePollAnswer::new("id", "yes")].try_into().unwrap(),
519            )))
520            .into();
521        assert_let!(
522            PossibleLatestEvent::YesPoll(SyncMessageLikeEvent::Original(m)) =
523                is_suitable_for_latest_event(&event, None)
524        );
525
526        assert_eq!(m.content.poll_start().question.text, "do you like rust?");
527    }
528
529    #[cfg(feature = "e2e-encryption")]
530    #[test]
531    fn test_call_invites_are_suitable() {
532        let event = EventFactory::new()
533            .sender(user_id!("@a:b.c"))
534            .event(CallInviteEventContent::new(
535                "call_id".into(),
536                UInt::new(123).unwrap(),
537                SessionDescription::new("".into(), "".into()),
538                VoipVersionId::V1,
539            ))
540            .into();
541        assert_let!(
542            PossibleLatestEvent::YesCallInvite(SyncMessageLikeEvent::Original(_)) =
543                is_suitable_for_latest_event(&event, None)
544        );
545    }
546
547    #[cfg(feature = "e2e-encryption")]
548    #[test]
549    fn test_call_notifications_are_suitable() {
550        let event = EventFactory::new()
551            .sender(user_id!("@a:b.c"))
552            .event(RtcNotificationEventContent::new(
553                MilliSecondsSinceUnixEpoch::now(),
554                Duration::new(30, 0),
555                NotificationType::Ring,
556            ))
557            .into();
558        assert_let!(
559            PossibleLatestEvent::YesRtcNotification(SyncMessageLikeEvent::Original(_)) =
560                is_suitable_for_latest_event(&event, None)
561        );
562    }
563
564    #[cfg(feature = "e2e-encryption")]
565    #[test]
566    fn test_stickers_are_suitable() {
567        let event = EventFactory::new()
568            .sender(user_id!("@a:b.c"))
569            .event(StickerEventContent::new(
570                "sticker!".to_owned(),
571                ImageInfo::new(),
572                owned_mxc_uri!("mxc://example.com/1"),
573            ))
574            .into();
575
576        assert_matches!(
577            is_suitable_for_latest_event(&event, None),
578            PossibleLatestEvent::YesSticker(SyncStickerEvent::Original(_))
579        );
580    }
581
582    #[cfg(feature = "e2e-encryption")]
583    #[test]
584    fn test_different_types_of_messagelike_are_unsuitable() {
585        let event = EventFactory::new()
586            .sender(user_id!("@a:b.c"))
587            .event(UnstablePollResponseEventContent::new(
588                vec![String::from("option1")],
589                owned_event_id!("$1"),
590            ))
591            .into();
592
593        assert_matches!(
594            is_suitable_for_latest_event(&event, None),
595            PossibleLatestEvent::NoUnsupportedMessageLikeType
596        );
597    }
598
599    #[cfg(feature = "e2e-encryption")]
600    #[test]
601    fn test_redacted_messages_are_suitable() {
602        let event = EventFactory::new()
603            .sender(user_id!("@a:b.c"))
604            .redacted(user_id!("@x:y.za"), RedactedRoomMessageEventContent::new())
605            .into();
606
607        assert_matches!(
608            is_suitable_for_latest_event(&event, None),
609            PossibleLatestEvent::YesRoomMessage(SyncMessageLikeEvent::Redacted(_))
610        );
611    }
612
613    #[cfg(feature = "e2e-encryption")]
614    #[test]
615    fn test_encrypted_messages_are_unsuitable() {
616        let event = EventFactory::new()
617            .sender(user_id!("@a:b.c"))
618            .event(RoomEncryptedEventContent::new(
619                EncryptedEventScheme::OlmV1Curve25519AesSha2(OlmV1Curve25519AesSha2Content::new(
620                    BTreeMap::new(),
621                    "".to_owned(),
622                )),
623                None,
624            ))
625            .into();
626
627        assert_matches!(
628            is_suitable_for_latest_event(&event, None),
629            PossibleLatestEvent::NoEncrypted
630        );
631    }
632
633    #[cfg(feature = "e2e-encryption")]
634    #[test]
635    fn test_state_events_are_unsuitable() {
636        let event = EventFactory::new()
637            .sender(user_id!("@a:b.c"))
638            .event(RoomTopicEventContent::new("".to_owned()))
639            .state_key("")
640            .into();
641
642        assert_matches!(
643            is_suitable_for_latest_event(&event, None),
644            PossibleLatestEvent::NoUnsupportedEventType
645        );
646    }
647
648    #[cfg(feature = "e2e-encryption")]
649    #[test]
650    fn test_replacement_events_are_unsuitable() {
651        let mut event_content = RoomMessageEventContent::text_plain("Bye bye, world!");
652        event_content.relates_to = Some(Relation::Replacement(Replacement::new(
653            owned_event_id!("$1"),
654            RoomMessageEventContent::text_plain("Hello, world!").into(),
655        )));
656
657        let event = EventFactory::new().sender(user_id!("@a:b.c")).event(event_content).into();
658
659        assert_matches!(
660            is_suitable_for_latest_event(&event, None),
661            PossibleLatestEvent::NoUnsupportedMessageLikeType
662        );
663    }
664
665    #[cfg(feature = "e2e-encryption")]
666    #[test]
667    fn test_verification_requests_are_unsuitable() {
668        use ruma::{device_id, events::room::message::KeyVerificationRequestEventContent, user_id};
669
670        let event = EventFactory::new()
671            .sender(user_id!("@a:b.c"))
672            .event(RoomMessageEventContent::new(MessageType::VerificationRequest(
673                KeyVerificationRequestEventContent::new(
674                    "body".to_owned(),
675                    vec![],
676                    device_id!("device_id").to_owned(),
677                    user_id!("@user_id:example.com").to_owned(),
678                ),
679            )))
680            .into();
681
682        assert_let!(
683            PossibleLatestEvent::NoUnsupportedMessageLikeType =
684                is_suitable_for_latest_event(&event, None)
685        );
686    }
687
688    #[test]
689    fn test_deserialize_latest_event() {
690        #[derive(Debug, serde::Serialize, serde::Deserialize)]
691        struct TestStruct {
692            latest_event: LatestEvent,
693        }
694
695        let event = TimelineEvent::from_plaintext(
696            Raw::from_json_string(json!({ "event_id": "$1" }).to_string()).unwrap(),
697        );
698
699        let initial = TestStruct {
700            latest_event: LatestEvent {
701                event: event.clone(),
702                sender_profile: None,
703                sender_name_is_ambiguous: None,
704            },
705        };
706
707        // When serialized, LatestEvent always uses the new format.
708        let serialized = serde_json::to_value(&initial).unwrap();
709        assert_eq!(
710            serialized,
711            json!({
712                "latest_event": {
713                    "event": {
714                        "kind": {
715                            "PlainText": {
716                                "event": {
717                                    "event_id": "$1"
718                                }
719                            }
720                        },
721                        "thread_summary": "None",
722                        "timestamp": null,
723                    }
724                }
725            })
726        );
727
728        // And it can be properly deserialized from the new format.
729        let deserialized: TestStruct = serde_json::from_value(serialized).unwrap();
730        assert_eq!(deserialized.latest_event.event().event_id().unwrap(), "$1");
731        assert!(deserialized.latest_event.sender_profile.is_none());
732        assert!(deserialized.latest_event.sender_name_is_ambiguous.is_none());
733
734        // The previous format can also be deserialized.
735        let serialized = json!({
736                "latest_event": {
737                    "event": {
738                        "encryption_info": null,
739                        "event": {
740                            "event_id": "$1"
741                        }
742                    },
743                }
744        });
745
746        let deserialized: TestStruct = serde_json::from_value(serialized).unwrap();
747        assert_eq!(deserialized.latest_event.event().event_id().unwrap(), "$1");
748        assert!(deserialized.latest_event.sender_profile.is_none());
749        assert!(deserialized.latest_event.sender_name_is_ambiguous.is_none());
750
751        // The even older format can also be deserialized.
752        let serialized = json!({
753            "latest_event": event
754        });
755
756        let deserialized: TestStruct = serde_json::from_value(serialized).unwrap();
757        assert_eq!(deserialized.latest_event.event().event_id().unwrap(), "$1");
758        assert!(deserialized.latest_event.sender_profile.is_none());
759        assert!(deserialized.latest_event.sender_name_is_ambiguous.is_none());
760    }
761}