atagen

macltok

public
Star0
Watch0
fa9114fcf75f7bdf1a7cec3b3367d604cf7de472
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
tests/nonblocking.rsRust207 lines6.7 KB
1
use std::time::Duration;
2
3
use macltok::{
4
    Command, CommandErrorKind, CommandKind, Engine, EngineConfig, Event, LifecycleCommand,
5
    MediaCommand, MediaSize, MessageContent, RoomCommand, TimelineCommand, TimelineDiff,
6
    TimelineItem,
7
};
8
use matrix_sdk::{
9
    ruma::{api::client::sync::sync_events::v5, mxc_uri, room_id},
10
    test_utils::mocks::MatrixMockServer,
11
};
12
use matrix_sdk_test::{ALICE, JoinedRoomBuilder, event_factory::EventFactory};
13
use wiremock::ResponseTemplate;
14
15
#[tokio::test]
16
async fn delayed_media_fetch_does_not_block_timeline_command() {
17
    let server = MatrixMockServer::new().await;
18
    server
19
        .mock_sliding_sync()
20
        .ok(v5::Response::new("1".to_owned()))
21
        .mount()
22
        .await;
23
    let client = server.client_builder().build().await;
24
    let sync_client = client.clone();
25
    let room_id = room_id!("!nonblocking:localhost");
26
    server.mock_room_state_encryption().plain().mount().await;
27
    server.sync_joined_room(&client, room_id).await;
28
    let config = EngineConfig::default().with_initial_history_limit(0);
29
    let (engine, handle, mut events) = Engine::from_client(client, config).await.unwrap();
30
    let running = tokio::spawn(engine.run());
31
32
    handle
33
        .send(Command::Room(RoomCommand::Open(room_id.to_owned())))
34
        .await
35
        .unwrap();
36
    receive_until(
37
        &mut events,
38
        |event| matches!(event, Event::TimelineReset { timeline_id: reset, .. } if reset.room_id() == room_id),
39
    )
40
    .await;
41
42
    server
43
        .sync_room(
44
            &sync_client,
45
            JoinedRoomBuilder::new(room_id).add_timeline_event(
46
                EventFactory::new()
47
                    .image(
48
                        "image.jpg".into(),
49
                        mxc_uri!("mxc://localhost/image").to_owned(),
50
                    )
51
                    .sender(&ALICE),
52
            ),
53
        )
54
        .await;
55
    let media_event = receive_until(&mut events, |event| {
56
        event_messages(event).any(|message| message.media.is_some())
57
    })
58
    .await;
59
    let media_id = event_messages(&media_event)
60
        .find_map(|message| message.media.as_ref().map(|media| media.id.clone()))
61
        .unwrap();
62
63
    let delayed_image = || {
64
        ResponseTemplate::new(200)
65
            .set_body_raw(b"binaryjpegfullimagedata", "image/jpeg")
66
            .set_delay(Duration::from_millis(500))
67
    };
68
    server
69
        .mock_media_download()
70
        .respond_with(delayed_image())
71
        .mount()
72
        .await;
73
    server
74
        .mock_authed_media_download()
75
        .respond_with(delayed_image())
76
        .mount()
77
        .await;
78
79
    let media_command_id = handle
80
        .send(Command::Media(MediaCommand::Fetch {
81
            id: media_id.clone(),
82
            size: MediaSize::Full,
83
        }))
84
        .await
85
        .unwrap();
86
    wait_for_media_request(&server).await;
87
    let timeline_command_id = handle
88
        .send(Command::Timeline(TimelineCommand::Send {
89
            timeline_id: room_id.to_owned().into(),
90
            content: MessageContent::text(String::new()),
91
        }))
92
        .await
93
        .unwrap();
94
95
    tokio::time::timeout(Duration::from_secs(2), async {
96
        loop {
97
            match events.recv().await.expect("event stream closed") {
98
                Event::CommandFailed(failure) if failure.id == timeline_command_id => {
99
                    assert_eq!(failure.command, CommandKind::Timeline);
100
                    assert_eq!(failure.kind, CommandErrorKind::EmptyBody);
101
                    break;
102
                }
103
                Event::CommandSucceeded { id, .. } if id == media_command_id => {
104
                    panic!("media command completed before the timeline command")
105
                }
106
                Event::CommandFailed(failure) if failure.id == media_command_id => {
107
                    panic!("media command failed before the timeline command {failure:?}")
108
                }
109
                Event::MediaReady {
110
                    media_id: ready_id, ..
111
                } if ready_id == media_id => {
112
                    panic!("media became ready before the timeline command completed")
113
                }
114
                _ => {}
115
            }
116
        }
117
    })
118
    .await
119
    .expect("timeline command was blocked by media fetch");
120
121
    receive_until(&mut events, |event| {
122
        matches!(
123
            event,
124
            Event::CommandSucceeded {
125
                id,
126
                command: CommandKind::Media,
127
            } if id == &media_command_id
128
        )
129
    })
130
    .await;
131
132
    handle
133
        .send(Command::Lifecycle(LifecycleCommand::Shutdown))
134
        .await
135
        .unwrap();
136
    receive_until(&mut events, |event| matches!(event, Event::Stopped)).await;
137
    running.await.unwrap().unwrap();
138
}
139
140
async fn wait_for_media_request(server: &MatrixMockServer) {
141
    tokio::time::timeout(Duration::from_secs(2), async {
142
        loop {
143
            if server
144
                .received_requests()
145
                .await
146
                .unwrap()
147
                .iter()
148
                .any(|request| request.url.path().ends_with("/download/localhost/image"))
149
            {
150
                break;
151
            }
152
            tokio::task::yield_now().await;
153
        }
154
    })
155
    .await
156
    .expect("media request did not reach the homeserver");
157
}
158
159
fn event_messages(event: &Event) -> std::vec::IntoIter<&macltok::TimelineMessage> {
160
    let items = match event {
161
        Event::TimelineReset { items, .. } => items.iter().collect::<Vec<_>>(),
162
        Event::TimelineChanged { diffs, .. } => {
163
            diffs.iter().flat_map(diff_items).collect::<Vec<_>>()
164
        }
165
        _ => Vec::new(),
166
    };
167
    items
168
        .into_iter()
169
        .filter_map(|item| match item {
170
            TimelineItem::Message(message) => Some(message.as_ref()),
171
            _ => None,
172
        })
173
        .collect::<Vec<_>>()
174
        .into_iter()
175
}
176
177
fn diff_items(diff: &TimelineDiff) -> Vec<&TimelineItem> {
178
    match diff {
179
        TimelineDiff::Append { values } | TimelineDiff::Reset { values } => values.iter().collect(),
180
        TimelineDiff::PushFront { value }
181
        | TimelineDiff::PushBack { value }
182
        | TimelineDiff::Insert { value, .. }
183
        | TimelineDiff::Set { value, .. } => vec![value],
184
        TimelineDiff::Clear
185
        | TimelineDiff::PopFront
186
        | TimelineDiff::PopBack
187
        | TimelineDiff::Remove { .. }
188
        | TimelineDiff::Truncate { .. } => Vec::new(),
189
        _ => Vec::new(),
190
    }
191
}
192
193
async fn receive_until(
194
    events: &mut macltok::EventStream,
195
    predicate: impl Fn(&Event) -> bool,
196
) -> Event {
197
    tokio::time::timeout(Duration::from_secs(2), async {
198
        while let Some(event) = events.recv().await {
199
            if predicate(&event) {
200
                return event;
201
            }
202
        }
203
        panic!("event stream closed before the expected event")
204
    })
205
    .await
206
    .expect("expected event did not arrive")
207
}