atagen

macltok

public
Star0
Watch0
5b585cd970fd8c14c399754bfd12500c872c8c08
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
tests/mentions_late_decryption.rsRust207 lines6.7 KB
1
mod support;
2
3
use std::sync::{
4
    Arc,
5
    atomic::{AtomicUsize, Ordering},
6
};
7
use std::time::Duration;
8
9
use macltok::{Engine, EngineConfig, Event, EventStream, MentionsPage};
10
use matrix_sdk::{
11
    ruma::{RoomVersionId, device_id, event_id, room_id, user_id},
12
    test_utils::mocks::MatrixMockServer,
13
};
14
use matrix_sdk_test::{JoinedRoomBuilder, event_factory::EventFactory};
15
use serde_json::{Value, json};
16
use support::shut_down;
17
use wiremock::{
18
    Mock, Request, Respond, ResponseTemplate,
19
    matchers::{method, path},
20
};
21
22
const SLIDING_SYNC_PATH: &str = "/_matrix/client/unstable/org.matrix.simplified_msc3575/sync";
23
24
struct EncryptedSlidingFeed {
25
    room_id: String,
26
    event: Value,
27
    requests: Arc<AtomicUsize>,
28
}
29
30
impl Clone for EncryptedSlidingFeed {
31
    fn clone(&self) -> Self {
32
        Self {
33
            room_id: self.room_id.clone(),
34
            event: self.event.clone(),
35
            requests: self.requests.clone(),
36
        }
37
    }
38
}
39
40
impl EncryptedSlidingFeed {
41
    async fn mount(&self, server: &MatrixMockServer) {
42
        Mock::given(method("POST"))
43
            .and(path(SLIDING_SYNC_PATH))
44
            .respond_with(self.clone())
45
            .mount(server.server())
46
            .await;
47
    }
48
}
49
50
impl Respond for EncryptedSlidingFeed {
51
    fn respond(&self, _request: &Request) -> ResponseTemplate {
52
        let request = self.requests.fetch_add(1, Ordering::AcqRel);
53
        ResponseTemplate::new(200)
54
            .set_delay(Duration::from_millis(20))
55
            .set_body_json(json!({
56
                "txn_id": null,
57
                "pos": format!("late-decryption-{request}"),
58
                "lists": {},
59
                "rooms": {
60
                    (self.room_id.clone()): {
61
                        "initial": request == 0,
62
                        "required_state": [],
63
                        "timeline": [self.event.clone()],
64
                    }
65
                },
66
                "extensions": {},
67
            }))
68
    }
69
}
70
71
async fn mentions_page(
72
    events: &mut EventStream,
73
    room_id: &ruma::RoomId,
74
    predicate: impl Fn(&MentionsPage) -> bool,
75
) -> MentionsPage {
76
    tokio::time::timeout(Duration::from_secs(5), async {
77
        while let Some(event) = events.recv().await {
78
            match event {
79
                Event::TimelineReset { timeline_id, .. } if timeline_id.room_id() == room_id => {
80
                    panic!("the unopened room produced a timeline reset");
81
                }
82
                Event::MentionsChanged(update) if predicate(&update.snapshot) => {
83
                    return update.snapshot;
84
                }
85
                _ => {}
86
            }
87
        }
88
        panic!("event stream closed before the expected mentions update");
89
    })
90
    .await
91
    .expect("expected mentions update did not arrive")
92
}
93
94
async fn wait_for_requests(feed: &EncryptedSlidingFeed, minimum: usize) {
95
    tokio::time::timeout(Duration::from_secs(5), async {
96
        while feed.requests.load(Ordering::Acquire) < minimum {
97
            tokio::task::yield_now().await;
98
        }
99
    })
100
    .await
101
    .expect("the sliding sync feed did not receive another request");
102
}
103
104
#[tokio::test]
105
async fn late_room_key_updates_the_production_mentions_inbox_once() {
106
    let server = MatrixMockServer::new().await;
107
    server.mock_crypto_endpoints_preset().await;
108
    let alice_id = user_id!("@alice:localhost");
109
    let bob_id = user_id!("@bob:localhost");
110
    let room_id = room_id!("!mentions-late-decryption:localhost");
111
    let event_id = event_id!("$late-production-mention");
112
    let alice = server
113
        .client_builder_for_crypto_end_to_end(alice_id, device_id!("ALICEDEVICE"))
114
        .build()
115
        .await;
116
    let bob = server
117
        .client_builder_for_crypto_end_to_end(bob_id, device_id!("BOBDEVICE"))
118
        .build()
119
        .await;
120
    bob.event_cache().subscribe().unwrap();
121
    server.exchange_e2ee_identities(&alice, &bob).await;
122
123
    let factory = EventFactory::new().room(room_id).sender(alice_id);
124
    let joined = JoinedRoomBuilder::new(room_id)
125
        .add_state_event(factory.create(alice_id, RoomVersionId::V1))
126
        .add_state_event(factory.room_encryption())
127
        .add_state_event(factory.member(alice_id))
128
        .add_state_event(factory.member(bob_id));
129
    server.sync_room(&alice, joined.clone()).await;
130
    server.sync_room(&bob, joined).await;
131
132
    let room = alice.get_room(room_id).unwrap();
133
    let (encrypted_event, send) = server.mock_room_send().ok_with_capture(event_id, alice_id);
134
    let _send = send.mock_once().mount_as_scoped().await;
135
    let (_keys, room_key) = server.mock_capture_put_to_device(alice_id).await;
136
    server
137
        .mock_get_members()
138
        .ok(vec![
139
            factory.member(alice_id).into_raw(),
140
            factory.member(bob_id).into_raw(),
141
        ])
142
        .mock_once()
143
        .mount()
144
        .await;
145
    room.send_raw(
146
        "m.room.message",
147
        json!({
148
            "msgtype": "m.text",
149
            "body": "a private mention",
150
            "m.mentions": { "user_ids": [bob_id] }
151
        }),
152
    )
153
    .await
154
    .unwrap();
155
    let encrypted_event = encrypted_event.await.unwrap();
156
    let room_key = room_key.await;
157
    let encrypted_event: Value = serde_json::from_str(encrypted_event.json().get()).unwrap();
158
159
    let feed = EncryptedSlidingFeed {
160
        room_id: room_id.to_string(),
161
        event: encrypted_event,
162
        requests: Arc::new(AtomicUsize::new(0)),
163
    };
164
    feed.mount(&server).await;
165
166
    let (engine, handle, mut events) = Engine::from_client(
167
        bob.clone(),
168
        EngineConfig::default().with_initial_history_limit(0),
169
    )
170
    .await
171
    .unwrap();
172
    let running = tokio::spawn(engine.run());
173
174
    let unresolved = mentions_page(&mut events, room_id, |page| {
175
        page.coverage.unresolved_candidates == 1
176
    })
177
    .await;
178
    assert!(unresolved.entries.is_empty());
179
180
    server
181
        .mock_sync()
182
        .ok_and_run(&bob, |sync| {
183
            sync.add_to_device_event(room_key.deserialize_as().unwrap());
184
        })
185
        .await;
186
187
    let decrypted = mentions_page(&mut events, room_id, |page| {
188
        page.entries.len() == 1 && page.entries[0].key.event_id == event_id
189
    })
190
    .await;
191
    assert_eq!(decrypted.entries.len(), 1);
192
    assert_eq!(decrypted.entries[0].key.event_id, event_id);
193
    assert_eq!(decrypted.coverage.unresolved_candidates, 0);
194
195
    let replay_start = feed.requests.load(Ordering::Acquire);
196
    wait_for_requests(&feed, replay_start + 3).await;
197
    let replayed = handle.mentions_page(None, 50).await.unwrap();
198
    assert_eq!(replayed.entries.len(), 1);
199
    assert_eq!(replayed.entries[0].key.event_id, event_id);
200
    while let Ok(event) = events.try_recv() {
201
        if let Event::TimelineReset { timeline_id, .. } = event {
202
            assert_ne!(timeline_id.room_id(), room_id);
203
        }
204
    }
205
206
    shut_down(&handle, &mut events, running).await;
207
}