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    EventId, OwnedEventId, OwnedUserId, RoomId, UserId,
131    events::{
132        AnySyncMessageLikeEvent, AnySyncTimelineEvent, OriginalSyncMessageLikeEvent,
133        SyncMessageLikeEvent,
134        poll::{start::PollStartEventContent, unstable_start::UnstablePollStartEventContent},
135        receipt::{ReceiptEventContent, ReceiptThread, ReceiptType},
136        room::message::Relation,
137    },
138    serde::Raw,
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                && event_id == receipt_event_id
269            {
270                // Bingo! Switch over to the counting state, after resetting the
271                // previous counts.
272                trace!("Found the event the receipt was referring to! Starting to count.");
273                self.reset();
274                counting_receipts = true;
275                continue;
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(receipts) = receipts.get(&ty)
391                    && let Some(receipt) = receipts.get(user_id)
392                    && matches!(receipt.thread, ReceiptThread::Main | ReceiptThread::Unthreaded)
393                {
394                    trace!(%event_id, "found new candidate");
395                    if let Some(event_pos) = self.event_id_to_pos.get(event_id) {
396                        self.try_select_later(event_id, *event_pos);
397                    } else {
398                        // It's a new pending receipt.
399                        trace!(%event_id, "stashed as pending");
400                        pending.push(event_id.clone());
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        EventId, UserId, 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,
642    };
643
644    use super::compute_unread_counts;
645    use crate::{
646        ThreadingSupport,
647        read_receipts::{ReceiptSelector, RoomReadReceipts, marks_as_unread},
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!(
809            receipts.find_and_process_events(ev0, user_id, &[], ThreadingSupport::Disabled).not()
810        );
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!(
832            receipts
833                .find_and_process_events(
834                    ev0,
835                    user_id,
836                    &[make_event(event_id!("$1"))],
837                    ThreadingSupport::Disabled
838                )
839                .not()
840        );
841        assert_eq!(receipts.num_unread, 42);
842        assert_eq!(receipts.num_notifications, 13);
843        assert_eq!(receipts.num_mentions, 37);
844
845        // When provided with one event that's the receipt target, we find it, reset the
846        // count, and since there's nothing else, we stop there and end up with
847        // zero counts.
848        let mut receipts = RoomReadReceipts {
849            num_unread: 42,
850            num_notifications: 13,
851            num_mentions: 37,
852            ..Default::default()
853        };
854        assert!(receipts.find_and_process_events(
855            ev0,
856            user_id,
857            &[make_event(ev0)],
858            ThreadingSupport::Disabled
859        ),);
860        assert_eq!(receipts.num_unread, 0);
861        assert_eq!(receipts.num_notifications, 0);
862        assert_eq!(receipts.num_mentions, 0);
863
864        // When provided with multiple events and not the receipt event, we do not count
865        // anything..
866        let mut receipts = RoomReadReceipts {
867            num_unread: 42,
868            num_notifications: 13,
869            num_mentions: 37,
870            ..Default::default()
871        };
872        assert!(
873            receipts
874                .find_and_process_events(
875                    ev0,
876                    user_id,
877                    &[
878                        make_event(event_id!("$1")),
879                        make_event(event_id!("$2")),
880                        make_event(event_id!("$3"))
881                    ],
882                    ThreadingSupport::Disabled
883                )
884                .not()
885        );
886        assert_eq!(receipts.num_unread, 42);
887        assert_eq!(receipts.num_notifications, 13);
888        assert_eq!(receipts.num_mentions, 37);
889
890        // When provided with multiple events including one that's the receipt event, we
891        // find it and count from it.
892        let mut receipts = RoomReadReceipts {
893            num_unread: 42,
894            num_notifications: 13,
895            num_mentions: 37,
896            ..Default::default()
897        };
898        assert!(receipts.find_and_process_events(
899            ev0,
900            user_id,
901            &[
902                make_event(event_id!("$1")),
903                make_event(ev0),
904                make_event(event_id!("$2")),
905                make_event(event_id!("$3"))
906            ],
907            ThreadingSupport::Disabled
908        ));
909        assert_eq!(receipts.num_unread, 2);
910        assert_eq!(receipts.num_notifications, 0);
911        assert_eq!(receipts.num_mentions, 0);
912
913        // Even if duplicates are present in the new events list, the count is correct.
914        let mut receipts = RoomReadReceipts {
915            num_unread: 42,
916            num_notifications: 13,
917            num_mentions: 37,
918            ..Default::default()
919        };
920        assert!(receipts.find_and_process_events(
921            ev0,
922            user_id,
923            &[
924                make_event(ev0),
925                make_event(event_id!("$1")),
926                make_event(ev0),
927                make_event(event_id!("$2")),
928                make_event(event_id!("$3"))
929            ],
930            ThreadingSupport::Disabled
931        ));
932        assert_eq!(receipts.num_unread, 2);
933        assert_eq!(receipts.num_notifications, 0);
934        assert_eq!(receipts.num_mentions, 0);
935    }
936
937    /// Smoke test for `compute_unread_counts`.
938    #[test]
939    fn test_basic_compute_unread_counts() {
940        let user_id = user_id!("@alice:example.org");
941        let other_user_id = user_id!("@bob:example.org");
942        let room_id = room_id!("!room:example.org");
943        let receipt_event_id = event_id!("$1");
944
945        let mut previous_events = Vec::new();
946
947        let f = EventFactory::new();
948        let ev1 = f.text_msg("A").sender(other_user_id).event_id(receipt_event_id).into_event();
949        let ev2 = f.text_msg("A").sender(other_user_id).event_id(event_id!("$2")).into_event();
950
951        let receipt_event = f
952            .read_receipts()
953            .add(receipt_event_id, user_id, ReceiptType::Read, ReceiptThread::Unthreaded)
954            .into_content();
955
956        let mut read_receipts = RoomReadReceipts::default();
957        compute_unread_counts(
958            user_id,
959            room_id,
960            Some(&receipt_event),
961            previous_events.clone(),
962            &[ev1.clone(), ev2.clone()],
963            &mut read_receipts,
964            ThreadingSupport::Disabled,
965        );
966
967        // It did find the receipt event (ev1).
968        assert_eq!(read_receipts.num_unread, 1);
969
970        // Receive the same receipt event, with a new sync event.
971        previous_events.push(ev1);
972        previous_events.push(ev2);
973
974        let new_event =
975            f.text_msg("A").sender(other_user_id).event_id(event_id!("$3")).into_event();
976        compute_unread_counts(
977            user_id,
978            room_id,
979            Some(&receipt_event),
980            previous_events,
981            &[new_event],
982            &mut read_receipts,
983            ThreadingSupport::Disabled,
984        );
985
986        // Only the new event should be added.
987        assert_eq!(read_receipts.num_unread, 2);
988    }
989
990    fn make_test_events(user_id: &UserId) -> Vec<TimelineEvent> {
991        let f = EventFactory::new().sender(user_id);
992        let ev1 = f.text_msg("With the lights out, it's less dangerous").event_id(event_id!("$1"));
993        let ev2 = f.text_msg("Here we are now, entertain us").event_id(event_id!("$2"));
994        let ev3 = f.text_msg("I feel stupid and contagious").event_id(event_id!("$3"));
995        let ev4 = f.text_msg("Here we are now, entertain us").event_id(event_id!("$4"));
996        let ev5 = f.text_msg("Hello, hello, hello, how low?").event_id(event_id!("$5"));
997        [ev1, ev2, ev3, ev4, ev5].into_iter().map(Into::into).collect()
998    }
999
1000    /// Test that when multiple receipts come in a single event, we can still
1001    /// find the latest one according to the sync order.
1002    #[test]
1003    fn test_compute_unread_counts_multiple_receipts_in_one_event() {
1004        let user_id = user_id!("@alice:example.org");
1005        let room_id = room_id!("!room:example.org");
1006
1007        let all_events = make_test_events(user_id!("@bob:example.org"));
1008        let head_events: Vec<_> = all_events.iter().take(2).cloned().collect();
1009        let tail_events: Vec<_> = all_events.iter().skip(2).cloned().collect();
1010
1011        // Given a receipt event marking events 1-3 as read using a combination of
1012        // different thread and privacy types,
1013        let f = EventFactory::new();
1014        for receipt_type_1 in &[ReceiptType::Read, ReceiptType::ReadPrivate] {
1015            for receipt_thread_1 in &[ReceiptThread::Unthreaded, ReceiptThread::Main] {
1016                for receipt_type_2 in &[ReceiptType::Read, ReceiptType::ReadPrivate] {
1017                    for receipt_thread_2 in &[ReceiptThread::Unthreaded, ReceiptThread::Main] {
1018                        let receipt_event = f
1019                            .read_receipts()
1020                            .add(
1021                                event_id!("$2"),
1022                                user_id,
1023                                receipt_type_1.clone(),
1024                                receipt_thread_1.clone(),
1025                            )
1026                            .add(
1027                                event_id!("$3"),
1028                                user_id,
1029                                receipt_type_2.clone(),
1030                                receipt_thread_2.clone(),
1031                            )
1032                            .add(
1033                                event_id!("$1"),
1034                                user_id,
1035                                receipt_type_1.clone(),
1036                                receipt_thread_2.clone(),
1037                            )
1038                            .into_content();
1039
1040                        // When I compute the notifications for this room (with no new events),
1041                        let mut read_receipts = RoomReadReceipts::default();
1042
1043                        compute_unread_counts(
1044                            user_id,
1045                            room_id,
1046                            Some(&receipt_event),
1047                            all_events.clone(),
1048                            &[],
1049                            &mut read_receipts,
1050                            ThreadingSupport::Disabled,
1051                        );
1052
1053                        assert!(
1054                            read_receipts != Default::default(),
1055                            "read receipts have been updated"
1056                        );
1057
1058                        // Then events 1-3 are considered read, but 4 and 5 are not.
1059                        assert_eq!(read_receipts.num_unread, 2);
1060                        assert_eq!(read_receipts.num_mentions, 0);
1061                        assert_eq!(read_receipts.num_notifications, 0);
1062
1063                        // And when I compute notifications again, with some old and new events,
1064                        let mut read_receipts = RoomReadReceipts::default();
1065                        compute_unread_counts(
1066                            user_id,
1067                            room_id,
1068                            Some(&receipt_event),
1069                            head_events.clone(),
1070                            &tail_events,
1071                            &mut read_receipts,
1072                            ThreadingSupport::Disabled,
1073                        );
1074
1075                        assert!(
1076                            read_receipts != Default::default(),
1077                            "read receipts have been updated"
1078                        );
1079
1080                        // Then events 1-3 are considered read, but 4 and 5 are not.
1081                        assert_eq!(read_receipts.num_unread, 2);
1082                        assert_eq!(read_receipts.num_mentions, 0);
1083                        assert_eq!(read_receipts.num_notifications, 0);
1084                    }
1085                }
1086            }
1087        }
1088    }
1089
1090    /// Updating the pending list should cause a change in the
1091    /// `RoomReadReceipts` fields, and `compute_unread_counts` should return
1092    /// true then.
1093    #[test]
1094    fn test_compute_unread_counts_updated_after_field_tracking() {
1095        let user_id = owned_user_id!("@alice:example.org");
1096        let room_id = room_id!("!room:example.org");
1097
1098        let events = make_test_events(user_id!("@bob:example.org"));
1099
1100        let receipt_event = EventFactory::new()
1101            .read_receipts()
1102            .add(event_id!("$6"), &user_id, ReceiptType::Read, ReceiptThread::Unthreaded)
1103            .into_content();
1104
1105        let mut read_receipts = RoomReadReceipts::default();
1106        assert!(read_receipts.pending.is_empty());
1107
1108        // Given a receipt event that contains a read receipt referring to an unknown
1109        // event, and some preexisting events with different ids,
1110        compute_unread_counts(
1111            &user_id,
1112            room_id,
1113            Some(&receipt_event),
1114            events,
1115            &[], // no new events
1116            &mut read_receipts,
1117            ThreadingSupport::Disabled,
1118        );
1119
1120        // Then there are no unread events,
1121        assert_eq!(read_receipts.num_unread, 0);
1122
1123        // And the event referred to by the read receipt is in the pending state.
1124        assert_eq!(read_receipts.pending.len(), 1);
1125        assert!(read_receipts.pending.iter().any(|ev| ev == event_id!("$6")));
1126    }
1127
1128    #[test]
1129    fn test_compute_unread_counts_limited_sync() {
1130        let user_id = owned_user_id!("@alice:example.org");
1131        let room_id = room_id!("!room:example.org");
1132
1133        let events = make_test_events(user_id!("@bob:example.org"));
1134
1135        let receipt_event = EventFactory::new()
1136            .read_receipts()
1137            .add(event_id!("$1"), &user_id, ReceiptType::Read, ReceiptThread::Unthreaded)
1138            .into_content();
1139
1140        // Sync with a read receipt *and* a single event that was already known: in that
1141        // case, only consider the new events in isolation, and compute the
1142        // correct count.
1143        let mut read_receipts = RoomReadReceipts::default();
1144        assert!(read_receipts.pending.is_empty());
1145
1146        let ev0 = events[0].clone();
1147
1148        compute_unread_counts(
1149            &user_id,
1150            room_id,
1151            Some(&receipt_event),
1152            events,
1153            &[ev0], // duplicate event!
1154            &mut read_receipts,
1155            ThreadingSupport::Disabled,
1156        );
1157
1158        // All events are unread, and there's no pending receipt.
1159        assert_eq!(read_receipts.num_unread, 0);
1160        assert!(read_receipts.pending.is_empty());
1161    }
1162
1163    #[test]
1164    fn test_receipt_selector_create_sync_index() {
1165        let uid = user_id!("@bob:example.org");
1166
1167        let events = make_test_events(uid);
1168
1169        // An event with no id.
1170        let ev6 = EventFactory::new().text_msg("yolo").sender(uid).no_event_id().into_event();
1171
1172        let index = ReceiptSelector::create_sync_index(events.iter().chain(&[ev6]));
1173
1174        assert_eq!(*index.get(event_id!("$1")).unwrap(), 0);
1175        assert_eq!(*index.get(event_id!("$2")).unwrap(), 1);
1176        assert_eq!(*index.get(event_id!("$3")).unwrap(), 2);
1177        assert_eq!(*index.get(event_id!("$4")).unwrap(), 3);
1178        assert_eq!(*index.get(event_id!("$5")).unwrap(), 4);
1179        assert_eq!(index.get(event_id!("$6")), None);
1180
1181        assert_eq!(index.len(), 5);
1182
1183        // Sync order are set according to the position in the vector.
1184        let index = ReceiptSelector::create_sync_index(
1185            [events[1].clone(), events[2].clone(), events[4].clone()].iter(),
1186        );
1187
1188        assert_eq!(*index.get(event_id!("$2")).unwrap(), 0);
1189        assert_eq!(*index.get(event_id!("$3")).unwrap(), 1);
1190        assert_eq!(*index.get(event_id!("$5")).unwrap(), 2);
1191
1192        assert_eq!(index.len(), 3);
1193    }
1194
1195    #[test]
1196    fn test_receipt_selector_try_select_later() {
1197        let events = make_test_events(user_id!("@bob:example.org"));
1198
1199        {
1200            // No initial active receipt, so the first receipt we get *will* win.
1201            let mut selector = ReceiptSelector::new(&[], None);
1202            selector.try_select_later(event_id!("$1"), 0);
1203            let best_receipt = selector.select();
1204            assert_eq!(best_receipt.unwrap().event_id, event_id!("$1"));
1205        }
1206
1207        {
1208            // $3 is at pos 2, $1 at position 0, so $3 wins => no new change.
1209            let mut selector = ReceiptSelector::new(&events, Some(event_id!("$3")));
1210            selector.try_select_later(event_id!("$1"), 0);
1211            let best_receipt = selector.select();
1212            assert!(best_receipt.is_none());
1213        }
1214
1215        {
1216            // The initial active receipt is returned, when it's part of the scanned
1217            // elements.
1218            let mut selector = ReceiptSelector::new(&events, Some(event_id!("$1")));
1219            selector.try_select_later(event_id!("$1"), 0);
1220            let best_receipt = selector.select();
1221            assert_eq!(best_receipt.unwrap().event_id, event_id!("$1"));
1222        }
1223
1224        {
1225            // $3 is at pos 2, $4 at position 3, so $4 wins.
1226            let mut selector = ReceiptSelector::new(&events, Some(event_id!("$3")));
1227            selector.try_select_later(event_id!("$4"), 3);
1228            let best_receipt = selector.select();
1229            assert_eq!(best_receipt.unwrap().event_id, event_id!("$4"));
1230        }
1231    }
1232
1233    #[test]
1234    fn test_receipt_selector_handle_pending_receipts_noop() {
1235        let sender = user_id!("@bob:example.org");
1236        let f = EventFactory::new().sender(sender);
1237        let ev1 = f.text_msg("yo").event_id(event_id!("$1")).into_event();
1238        let ev2 = f.text_msg("well?").event_id(event_id!("$2")).into_event();
1239        let events = &[ev1, ev2][..];
1240
1241        {
1242            // No pending receipt => no better receipt.
1243            let mut selector = ReceiptSelector::new(events, None);
1244
1245            let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1246            selector.handle_pending_receipts(&mut pending);
1247
1248            assert!(pending.is_empty());
1249
1250            let best_receipt = selector.select();
1251            assert!(best_receipt.is_none());
1252        }
1253
1254        {
1255            // No pending receipt, and there was an active last receipt => no better
1256            // receipt.
1257            let mut selector = ReceiptSelector::new(events, Some(event_id!("$1")));
1258
1259            let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1260            selector.handle_pending_receipts(&mut pending);
1261
1262            assert!(pending.is_empty());
1263
1264            let best_receipt = selector.select();
1265            assert!(best_receipt.is_none());
1266        }
1267    }
1268
1269    #[test]
1270    fn test_receipt_selector_handle_pending_receipts_doesnt_match_known_events() {
1271        let sender = user_id!("@bob:example.org");
1272        let f = EventFactory::new().sender(sender);
1273        let ev1 = f.text_msg("yo").event_id(event_id!("$1")).into_event();
1274        let ev2 = f.text_msg("well?").event_id(event_id!("$2")).into_event();
1275        let events = &[ev1, ev2][..];
1276
1277        {
1278            // A pending receipt for an event that is still missing => no better receipt.
1279            let mut selector = ReceiptSelector::new(events, None);
1280
1281            let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1282            pending.push(owned_event_id!("$3"));
1283            selector.handle_pending_receipts(&mut pending);
1284
1285            assert_eq!(pending.len(), 1);
1286
1287            let best_receipt = selector.select();
1288            assert!(best_receipt.is_none());
1289        }
1290
1291        {
1292            // Ditto but there was an active receipt => no better receipt.
1293            let mut selector = ReceiptSelector::new(events, Some(event_id!("$1")));
1294
1295            let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1296            pending.push(owned_event_id!("$3"));
1297            selector.handle_pending_receipts(&mut pending);
1298
1299            assert_eq!(pending.len(), 1);
1300
1301            let best_receipt = selector.select();
1302            assert!(best_receipt.is_none());
1303        }
1304    }
1305
1306    #[test]
1307    fn test_receipt_selector_handle_pending_receipts_matches_known_events_no_initial() {
1308        let sender = user_id!("@bob:example.org");
1309        let f = EventFactory::new().sender(sender);
1310        let ev1 = f.text_msg("yo").event_id(event_id!("$1")).into_event();
1311        let ev2 = f.text_msg("well?").event_id(event_id!("$2")).into_event();
1312        let events = &[ev1, ev2][..];
1313
1314        {
1315            // A pending receipt for an event that is present => better receipt.
1316            let mut selector = ReceiptSelector::new(events, None);
1317
1318            let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1319            pending.push(owned_event_id!("$2"));
1320            selector.handle_pending_receipts(&mut pending);
1321
1322            // The receipt for $2 has been found.
1323            assert!(pending.is_empty());
1324
1325            // The new receipt has been returned.
1326            let best_receipt = selector.select();
1327            assert_eq!(best_receipt.unwrap().event_id, event_id!("$2"));
1328        }
1329
1330        {
1331            // Mixed found and not found receipt => better receipt.
1332            let mut selector = ReceiptSelector::new(events, None);
1333
1334            let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1335            pending.push(owned_event_id!("$1"));
1336            pending.push(owned_event_id!("$3"));
1337            selector.handle_pending_receipts(&mut pending);
1338
1339            // The receipt for $1 has been found, but not that for $3.
1340            assert_eq!(pending.len(), 1);
1341            assert!(pending.iter().any(|ev| ev == event_id!("$3")));
1342
1343            let best_receipt = selector.select();
1344            assert_eq!(best_receipt.unwrap().event_id, event_id!("$1"));
1345        }
1346    }
1347
1348    #[test]
1349    fn test_receipt_selector_handle_pending_receipts_matches_known_events_with_initial() {
1350        let sender = user_id!("@bob:example.org");
1351        let f = EventFactory::new().sender(sender);
1352        let ev1 = f.text_msg("yo").event_id(event_id!("$1")).into_event();
1353        let ev2 = f.text_msg("well?").event_id(event_id!("$2")).into_event();
1354        let events = &[ev1, ev2][..];
1355
1356        {
1357            // Same, and there was an initial receipt that was less good than the one we
1358            // selected => better receipt.
1359            let mut selector = ReceiptSelector::new(events, Some(event_id!("$1")));
1360
1361            let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1362            pending.push(owned_event_id!("$2"));
1363            selector.handle_pending_receipts(&mut pending);
1364
1365            // The receipt for $2 has been found.
1366            assert!(pending.is_empty());
1367
1368            // The new receipt has been returned.
1369            let best_receipt = selector.select();
1370            assert_eq!(best_receipt.unwrap().event_id, event_id!("$2"));
1371        }
1372
1373        {
1374            // Same, but the previous receipt was better => no better receipt.
1375            let mut selector = ReceiptSelector::new(events, Some(event_id!("$2")));
1376
1377            let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1378            pending.push(owned_event_id!("$1"));
1379            selector.handle_pending_receipts(&mut pending);
1380
1381            // The receipt for $1 has been found.
1382            assert!(pending.is_empty());
1383
1384            let best_receipt = selector.select();
1385            assert!(best_receipt.is_none());
1386        }
1387    }
1388
1389    #[test]
1390    fn test_receipt_selector_handle_new_receipt() {
1391        let myself = user_id!("@alice:example.org");
1392        let events = make_test_events(user_id!("@bob:example.org"));
1393
1394        let f = EventFactory::new();
1395        {
1396            // Thread receipts are ignored.
1397            let mut selector = ReceiptSelector::new(&events, None);
1398
1399            let receipt_event = f
1400                .read_receipts()
1401                .add(
1402                    event_id!("$5"),
1403                    myself,
1404                    ReceiptType::Read,
1405                    ReceiptThread::Thread(owned_event_id!("$2")),
1406                )
1407                .into_content();
1408
1409            let pending = selector.handle_new_receipt(myself, &receipt_event);
1410            assert!(pending.is_empty());
1411
1412            let best_receipt = selector.select();
1413            assert!(best_receipt.is_none());
1414        }
1415
1416        for receipt_type in [ReceiptType::Read, ReceiptType::ReadPrivate] {
1417            for receipt_thread in [ReceiptThread::Main, ReceiptThread::Unthreaded] {
1418                {
1419                    // Receipt for an event we don't know about => it's pending, and no better
1420                    // receipt.
1421                    let mut selector = ReceiptSelector::new(&events, None);
1422
1423                    let receipt_event = f
1424                        .read_receipts()
1425                        .add(event_id!("$6"), myself, receipt_type.clone(), receipt_thread.clone())
1426                        .into_content();
1427
1428                    let pending = selector.handle_new_receipt(myself, &receipt_event);
1429                    assert_eq!(pending[0], event_id!("$6"));
1430                    assert_eq!(pending.len(), 1);
1431
1432                    let best_receipt = selector.select();
1433                    assert!(best_receipt.is_none());
1434                }
1435
1436                {
1437                    // Receipt for an event we knew about, no initial active receipt => better
1438                    // receipt.
1439                    let mut selector = ReceiptSelector::new(&events, None);
1440
1441                    let receipt_event = f
1442                        .read_receipts()
1443                        .add(event_id!("$3"), myself, receipt_type.clone(), receipt_thread.clone())
1444                        .into_content();
1445
1446                    let pending = selector.handle_new_receipt(myself, &receipt_event);
1447                    assert!(pending.is_empty());
1448
1449                    let best_receipt = selector.select();
1450                    assert_eq!(best_receipt.unwrap().event_id, event_id!("$3"));
1451                }
1452
1453                {
1454                    // Receipt for an event we knew about, initial active receipt was better => no
1455                    // better receipt.
1456                    let mut selector = ReceiptSelector::new(&events, Some(event_id!("$4")));
1457
1458                    let receipt_event = f
1459                        .read_receipts()
1460                        .add(event_id!("$3"), myself, receipt_type.clone(), receipt_thread.clone())
1461                        .into_content();
1462
1463                    let pending = selector.handle_new_receipt(myself, &receipt_event);
1464                    assert!(pending.is_empty());
1465
1466                    let best_receipt = selector.select();
1467                    assert!(best_receipt.is_none());
1468                }
1469
1470                {
1471                    // Receipt for an event we knew about, initial active receipt was less good =>
1472                    // new better receipt.
1473                    let mut selector = ReceiptSelector::new(&events, Some(event_id!("$2")));
1474
1475                    let receipt_event = f
1476                        .read_receipts()
1477                        .add(event_id!("$3"), myself, receipt_type.clone(), receipt_thread.clone())
1478                        .into_content();
1479
1480                    let pending = selector.handle_new_receipt(myself, &receipt_event);
1481                    assert!(pending.is_empty());
1482
1483                    let best_receipt = selector.select();
1484                    assert_eq!(best_receipt.unwrap().event_id, event_id!("$3"));
1485                }
1486            }
1487        } // end for
1488
1489        {
1490            // Final boss: multiple receipts in the receipt event, the best one is used =>
1491            // new better receipt.
1492            let mut selector = ReceiptSelector::new(&events, Some(event_id!("$2")));
1493
1494            let receipt_event = f
1495                .read_receipts()
1496                .add(event_id!("$4"), myself, ReceiptType::ReadPrivate, ReceiptThread::Unthreaded)
1497                .add(event_id!("$6"), myself, ReceiptType::ReadPrivate, ReceiptThread::Main)
1498                .add(event_id!("$3"), myself, ReceiptType::Read, ReceiptThread::Main)
1499                .into_content();
1500
1501            let pending = selector.handle_new_receipt(myself, &receipt_event);
1502            assert_eq!(pending.len(), 1);
1503            assert_eq!(pending[0], event_id!("$6"));
1504
1505            let best_receipt = selector.select();
1506            assert_eq!(best_receipt.unwrap().event_id, event_id!("$4"));
1507        }
1508    }
1509
1510    #[test]
1511    fn test_try_match_implicit() {
1512        let myself = owned_user_id!("@alice:example.org");
1513        let bob = user_id!("@bob:example.org");
1514
1515        let mut events = make_test_events(bob);
1516
1517        // When the selector sees only other users' events,
1518        let mut selector = ReceiptSelector::new(&events, None);
1519        // And I search for my implicit read receipt,
1520        selector.try_match_implicit(&myself, &events);
1521        // Then I don't find any.
1522        let best_receipt = selector.select();
1523        assert!(best_receipt.is_none());
1524
1525        // Now, if there are events I've written too...
1526        let f = EventFactory::new();
1527        events.push(
1528            f.text_msg("A mulatto, an albino")
1529                .sender(&myself)
1530                .event_id(event_id!("$6"))
1531                .into_event(),
1532        );
1533        events.push(
1534            f.text_msg("A mosquito, my libido").sender(bob).event_id(event_id!("$7")).into_event(),
1535        );
1536
1537        let mut selector = ReceiptSelector::new(&events, None);
1538        // And I search for my implicit read receipt,
1539        selector.try_match_implicit(&myself, &events);
1540        // Then my last sent event counts as a read receipt.
1541        let best_receipt = selector.select();
1542        assert_eq!(best_receipt.unwrap().event_id, event_id!("$6"));
1543    }
1544
1545    #[test]
1546    fn test_compute_unread_counts_with_implicit_receipt() {
1547        let user_id = user_id!("@alice:example.org");
1548        let bob = user_id!("@bob:example.org");
1549        let room_id = room_id!("!room:example.org");
1550
1551        // Given a set of events sent by Bob,
1552        let mut events = make_test_events(bob);
1553
1554        // One by me,
1555        let f = EventFactory::new();
1556        events.push(
1557            f.text_msg("A mulatto, an albino")
1558                .sender(user_id)
1559                .event_id(event_id!("$6"))
1560                .into_event(),
1561        );
1562
1563        // And others by Bob,
1564        events.push(
1565            f.text_msg("A mosquito, my libido").sender(bob).event_id(event_id!("$7")).into_event(),
1566        );
1567        events.push(
1568            f.text_msg("A denial, a denial").sender(bob).event_id(event_id!("$8")).into_event(),
1569        );
1570
1571        let events: Vec<_> = events.into_iter().collect();
1572
1573        // I have a read receipt attached to one of Bob's event sent before my message,
1574        let receipt_event = f
1575            .read_receipts()
1576            .add(event_id!("$3"), user_id, ReceiptType::Read, ReceiptThread::Unthreaded)
1577            .into_content();
1578
1579        let mut read_receipts = RoomReadReceipts::default();
1580
1581        // And I compute the unread counts for all those new events (no previous events
1582        // in that room),
1583        compute_unread_counts(
1584            user_id,
1585            room_id,
1586            Some(&receipt_event),
1587            Vec::new(),
1588            &events,
1589            &mut read_receipts,
1590            ThreadingSupport::Disabled,
1591        );
1592
1593        // Only the last two events sent by Bob count as unread.
1594        assert_eq!(read_receipts.num_unread, 2);
1595
1596        // There are no pending receipts.
1597        assert!(read_receipts.pending.is_empty());
1598
1599        // And the active receipt is the implicit one on my event.
1600        assert_eq!(read_receipts.latest_active.unwrap().event_id, event_id!("$6"));
1601    }
1602
1603    #[test]
1604    fn test_compute_unread_counts_with_threading_enabled() {
1605        fn make_event(user_id: &UserId, thread_root: &EventId) -> TimelineEvent {
1606            EventFactory::new()
1607                .text_msg("A")
1608                .sender(user_id)
1609                .event_id(event_id!("$ida"))
1610                .in_thread(thread_root, event_id!("$latest_event"))
1611                .into_event()
1612        }
1613
1614        let mut receipts = RoomReadReceipts::default();
1615
1616        let own_alice = user_id!("@alice:example.org");
1617        let bob = user_id!("@bob:example.org");
1618
1619        // Threaded messages from myself or other users shouldn't change the
1620        // unread counts.
1621        receipts.process_event(
1622            &make_event(own_alice, event_id!("$some_thread_root")),
1623            own_alice,
1624            ThreadingSupport::Enabled { with_subscriptions: false },
1625        );
1626        receipts.process_event(
1627            &make_event(own_alice, event_id!("$some_other_thread_root")),
1628            own_alice,
1629            ThreadingSupport::Enabled { with_subscriptions: false },
1630        );
1631
1632        receipts.process_event(
1633            &make_event(bob, event_id!("$some_thread_root")),
1634            own_alice,
1635            ThreadingSupport::Enabled { with_subscriptions: false },
1636        );
1637        receipts.process_event(
1638            &make_event(bob, event_id!("$some_other_thread_root")),
1639            own_alice,
1640            ThreadingSupport::Enabled { with_subscriptions: false },
1641        );
1642
1643        assert_eq!(receipts.num_unread, 0);
1644        assert_eq!(receipts.num_mentions, 0);
1645        assert_eq!(receipts.num_notifications, 0);
1646
1647        // Processing an unthreaded message should still count as unread.
1648        receipts.process_event(
1649            &EventFactory::new().text_msg("A").sender(bob).event_id(event_id!("$ida")).into_event(),
1650            own_alice,
1651            ThreadingSupport::Enabled { with_subscriptions: false },
1652        );
1653
1654        assert_eq!(receipts.num_unread, 1);
1655        assert_eq!(receipts.num_mentions, 0);
1656        assert_eq!(receipts.num_notifications, 0);
1657    }
1658}