atagen

macltok

public
Star0
Watch0
fa9114fcf75f7bdf1a7cec3b3367d604cf7de472
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
tests/tier0.rsRust401 lines13.1 KB
1
use std::time::Duration;
2
3
use macltok::{
4
    ClientHandle, ClientQueryError, Command, CommandErrorKind, CommandId, CommandKind,
5
    CreateRoomOptions, Engine, EngineConfig, EngineError, Event, EventStream, LifecycleCommand,
6
    RoomCommand, RoomPreset, RoomVisibility,
7
};
8
use matrix_sdk::{
9
    ruma::{
10
        api::client::sync::sync_events::v5,
11
        events::{GlobalAccountDataEventType, RoomAccountDataEventType},
12
        room_id,
13
        serde::Raw,
14
        server_name, user_id,
15
    },
16
    test_utils::mocks::MatrixMockServer,
17
};
18
use matrix_sdk_test::{InvitedRoomBuilder, JoinedRoomBuilder};
19
use serde_json::json;
20
use tokio::task::JoinHandle;
21
use wiremock::{
22
    Mock, ResponseTemplate,
23
    matchers::{body_partial_json, method, path, path_regex, query_param},
24
};
25
26
#[tokio::test]
27
async fn joining_by_room_id_forwards_via_servers() {
28
    let room_id = room_id!("!join:localhost");
29
    let (server, handle, mut events, running) = start_engine().await;
30
    Mock::given(method("POST"))
31
        .and(path(format!("/_matrix/client/v3/join/{room_id}")))
32
        .and(query_param("via", "relay.example"))
33
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({ "room_id": room_id })))
34
        .expect(1)
35
        .mount(server.server())
36
        .await;
37
38
    let joined_room = handle
39
        .join_room(
40
            room_id.to_owned().into(),
41
            vec![server_name!("relay.example").to_owned()],
42
        )
43
        .await
44
        .unwrap();
45
    assert_eq!(joined_room, room_id);
46
    stop_engine(handle, &mut events, running).await;
