matrix_sdk_base/
read_receipts.rs

1// Copyright 2023 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! # Client-side read receipts computation
16//!
17//! While Matrix servers have the ability to provide basic information about the
18//! unread status of rooms, via [`crate::sync::UnreadNotificationsCount`], it's
19//! not reliable for encrypted rooms. Indeed, the server doesn't have access to
20//! the content of encrypted events, so it can only makes guesses when
21//! estimating unread and highlight counts.
22//!
23//! Instead, this module provides facilities to compute the number of unread
24//! messages, unread notifications and unread highlights in a room.
25//!
26//! Counting unread messages is performed by looking at the latest receipt of
27//! the current user, and inferring which events are following it, according to
28//! the sync ordering.
29//!
30//! For notifications and highlights to be precisely accounted for, we also need
31//! to pay attention to the user's notification settings. Fortunately, this is
32//! also something we need to for notifications, so we can reuse this code.
33//!
34//! Of course, not all events are created equal, and some are less interesting
35//! than others, and shouldn't cause a room to be marked unread. This module's
36//! `marks_as_unread` function shows the opiniated set of rules that will filter
37//! out uninterested events.
38//!
39//! The only `pub(crate)` method in that module is `compute_unread_counts`,
40//! which updates the `RoomInfo` in place according to the new counts.
41//!
42//! ## Implementation details: How to get the latest receipt?
43//!
44//! ### Preliminary context
45//!
46//! We do have an unbounded, in-memory cache for sync events, as part of sliding
47//! sync. It's reset as soon as we get a "limited" (gappy) sync for a room. Not
48//! as ideal as an on-disk timeline, but it's sufficient to do some interesting
49//! computations already.
50//!
51//! ### How-to
52//!
53//! When we call `compute_unread_counts`, that's for one of two reasons (and
54//! maybe both at once, or maybe none at all):
55//! - we received a new receipt
56//! - new events came in.
57//!
58//! A read receipt is considered _active_ if it's been received from sync
59//! *and* it matches a known event in the in-memory sync events cache.
60//!
61//! The *latest active* receipt is the one that's active, with the latest order
62//! (according to sync ordering, aka position in the sync cache).
63//!
64//! The problem of keeping a precise read count is thus equivalent to finding
65//! the latest active receipt, and counting interesting events after it (in the
66//! sync ordering).
67//!
68//! When we get new events, we'll incorporate them into an inverse mapping of
69//! event id -> sync order (`event_id_to_pos`). This gives us a simple way to
70//! select a "better" active receipt, using the `ReceiptSelector`. An event that
71//! has a read receipt can be passed to `ReceiptSelector::try_select_later`,
72//! which compares the order of the current best active, to that of the new
73//! event, and records the better one, if applicable.
74//!
75//! When we receive a new receipt event in
76//! `ReceiptSelector::handle_new_receipt`, if we find a {public|private}
77//! {main-threaded|unthreaded} receipt attached to an event, there are two
78//! possibilities:
79//! - we knew the event, so we can immediately try to select it as a better
80//!   event with `try_select_later`,
81//! - or we don't, which may mean the receipt refers to a past event we lost
82//!   track of (because of a restart of the app — remember the cache is mostly
83//!   memory-only, and a few items on disk), or the receipt refers to a future
84//!   event. To cover for the latter possibility, we stash the receipt and mark
85//!   it as pending (we only keep a limited number of pending read receipts
86//!   using a `RingBuffer`).
87//!
88//! That means that when we receive new events, we'll check if their id matches
89//! one of the pending receipts in `handle_pending_receipts`; if so, we can
90//! remove it from the pending set, and try to consider it a better receipt with
91//! `try_select_later`. If not, it's still pending, until it'll be forgotten or
92//! matched.
93//!
94//! Once we have a new *better active receipt*, we'll save it in the
95//! `RoomReadReceipt` data (stored in `RoomInfo`), and we'll compute the counts,
96//! starting from the event the better active receipt was referring to.
97//!
98//! If we *don't* have a better active receipt, that means that all the events
99//! received in that sync batch aren't referred to by a known read receipt,
100//! _and_ we didn't get a new better receipt that matched known events. In that
101//! case, we can just consider that all the events are new, and count them as
102//! such.
103//!
104//! ### Edge cases
105//!
106//! - `compute_unread_counts` is called after receiving a sliding sync response,
107//!   at a time where we haven't tried to "reconcile" the cached timeline items
108//!   with the new ones. The only kind of reconciliation we'd do anyways is
109//!   clearing the timeline if it was limited, which equates to having common
110//!   events ids in both sets. As a matter of fact, we have to manually handle
111//!   this edge case here. I hope that having an event database will help avoid
112//!   this kind of workaround here later.
113//! - In addition to that, and as noted in the timeline code, it seems that
114//!   sliding sync could return the same event multiple times in a sync
115//!   timeline, leading to incorrect results. We have to take that into account
116//!   by resetting the read counts *every* time we see an event that was the
117//!   target of the latest active read receipt.
118#![allow(dead_code)] // too many different build configurations, I give up
119
120use std::{
121    collections::{BTreeMap, BTreeSet},
122    num::NonZeroUsize,
123};
124
125use matrix_sdk_common::{
126    deserialized_responses::TimelineEvent, ring_buffer::RingBuffer,
127    serde_helpers::extract_thread_root,
128};
129use ruma::{
130    events::{
131        poll::{start::PollStartEventContent, unstable_start::UnstablePollStartEventContent},
132        receipt::{ReceiptEventContent, ReceiptThread, ReceiptType},
133        room::message::Relation,
134        AnySyncMessageLikeEvent, AnySyncTimelineEvent, OriginalSyncMessageLikeEvent,
135        SyncMessageLikeEvent,
136    },
137    serde::Raw,
138    EventId, OwnedEventId, OwnedUserId, RoomId, UserId,
139};
140use serde::{Deserialize, Serialize};
141use tracing::{debug, instrument, trace, warn};
142
143use crate::ThreadingSupport;
144
145#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
146struct LatestReadReceipt {
147    /// The id of the event the read receipt is referring to. (Not the read
148    /// receipt event id.)
149    event_id: OwnedEventId,
150}
151
152/// Public data about read receipts collected during processing of that room.
153///
154/// Remember that each time a field of `RoomReadReceipts` is updated in
155/// `compute_unread_counts`, this function must return true!
156#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
157pub struct RoomReadReceipts {
158    /// Does the room have unread messages?
159    pub num_unread: u64,
160
161    /// Does the room have unread events that should notify?
162    pub num_notifications: u64,
163
164    /// Does the room have messages causing highlights for the users? (aka
165    /// mentions)
166    pub num_mentions: u64,
167
168    /// The latest read receipt (main-threaded or unthreaded) known for the
169    /// room.
170    #[serde(default)]
171    latest_active: Option<LatestReadReceipt>,
172
173    /// Read receipts that haven't been matched to their event.
174    ///
175    /// This might mean that the read receipt is in the past further than we
176    /// recall (i.e. before the first event we've ever cached), or in the
177    /// future (i.e. the event is lagging behind because of federation).
178    ///
179    /// Note: this contains event ids of the event *targets* of the receipts,
180    /// not the event ids of the receipt events themselves.
181    #[serde(default = "new_nonempty_ring_buffer")]
182    pending: RingBuffer<OwnedEventId>,
183}
184
185impl Default for RoomReadReceipts {
186    fn default() -> Self {
187        Self {
188            num_unread: Default::default(),
189            num_notifications: Default::default(),
190            num_mentions: Default::default(),
191            latest_active: Default::default(),
192            pending: new_nonempty_ring_buffer(),
193        }
194    }
195}
196
197fn new_nonempty_ring_buffer() -> RingBuffer<OwnedEventId> {
198    // 10 pending read receipts per room should be enough for everyone.
199    // SAFETY: `unwrap` is safe because 10 is not zero.
200    RingBuffer::new(NonZeroUsize::new(10).unwrap())
201}
202
203impl RoomReadReceipts {
204    /// Update the [`RoomReadReceipts`] unread counts according to the new
205    /// event.
206    ///
207    /// Returns whether a new event triggered a new unread/notification/mention.
208    #[inline(always)]
209    fn process_event(
210        &mut self,
211        event: &TimelineEvent,
212        user_id: &UserId,
213        threading_support: ThreadingSupport,
214    ) {
215        if matches!(threading_support, ThreadingSupport::Enabled)
216            && extract_thread_root(event.raw()).is_some()
217        {
218            return;
219        }
220
221        if marks_as_unread(event.raw(), user_id) {
222            self.num_unread += 1;
223        }
224
225        let mut has_notify = false;
226        let mut has_mention = false;
227
228        let Some(actions) = event.push_actions() else {
229            return;
230        };
231
232        for action in actions.iter() {
233            if !has_notify && action.should_notify() {
234                self.num_notifications += 1;
235                has_notify = true;
236            }
237            if !has_mention && action.is_highlight() {
238                self.num_mentions += 1;
239                has_mention = true;
240            }
241        }
242    }
243
244    #[inline(always)]
245    fn reset(&mut self) {
246        self.num_unread = 0;
247        self.num_notifications = 0;
248        self.num_mentions = 0;
249    }
250
251    /// Try to find the event to which the receipt attaches to, and if found,
252    /// will update the notification count in the room.
253    #[instrument(skip_all)]
254    fn find_and_process_events<'a>(
255        &mut self,
256        receipt_event_id: &EventId,
257        user_id: &UserId,
258        events: impl IntoIterator<Item = &'a TimelineEvent>,
259        threading_support: ThreadingSupport,
260    ) -> bool {
261        let mut counting_receipts = false;
262
263        for event in events {
264            // Sliding sync sometimes sends the same event multiple times, so it can be at
265            // the beginning and end of a batch, for instance. In that case, just reset
266            // every time we see the event matching the receipt.
267            if let Some(event_id) = event.event_id() {
268                if event_id == receipt_event_id {
269                    // Bingo! Switch over to the counting state, after resetting the
270                    // previous counts.
271                    trace!("Found the event the receipt was referring to! Starting to count.");
272                    self.reset();
273                    counting_receipts = true;
274                    continue;
275                }
276            }
277
278            if counting_receipts {
279                self.process_event(event, user_id, threading_support);
280            }
281        }
282
283        counting_receipts
284    }
285}
286
287/// Small helper to select the "best" receipt (that with the biggest sync
288/// order).
289struct ReceiptSelector {
290    /// Mapping of known event IDs to their sync order.
291    event_id_to_pos: BTreeMap<OwnedEventId, usize>,
292    /// The event with the greatest sync order, for which we had a read-receipt,
293    /// so far.
294    latest_event_with_receipt: Option<OwnedEventId>,
295    /// The biggest sync order attached to the `best_receipt`.
296    latest_event_pos: Option<usize>,
297}
298
299impl ReceiptSelector {
300    fn new(all_events: &[TimelineEvent], latest_active_receipt_event: Option<&EventId>) -> Self {
301        let event_id_to_pos = Self::create_sync_index(all_events.iter());
302
303        let best_pos =
304            latest_active_receipt_event.and_then(|event_id| event_id_to_pos.get(event_id)).copied();
305
306        // Note: `best_receipt` isn't initialized to the latest active receipt, if set,
307        // so that `finish` will return only *new* better receipts, making it
308        // possible to take the fast path in `compute_unread_counts` where every
309        // event is considered new.
310        Self { latest_event_pos: best_pos, latest_event_with_receipt: None, event_id_to_pos }
311    }
312
313    /// Create a mapping of `event_id` -> sync order for all events that have an
314    /// `event_id`.
315    fn create_sync_index<'a>(
316        events: impl Iterator<Item = &'a TimelineEvent> + 'a,
317    ) -> BTreeMap<OwnedEventId, usize> {
318        // TODO: this should be cached and incrementally updated.
319        BTreeMap::from_iter(
320            events
321                .enumerate()
322                .filter_map(|(pos, event)| event.event_id().map(|event_id| (event_id, pos))),
323        )
324    }
325
326    /// Consider the current event and its position as a better read receipt.
327    #[instrument(skip(self), fields(prev_pos = ?self.latest_event_pos, prev_receipt = ?self.latest_event_with_receipt))]
328    fn try_select_later(&mut self, event_id: &EventId, event_pos: usize) {
329        // We now have a position for an event that had a read receipt, but wasn't found
330        // before. Consider if it is the most recent now.
331        if let Some(best_pos) = self.latest_event_pos.as_mut() {
332            // Note: by using a lax comparison here, we properly handle the case where we
333            // received events that we have already seen with a persisted read
334            // receipt.
335            if event_pos >= *best_pos {
336                *best_pos = event_pos;
337                self.latest_event_with_receipt = Some(event_id.to_owned());
338                debug!("saving better");
339            } else {
340                trace!("not better, keeping previous");
341            }
342        } else {
343            // We didn't have a previous receipt, this is the first one we
344            // store: remember it.
345            self.latest_event_pos = Some(event_pos);
346            self.latest_event_with_receipt = Some(event_id.to_owned());
347            debug!("saving for the first time");
348        }
349    }
350
351    /// Try to match pending receipts against new events.
352    #[instrument(skip_all)]
353    fn handle_pending_receipts(&mut self, pending: &mut RingBuffer<OwnedEventId>) {
354        // Try to match stashed receipts against the new events.
355        pending.retain(|event_id| {
356            if let Some(event_pos) = self.event_id_to_pos.get(event_id) {
357                // Maybe select this read receipt as it might be better than the ones we had.
358                trace!(%event_id, "matching event against its stashed receipt");
359                self.try_select_later(event_id, *event_pos);
360
361                // Remove this stashed read receipt from the pending list, as it's been
362                // reconciled with its event.
363                false
364            } else {
365                // Keep it for further iterations.
366                true
367            }
368        });
369    }
370
371    /// Try to match the receipts inside a receipt event against any of the
372    /// events we know about.
373    ///
374    /// If we find a receipt (for the current user) for an event we know, call
375    /// `try_select_later` to see whether this is our new latest receipted
376    /// event.
377    ///
378    /// Returns any receipts (for the current user) that we could not match
379    /// against any event - these are "pending".
380    #[instrument(skip_all)]
381    fn handle_new_receipt(
382        &mut self,
383        user_id: &UserId,
384        receipt_event: &ReceiptEventContent,
385    ) -> Vec<OwnedEventId> {
386        let mut pending = Vec::new();
387        // Now consider new receipts.
388        for (event_id, receipts) in &receipt_event.0 {
389            for ty in [ReceiptType::Read, ReceiptType::ReadPrivate] {
390                if let Some(receipt) = receipts.get(&ty).and_then(|receipts| receipts.get(user_id))
391                {
392                    if matches!(receipt.thread, ReceiptThread::Main | ReceiptThread::Unthreaded) {
393                        trace!(%event_id, "found new candidate");
394                        if let Some(event_pos) = self.event_id_to_pos.get(event_id) {
395                            self.try_select_later(event_id, *event_pos);
396                        } else {
397                            // It's a new pending receipt.
398                            trace!(%event_id, "stashed as pending");
399                            pending.push(event_id.clone());
400                        }
401                    }
402                }
403            }
404        }
405        pending
406    }
407
408    /// Try to match an implicit receipt, that is, the one we get for events we
409    /// sent ourselves.
410    #[instrument(skip_all)]
411    fn try_match_implicit(&mut self, user_id: &UserId, new_events: &[TimelineEvent]) {
412        for ev in new_events {
413            // Get the `sender` field, if any, or skip this event.
414            let Ok(Some(sender)) = ev.raw().get_field::<OwnedUserId>("sender") else { continue };
415            if sender == user_id {
416                // Get the event id, if any, or skip this event.
417                let Some(event_id) = ev.event_id() else { continue };
418                if let Some(event_pos) = self.event_id_to_pos.get(&event_id) {
419                    trace!(%event_id, "found an implicit receipt candidate");
420                    self.try_select_later(&event_id, *event_pos);
421                }
422            }
423        }
424    }
425
426    /// Returns the event id referred to by a new later active read receipt.
427    ///
428    /// If it's not set, we can consider that each new event is *after* the
429    /// previous active read receipt.
430    fn select(self) -> Option<LatestReadReceipt> {
431        self.latest_event_with_receipt.map(|event_id| LatestReadReceipt { event_id })
432    }
433}
434
435/// Returns true if there's an event common to both groups of events, based on
436/// their event id.
437fn events_intersects<'a>(
438    previous_events: impl Iterator<Item = &'a TimelineEvent>,
439    new_events: &[TimelineEvent],
440) -> bool {
441    let previous_events_ids = BTreeSet::from_iter(previous_events.filter_map(|ev| ev.event_id()));
442    new_events
443        .iter()
444        .any(|ev| ev.event_id().is_some_and(|event_id| previous_events_ids.contains(&event_id)))
445}
446
447/// Given a set of events coming from sync, for a room, update the
448/// [`RoomReadReceipts`]'s counts of unread messages, notifications and
449/// highlights' in place.
450///
451/// A provider of previous events may be required to reconcile a read receipt
452/// that has been just received for an event that came in a previous sync.
453///
454/// See this module's documentation for more information.
455#[instrument(skip_all, fields(room_id = %room_id))]
456pub(crate) fn compute_unread_counts(
457    user_id: &UserId,
458    room_id: &RoomId,
459    receipt_event: Option<&ReceiptEventContent>,
460    mut previous_events: Vec<TimelineEvent>,
461    new_events: &[TimelineEvent],
462    read_receipts: &mut RoomReadReceipts,
463    threading_support: ThreadingSupport,
464) {
465    debug!(?read_receipts, "Starting");
466
467    let all_events = if events_intersects(previous_events.iter(), new_events) {
468        // The previous and new events sets can intersect, for instance if we restored
469        // previous events from the disk cache, or a timeline was limited. This
470        // means the old events will be cleared, because we don't reconcile
471        // timelines in the event cache (yet). As a result, forget
472        // about the previous events.
473        new_events.to_owned()
474    } else {
475        previous_events.extend(new_events.iter().cloned());
476        previous_events
477    };
478
479    let new_receipt = {
480        let mut selector = ReceiptSelector::new(
481            &all_events,
482            read_receipts.latest_active.as_ref().map(|receipt| &*receipt.event_id),
483        );
484
485        selector.try_match_implicit(user_id, new_events);
486        selector.handle_pending_receipts(&mut read_receipts.pending);
487        if let Some(receipt_event) = receipt_event {
488            let new_pending = selector.handle_new_receipt(user_id, receipt_event);
489            if !new_pending.is_empty() {
490                read_receipts.pending.extend(new_pending);
491            }
492        }
493        selector.select()
494    };
495
496    if let Some(new_receipt) = new_receipt {
497        // We've found the id of an event to which the receipt attaches. The associated
498        // event may either come from the new batch of events associated to
499        // this sync, or it may live in the past timeline events we know
500        // about.
501
502        let event_id = new_receipt.event_id.clone();
503
504        // First, save the event id as the latest one that has a read receipt.
505        trace!(%event_id, "Saving a new active read receipt");
506        read_receipts.latest_active = Some(new_receipt);
507
508        // The event for the receipt is in `all_events`, so we'll find it and can count
509        // safely from here.
510        read_receipts.find_and_process_events(
511            &event_id,
512            user_id,
513            all_events.iter(),
514            threading_support,
515        );
516
517        debug!(?read_receipts, "after finding a better receipt");
518        return;
519    }
520
521    // If we haven't returned at this point, it means we don't have any new "active"
522    // read receipt. So either there was a previous one further in the past, or
523    // none.
524    //
525    // In that case, accumulate all events as part of the current batch, and wait
526    // for the next receipt.
527
528    for event in new_events {
529        read_receipts.process_event(event, user_id, threading_support);
530    }
531
532    debug!(?read_receipts, "no better receipt, {} new events", new_events.len());
533}
534
535/// Is the event worth marking a room as unread?
536fn marks_as_unread(event: &Raw<AnySyncTimelineEvent>, user_id: &UserId) -> bool {
537    let event = match event.deserialize() {
538        Ok(event) => event,
539        Err(err) => {
540            warn!(
541                "couldn't deserialize event {:?}: {err}",
542                event.get_field::<String>("event_id").ok().flatten()
543            );
544            return false;
545        }
546    };
547
548    if event.sender() == user_id {
549        // Not interested in one's own events.
550        return false;
551    }
552
553    match event {
554        AnySyncTimelineEvent::MessageLike(event) => {
555            // Filter out redactions.
556            let Some(content) = event.original_content() else {
557                tracing::trace!("not interesting because redacted");
558                return false;
559            };
560
561            // Filter out edits.
562            if matches!(
563                content.relation(),
564                Some(ruma::events::room::encrypted::Relation::Replacement(..))
565            ) {
566                tracing::trace!("not interesting because edited");
567                return false;
568            }
569
570            match event {
571                AnySyncMessageLikeEvent::CallAnswer(_)
572                | AnySyncMessageLikeEvent::CallInvite(_)
573                | AnySyncMessageLikeEvent::CallNotify(_)
574                | AnySyncMessageLikeEvent::CallHangup(_)
575                | AnySyncMessageLikeEvent::CallCandidates(_)
576                | AnySyncMessageLikeEvent::CallNegotiate(_)
577                | AnySyncMessageLikeEvent::CallReject(_)
578                | AnySyncMessageLikeEvent::CallSelectAnswer(_)
579                | AnySyncMessageLikeEvent::PollResponse(_)
580                | AnySyncMessageLikeEvent::UnstablePollResponse(_)
581                | AnySyncMessageLikeEvent::Reaction(_)
582                | AnySyncMessageLikeEvent::RoomRedaction(_)
583                | AnySyncMessageLikeEvent::KeyVerificationStart(_)
584                | AnySyncMessageLikeEvent::KeyVerificationReady(_)
585                | AnySyncMessageLikeEvent::KeyVerificationCancel(_)
586                | AnySyncMessageLikeEvent::KeyVerificationAccept(_)
587                | AnySyncMessageLikeEvent::KeyVerificationDone(_)
588                | AnySyncMessageLikeEvent::KeyVerificationMac(_)
589                | AnySyncMessageLikeEvent::KeyVerificationKey(_) => false,
590
591                // For some reason, Ruma doesn't handle these two in `content.relation()` above.
592                AnySyncMessageLikeEvent::PollStart(SyncMessageLikeEvent::Original(
593                    OriginalSyncMessageLikeEvent {
594                        content:
595                            PollStartEventContent { relates_to: Some(Relation::Replacement(_)), .. },
596                        ..
597                    },
598                ))
599                | AnySyncMessageLikeEvent::UnstablePollStart(SyncMessageLikeEvent::Original(
600                    OriginalSyncMessageLikeEvent {
601                        content: UnstablePollStartEventContent::Replacement(_),
602                        ..
603                    },
604                )) => false,
605
606                AnySyncMessageLikeEvent::Message(_)
607                | AnySyncMessageLikeEvent::PollStart(_)
608                | AnySyncMessageLikeEvent::UnstablePollStart(_)
609                | AnySyncMessageLikeEvent::PollEnd(_)
610                | AnySyncMessageLikeEvent::UnstablePollEnd(_)
611                | AnySyncMessageLikeEvent::RoomEncrypted(_)
612                | AnySyncMessageLikeEvent::RoomMessage(_)
613                | AnySyncMessageLikeEvent::Sticker(_) => true,
614
615                _ => {
616                    // What I don't know about, I don't care about.
617                    warn!("unhandled timeline event type: {}", event.event_type());
618                    false
619                }
620            }
621        }
622
623        AnySyncTimelineEvent::State(_) => false,
624    }
625}
626
627#[cfg(test)]
628mod tests {
629    use std::{num::NonZeroUsize, ops::Not as _};
630
631    use matrix_sdk_common::{deserialized_responses::TimelineEvent, ring_buffer::RingBuffer};
632    use matrix_sdk_test::event_factory::EventFactory;
633    use ruma::{
634        event_id,
635        events::{
636            receipt::{ReceiptThread, ReceiptType},
637            room::{member::MembershipState, message::MessageType},
638        },
639        owned_event_id, owned_user_id,
640        push::Action,
641        room_id, user_id, EventId, UserId,
642    };
643
644    use super::compute_unread_counts;
645    use crate::{
646        read_receipts::{marks_as_unread, ReceiptSelector, RoomReadReceipts},
647        ThreadingSupport,
648    };
649
650    #[test]
651    fn test_room_message_marks_as_unread() {
652        let user_id = user_id!("@alice:example.org");
653        let other_user_id = user_id!("@bob:example.org");
654
655        let f = EventFactory::new();
656
657        // A message from somebody else marks the room as unread...
658        let ev = f.text_msg("A").event_id(event_id!("$ida")).sender(other_user_id).into_raw_sync();
659        assert!(marks_as_unread(&ev, user_id));
660
661        // ... but a message from ourselves doesn't.
662        let ev = f.text_msg("A").event_id(event_id!("$ida")).sender(user_id).into_raw_sync();
663        assert!(marks_as_unread(&ev, user_id).not());
664    }
665
666    #[test]
667    fn test_room_edit_doesnt_mark_as_unread() {
668        let user_id = user_id!("@alice:example.org");
669        let other_user_id = user_id!("@bob:example.org");
670
671        // An edit to a message from somebody else doesn't mark the room as unread.
672        let ev = EventFactory::new()
673            .text_msg("* edited message")
674            .edit(
675                event_id!("$someeventid:localhost"),
676                MessageType::text_plain("edited message").into(),
677            )
678            .event_id(event_id!("$ida"))
679            .sender(other_user_id)
680            .into_raw_sync();
681
682        assert!(marks_as_unread(&ev, user_id).not());
683    }
684
685    #[test]
686    fn test_redaction_doesnt_mark_room_as_unread() {
687        let user_id = user_id!("@alice:example.org");
688        let other_user_id = user_id!("@bob:example.org");
689
690        // A redact of a message from somebody else doesn't mark the room as unread.
691        let ev = EventFactory::new()
692            .redaction(event_id!("$151957878228ssqrj:localhost"))
693            .sender(other_user_id)
694            .event_id(event_id!("$151957878228ssqrJ:localhost"))
695            .into_raw_sync();
696
697        assert!(marks_as_unread(&ev, user_id).not());
698    }
699
700    #[test]
701    fn test_reaction_doesnt_mark_room_as_unread() {
702        let user_id = user_id!("@alice:example.org");
703        let other_user_id = user_id!("@bob:example.org");
704
705        // A reaction from somebody else to a message doesn't mark the room as unread.
706        let ev = EventFactory::new()
707            .reaction(event_id!("$15275047031IXQRj:localhost"), "👍")
708            .sender(other_user_id)
709            .event_id(event_id!("$15275047031IXQRi:localhost"))
710            .into_raw_sync();
711
712        assert!(marks_as_unread(&ev, user_id).not());
713    }
714
715    #[test]
716    fn test_state_event_doesnt_mark_as_unread() {
717        let user_id = user_id!("@alice:example.org");
718        let event_id = event_id!("$1");
719
720        let ev = EventFactory::new()
721            .member(user_id)
722            .membership(MembershipState::Join)
723            .display_name("Alice")
724            .event_id(event_id)
725            .into_raw_sync();
726        assert!(marks_as_unread(&ev, user_id).not());
727
728        let other_user_id = user_id!("@bob:example.org");
729        assert!(marks_as_unread(&ev, other_user_id).not());
730    }
731
732    #[test]
733    fn test_count_unread_and_mentions() {
734        fn make_event(user_id: &UserId, push_actions: Vec<Action>) -> TimelineEvent {
735            let mut ev = EventFactory::new()
736                .text_msg("A")
737                .sender(user_id)
738                .event_id(event_id!("$ida"))
739                .into_event();
740            ev.set_push_actions(push_actions);
741            ev
742        }
743
744        let user_id = user_id!("@alice:example.org");
745
746        // An interesting event from oneself doesn't count as a new unread message.
747        let event = make_event(user_id, Vec::new());
748        let mut receipts = RoomReadReceipts::default();
749        receipts.process_event(&event, user_id, ThreadingSupport::Disabled);
750        assert_eq!(receipts.num_unread, 0);
751        assert_eq!(receipts.num_mentions, 0);
752        assert_eq!(receipts.num_notifications, 0);
753
754        // An interesting event from someone else does count as a new unread message.
755        let event = make_event(user_id!("@bob:example.org"), Vec::new());
756        let mut receipts = RoomReadReceipts::default();
757        receipts.process_event(&event, user_id, ThreadingSupport::Disabled);
758        assert_eq!(receipts.num_unread, 1);
759        assert_eq!(receipts.num_mentions, 0);
760        assert_eq!(receipts.num_notifications, 0);
761
762        // Push actions computed beforehand are respected.
763        let event = make_event(user_id!("@bob:example.org"), vec![Action::Notify]);
764        let mut receipts = RoomReadReceipts::default();
765        receipts.process_event(&event, user_id, ThreadingSupport::Disabled);
766        assert_eq!(receipts.num_unread, 1);
767        assert_eq!(receipts.num_mentions, 0);
768        assert_eq!(receipts.num_notifications, 1);
769
770        let event = make_event(
771            user_id!("@bob:example.org"),
772            vec![Action::SetTweak(ruma::push::Tweak::Highlight(true))],
773        );
774        let mut receipts = RoomReadReceipts::default();
775        receipts.process_event(&event, user_id, ThreadingSupport::Disabled);
776        assert_eq!(receipts.num_unread, 1);
777        assert_eq!(receipts.num_mentions, 1);
778        assert_eq!(receipts.num_notifications, 0);
779
780        let event = make_event(
781            user_id!("@bob:example.org"),
782            vec![Action::SetTweak(ruma::push::Tweak::Highlight(true)), Action::Notify],
783        );
784        let mut receipts = RoomReadReceipts::default();
785        receipts.process_event(&event, user_id, ThreadingSupport::Disabled);
786        assert_eq!(receipts.num_unread, 1);
787        assert_eq!(receipts.num_mentions, 1);
788        assert_eq!(receipts.num_notifications, 1);
789
790        // Technically this `push_actions` set would be a bug somewhere else, but let's
791        // make sure to resist against it.
792        let event = make_event(user_id!("@bob:example.org"), vec![Action::Notify, Action::Notify]);
793        let mut receipts = RoomReadReceipts::default();
794        receipts.process_event(&event, user_id, ThreadingSupport::Disabled);
795        assert_eq!(receipts.num_unread, 1);
796        assert_eq!(receipts.num_mentions, 0);
797        assert_eq!(receipts.num_notifications, 1);
798    }
799
800    #[test]
801    fn test_find_and_process_events() {
802        let ev0 = event_id!("$0");
803        let user_id = user_id!("@alice:example.org");
804
805        // When provided with no events, we report not finding the event to which the
806        // receipt relates.
807        let mut receipts = RoomReadReceipts::default();
808        assert!(receipts
809            .find_and_process_events(ev0, user_id, &[], ThreadingSupport::Disabled)
810            .not());
811        assert_eq!(receipts.num_unread, 0);
812        assert_eq!(receipts.num_notifications, 0);
813        assert_eq!(receipts.num_mentions, 0);
814
815        // When provided with one event, that's not the receipt event, we don't count
816        // it.
817        fn make_event(event_id: &EventId) -> TimelineEvent {
818            EventFactory::new()
819                .text_msg("A")
820                .sender(user_id!("@bob:example.org"))
821                .event_id(event_id)
822                .into()
823        }
824
825        let mut receipts = RoomReadReceipts {
826            num_unread: 42,
827            num_notifications: 13,
828            num_mentions: 37,
829            ..Default::default()
830        };
831        assert!(receipts
832            .find_and_process_events(
833                ev0,
834                user_id,
835                &[make_event(event_id!("$1"))],
836                ThreadingSupport::Disabled
837            )
838            .not());
839        assert_eq!(receipts.num_unread, 42);
840        assert_eq!(receipts.num_notifications, 13);
841        assert_eq!(receipts.num_mentions, 37);
842
843        // When provided with one event that's the receipt target, we find it, reset the
844        // count, and since there's nothing else, we stop there and end up with
845        // zero counts.
846        let mut receipts = RoomReadReceipts {
847            num_unread: 42,
848            num_notifications: 13,
849            num_mentions: 37,
850            ..Default::default()
851        };
852        assert!(receipts.find_and_process_events(
853            ev0,
854            user_id,
855            &[make_event(ev0)],
856            ThreadingSupport::Disabled
857        ),);
858        assert_eq!(receipts.num_unread, 0);
859        assert_eq!(receipts.num_notifications, 0);
860        assert_eq!(receipts.num_mentions, 0);
861
862        // When provided with multiple events and not the receipt event, we do not count
863        // anything..
864        let mut receipts = RoomReadReceipts {
865            num_unread: 42,
866            num_notifications: 13,
867            num_mentions: 37,
868            ..Default::default()
869        };
870        assert!(receipts
871            .find_and_process_events(
872                ev0,
873                user_id,
874                &[
875                    make_event(event_id!("$1")),
876                    make_event(event_id!("$2")),
877                    make_event(event_id!("$3"))
878                ],
879                ThreadingSupport::Disabled
880            )
881            .not());
882        assert_eq!(receipts.num_unread, 42);
883        assert_eq!(receipts.num_notifications, 13);
884        assert_eq!(receipts.num_mentions, 37);
885
886        // When provided with multiple events including one that's the receipt event, we
887        // find it and count from it.
888        let mut receipts = RoomReadReceipts {
889            num_unread: 42,
890            num_notifications: 13,
891            num_mentions: 37,
892            ..Default::default()
893        };
894        assert!(receipts.find_and_process_events(
895            ev0,
896            user_id,
897            &[
898                make_event(event_id!("$1")),
899                make_event(ev0),
900                make_event(event_id!("$2")),
901                make_event(event_id!("$3"))
902            ],
903            ThreadingSupport::Disabled
904        ));
905        assert_eq!(receipts.num_unread, 2);
906        assert_eq!(receipts.num_notifications, 0);
907        assert_eq!(receipts.num_mentions, 0);
908
909        // Even if duplicates are present in the new events list, the count is correct.
910        let mut receipts = RoomReadReceipts {
911            num_unread: 42,
912            num_notifications: 13,
913            num_mentions: 37,
914            ..Default::default()
915        };
916        assert!(receipts.find_and_process_events(
917            ev0,
918            user_id,
919            &[
920                make_event(ev0),
921                make_event(event_id!("$1")),
922                make_event(ev0),
923                make_event(event_id!("$2")),
924                make_event(event_id!("$3"))
925            ],
926            ThreadingSupport::Disabled
927        ));
928        assert_eq!(receipts.num_unread, 2);
929        assert_eq!(receipts.num_notifications, 0);
930        assert_eq!(receipts.num_mentions, 0);
931    }
932
933    /// Smoke test for `compute_unread_counts`.
934    #[test]
935    fn test_basic_compute_unread_counts() {
936        let user_id = user_id!("@alice:example.org");
937        let other_user_id = user_id!("@bob:example.org");
938        let room_id = room_id!("!room:example.org");
939        let receipt_event_id = event_id!("$1");
940
941        let mut previous_events = Vec::new();
942
943        let f = EventFactory::new();
944        let ev1 = f.text_msg("A").sender(other_user_id).event_id(receipt_event_id).into_event();
945        let ev2 = f.text_msg("A").sender(other_user_id).event_id(event_id!("$2")).into_event();
946
947        let receipt_event = f
948            .read_receipts()
949            .add(receipt_event_id, user_id, ReceiptType::Read, ReceiptThread::Unthreaded)
950            .into_content();
951
952        let mut read_receipts = RoomReadReceipts::default();
953        compute_unread_counts(
954            user_id,
955            room_id,
956            Some(&receipt_event),
957            previous_events.clone(),
958            &[ev1.clone(), ev2.clone()],
959            &mut read_receipts,
960            ThreadingSupport::Disabled,
961        );
962
963        // It did find the receipt event (ev1).
964        assert_eq!(read_receipts.num_unread, 1);
965
966        // Receive the same receipt event, with a new sync event.
967        previous_events.push(ev1);
968        previous_events.push(ev2);
969
970        let new_event =
971            f.text_msg("A").sender(other_user_id).event_id(event_id!("$3")).into_event();
972        compute_unread_counts(
973            user_id,
974            room_id,
975            Some(&receipt_event),
976            previous_events,
977            &[new_event],
978            &mut read_receipts,
979            ThreadingSupport::Disabled,
980        );
981
982        // Only the new event should be added.
983        assert_eq!(read_receipts.num_unread, 2);
984    }
985
986    fn make_test_events(user_id: &UserId) -> Vec<TimelineEvent> {
987        let f = EventFactory::new().sender(user_id);
988        let ev1 = f.text_msg("With the lights out, it's less dangerous").event_id(event_id!("$1"));
989        let ev2 = f.text_msg("Here we are now, entertain us").event_id(event_id!("$2"));
990        let ev3 = f.text_msg("I feel stupid and contagious").event_id(event_id!("$3"));
991        let ev4 = f.text_msg("Here we are now, entertain us").event_id(event_id!("$4"));
992        let ev5 = f.text_msg("Hello, hello, hello, how low?").event_id(event_id!("$5"));
993        [ev1, ev2, ev3, ev4, ev5].into_iter().map(Into::into).collect()
994    }
995
996    /// Test that when multiple receipts come in a single event, we can still
997    /// find the latest one according to the sync order.
998    #[test]
999    fn test_compute_unread_counts_multiple_receipts_in_one_event() {
1000        let user_id = user_id!("@alice:example.org");
1001        let room_id = room_id!("!room:example.org");
1002
1003        let all_events = make_test_events(user_id!("@bob:example.org"));
1004        let head_events: Vec<_> = all_events.iter().take(2).cloned().collect();
1005        let tail_events: Vec<_> = all_events.iter().skip(2).cloned().collect();
1006
1007        // Given a receipt event marking events 1-3 as read using a combination of
1008        // different thread and privacy types,
1009        let f = EventFactory::new();
1010        for receipt_type_1 in &[ReceiptType::Read, ReceiptType::ReadPrivate] {
1011            for receipt_thread_1 in &[ReceiptThread::Unthreaded, ReceiptThread::Main] {
1012                for receipt_type_2 in &[ReceiptType::Read, ReceiptType::ReadPrivate] {
1013                    for receipt_thread_2 in &[ReceiptThread::Unthreaded, ReceiptThread::Main] {
1014                        let receipt_event = f
1015                            .read_receipts()
1016                            .add(
1017                                event_id!("$2"),
1018                                user_id,
1019                                receipt_type_1.clone(),
1020                                receipt_thread_1.clone(),
1021                            )
1022                            .add(
1023                                event_id!("$3"),
1024                                user_id,
1025                                receipt_type_2.clone(),
1026                                receipt_thread_2.clone(),
1027                            )
1028                            .add(
1029                                event_id!("$1"),
1030                                user_id,
1031                                receipt_type_1.clone(),
1032                                receipt_thread_2.clone(),
1033                            )
1034                            .into_content();
1035
1036                        // When I compute the notifications for this room (with no new events),
1037                        let mut read_receipts = RoomReadReceipts::default();
1038
1039                        compute_unread_counts(
1040                            user_id,
1041                            room_id,
1042                            Some(&receipt_event),
1043                            all_events.clone(),
1044                            &[],
1045                            &mut read_receipts,
1046                            ThreadingSupport::Disabled,
1047                        );
1048
1049                        assert!(
1050                            read_receipts != Default::default(),
1051                            "read receipts have been updated"
1052                        );
1053
1054                        // Then events 1-3 are considered read, but 4 and 5 are not.
1055                        assert_eq!(read_receipts.num_unread, 2);
1056                        assert_eq!(read_receipts.num_mentions, 0);
1057                        assert_eq!(read_receipts.num_notifications, 0);
1058
1059                        // And when I compute notifications again, with some old and new events,
1060                        let mut read_receipts = RoomReadReceipts::default();
1061                        compute_unread_counts(
1062                            user_id,
1063                            room_id,
1064                            Some(&receipt_event),
1065                            head_events.clone(),
1066                            &tail_events,
1067                            &mut read_receipts,
1068                            ThreadingSupport::Disabled,
1069                        );
1070
1071                        assert!(
1072                            read_receipts != Default::default(),
1073                            "read receipts have been updated"
1074                        );
1075
1076                        // Then events 1-3 are considered read, but 4 and 5 are not.
1077                        assert_eq!(read_receipts.num_unread, 2);
1078                        assert_eq!(read_receipts.num_mentions, 0);
1079                        assert_eq!(read_receipts.num_notifications, 0);
1080                    }
1081                }
1082            }
1083        }
1084    }
1085
1086    /// Updating the pending list should cause a change in the
1087    /// `RoomReadReceipts` fields, and `compute_unread_counts` should return
1088    /// true then.
1089    #[test]
1090    fn test_compute_unread_counts_updated_after_field_tracking() {
1091        let user_id = owned_user_id!("@alice:example.org");
1092        let room_id = room_id!("!room:example.org");
1093
1094        let events = make_test_events(user_id!("@bob:example.org"));
1095
1096        let receipt_event = EventFactory::new()
1097            .read_receipts()
1098            .add(event_id!("$6"), &user_id, ReceiptType::Read, ReceiptThread::Unthreaded)
1099            .into_content();
1100
1101        let mut read_receipts = RoomReadReceipts::default();
1102        assert!(read_receipts.pending.is_empty());
1103
1104        // Given a receipt event that contains a read receipt referring to an unknown
1105        // event, and some preexisting events with different ids,
1106        compute_unread_counts(
1107            &user_id,
1108            room_id,
1109            Some(&receipt_event),
1110            events,
1111            &[], // no new events
1112            &mut read_receipts,
1113            ThreadingSupport::Disabled,
1114        );
1115
1116        // Then there are no unread events,
1117        assert_eq!(read_receipts.num_unread, 0);
1118
1119        // And the event referred to by the read receipt is in the pending state.
1120        assert_eq!(read_receipts.pending.len(), 1);
1121        assert!(read_receipts.pending.iter().any(|ev| ev == event_id!("$6")));
1122    }
1123
1124    #[test]
1125    fn test_compute_unread_counts_limited_sync() {
1126        let user_id = owned_user_id!("@alice:example.org");
1127        let room_id = room_id!("!room:example.org");
1128
1129        let events = make_test_events(user_id!("@bob:example.org"));
1130
1131        let receipt_event = EventFactory::new()
1132            .read_receipts()
1133            .add(event_id!("$1"), &user_id, ReceiptType::Read, ReceiptThread::Unthreaded)
1134            .into_content();
1135
1136        // Sync with a read receipt *and* a single event that was already known: in that
1137        // case, only consider the new events in isolation, and compute the
1138        // correct count.
1139        let mut read_receipts = RoomReadReceipts::default();
1140        assert!(read_receipts.pending.is_empty());
1141
1142        let ev0 = events[0].clone();
1143
1144        compute_unread_counts(
1145            &user_id,
1146            room_id,
1147            Some(&receipt_event),
1148            events,
1149            &[ev0], // duplicate event!
1150            &mut read_receipts,
1151            ThreadingSupport::Disabled,
1152        );
1153
1154        // All events are unread, and there's no pending receipt.
1155        assert_eq!(read_receipts.num_unread, 0);
1156        assert!(read_receipts.pending.is_empty());
1157    }
1158
1159    #[test]
1160    fn test_receipt_selector_create_sync_index() {
1161        let uid = user_id!("@bob:example.org");
1162
1163        let events = make_test_events(uid);
1164
1165        // An event with no id.
1166        let ev6 = EventFactory::new().text_msg("yolo").sender(uid).no_event_id().into_event();
1167
1168        let index = ReceiptSelector::create_sync_index(events.iter().chain(&[ev6]));
1169
1170        assert_eq!(*index.get(event_id!("$1")).unwrap(), 0);
1171        assert_eq!(*index.get(event_id!("$2")).unwrap(), 1);
1172        assert_eq!(*index.get(event_id!("$3")).unwrap(), 2);
1173        assert_eq!(*index.get(event_id!("$4")).unwrap(), 3);
1174        assert_eq!(*index.get(event_id!("$5")).unwrap(), 4);
1175        assert_eq!(index.get(event_id!("$6")), None);
1176
1177        assert_eq!(index.len(), 5);
1178
1179        // Sync order are set according to the position in the vector.
1180        let index = ReceiptSelector::create_sync_index(
1181            [events[1].clone(), events[2].clone(), events[4].clone()].iter(),
1182        );
1183
1184        assert_eq!(*index.get(event_id!("$2")).unwrap(), 0);
1185        assert_eq!(*index.get(event_id!("$3")).unwrap(), 1);
1186        assert_eq!(*index.get(event_id!("$5")).unwrap(), 2);
1187
1188        assert_eq!(index.len(), 3);
1189    }
1190
1191    #[test]
1192    fn test_receipt_selector_try_select_later() {
1193        let events = make_test_events(user_id!("@bob:example.org"));
1194
1195        {
1196            // No initial active receipt, so the first receipt we get *will* win.
1197            let mut selector = ReceiptSelector::new(&[], None);
1198            selector.try_select_later(event_id!("$1"), 0);
1199            let best_receipt = selector.select();
1200            assert_eq!(best_receipt.unwrap().event_id, event_id!("$1"));
1201        }
1202
1203        {
1204            // $3 is at pos 2, $1 at position 0, so $3 wins => no new change.
1205            let mut selector = ReceiptSelector::new(&events, Some(event_id!("$3")));
1206            selector.try_select_later(event_id!("$1"), 0);
1207            let best_receipt = selector.select();
1208            assert!(best_receipt.is_none());
1209        }
1210
1211        {
1212            // The initial active receipt is returned, when it's part of the scanned
1213            // elements.
1214            let mut selector = ReceiptSelector::new(&events, Some(event_id!("$1")));
1215            selector.try_select_later(event_id!("$1"), 0);
1216            let best_receipt = selector.select();
1217            assert_eq!(best_receipt.unwrap().event_id, event_id!("$1"));
1218        }
1219
1220        {
1221            // $3 is at pos 2, $4 at position 3, so $4 wins.
1222            let mut selector = ReceiptSelector::new(&events, Some(event_id!("$3")));
1223            selector.try_select_later(event_id!("$4"), 3);
1224            let best_receipt = selector.select();
1225            assert_eq!(best_receipt.unwrap().event_id, event_id!("$4"));
1226        }
1227    }
1228
1229    #[test]
1230    fn test_receipt_selector_handle_pending_receipts_noop() {
1231        let sender = user_id!("@bob:example.org");
1232        let f = EventFactory::new().sender(sender);
1233        let ev1 = f.text_msg("yo").event_id(event_id!("$1")).into_event();
1234        let ev2 = f.text_msg("well?").event_id(event_id!("$2")).into_event();
1235        let events = &[ev1, ev2][..];
1236
1237        {
1238            // No pending receipt => no better receipt.
1239            let mut selector = ReceiptSelector::new(events, None);
1240
1241            let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1242            selector.handle_pending_receipts(&mut pending);
1243
1244            assert!(pending.is_empty());
1245
1246            let best_receipt = selector.select();
1247            assert!(best_receipt.is_none());
1248        }
1249
1250        {
1251            // No pending receipt, and there was an active last receipt => no better
1252            // receipt.
1253            let mut selector = ReceiptSelector::new(events, Some(event_id!("$1")));
1254
1255            let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1256            selector.handle_pending_receipts(&mut pending);
1257
1258            assert!(pending.is_empty());
1259
1260            let best_receipt = selector.select();
1261            assert!(best_receipt.is_none());
1262        }
1263    }
1264
1265    #[test]
1266    fn test_receipt_selector_handle_pending_receipts_doesnt_match_known_events() {
1267        let sender = user_id!("@bob:example.org");
1268        let f = EventFactory::new().sender(sender);
1269        let ev1 = f.text_msg("yo").event_id(event_id!("$1")).into_event();
1270        let ev2 = f.text_msg("well?").event_id(event_id!("$2")).into_event();
1271        let events = &[ev1, ev2][..];
1272
1273        {
1274            // A pending receipt for an event that is still missing => no better receipt.
1275            let mut selector = ReceiptSelector::new(events, None);
1276
1277            let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1278            pending.push(owned_event_id!("$3"));
1279            selector.handle_pending_receipts(&mut pending);
1280
1281            assert_eq!(pending.len(), 1);
1282
1283            let best_receipt = selector.select();
1284            assert!(best_receipt.is_none());
1285        }
1286
1287        {
1288            // Ditto but there was an active receipt => no better receipt.
1289            let mut selector = ReceiptSelector::new(events, Some(event_id!("$1")));
1290
1291            let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1292            pending.push(owned_event_id!("$3"));
1293            selector.handle_pending_receipts(&mut pending);
1294
1295            assert_eq!(pending.len(), 1);
1296
1297            let best_receipt = selector.select();
1298            assert!(best_receipt.is_none());
1299        }
1300    }
1301
1302    #[test]
1303    fn test_receipt_selector_handle_pending_receipts_matches_known_events_no_initial() {
1304        let sender = user_id!("@bob:example.org");
1305        let f = EventFactory::new().sender(sender);
1306        let ev1 = f.text_msg("yo").event_id(event_id!("$1")).into_event();
1307        let ev2 = f.text_msg("well?").event_id(event_id!("$2")).into_event();
1308        let events = &[ev1, ev2][..];
1309
1310        {
1311            // A pending receipt for an event that is present => better receipt.
1312            let mut selector = ReceiptSelector::new(events, None);
1313
1314            let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1315            pending.push(owned_event_id!("$2"));
1316            selector.handle_pending_receipts(&mut pending);
1317
1318            // The receipt for $2 has been found.
1319            assert!(pending.is_empty());
1320
1321            // The new receipt has been returned.
1322            let best_receipt = selector.select();
1323            assert_eq!(best_receipt.unwrap().event_id, event_id!("$2"));
1324        }
1325
1326        {
1327            // Mixed found and not found receipt => better receipt.
1328            let mut selector = ReceiptSelector::new(events, None);
1329
1330            let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1331            pending.push(owned_event_id!("$1"));
1332            pending.push(owned_event_id!("$3"));
1333            selector.handle_pending_receipts(&mut pending);
1334
1335            // The receipt for $1 has been found, but not that for $3.
1336            assert_eq!(pending.len(), 1);
1337            assert!(pending.iter().any(|ev| ev == event_id!("$3")));
1338
1339            let best_receipt = selector.select();
1340            assert_eq!(best_receipt.unwrap().event_id, event_id!("$1"));
1341        }
1342    }
1343
1344    #[test]
1345    fn test_receipt_selector_handle_pending_receipts_matches_known_events_with_initial() {
1346        let sender = user_id!("@bob:example.org");
1347        let f = EventFactory::new().sender(sender);
1348        let ev1 = f.text_msg("yo").event_id(event_id!("$1")).into_event();
1349        let ev2 = f.text_msg("well?").event_id(event_id!("$2")).into_event();
1350        let events = &[ev1, ev2][..];
1351
1352        {
1353            // Same, and there was an initial receipt that was less good than the one we
1354            // selected => better receipt.
1355            let mut selector = ReceiptSelector::new(events, Some(event_id!("$1")));
1356
1357            let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1358            pending.push(owned_event_id!("$2"));
1359            selector.handle_pending_receipts(&mut pending);
1360
1361            // The receipt for $2 has been found.
1362            assert!(pending.is_empty());
1363
1364            // The new receipt has been returned.
1365            let best_receipt = selector.select();
1366            assert_eq!(best_receipt.unwrap().event_id, event_id!("$2"));
1367        }
1368
1369        {
1370            // Same, but the previous receipt was better => no better receipt.
1371            let mut selector = ReceiptSelector::new(events, Some(event_id!("$2")));
1372
1373            let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1374            pending.push(owned_event_id!("$1"));
1375            selector.handle_pending_receipts(&mut pending);
1376
1377            // The receipt for $1 has been found.
1378            assert!(pending.is_empty());
1379
1380            let best_receipt = selector.select();
1381            assert!(best_receipt.is_none());
1382        }
1383    }
1384
1385    #[test]
1386    fn test_receipt_selector_handle_new_receipt() {
1387        let myself = user_id!("@alice:example.org");
1388        let events = make_test_events(user_id!("@bob:example.org"));
1389
1390        let f = EventFactory::new();
1391        {
1392            // Thread receipts are ignored.
1393            let mut selector = ReceiptSelector::new(&events, None);
1394
1395            let receipt_event = f
1396                .read_receipts()
1397                .add(
1398                    event_id!("$5"),
1399                    myself,
1400                    ReceiptType::Read,
1401                    ReceiptThread::Thread(owned_event_id!("$2")),
1402                )
1403                .into_content();
1404
1405            let pending = selector.handle_new_receipt(myself, &receipt_event);
1406            assert!(pending.is_empty());
1407
1408            let best_receipt = selector.select();
1409            assert!(best_receipt.is_none());
1410        }
1411
1412        for receipt_type in [ReceiptType::Read, ReceiptType::ReadPrivate] {
1413            for receipt_thread in [ReceiptThread::Main, ReceiptThread::Unthreaded] {
1414                {
1415                    // Receipt for an event we don't know about => it's pending, and no better
1416                    // receipt.
1417                    let mut selector = ReceiptSelector::new(&events, None);
1418
1419                    let receipt_event = f
1420                        .read_receipts()
1421                        .add(event_id!("$6"), myself, receipt_type.clone(), receipt_thread.clone())
1422                        .into_content();
1423
1424                    let pending = selector.handle_new_receipt(myself, &receipt_event);
1425                    assert_eq!(pending[0], event_id!("$6"));
1426                    assert_eq!(pending.len(), 1);
1427
1428                    let best_receipt = selector.select();
1429                    assert!(best_receipt.is_none());
1430                }
1431
1432                {
1433                    // Receipt for an event we knew about, no initial active receipt => better
1434                    // receipt.
1435                    let mut selector = ReceiptSelector::new(&events, None);
1436
1437                    let receipt_event = f
1438                        .read_receipts()
1439                        .add(event_id!("$3"), myself, receipt_type.clone(), receipt_thread.clone())
1440                        .into_content();
1441
1442                    let pending = selector.handle_new_receipt(myself, &receipt_event);
1443                    assert!(pending.is_empty());
1444
1445                    let best_receipt = selector.select();
1446                    assert_eq!(best_receipt.unwrap().event_id, event_id!("$3"));
1447                }
1448
1449                {
1450                    // Receipt for an event we knew about, initial active receipt was better => no
1451                    // better receipt.
1452                    let mut selector = ReceiptSelector::new(&events, Some(event_id!("$4")));
1453
1454                    let receipt_event = f
1455                        .read_receipts()
1456                        .add(event_id!("$3"), myself, receipt_type.clone(), receipt_thread.clone())
1457                        .into_content();
1458
1459                    let pending = selector.handle_new_receipt(myself, &receipt_event);
1460                    assert!(pending.is_empty());
1461
1462                    let best_receipt = selector.select();
1463                    assert!(best_receipt.is_none());
1464                }
1465
1466                {
1467                    // Receipt for an event we knew about, initial active receipt was less good =>
1468                    // new better receipt.
1469                    let mut selector = ReceiptSelector::new(&events, Some(event_id!("$2")));
1470
1471                    let receipt_event = f
1472                        .read_receipts()
1473                        .add(event_id!("$3"), myself, receipt_type.clone(), receipt_thread.clone())
1474                        .into_content();
1475
1476                    let pending = selector.handle_new_receipt(myself, &receipt_event);
1477                    assert!(pending.is_empty());
1478
1479                    let best_receipt = selector.select();
1480                    assert_eq!(best_receipt.unwrap().event_id, event_id!("$3"));
1481                }
1482            }
1483        } // end for
1484
1485        {
1486            // Final boss: multiple receipts in the receipt event, the best one is used =>
1487            // new better receipt.
1488            let mut selector = ReceiptSelector::new(&events, Some(event_id!("$2")));
1489
1490            let receipt_event = f
1491                .read_receipts()
1492                .add(event_id!("$4"), myself, ReceiptType::ReadPrivate, ReceiptThread::Unthreaded)
1493                .add(event_id!("$6"), myself, ReceiptType::ReadPrivate, ReceiptThread::Main)
1494                .add(event_id!("$3"), myself, ReceiptType::Read, ReceiptThread::Main)
1495                .into_content();
1496
1497            let pending = selector.handle_new_receipt(myself, &receipt_event);
1498            assert_eq!(pending.len(), 1);
1499            assert_eq!(pending[0], event_id!("$6"));
1500
1501            let best_receipt = selector.select();
1502            assert_eq!(best_receipt.unwrap().event_id, event_id!("$4"));
1503        }
1504    }
1505
1506    #[test]
1507    fn test_try_match_implicit() {
1508        let myself = owned_user_id!("@alice:example.org");
1509        let bob = user_id!("@bob:example.org");
1510
1511        let mut events = make_test_events(bob);
1512
1513        // When the selector sees only other users' events,
1514        let mut selector = ReceiptSelector::new(&events, None);
1515        // And I search for my implicit read receipt,
1516        selector.try_match_implicit(&myself, &events);
1517        // Then I don't find any.
1518        let best_receipt = selector.select();
1519        assert!(best_receipt.is_none());
1520
1521        // Now, if there are events I've written too...
1522        let f = EventFactory::new();
1523        events.push(
1524            f.text_msg("A mulatto, an albino")
1525                .sender(&myself)
1526                .event_id(event_id!("$6"))
1527                .into_event(),
1528        );
1529        events.push(
1530            f.text_msg("A mosquito, my libido").sender(bob).event_id(event_id!("$7")).into_event(),
1531        );
1532
1533        let mut selector = ReceiptSelector::new(&events, None);
1534        // And I search for my implicit read receipt,
1535        selector.try_match_implicit(&myself, &events);
1536        // Then my last sent event counts as a read receipt.
1537        let best_receipt = selector.select();
1538        assert_eq!(best_receipt.unwrap().event_id, event_id!("$6"));
1539    }
1540
1541    #[test]
1542    fn test_compute_unread_counts_with_implicit_receipt() {
1543        let user_id = user_id!("@alice:example.org");
1544        let bob = user_id!("@bob:example.org");
1545        let room_id = room_id!("!room:example.org");
1546
1547        // Given a set of events sent by Bob,
1548        let mut events = make_test_events(bob);
1549
1550        // One by me,
1551        let f = EventFactory::new();
1552        events.push(
1553            f.text_msg("A mulatto, an albino")
1554                .sender(user_id)
1555                .event_id(event_id!("$6"))
1556                .into_event(),
1557        );
1558
1559        // And others by Bob,
1560        events.push(
1561            f.text_msg("A mosquito, my libido").sender(bob).event_id(event_id!("$7")).into_event(),
1562        );
1563        events.push(
1564            f.text_msg("A denial, a denial").sender(bob).event_id(event_id!("$8")).into_event(),
1565        );
1566
1567        let events: Vec<_> = events.into_iter().collect();
1568
1569        // I have a read receipt attached to one of Bob's event sent before my message,
1570        let receipt_event = f
1571            .read_receipts()
1572            .add(event_id!("$3"), user_id, ReceiptType::Read, ReceiptThread::Unthreaded)
1573            .into_content();
1574
1575        let mut read_receipts = RoomReadReceipts::default();
1576
1577        // And I compute the unread counts for all those new events (no previous events
1578        // in that room),
1579        compute_unread_counts(
1580            user_id,
1581            room_id,
1582            Some(&receipt_event),
1583            Vec::new(),
1584            &events,
1585            &mut read_receipts,
1586            ThreadingSupport::Disabled,
1587        );
1588
1589        // Only the last two events sent by Bob count as unread.
1590        assert_eq!(read_receipts.num_unread, 2);
1591
1592        // There are no pending receipts.
1593        assert!(read_receipts.pending.is_empty());
1594
1595        // And the active receipt is the implicit one on my event.
1596        assert_eq!(read_receipts.latest_active.unwrap().event_id, event_id!("$6"));
1597    }
1598
1599    #[test]
1600    fn test_compute_unread_counts_with_threading_enabled() {
1601        fn make_event(user_id: &UserId, thread_root: &EventId) -> TimelineEvent {
1602            EventFactory::new()
1603                .text_msg("A")
1604                .sender(user_id)
1605                .event_id(event_id!("$ida"))
1606                .in_thread(thread_root, event_id!("$latest_event"))
1607                .into_event()
1608        }
1609
1610        let mut receipts = RoomReadReceipts::default();
1611
1612        let own_alice = user_id!("@alice:example.org");
1613        let bob = user_id!("@bob:example.org");
1614
1615        // Threaded messages from myself or other users shouldn't change the
1616        // unread counts.
1617        receipts.process_event(
1618            &make_event(own_alice, event_id!("$some_thread_root")),
1619            own_alice,
1620            ThreadingSupport::Enabled,
1621        );
1622        receipts.process_event(
1623            &make_event(own_alice, event_id!("$some_other_thread_root")),
1624            own_alice,
1625            ThreadingSupport::Enabled,
1626        );
1627
1628        receipts.process_event(
1629            &make_event(bob, event_id!("$some_thread_root")),
1630            own_alice,
1631            ThreadingSupport::Enabled,
1632        );
1633        receipts.process_event(
1634            &make_event(bob, event_id!("$some_other_thread_root")),
1635            own_alice,
1636            ThreadingSupport::Enabled,
1637        );
1638
1639        assert_eq!(receipts.num_unread, 0);
1640        assert_eq!(receipts.num_mentions, 0);
1641        assert_eq!(receipts.num_notifications, 0);
1642
1643        // Processing an unthreaded message should still count as unread.
1644        receipts.process_event(
1645            &EventFactory::new().text_msg("A").sender(bob).event_id(event_id!("$ida")).into_event(),
1646            own_alice,
1647            ThreadingSupport::Enabled,
1648        );
1649
1650        assert_eq!(receipts.num_unread, 1);
1651        assert_eq!(receipts.num_mentions, 0);
1652        assert_eq!(receipts.num_notifications, 0);
1653    }
1654}