test(sdk): Test LatestEventValue is being updated.

This commit is contained in:
Ivan Enderlin
2025-07-07 14:30:37 +02:00
parent a7bef8870f
commit 1c549a3ca1
3 changed files with 490 additions and 20 deletions
@@ -153,7 +153,7 @@ impl LatestEventValue {
}
}
pub fn find_and_map(
fn find_and_map(
event: &Event,
power_levels: &Option<(&UserId, RoomPowerLevels)>,
) -> Option<LatestEventValue> {
@@ -239,3 +239,366 @@ pub fn find_and_map(
}
}
}
#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
use matrix_sdk_test::event_factory::EventFactory;
use ruma::{event_id, user_id};
use super::{find_and_map, LatestEventValue};
macro_rules! assert_latest_event_value {
( with | $event_factory:ident | $event_builder:block
it produces $match:pat ) => {
let user_id = user_id!("@mnt_io:matrix.org");
let event_factory = EventFactory::new().sender(user_id);
let event = {
let $event_factory = event_factory;
$event_builder
};
assert_matches!(find_and_map(&event, &None), $match);
};
}
#[test]
fn test_latest_event_value_room_message() {
assert_latest_event_value!(
with |event_factory| {
event_factory.text_msg("hello").into_event()
}
it produces Some(LatestEventValue::RoomMessage(_))
);
}
#[test]
fn test_latest_event_value_room_message_redacted() {
assert_latest_event_value!(
with |event_factory| {
event_factory
.redacted(
user_id!("@mnt_io:matrix.org"),
ruma::events::room::message::RedactedRoomMessageEventContent::new()
)
.into_event()
}
it produces Some(LatestEventValue::RoomMessage(_))
);
}
#[test]
fn test_latest_event_value_room_message_replacement() {
assert_latest_event_value!(
with |event_factory| {
event_factory
.text_msg("bonjour")
.edit(
event_id!("$ev0"),
ruma::events::room::message::RoomMessageEventContent::text_plain("hello").into()
)
.into_event()
}
it produces None
);
}
#[test]
fn test_latest_event_value_poll() {
assert_latest_event_value!(
with |event_factory| {
event_factory
.poll_start(
"the people need to know",
"comté > gruyère",
vec!["yes", "oui"]
)
.into_event()
}
it produces Some(LatestEventValue::Poll(_))
);
}
#[test]
fn test_latest_event_value_call_invite() {
assert_latest_event_value!(
with |event_factory| {
event_factory
.call_invite(
ruma::OwnedVoipId::from("vvooiipp".to_owned()),
ruma::UInt::from(1234u32),
ruma::events::call::SessionDescription::new("type".to_owned(), "sdp".to_owned()),
ruma::VoipVersionId::V1,
)
.into_event()
}
it produces Some(LatestEventValue::CallInvite(_))
);
}
#[test]
fn test_latest_event_value_call_notify() {
assert_latest_event_value!(
with |event_factory| {
event_factory
.call_notify(
"call_id".to_owned(),
ruma::events::call::notify::ApplicationType::Call,
ruma::events::call::notify::NotifyType::Ring,
ruma::events::Mentions::new(),
)
.into_event()
}
it produces Some(LatestEventValue::CallNotify(_))
);
}
#[test]
fn test_latest_event_value_sticker() {
assert_latest_event_value!(
with |event_factory| {
event_factory
.sticker(
"wink wink",
ruma::events::room::ImageInfo::new(),
ruma::OwnedMxcUri::from("mxc://foo/bar")
)
.into_event()
}
it produces Some(LatestEventValue::Sticker(_))
);
}
#[test]
fn test_latest_event_value_encrypted_room_message() {
assert_latest_event_value!(
with |event_factory| {
event_factory
.event(ruma::events::room::encrypted::RoomEncryptedEventContent::new(
ruma::events::room::encrypted::EncryptedEventScheme::MegolmV1AesSha2(
ruma::events::room::encrypted::MegolmV1AesSha2ContentInit {
ciphertext: "cipher".to_owned(),
sender_key: "sender_key".to_owned(),
device_id: "device_id".into(),
session_id: "session_id".to_owned(),
}
.into(),
),
None,
))
.into_event()
}
it produces None
);
}
#[test]
fn test_latest_event_value_reaction() {
// Take a random message-like event.
assert_latest_event_value!(
with |event_factory| {
event_factory
.reaction(event_id!("$ev0"), "+1")
.into_event()
}
it produces None
);
}
#[test]
fn test_latest_event_state_event() {
assert_latest_event_value!(
with |event_factory| {
event_factory
.room_topic("new room topic")
.into_event()
}
it produces None
);
}
#[test]
fn test_latest_event_knocked_state_event_without_power_levels() {
assert_latest_event_value!(
with |event_factory| {
event_factory
.member(user_id!("@other_mnt_io:server.name"))
.membership(ruma::events::room::member::MembershipState::Knock)
.into_event()
}
it produces None
);
}
#[test]
fn test_latest_event_knocked_state_event_with_power_levels() {
use ruma::events::room::power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent};
let user_id = user_id!("@mnt_io:matrix.org");
let other_user_id = user_id!("@other_mnt_io:server.name");
let event_factory = EventFactory::new().sender(user_id);
let event = event_factory
.member(other_user_id)
.membership(ruma::events::room::member::MembershipState::Knock)
.into_event();
let room_power_levels_event = RoomPowerLevelsEventContent::new();
let mut room_power_levels = RoomPowerLevels::from(room_power_levels_event);
room_power_levels.users_default = 5.into();
// Cannot accept. Cannot decline.
{
let mut room_power_levels = room_power_levels.clone();
room_power_levels.invite = 10.into();
room_power_levels.kick = 10.into();
assert_matches!(
find_and_map(&event, &Some((user_id, room_power_levels))),
None,
"cannot accept, cannot decline",
);
}
// Can accept. Cannot decline.
{
let mut room_power_levels = room_power_levels.clone();
room_power_levels.invite = 0.into();
room_power_levels.kick = 10.into();
assert_matches!(
find_and_map(&event, &Some((user_id, room_power_levels))),
Some(LatestEventValue::KnockedStateEvent(_)),
"can accept, cannot decline",
);
}
// Cannot accept. Can decline.
{
let mut room_power_levels = room_power_levels.clone();
room_power_levels.invite = 10.into();
room_power_levels.kick = 0.into();
assert_matches!(
find_and_map(&event, &Some((user_id, room_power_levels))),
Some(LatestEventValue::KnockedStateEvent(_)),
"cannot accept, can decline",
);
}
// Can accept. Can decline.
{
room_power_levels.invite = 0.into();
room_power_levels.kick = 0.into();
assert_matches!(
find_and_map(&event, &Some((user_id, room_power_levels))),
Some(LatestEventValue::KnockedStateEvent(_)),
"can accept, can decline",
);
}
}
#[test]
fn test_latest_event_value_room_message_verification_request() {
assert_latest_event_value!(
with |event_factory| {
event_factory
.event(
ruma::events::room::message::RoomMessageEventContent::new(
ruma::events::room::message::MessageType::VerificationRequest(
ruma::events::room::message::KeyVerificationRequestEventContent::new(
"body".to_owned(),
vec![],
ruma::OwnedDeviceId::from("device_id"),
user_id!("@user:server.name").to_owned(),
)
)
)
)
.into_event()
}
it produces None
);
}
}
#[cfg(all(not(target_family = "wasm"), test))]
mod tests_non_wasm {
use assert_matches::assert_matches;
use matrix_sdk_test::{async_test, event_factory::EventFactory};
use ruma::{event_id, room_id, user_id};
use super::LatestEventValue;
use crate::test_utils::mocks::MatrixMockServer;
#[async_test]
async fn test_latest_event_value_is_scanning_event_backwards_from_event_cache() {
use matrix_sdk_base::{
linked_chunk::{ChunkIdentifier, Position, Update},
RoomState,
};
use crate::{client::WeakClient, room::WeakRoom};
let room_id = room_id!("!r0");
let user_id = user_id!("@mnt_io:matrix.org");
let event_factory = EventFactory::new().sender(user_id).room(room_id);
let event_id_0 = event_id!("$ev0");
let event_id_1 = event_id!("$ev1");
let event_id_2 = event_id!("$ev2");
let server = MatrixMockServer::new().await;
let client = server.client_builder().build().await;
// Prelude.
{
// Create the room.
client.base_client().get_or_create_room(room_id, RoomState::Joined);
// Initialise the event cache store.
client
.event_cache_store()
.lock()
.await
.unwrap()
.handle_linked_chunk_updates(
matrix_sdk_base::linked_chunk::LinkedChunkId::Room(room_id),
vec![
Update::NewItemsChunk {
previous: None,
new: ChunkIdentifier::new(0),
next: None,
},
Update::PushItems {
at: Position::new(ChunkIdentifier::new(0), 0),
items: vec![
// a latest event candidate
event_factory.text_msg("hello").event_id(event_id_0).into(),
// a latest event candidate
event_factory.text_msg("world").event_id(event_id_1).into(),
// not a latest event candidate
event_factory
.room_topic("new room topic")
.event_id(event_id_2)
.into(),
],
},
],
)
.await
.unwrap();
}
let event_cache = client.event_cache();
event_cache.subscribe().unwrap();
let (room_event_cache, _) = event_cache.for_room(room_id).await.unwrap();
let weak_room = WeakRoom::new(WeakClient::from_client(&client), room_id.to_owned());
assert_matches!(
LatestEventValue::new(room_id, None, &room_event_cache, &weak_room).await,
LatestEventValue::RoomMessage(given_event) => {
// We get `event_id_1` because `event_id_2` isn't a candidate,
// and `event_id_0` hasn't been read yet (because events are
// read backwards).
assert_eq!(given_event.event_id(), event_id_1);
}
);
}
}
+96 -16
View File
@@ -647,15 +647,20 @@ async fn compute_latest_events(registered_rooms: &RegisteredRooms, for_rooms: &[
mod tests {
use std::ops::Not;
use matrix_sdk_base::RoomState;
use matrix_sdk_test::async_test;
use ruma::{event_id, owned_room_id, room_id};
use assert_matches::assert_matches;
use matrix_sdk_base::{
linked_chunk::{ChunkIdentifier, LinkedChunkId, Position, Update},
RoomState,
};
use matrix_sdk_test::{async_test, event_factory::EventFactory, JoinedRoomBuilder};
use ruma::{event_id, owned_room_id, room_id, user_id};
use stream_assert::assert_pending;
use super::{
broadcast, compute_latest_events, listen_to_event_cache_and_send_queue_updates, mpsc,
HashSet, RegisteredRooms, RoomEventCacheGenericUpdate, RoomRegistration, WeakClient,
broadcast, listen_to_event_cache_and_send_queue_updates, mpsc, HashSet, LatestEventValue,
RoomEventCacheGenericUpdate, RoomRegistration,
};
use crate::test_utils::logged_in_client_with_server;
use crate::test_utils::mocks::MatrixMockServer;
#[async_test]
async fn test_latest_events_are_lazy() {
@@ -665,7 +670,8 @@ mod tests {
let thread_id_1_0 = event_id!("$ev1.0");
let thread_id_2_0 = event_id!("$ev2.0");
let (client, _server) = logged_in_client_with_server().await;
let server = MatrixMockServer::new().await;
let client = server.client_builder().build().await;
client.base_client().get_or_create_room(room_id_0, RoomState::Joined);
client.base_client().get_or_create_room(room_id_1, RoomState::Joined);
@@ -727,7 +733,8 @@ mod tests {
let room_id_0 = room_id!("!r0");
let room_id_1 = room_id!("!r1");
let (client, _server) = logged_in_client_with_server().await;
let server = MatrixMockServer::new().await;
let client = server.client_builder().build().await;
client.base_client().get_or_create_room(room_id_0, RoomState::Joined);
client.base_client().get_or_create_room(room_id_1, RoomState::Joined);
@@ -766,7 +773,8 @@ mod tests {
let room_id_1 = room_id!("!r1");
let thread_id_0_0 = event_id!("$ev0.0");
let (client, _server) = logged_in_client_with_server().await;
let server = MatrixMockServer::new().await;
let client = server.client_builder().build().await;
client.base_client().get_or_create_room(room_id_0, RoomState::Joined);
client.base_client().get_or_create_room(room_id_1, RoomState::Joined);
@@ -995,19 +1003,91 @@ mod tests {
}
#[async_test]
async fn test_compute_latest_events() {
async fn test_latest_event_value_is_updated_via_event_cache() {
let room_id = owned_room_id!("!r0");
let user_id = user_id!("@mnt_io:matrix.org");
let event_factory = EventFactory::new().sender(user_id).room(&room_id);
let event_id_0 = event_id!("$ev0");
let event_id_1 = event_id!("$ev1");
let event_id_2 = event_id!("$ev2");
let (client, _server) = logged_in_client_with_server().await;
let weak_client = WeakClient::from_client(&client);
let server = MatrixMockServer::new().await;
let client = server.client_builder().build().await;
// Prelude.
{
// Create the room.
client.base_client().get_or_create_room(&room_id, RoomState::Joined);
// Initialise the event cache store.
client
.event_cache_store()
.lock()
.await
.unwrap()
.handle_linked_chunk_updates(
LinkedChunkId::Room(&room_id),
vec![
Update::NewItemsChunk {
previous: None,
new: ChunkIdentifier::new(0),
next: None,
},
Update::PushItems {
at: Position::new(ChunkIdentifier::new(0), 0),
items: vec![
event_factory.text_msg("hello").event_id(event_id_0).into(),
event_factory.text_msg("world").event_id(event_id_1).into(),
],
},
],
)
.await
.unwrap();
}
let event_cache = client.event_cache();
event_cache.subscribe().unwrap();
let (room_registration_sender, _room_registration_receiver) = mpsc::channel(1);
let registered_rooms =
RegisteredRooms::new(room_registration_sender, weak_client, event_cache);
let latest_events = client.latest_events().await;
compute_latest_events(&registered_rooms, &[room_id]).await;
// Subscribe to the latest event values for this room.
let mut latest_event_stream =
latest_events.listen_and_subscribe_to_room(&room_id).await.unwrap().unwrap();
// The initial latest event value is set to `event_id_1` because it's… the…
// latest event!
assert_matches!(
latest_event_stream.get().await,
LatestEventValue::RoomMessage(event) => {
assert_eq!(event.event_id(), event_id_1);
}
);
// The stream is pending: no new latest event for the moment.
assert_pending!(latest_event_stream);
// Update the event cache with a sync.
server
.sync_room(
&client,
JoinedRoomBuilder::new(&room_id).add_timeline_event(
event_factory
.text_msg("venez découvrir cette nouvelle raclette !")
.event_id(event_id_2)
.into_raw(),
),
)
.await;
// The event cache has received its update from the sync. It has emitted a
// generic update, which has been received by `LatestEvents` tasks, up to the
// `compute_latest_events` which has updated the latest event value.
assert_matches!(
latest_event_stream.next().await,
Some(LatestEventValue::RoomMessage(event)) => {
assert_eq!(event.event_id(), event_id_2);
}
);
}
}
+30 -3
View File
@@ -26,6 +26,11 @@ use matrix_sdk_common::deserialized_responses::{
use ruma::{
events::{
beacon::BeaconEventContent,
call::{
invite::CallInviteEventContent,
notify::{ApplicationType, CallNotifyEventContent, NotifyType},
SessionDescription,
},
member_hints::MemberHintsEventContent,
poll::{
unstable_end::UnstablePollEndEventContent,
@@ -60,13 +65,13 @@ use ruma::{
sticker::StickerEventContent,
typing::TypingEventContent,
AnyMessageLikeEvent, AnyStateEvent, AnySyncStateEvent, AnySyncTimelineEvent,
AnyTimelineEvent, BundledMessageLikeRelations, RedactedMessageLikeEventContent,
AnyTimelineEvent, BundledMessageLikeRelations, Mentions, RedactedMessageLikeEventContent,
RedactedStateEventContent, StateEventContent, StaticEventContent,
},
serde::Raw,
server_name, EventId, Int, MilliSecondsSinceUnixEpoch, MxcUri, OwnedEventId, OwnedMxcUri,
OwnedRoomAliasId, OwnedRoomId, OwnedTransactionId, OwnedUserId, RoomId, RoomVersionId,
TransactionId, UInt, UserId,
OwnedRoomAliasId, OwnedRoomId, OwnedTransactionId, OwnedUserId, OwnedVoipId, RoomId,
RoomVersionId, TransactionId, UInt, UserId, VoipVersionId,
};
use serde::Serialize;
use serde_json::json;
@@ -928,6 +933,28 @@ impl EventFactory {
self.event(StickerEventContent::new(body.into(), info, url))
}
/// Create a new `m.call.invite` event.
pub fn call_invite(
&self,
call_id: OwnedVoipId,
lifetime: UInt,
offer: SessionDescription,
version: VoipVersionId,
) -> EventBuilder<CallInviteEventContent> {
self.event(CallInviteEventContent::new(call_id, lifetime, offer, version))
}
/// Create a new `m.call.notify` event.
pub fn call_notify(
&self,
call_id: String,
application: ApplicationType,
notify_type: NotifyType,
mentions: Mentions,
) -> EventBuilder<CallNotifyEventContent> {
self.event(CallNotifyEventContent::new(call_id, application, notify_type, mentions))
}
/// Set the next server timestamp.
///
/// Timestamps will continue to increase by 1 (millisecond) from that value.