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