atagen

macltok

public
Star0
Watch0
fa9114fcf75f7bdf1a7cec3b3367d604cf7de472
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
tests/engine.rsRust708 lines24.2 KB
1
use std::time::Duration;
2
3
use macltok::{
4
    Command, CommandErrorKind, Engine, EngineConfig, Event, HistoryState, LifecycleCommand,
5
    MediaCommand, MessageContent, RoomCommand, TimelineCommand, TimelineDiff, TimelineId,
6
    TimelineItem,
7
};
8
use matrix_sdk::{
9
    ruma::{api::client::sync::sync_events::v5, event_id, mxc_uri, room_id},
10
    test_utils::mocks::{MatrixMockServer, RoomMessagesResponseTemplate},
11
};
12
use matrix_sdk_test::{ALICE, JoinedRoomBuilder, event_factory::EventFactory};
13
14
#[tokio::test]
15
async fn opens_a_room_and_sends_messages() {
16
    let server = MatrixMockServer::new().await;
17
    server
18
        .mock_sliding_sync()
19
        .ok(v5::Response::new("1".to_owned()))
20
        .mount()
21
        .await;
22
    let client = server.client_builder().build().await;
23
    let room_id = room_id!("!toolkit:localhost");
24
    server.mock_room_state_encryption().plain().mount().await;
25
    server.sync_joined_room(&client, room_id).await;
26
    server
27
        .mock_room_send()
28
        .body_matches_partial_json(serde_json::json!({ "body": "hello from macltok" }))
29
        .ok(event_id!("$sent:localhost"))
30
        .expect(1)
31
        .mount()
32
        .await;
33
34
    let config = EngineConfig::default().with_initial_history_limit(0);
35
    let sync_client = client.clone();
36
    let (engine, handle, mut events) = Engine::from_client(client, config).await.unwrap();
37
    let running = tokio::spawn(engine.run());
38
39
    handle
40
        .send(Command::Room(RoomCommand::Open(room_id.to_owned())))
41
        .await
42
        .unwrap();
43
    receive_until(
44
        &mut events,
45
        |event| matches!(event, Event::TimelineReset { timeline_id: reset, .. } if reset.room_id() == room_id),
46
    )
47
    .await;
48
49
    handle
50
        .send(Command::Timeline(TimelineCommand::Send {
51
            timeline_id: room_id.to_owned().into(),
52
            content: MessageContent::text("hello from macltok"),
53
        }))
54
        .await
55
        .unwrap();
56
    tokio::time::timeout(Duration::from_secs(2), async {
57
        loop {
58
            let sent = server
59
                .received_requests()
60
                .await
61
                .unwrap()
62
                .iter()
63
                .any(|request| {
64
                    request.url.path().contains("/send/m.room.message/")
65
                        && serde_json::from_slice::<serde_json::Value>(&request.body)
66
                            .is_ok_and(|body| body["body"] == "hello from macltok")
67
                });
68
            if sent {
69
                break;
70
            }
71
            tokio::task::yield_now().await;
72
        }
73
    })
74
    .await
75
    .expect("message did not reach the homeserver");
76
77
    server.mock_media_download().ok_image().mount().await;
78
    server.mock_authed_media_download().ok_image().mount().await;
79
    server
80
        .sync_room(
81
            &sync_client,
82
            JoinedRoomBuilder::new(room_id).add_timeline_event(
83
                EventFactory::new()
84
                    .image(
85
                        "image.jpg".into(),
86
                        mxc_uri!("mxc://localhost/image").to_owned(),
87
                    )
88
                    .sender(&ALICE),
89
            ),
90
        )
91
        .await;
92
    let media_event = receive_until(&mut events, |event| {
93
        event_messages(event).any(|message| message.media.is_some())
94
    })
95
    .await;
96
    let media_id = event_messages(&media_event)
97
        .find_map(|message| message.media.as_ref().map(|media| media.id.clone()))
98
        .unwrap();
99
    handle
100
        .send(Command::Media(MediaCommand::Fetch {
101
            id: media_id.clone(),
102
            size: macltok::MediaSize::Full,
103
        }))
104
        .await
105
        .unwrap();
106
    let media = receive_until(&mut events, |event| {
107
        matches!(event, Event::MediaReady { media_id: received, .. } if received == &media_id)
108
    })
109
    .await;
110
    assert!(matches!(media, Event::MediaReady { bytes, .. } if !bytes.is_empty()));
111
112
    handle
113
        .send(Command::Lifecycle(LifecycleCommand::Shutdown))
114
        .await
115
        .unwrap();
116
    receive_until(&mut events, |event| matches!(event, Event::Stopped)).await;
117
    running.await.unwrap().unwrap();
118
}
119
120
#[tokio::test]
121
async fn keeps_open_room_timelines_independent() {
122
    let server = MatrixMockServer::new().await;
123
    server
124
        .mock_sliding_sync()
125
        .ok(v5::Response::new("1".to_owned()))
126
        .mount()
127
        .await;
128
    let client = server.client_builder().build().await;
129
    let sync_client = client.clone();
130
    let first_id = room_id!("!first:localhost");
131
    let second_id = room_id!("!second:localhost");
132
    server.mock_room_state_encryption().plain().mount().await;
133
    server.sync_joined_room(&client, first_id).await;
134
    server.sync_joined_room(&client, second_id).await;
135
    let config = EngineConfig::default().with_initial_history_limit(0);
136
    let (engine, handle, mut events) = Engine::from_client(client, config).await.unwrap();
137
    let running = tokio::spawn(engine.run());
138
139
    handle
140
        .send(Command::Room(RoomCommand::Open(first_id.to_owned())))
141
        .await
142
        .unwrap();
143
    handle
144
        .send(Command::Room(RoomCommand::Open(second_id.to_owned())))
145
        .await
146
        .unwrap();
147
    receive_until(
148
        &mut events,
149
        |event| matches!(event, Event::TimelineReset { timeline_id, .. } if timeline_id.room_id() == first_id),
150
    )
151
    .await;
152
    receive_until(
153
        &mut events,
154
        |event| matches!(event, Event::TimelineReset { timeline_id, .. } if timeline_id.room_id() == second_id),
155
    )
156
    .await;
157
    wait_for_room_subscriptions(&server, &[first_id, second_id]).await;
158
159
    let factory = EventFactory::new();
160
    server
161
        .sync_room(
162
            &sync_client,
163
            JoinedRoomBuilder::new(first_id)
164
                .add_timeline_event(factory.text_msg("first update").sender(&ALICE)),
165
        )
166
        .await;
167
    server
168
        .sync_room(
169
            &sync_client,
170
            JoinedRoomBuilder::new(second_id)
171
                .add_timeline_event(factory.text_msg("second update").sender(&ALICE)),
172
        )
173
        .await;
174
175
    let mut first_updated = false;
176
    let mut second_updated = false;
177
    tokio::time::timeout(Duration::from_secs(2), async {
178
        while !(first_updated && second_updated) {
179
            let event = events.recv().await.expect("event stream closed");
180
            if let Event::TimelineChanged { timeline_id, .. } = &event {
181
                first_updated |= timeline_id.room_id() == first_id
182
                    && event_messages(&event).any(|message| message.body == "first update");
183
                second_updated |= timeline_id.room_id() == second_id
184
                    && event_messages(&event).any(|message| message.body == "second update");
185
            }
186
        }
187
    })
188
    .await
189
    .expect("both retained rooms did not update");
190
191
    handle
192
        .send(Command::Room(RoomCommand::Close(first_id.to_owned())))
193
        .await
194
        .unwrap();
195
    server
196
        .sync_room(
197
            &sync_client,
198
            JoinedRoomBuilder::new(second_id)
199
                .add_timeline_event(factory.text_msg("still live").sender(&ALICE)),
200
        )
201
        .await;
202
    receive_until(&mut events, |event| {
203
        matches!(event, Event::TimelineChanged { timeline_id, .. } if timeline_id.room_id() == second_id)
204
            && event_messages(event).any(|message| message.body == "still live")
205
    })
206
    .await;
207
208
    handle
209
        .send(Command::Lifecycle(LifecycleCommand::Shutdown))
210
        .await
211
        .unwrap();
212
    receive_until(&mut events, |event| matches!(event, Event::Stopped)).await;
213
    running.await.unwrap().unwrap();
214
}
215
216
#[tokio::test]
217
async fn resync_replaces_timeline_before_ack_and_preserves_following_diffs() {
218
    let server = MatrixMockServer::new().await;
219
    server
220
        .mock_sliding_sync()
221
        .ok(v5::Response::new("1".to_owned()))
222
        .mount()
223
        .await;
224
    let client = server.client_builder().build().await;
225
    let sync_client = client.clone();
226
    let room_id = room_id!("!resync:localhost");
227
    server.mock_room_state_encryption().plain().mount().await;
228
    server.sync_joined_room(&client, room_id).await;
229
    let config = EngineConfig::default().with_initial_history_limit(0);
230
    let (engine, handle, mut events) = Engine::from_client(client, config).await.unwrap();
231
    let running = tokio::spawn(engine.run());
232
233
    handle
234
        .send(Command::Room(RoomCommand::Open(room_id.to_owned())))
235
        .await
236
        .unwrap();
237
    receive_until(&mut events, |event| {
238
        matches!(event,
239
            Event::TimelineReset { timeline_id, .. } if timeline_id.room_id() == room_id
240
        )
241
    })
242
    .await;
243
    server
244
        .sync_room(
245
            &sync_client,
246
            JoinedRoomBuilder::new(room_id)
247
                .add_timeline_event(EventFactory::new().text_msg("before reset").sender(&ALICE)),
248
        )
249
        .await;
250
    receive_until(&mut events, |event| {
251
        event_messages(event).any(|message| message.body == "before reset")
252
    })
253
    .await;
254
255
    let id = handle
256
        .send(Command::Timeline(TimelineCommand::Resync {
257
            timeline_id: room_id.to_owned().into(),
258
        }))
259
        .await
260
        .unwrap();
261
    let mut reset = None;
262
    tokio::time::timeout(Duration::from_secs(2), async {
263
        loop {
264
            match events.recv().await.expect("event stream closed") {
265
                Event::TimelineReset { timeline_id, items } if timeline_id.room_id() == room_id => {
266
                    reset = Some(items);
267
                }
268
                Event::CommandSucceeded { id: completed, .. } if completed == id => {
269
                    assert!(reset.is_some(), "resync completed before its reset");
270
                    break;
271
                }
272
                Event::CommandFailed(failure) if failure.id == id => {
273
                    panic!("resync failed: {failure:?}")
274
                }
275
                _ => {}
276
            }
277
        }
278
    })
279
    .await
280
    .expect("resync did not complete");
281
    let mut items = reset.unwrap();
282
    assert!(items.iter().any(|item| matches!(item,
283
        TimelineItem::Message(message) if message.body == "before reset"
284
    )));
285
286
    server
287
        .sync_room(
288
            &sync_client,
289
            JoinedRoomBuilder::new(room_id)
290
                .add_timeline_event(EventFactory::new().text_msg("after reset").sender(&ALICE)),
291
        )
292
        .await;
293
    tokio::time::timeout(Duration::from_secs(2), async {
294
        loop {
295
            if let Event::TimelineChanged { timeline_id, diffs } =
296
                events.recv().await.expect("event stream closed")
297
                && timeline_id.room_id() == room_id
298
            {
299
                for diff in diffs {
300
                    diff.try_apply(&mut items)
301
                        .expect("resynced snapshot accepts following diffs");
302
                }
303
                if items.iter().any(|item| {
304
                    matches!(item,
305
                        TimelineItem::Message(message) if message.body == "after reset"
306
                    )
307
                }) {
308
                    break;
309
                }
310
            }
311
        }
312
    })
313
    .await
314
    .expect("resynced timeline did not update");
315
316
    handle
317
        .send(Command::Lifecycle(LifecycleCommand::Shutdown))
318
        .await
319
        .unwrap();
320
    receive_until(&mut events, |event| matches!(event, Event::Stopped)).await;
321
    running.await.unwrap().unwrap();
322
}
323
324
#[tokio::test]
325
async fn closes_and_reopens_a_room_in_command_order() {
326
    let server = MatrixMockServer::new().await;
327
    server
328
        .mock_sliding_sync()
329
        .ok(v5::Response::new("1".to_owned()))
330
        .mount()
331
        .await;
332
    let client = server.client_builder().build().await;
333
    let room_id = room_id!("!reopen:localhost");
334
    server.mock_room_state_encryption().plain().mount().await;
335
    server.sync_joined_room(&client, room_id).await;
336
    let config = EngineConfig::default().with_initial_history_limit(0);
337
    let (engine, handle, mut events) = Engine::from_client(client, config).await.unwrap();
338
    let running = tokio::spawn(engine.run());
339
340
    let initial_open = handle
341
        .send(Command::Room(RoomCommand::Open(room_id.to_owned())))
342
        .await
343
        .unwrap();
344
    receive_until(
345
        &mut events,
346
        |event| matches!(event, Event::TimelineReset { timeline_id, .. } if timeline_id.room_id() == room_id),
347
    )
348
    .await;
349
    receive_until(
350
        &mut events,
351
        |event| matches!(event, Event::CommandSucceeded { id, .. } if id == &initial_open),
352
    )
353
    .await;
354
355
    let close = handle
356
        .send(Command::Room(RoomCommand::Close(room_id.to_owned())))
357
        .await
358
        .unwrap();
359
    let replacement_open = handle
360
        .send(Command::Room(RoomCommand::Open(room_id.to_owned())))
361
        .await
362
        .unwrap();
363
    let second_close = handle
364
        .send(Command::Room(RoomCommand::Close(room_id.to_owned())))
365
        .await
366
        .unwrap();
367
    let final_open = handle
368
        .send(Command::Room(RoomCommand::Open(room_id.to_owned())))
369
        .await
370
        .unwrap();
371
    let expected = [close, replacement_open, second_close, final_open];
372
    let mut completed = 0;
373
    let mut resets = 0;
374
    tokio::time::timeout(Duration::from_secs(3), async {
375
        while completed < expected.len() {
376
            match events.recv().await.expect("event stream closed") {
377
                Event::TimelineReset { timeline_id, .. } if timeline_id.room_id() == room_id => {
378
                    resets += 1;
379
                    assert!(resets <= 2, "a room generation reset more than once");
380
                }
381
                Event::CommandSucceeded { id, .. } if expected.contains(&id) => {
382
                    assert_eq!(
383
                        id, expected[completed],
384
                        "room lifecycle commands completed out of order"
385
                    );
386
                    if completed == 1 || completed == 3 {
387
                        assert_eq!(
388
                            resets,
389
                            completed / 2 + 1,
390
                            "room open completed before timeline reset"
391
                        );
392
                    }
393
                    completed += 1;
394
                }
395
                Event::CommandFailed(failure) if expected.contains(&failure.id) => {
396
                    panic!("room lifecycle command failed: {failure:?}");
397
                }
398
                _ => {}
399
            }
400
        }
401
    })
402
    .await
403
    .expect("room lifecycle commands did not complete");
404
    handle
405
        .send(Command::Lifecycle(LifecycleCommand::Shutdown))
406
        .await
407
        .unwrap();
408
    receive_until(&mut events, |event| matches!(event, Event::Stopped)).await;
409
    running.await.unwrap().unwrap();
410
}
411
412
#[tokio::test]
413
async fn thread_command_waits_for_opening_timeline() {
414
    let server = MatrixMockServer::new().await;
415
    server
416
        .mock_sliding_sync()
417
        .ok(v5::Response::new("1".to_owned()))
418
        .mount()
419
        .await;
420
    let client = server.client_builder().build().await;
421
    let room_id = room_id!("!thread:localhost");
422
    let root = event_id!("$thread-root:localhost");
423
    server.mock_room_state_encryption().plain().mount().await;
424
    server.sync_joined_room(&client, room_id).await;
425
    let (engine, handle, mut events) = Engine::from_client(
426
        client,
427
        EngineConfig::default().with_initial_history_limit(0),
428
    )
429
    .await
430
    .unwrap();
431
    let running = tokio::spawn(engine.run());
432
    let room_open = handle
433
        .send(Command::Room(RoomCommand::Open(room_id.to_owned())))
434
        .await
435
        .unwrap();
436
    receive_until(
437
        &mut events,
438
        |event| matches!(event, Event::CommandSucceeded { id, .. } if *id == room_open),
439
    )
440
    .await;
441
442
    let open = handle
443
        .send(Command::Timeline(TimelineCommand::OpenThread {
444
            room_id: room_id.to_owned(),
445
            root_event_id: root.to_owned(),
446
        }))
447
        .await
448
        .unwrap();
449
    let send = handle
450
        .send(Command::Timeline(TimelineCommand::Send {
451
            timeline_id: TimelineId::thread(room_id.to_owned(), root.to_owned()),
452
            content: MessageContent::text(""),
453
        }))
454
        .await
455
        .unwrap();
456
    let resync = handle
457
        .send(Command::Timeline(TimelineCommand::Resync {
458
            timeline_id: TimelineId::thread(room_id.to_owned(), root.to_owned()),
459
        }))
460
        .await
461
        .unwrap();
462
    let mut open_succeeded = false;
463
    tokio::time::timeout(Duration::from_secs(3), async {
464
        loop {
465
            match events.recv().await.expect("event stream closed") {
466
                Event::CommandSucceeded { id, .. } if id == open => open_succeeded = true,
467
                Event::CommandFailed(failure) if failure.id == send => {
468
                    assert!(open_succeeded, "thread command completed before opening");
469
                    assert_eq!(failure.kind, CommandErrorKind::EmptyBody);
470
                    break;
471
                }
472
                Event::CommandFailed(failure) if failure.id == open => {
473
                    panic!("thread open failed: {failure:?}");
474
                }
475
                _ => {}
476
            }
477
        }
478
    })
479
    .await
480
    .expect("thread commands did not complete");
481
    let mut reset_seen = false;
482
    tokio::time::timeout(Duration::from_secs(3), async {
483
        loop {
484
            match events.recv().await.expect("event stream closed") {
485
                Event::TimelineReset { timeline_id, .. }
486
                    if timeline_id.thread_root().is_some_and(|event| event == root) =>
487
                {
488
                    reset_seen = true
489
                }
490
                Event::CommandSucceeded { id, .. } if id == resync => {
491
                    assert!(reset_seen, "thread resync completed before its reset");
492
                    break;
493
                }
494
                Event::CommandFailed(failure) if failure.id == resync => {
495
                    panic!("thread resync failed: {failure:?}")
496
                }
497
                _ => {}
498
            }
499
        }
500
    })
501
    .await
502
    .expect("thread resync did not complete");
503
    handle
504
        .send(Command::Lifecycle(LifecycleCommand::Shutdown))
505
        .await
506
        .unwrap();
507
    receive_until(&mut events, |event| matches!(event, Event::Stopped)).await;
508
    running.await.unwrap().unwrap();
509
}
510
511
#[tokio::test]
512
async fn pagination_reports_and_remembers_start_of_history() {
513
    let server = MatrixMockServer::new().await;
514
    server
515
        .mock_sliding_sync()
516
        .ok(v5::Response::new("1".to_owned()))
517
        .mount()
518
        .await;
519
    let client = server.client_builder().build().await;
520
    let sync_client = client.clone();
521
    let room_id = room_id!("!history:localhost");
522
    server.mock_room_state_encryption().plain().mount().await;
523
    server.sync_joined_room(&client, room_id).await;
524
    let config = EngineConfig::default().with_initial_history_limit(0);
525
    let (engine, handle, mut events) = Engine::from_client(client, config).await.unwrap();
526
    let running = tokio::spawn(engine.run());
527
528
    handle
529
        .send(Command::Room(RoomCommand::Open(room_id.to_owned())))
530
        .await
531
        .unwrap();
532
    receive_until(
533
        &mut events,
534
        |event| matches!(event, Event::TimelineReset { timeline_id: reset, .. } if reset.room_id() == room_id),
535
    )
536
    .await;
537
538
    server
539
        .sync_room(
540
            &sync_client,
541
            JoinedRoomBuilder::new(room_id)
542
                .add_timeline_event(EventFactory::new().text_msg("latest").sender(&ALICE))
543
                .set_timeline_limited()
544
                .set_timeline_prev_batch("before-latest"),
545
        )
546
        .await;
547
    receive_until(&mut events, |event| {
548
        matches!(event, Event::TimelineChanged { timeline_id: changed, .. } if changed.room_id() == room_id)
549
            && event_messages(event).any(|message| message.body == "latest")
550
    })
551
    .await;
552
553
    server
554
        .mock_room_messages()
555
        .match_from("before-latest")
556
        .ok(RoomMessagesResponseTemplate::default())
557
        .mock_once()
558
        .mount()
559
        .await;
560
    let mut reached_start = false;
561
    for _ in 0..3 {
562
        handle
563
            .send(Command::Timeline(TimelineCommand::Paginate {
564
                timeline_id: room_id.to_owned().into(),
565
                limit: 10,
566
            }))
567
            .await
568
            .unwrap();
569
        receive_history(&mut events, room_id, HistoryState::Loading).await;
570
        match receive_history_terminal(&mut events, room_id).await {
571
            HistoryState::StartReached => {
572
                reached_start = true;
573
                break;
574
            }
575
            HistoryState::Idle => {}
576
            state => panic!("unexpected pagination state {state:?}"),
577
        }
578
    }
579
    assert!(
580
        reached_start,
581
        "pagination did not reach the mocked room start"
582
    );
583
584
    handle
585
        .send(Command::Timeline(TimelineCommand::Paginate {
586
            timeline_id: room_id.to_owned().into(),
587
            limit: 10,
588
        }))
589
        .await
590
        .unwrap();
591
    receive_history(&mut events, room_id, HistoryState::StartReached).await;
592
593
    handle
594
        .send(Command::Lifecycle(LifecycleCommand::Shutdown))
595
        .await
596
        .unwrap();
597
    receive_until(&mut events, |event| matches!(event, Event::Stopped)).await;
598
    running.await.unwrap().unwrap();
599
}
600
601
async fn receive_history(
602
    events: &mut macltok::EventStream,
603
    room_id: &matrix_sdk::ruma::RoomId,
604
    expected: HistoryState,
605
) {
606
    receive_until(events, |event| {
607
        matches!(event, Event::HistoryChanged { timeline_id: changed, state } if changed.room_id() == room_id && state == &expected)
608
    })
609
    .await;
610
}
611
612
fn event_messages(event: &Event) -> std::vec::IntoIter<&macltok::TimelineMessage> {
613
    let items = match event {
614
        Event::TimelineReset { items, .. } => items.iter().collect::<Vec<_>>(),
615
        Event::TimelineChanged { diffs, .. } => {
616
            diffs.iter().flat_map(diff_items).collect::<Vec<_>>()
617
        }
618
        _ => Vec::new(),
619
    };
620
    items
621
        .into_iter()
622
        .filter_map(|item| match item {
623
            TimelineItem::Message(message) => Some(message.as_ref()),
624
            _ => None,
625
        })
626
        .collect::<Vec<_>>()
627
        .into_iter()
628
}
629
630
fn diff_items(diff: &TimelineDiff) -> Vec<&TimelineItem> {
631
    match diff {
632
        TimelineDiff::Append { values } | TimelineDiff::Reset { values } => values.iter().collect(),
633
        TimelineDiff::PushFront { value }
634
        | TimelineDiff::PushBack { value }
635
        | TimelineDiff::Insert { value, .. }
636
        | TimelineDiff::Set { value, .. } => vec![value],
637
        TimelineDiff::Clear
638
        | TimelineDiff::PopFront
639
        | TimelineDiff::PopBack
640
        | TimelineDiff::Remove { .. }
641
        | TimelineDiff::Truncate { .. } => Vec::new(),
642
        _ => Vec::new(),
643
    }
644
}
645
646
async fn receive_history_terminal(
647
    events: &mut macltok::EventStream,
648
    room_id: &matrix_sdk::ruma::RoomId,
649
) -> HistoryState {
650
    match receive_until(events, |event| {
651
        matches!(event, Event::HistoryChanged { timeline_id: changed, state } if changed.room_id() == room_id && !matches!(state, HistoryState::Loading))
652
    })
653
    .await
654
    {
655
        Event::HistoryChanged { state, .. } => state,
656
        _ => unreachable!(),
657
    }
658
}
659
660
async fn wait_for_room_subscriptions(
661
    server: &MatrixMockServer,
662
    room_ids: &[&matrix_sdk::ruma::RoomId],
663
) {
664
    tokio::time::timeout(Duration::from_secs(2), async {
665
        loop {
666
            let found = server
667
                .received_requests()
668
                .await
669
                .unwrap()
670
                .iter()
671
                .filter_map(|request| {
672
                    serde_json::from_slice::<serde_json::Value>(&request.body).ok()
673
                })
674
                .filter_map(|body| {
675
                    body.get("room_subscriptions")
676
                        .and_then(|value| value.as_object())
677
                        .cloned()
678
                })
679
                .any(|subscriptions| {
680
                    room_ids
681
                        .iter()
682
                        .all(|room_id| subscriptions.contains_key(room_id.as_str()))
683
                });
684
            if found {
685
                break;
686
            }
687
            tokio::task::yield_now().await;
688
        }
689
    })
690
    .await
691
    .expect("Sliding Sync did not retain every open room subscription");
692
}
693
694
async fn receive_until(
695
    events: &mut macltok::EventStream,
696
    predicate: impl Fn(&Event) -> bool,
697
) -> Event {
698
    tokio::time::timeout(Duration::from_secs(2), async {
699
        while let Some(event) = events.recv().await {
700
            if predicate(&event) {
701
                return event;
702
            }
703
        }
704
        panic!("event stream closed before the expected event");
705
    })
706
    .await
707
    .expect("expected event did not arrive")
708
}