Skip to content

Commit 8836004

Browse files
committed
test(timeline): Add some integration tests for the redecryption logic
These tests now fully mock all the end-to-end encryption server endpoints to test the redecryption and UTD item replacement logic of the timeline without any manual room key insertions. We test that the item replacement correctly handles supported event types as well as unsupported ones.
1 parent 4184e24 commit 8836004

File tree

3 files changed

+195
-0
lines changed

3 files changed

+195
-0
lines changed

crates/matrix-sdk-ui/tests/integration/timeline/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ mod profiles;
6060
mod queue;
6161
mod reactions;
6262
mod read_receipts;
63+
mod redecryption;
6364
mod replies;
6465
mod subscribe;
6566
mod thread;
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
use std::sync::Arc;
2+
3+
use assert_matches2::assert_matches;
4+
use eyeball_im::VectorDiff;
5+
use futures_util::pin_mut;
6+
use matrix_sdk::{
7+
assert_next_with_timeout, encryption::EncryptionSettings, test_utils::mocks::MatrixMockServer,
8+
};
9+
use matrix_sdk_test::{JoinedRoomBuilder, StateTestEvent, async_test, event_factory::EventFactory};
10+
use matrix_sdk_ui::timeline::{RoomExt, TimelineItem};
11+
use ruma::{device_id, event_id, room_id, user_id};
12+
use serde_json::{Value, json};
13+
14+
// Helper function to test the redecryption of different event types.
15+
async fn test_redecryption(
16+
event_type: &str,
17+
content: Value,
18+
assertion_function: impl FnOnce(&VectorDiff<Arc<TimelineItem>>),
19+
) {
20+
let room_id = room_id!("!test:localhost");
21+
22+
let alice_user_id = user_id!("@alice:localhost");
23+
let alice_device_id = device_id!("ALICEDEVICE");
24+
let bob_user_id = user_id!("@bob:localhost");
25+
let bob_device_id = device_id!("BOBDEVICE");
26+
27+
let matrix_mock_server = MatrixMockServer::new().await;
28+
matrix_mock_server.mock_crypto_endpoints_preset().await;
29+
30+
let encryption_settings =
31+
EncryptionSettings { auto_enable_cross_signing: true, ..Default::default() };
32+
33+
// First we create some clients.
34+
35+
let alice = matrix_mock_server
36+
.client_builder_for_crypto_end_to_end(alice_user_id, alice_device_id)
37+
.on_builder(|builder| {
38+
builder
39+
.with_enable_share_history_on_invite(true)
40+
.with_encryption_settings(encryption_settings)
41+
})
42+
.build()
43+
.await;
44+
45+
let bob = matrix_mock_server
46+
.client_builder_for_crypto_end_to_end(bob_user_id, bob_device_id)
47+
.on_builder(|builder| {
48+
builder
49+
.with_enable_share_history_on_invite(true)
50+
.with_encryption_settings(encryption_settings)
51+
})
52+
.build()
53+
.await;
54+
55+
// Ensure that Alice and Bob are aware of their devices and identities.
56+
matrix_mock_server.exchange_e2ee_identities(&alice, &bob).await;
57+
58+
let event_factory = EventFactory::new().room(room_id);
59+
let alice_member_event = event_factory.member(alice_user_id).into_raw();
60+
let bob_member_event = event_factory.member(bob_user_id).into_raw();
61+
62+
// Let us now create a room for them.
63+
let room_builder = JoinedRoomBuilder::new(room_id)
64+
.add_state_event(StateTestEvent::Create)
65+
.add_state_event(StateTestEvent::Encryption);
66+
67+
matrix_mock_server
68+
.mock_sync()
69+
.ok_and_run(&alice, |builder| {
70+
builder.add_joined_room(room_builder.clone());
71+
})
72+
.await;
73+
74+
matrix_mock_server
75+
.mock_sync()
76+
.ok_and_run(&bob, |builder| {
77+
builder.add_joined_room(room_builder);
78+
})
79+
.await;
80+
81+
let room =
82+
alice.get_room(room_id).expect("Alice should have access to the room now that we synced");
83+
84+
let bob_room = bob.get_room(room_id).expect("Bob should have access to the invited room");
85+
86+
// Alice will send a single event to the room, but this will trigger a to-device
87+
// message containing the room key to be sent as well. We capture both the event
88+
// and the to-device message.
89+
90+
let event_id = event_id!("$some_id");
91+
let (event_receiver, mock) =
92+
matrix_mock_server.mock_room_send().ok_with_capture(event_id, alice_user_id);
93+
let (_guard, room_key) = matrix_mock_server.mock_capture_put_to_device(alice_user_id).await;
94+
95+
{
96+
let _guard = mock.mock_once().mount_as_scoped().await;
97+
98+
matrix_mock_server
99+
.mock_get_members()
100+
.ok(vec![alice_member_event.clone(), bob_member_event.clone()])
101+
.mock_once()
102+
.mount()
103+
.await;
104+
105+
room.send_raw(event_type, content)
106+
.await
107+
.expect("We should be able to send an initial message");
108+
};
109+
110+
// Let's now see what Bob's timeline for the room does.
111+
112+
let timeline =
113+
bob_room.timeline().await.expect("We should be able to build a timeline for the room");
114+
let (initial, stream) = timeline.subscribe().await;
115+
assert!(initial.is_empty(), "Initially we have an empty timeline");
116+
pin_mut!(stream);
117+
118+
// Let us retrieve the captured event and to-device message.
119+
let event = event_receiver.await.expect("Alice should have sent the event by now");
120+
let room_key = room_key.await;
121+
122+
// Let us forward the event to Bob.
123+
matrix_mock_server
124+
.mock_sync()
125+
.ok_and_run(&bob, |builder| {
126+
builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_timeline_event(event));
127+
})
128+
.await;
129+
130+
// This triggers a timeline update with a pushed item, the item is a UTD.
131+
let updates = assert_next_with_timeout!(stream);
132+
let utd_item = &updates[0];
133+
134+
assert_matches!(utd_item, VectorDiff::PushBack { value });
135+
assert!(
136+
value.as_event().unwrap().content().is_unable_to_decrypt(),
137+
"Initially we should receive a UTD"
138+
);
139+
140+
// Now we send the room key to Bob.
141+
matrix_mock_server
142+
.mock_sync()
143+
.ok_and_run(&bob, |builder| {
144+
builder.add_to_device_event(
145+
room_key.deserialize_as().expect("We should be able to deserialize the room key"),
146+
);
147+
})
148+
.await;
149+
150+
// This should trigger a update.
151+
let updates = assert_next_with_timeout!(stream);
152+
let decrypted_item = &updates[0];
153+
154+
// Let us run the assertion function with the decrypted item provided by the
155+
// caller.
156+
assertion_function(decrypted_item);
157+
}
158+
159+
// Test ensuring that a late room key triggers a redecryption and the timeline
160+
// item containing a UTD gets successfully replaced.
161+
#[async_test]
162+
async fn test_redecryption_after_late_to_device() {
163+
test_redecryption(
164+
"m.room.message",
165+
json!({"body": "It's a secret to everybody", "msgtype": "m.text"}),
166+
|decrypted_item| {
167+
assert_matches!(decrypted_item, VectorDiff::Set { index: _, value });
168+
169+
let event = value.as_event().expect("The value should be an event");
170+
171+
let content = event.content();
172+
assert!(!content.is_unable_to_decrypt(), "The UTD should now have been resolved");
173+
174+
let content = content.as_message().expect("The event should contain a message");
175+
assert_eq!(content.body(), "It's a secret to everybody");
176+
},
177+
)
178+
.await;
179+
}
180+
181+
// Test ensuring that a late room key triggers a redecryption of unsupported
182+
// event type and the timeline item containing a UTD gets successfully removed.
183+
#[async_test]
184+
async fn test_redecryption_after_late_to_device_unsupported_event_type() {
185+
test_redecryption(
186+
"rust-sdk.custom.event",
187+
json!({"body": "It's a secret to everybody"}),
188+
|decrypted_item| {
189+
assert_matches!(decrypted_item, VectorDiff::Remove { index: _ });
190+
},
191+
)
192+
.await;
193+
}

testing/matrix-sdk-test/src/sync_builder/joined_room.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use serde_json::{Value as JsonValue, from_value as from_json_value};
1212
use super::RoomAccountDataTestEvent;
1313
use crate::{DEFAULT_TEST_ROOM_ID, event_factory::EventBuilder};
1414

15+
#[derive(Debug, Clone)]
1516
pub struct JoinedRoomBuilder {
1617
pub(super) room_id: OwnedRoomId,
1718
pub(super) inner: JoinedRoom,

0 commit comments

Comments
 (0)