47
}
48
49
#[tokio::test]
50
async fn leave_and_forget_report_correlated_success() {
51
    let room_id = room_id!("!leave:localhost");
52
    let (server, client) = configured_client().await;
53
    server.sync_joined_room(&client, room_id).await;
54
    server.mock_room_leave().ok(room_id).expect(1).mount().await;
55
    server.mock_room_forget().ok().expect(1).mount().await;
56
    let (handle, mut events, running) = run_client(client).await;
57
58
    let leave_id = handle.leave_room(room_id.to_owned()).await.unwrap();
59
    receive_room_success(&mut events, leave_id).await;
60
    let forget_id = handle
61
        .send(Command::Room(RoomCommand::Forget(room_id.to_owned())))
62
        .await
63
        .unwrap();
64
    receive_room_success(&mut events, forget_id).await;
65
    assert_ne!(leave_id, forget_id);
66
    stop_engine(handle, &mut events, running).await;
67
}
68
69
#[tokio::test]
70
async fn accepting_and_rejecting_invites_use_the_expected_endpoints() {
71
    let accepted = room_id!("!accepted:localhost");
72
    let rejected = room_id!("!rejected:localhost");
73
    let (server, client) = configured_client().await;
74
    server
75
        .sync_room(&client, InvitedRoomBuilder::new(accepted))
76
        .await;
77
    server
78
        .sync_room(&client, InvitedRoomBuilder::new(rejected))
79
        .await;
80
    server.mock_room_join(accepted).ok().expect(1).mount().await;
81
    server
82
        .mock_room_leave()
83
        .ok(rejected)
84
        .expect(1)
85
        .mount()
86
        .await;
87
    let (handle, mut events, running) = run_client(client).await;
88
89
    let accept_id = handle.accept_invite(accepted.to_owned()).await.unwrap();
90
    receive_room_success(&mut events, accept_id).await;
91
    let reject_id = handle.reject_invite(rejected.to_owned()).await.unwrap();
92
    receive_room_success(&mut events, reject_id).await;
93
    stop_engine(handle, &mut events, running).await;
94
}
95
96
#[tokio::test]
97
async fn creating_a_room_forwards_options() {
98
    let (server, handle, mut events, running) = start_engine().await;
99
    Mock::given(method("POST"))
100
        .and(path("/_matrix/client/v3/createRoom"))
101
        .and(body_partial_json(json!({
102
            "name": "Planning",
103
            "topic": "Next release",
104
            "room_alias_name": "planning",
105
            "invite": ["@partner:localhost"],
106
            "visibility": "public",
107
            "preset": "public_chat"
108
        })))
109
        .respond_with(
110
            ResponseTemplate::new(200).set_body_json(json!({ "room_id": "!created:localhost" })),
111
        )
112
        .expect(1)
113
        .mount(server.server())
114
        .await;
115
116
    let created_room = handle
117
        .create_room(CreateRoomOptions {
118
            name: Some("Planning".to_owned()),
119
            topic: Some("Next release".to_owned()),
120
            alias_localpart: Some("planning".to_owned()),
121
            invite: vec![user_id!("@partner:localhost").to_owned()],
122
            visibility: RoomVisibility::Public,
123
            preset: Some(RoomPreset::PublicChat),
124
            ..Default::default()
125
        })
126
        .await
127
        .unwrap();
128
    assert_eq!(created_room, room_id!("!created:localhost").to_owned());
129
    stop_engine(handle, &mut events, running).await;
130
}
131
132
#[tokio::test]
133
async fn creating_a_direct_room_invites_the_other_user() {
134
    let (server, handle, mut events, running) = start_engine().await;
135
    Mock::given(method("POST"))
136
        .and(path("/_matrix/client/v3/createRoom"))
137
        .and(body_partial_json(json!({
138
            "invite": ["@partner:localhost"],
139
            "is_direct": true,
140
            "preset": "trusted_private_chat"
141
        })))
142
        .respond_with(
143
            ResponseTemplate::new(200).set_body_json(json!({ "room_id": "!direct:localhost" })),
144
        )
145
        .expect(1)
146
        .mount(server.server())
147
        .await;
148
    Mock::given(method("GET"))
149
        .and(path_regex(
150
            r"^/_matrix/client/v3/user/.*/account_data/m\.direct$",
151
        ))
152
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
153
        .expect(1)
154
        .mount(server.server())
155
        .await;
156
    Mock::given(method("PUT"))
157
        .and(path_regex(
158
            r"^/_matrix/client/v3/user/.*/account_data/m\.direct$",
159
        ))
160
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
161
        .expect(1)
162
        .mount(server.server())
163
        .await;
164
165
    let direct_room = handle
166
        .create_direct(user_id!("@partner:localhost").to_owned())
167
        .await
168
        .unwrap();
169
    assert_eq!(direct_room, room_id!("!direct:localhost").to_owned());
170
    stop_engine(handle, &mut events, running).await;
171
}
172
173
#[tokio::test]
174
async fn unknown_rooms_fail_membership_commands_with_their_ids() {
175
    let (server, handle, mut events, running) = start_engine().await;
176
    let unknown = room_id!("!unknown:localhost");
177
    for command in [
178
        RoomCommand::Leave(unknown.to_owned()),
179
        RoomCommand::Forget(unknown.to_owned()),
180
        RoomCommand::AcceptInvite(unknown.to_owned()),
181
        RoomCommand::RejectInvite(unknown.to_owned()),
182
    ] {
183
        let id = handle.send(Command::Room(command)).await.unwrap();
184
        receive_room_failure(&mut events, id, CommandErrorKind::RoomUnknown).await;
185
    }
186
    stop_engine(handle, &mut events, running).await;
187
    drop(server);
188
}
189
190
#[tokio::test]
191
async fn account_data_reads_synced_values_and_writes_raw_content() {
192
    let room_id = room_id!("!account-data:localhost");
193
    let (server, client) = configured_client().await;
194
    server
195
        .sync_room(
196
            &client,
197
            JoinedRoomBuilder::new(room_id).add_account_data(
198
                Raw::new(&json!({ "type": "m.fully_read", "content": { "event_id": "$seen:localhost" } }))
199
                    .unwrap()
200
                    .cast_unchecked(),
201
            ),
202
        )
203
        .await;
204
    server
205
        .mock_sync()
206
        .ok_and_run(&client, |builder| {
207
            builder.add_custom_global_account_data(json!({
208
                "type": "com.example.preferences",
209
                "content": { "theme": "dark" }
210
            }));
211
        })
212
        .await;
213
    let (engine, handle, mut events) = Engine::from_client(client, EngineConfig::default())
214
        .await
215
        .unwrap();
216
    let running = tokio::spawn(engine.run());
217
    let global_type = GlobalAccountDataEventType::from("com.example.preferences".to_owned());
218
    let room_type = RoomAccountDataEventType::FullyRead;
219
220
    let global = handle
221
        .global_account_data(global_type.clone())
222
        .await
223
        .unwrap()
224
        .unwrap();
225
    assert_eq!(
226
        global.deserialize_as::<serde_json::Value>().unwrap()["theme"],
227
        "dark"
228
    );
229
    let room = handle
230
        .room_account_data(room_id, room_type.clone())
231
        .await
232
        .unwrap()
233
        .unwrap();
234
    assert_eq!(
235
        room.deserialize_as::<serde_json::Value>().unwrap()["content"]["event_id"],
236
        "$seen:localhost"
237
    );
238
    assert!(matches!(
239
        handle
240
            .room_account_data(room_id!("!missing:localhost"), room_type.clone())
241
            .await,
242
        Err(ClientQueryError::RoomUnknown(_))
243
    ));
244
245
    Mock::given(method("PUT"))
246
        .and(path(
247
            "/_matrix/client/v3/user/@example:localhost/account_data/com.example.preferences",
248
        ))
249
        .and(body_partial_json(json!({ "theme": "light" })))
250
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
251
        .expect(1)
252
        .mount(server.server())
253
        .await;
254
    server
255
        .mock_set_room_account_data(room_type.clone())
256
        .ok()
257
        .expect(1)
258
        .mount()
259
        .await;
260
    handle
261
        .set_global_account_data(
262
            global_type,
263
            Raw::new(&json!({ "theme": "light" }))
264
                .unwrap()
265
                .cast_unchecked(),
266
        )
267
        .await
268
        .unwrap();
269
    handle
270
        .set_room_account_data(
271
            room_id,
272
            room_type,
273
            Raw::new(&json!({ "event_id": "$later:localhost" }))
274
                .unwrap()
275
                .cast_unchecked(),
276
        )
277
        .await
278
        .unwrap();
279
    assert!(matches!(
280
        handle
281
            .set_room_account_data(
282
                room_id!("!missing:localhost"),
283
                RoomAccountDataEventType::FullyRead,
284
                Raw::new(&json!({ "event_id": "$later:localhost" }))
285
                    .unwrap()
286
                    .cast_unchecked()
287
            )
288
            .await,
289
        Err(ClientQueryError::RoomUnknown(_))
290
    ));
291
    stop_engine(handle, &mut events, running).await;
292
}
293
294
async fn configured_client() -> (MatrixMockServer, matrix_sdk::Client) {
295
    let server = MatrixMockServer::new().await;
296
    server
297
        .mock_sliding_sync()
298
        .ok(v5::Response::new("1".to_owned()))
299
        .mount()
300
        .await;
301
    let client = server.client_builder().build().await;
302
    server.mock_room_state_encryption().plain().mount().await;
303
    (server, client)
304
}
305
306
async fn start_engine() -> (
307
    MatrixMockServer,
308
    ClientHandle,
309
    EventStream,
310
    JoinHandle<Result<(), EngineError>>,
311
) {
312
    let (server, client) = configured_client().await;
313
    let (handle, events, running) = run_client(client).await;
314
    (server, handle, events, running)
315
}
316
317
async fn run_client(
318
    client: matrix_sdk::Client,
319
) -> (
320
    ClientHandle,
321
    EventStream,
322
    JoinHandle<Result<(), EngineError>>,
323
) {
324
    let config = EngineConfig::default().with_initial_history_limit(0);
325
    let (engine, handle, events) = Engine::from_client(client, config).await.unwrap();
326
    let running = tokio::spawn(engine.run());
327
    (handle, events, running)
328
}
329
330
async fn receive_room_success(events: &mut EventStream, expected_id: CommandId) {
331
    tokio::time::timeout(Duration::from_secs(3), async {
332
        loop {
333
            match events.recv().await.expect("event stream closed") {
334
                Event::CommandSucceeded {
335
                    id,
336
                    command: CommandKind::Room,
337
                } if id == expected_id => break,
338
                Event::CommandFailed(failure) if failure.id == expected_id => {
339
                    panic!("room command failed {failure:?}")
340
                }
341
                _ => {}
342
            }
343
        }
344
    })
345
    .await
346
    .expect("room command did not complete");
347
}
348
349
async fn receive_room_failure(
350
    events: &mut EventStream,
351
    expected_id: CommandId,
352
    expected_kind: CommandErrorKind,
353
) {
354
    tokio::time::timeout(Duration::from_secs(3), async {
355
        loop {
356
            match events.recv().await.expect("event stream closed") {
357
                Event::CommandFailed(failure) if failure.id == expected_id => {
358
                    assert_eq!(failure.command, CommandKind::Room);
359
                    assert_eq!(failure.kind, expected_kind);
360
                    break;
361
                }
362
                Event::CommandSucceeded { id, .. } if id == expected_id => {
363
                    panic!("room command unexpectedly succeeded")
364
                }
365
                _ => {}
366
            }
367
        }
368
    })
369
    .await
370
    .expect("room command did not fail");
371
}
372
373
async fn stop_engine(
374
    handle: ClientHandle,
375
    events: &mut EventStream,
376
    running: JoinHandle<Result<(), EngineError>>,
377
) {
378
    let id = handle
379
        .send(Command::Lifecycle(LifecycleCommand::Shutdown))
380
        .await
381
        .unwrap();
382
    tokio::time::timeout(Duration::from_secs(3), async {
383
        let mut succeeded = false;
384
        while let Some(event) = events.recv().await {
385
            match event {
386
                Event::CommandSucceeded {
387
                    id: completed,
388
                    command: CommandKind::Lifecycle,
389
                } if completed == id => succeeded = true,
390
                Event::Stopped => {
391
                    assert!(succeeded);
392
                    break;
393
                }
394
                _ => {}
395
            }
396
        }
397
    })
398
    .await
399
    .expect("engine did not stop");
400
    running.await.unwrap().unwrap();
401
}