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