atagen

macltok

public
Star0
Watch0
722b4c3d0a09ca2aa7b242170395338933a65d52
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
tests/commands.rsRust179 lines6.0 KB
1
mod support;
2
3
use std::time::Duration;
4
5
use macltok::{
6
    ClientHandle, Command, Engine, EngineConfig, EngineError, ErrorKind, Event, EventStream,
7
    MessageContent, MessageEditContent, RichText, RoomCommand, TimelineCommand,
8
};
9
use matrix_sdk::{
10
    ruma::{RoomId, api::client::sync::sync_events::v5, event_id, room_id},
11
    test_utils::mocks::MatrixMockServer,
12
};
13
use support::shut_down;
14
use tokio::task::JoinHandle;
15
16
#[tokio::test]
17
async fn empty_message_bodies_fail_with_empty_body() {
18
    let room_id = room_id!("!empty-bodies:localhost");
19
    let (server, handle, mut events, running) = start_engine(room_id).await;
20
    open_room(&handle, &mut events, room_id).await;
21
22
    let commands = [
23
        TimelineCommand::Send {
24
            timeline_id: room_id.to_owned().into(),
25
            content: MessageContent::text(String::new()),
26
        },
27
        TimelineCommand::Send {
28
            timeline_id: room_id.to_owned().into(),
29
            content: MessageContent::emote(RichText::plain("   "), Default::default()),
30
        },
31
        TimelineCommand::Send {
32
            timeline_id: room_id.to_owned().into(),
33
            content: MessageContent::reply(
34
                event_id!("$reply:localhost").to_owned(),
35
                RichText::plain("\n"),
36
                Default::default(),
37
            ),
38
        },
39
        TimelineCommand::Edit {
40
            timeline_id: room_id.to_owned().into(),
41
            target: event_id!("$edit:localhost").to_owned().into(),
42
            content: MessageEditContent::text("\t"),
43
        },
44
    ];
45
46
    for command in commands {
47
        let error = handle
48
            .execute(Command::Timeline(command))
49
            .await
50
            .expect_err("empty body command unexpectedly succeeded");
51
        assert_eq!(error.kind, ErrorKind::EmptyBody);
52
    }
53
54
    shut_down(&handle, &mut events, running).await;
55
    drop(server);
56
}
57
58
#[tokio::test]
59
async fn open_resets_the_timeline_before_succeeding() {
60
    let room_id = room_id!("!open-order:localhost");
61
    let (server, handle, mut events, running) = start_engine(room_id).await;
62
63
    open_room(&handle, &mut events, room_id).await;
64
65
    shut_down(&handle, &mut events, running).await;
66
    drop(server);
67
}
68
69
#[tokio::test]
70
async fn same_room_sends_complete_in_submission_order() {
71
    let room_id = room_id!("!send-order:localhost");
72
    let (server, handle, mut events, running) = start_engine(room_id).await;
73
    open_room(&handle, &mut events, room_id).await;
74
    server
75
        .mock_room_send()
76
        .ok(event_id!("$sent:localhost"))
77
        .expect(2)
78
        .mount()
79
        .await;
80
81
    let first = handle
82
        .try_send(Command::Timeline(TimelineCommand::Send {
83
            timeline_id: room_id.to_owned().into(),
84
            content: MessageContent::text("first"),
85
        }))
86
        .unwrap();
87
    let second = handle
88
        .try_send(Command::Timeline(TimelineCommand::Send {
89
            timeline_id: room_id.to_owned().into(),
90
            content: MessageContent::text("second"),
91
        }))
92
        .unwrap();
93
94
    // both answers land almost together, so racing them can't show which was
95
    // processed first, only the order the sends actually reached the homeserver can
96
    let (first_result, second_result) = tokio::join!(first, second);
97
    first_result.expect("first send failed");
98
    second_result.expect("second send failed");
99
100
    tokio::time::timeout(Duration::from_secs(2), async {
101
        loop {
102
            let bodies: Vec<String> = server
103
                .received_requests()
104
                .await
105
                .unwrap()
106
                .iter()
107
                .filter(|request| request.url.path().contains("/send/m.room.message/"))
108
                .filter_map(|request| {
109
                    serde_json::from_slice::<serde_json::Value>(&request.body).ok()
110
                })
111
                .filter_map(|body| body["body"].as_str().map(str::to_owned))
112
                .collect();
113
            if bodies.len() >= 2 {
114
                assert_eq!(bodies, ["first", "second"]);
115
                break;
116
            }
117
            tokio::task::yield_now().await;
118
        }
119
    })
120
    .await
121
    .expect("both sends did not reach the homeserver in submission order");
122
123
    shut_down(&handle, &mut events, running).await;
124
    drop(server);
125
}
126
127
async fn start_engine(
128
    room_id: &RoomId,
129
) -> (
130
    MatrixMockServer,
131
    ClientHandle,
132
    EventStream,
133
    JoinHandle<Result<macltok::Termination, EngineError>>,
134
) {
135
    let server = MatrixMockServer::new().await;
136
    server
137
        .mock_sliding_sync()
138
        .ok(v5::Response::new("1".to_owned()))
139
        .mount()
140
        .await;
141
    let client = server.client_builder().build().await;
142
    server.mock_room_state_encryption().plain().mount().await;
143
    server.sync_joined_room(&client, room_id).await;
144
    let config = EngineConfig::default().with_initial_history_limit(0);
145
    let (engine, handle, events) = Engine::from_client(client, config).await.unwrap();
146
    let running = tokio::spawn(engine.run());
147
    (server, handle, events, running)
148
}
149
150
/// opens `room_id` and confirms its `TimelineReset` was already on the
151
/// stream by the time the open command answered
152
async fn open_room(handle: &ClientHandle, events: &mut EventStream, room_id: &RoomId) {
153
    let mut pending = handle
154
        .send(Command::Room(RoomCommand::Open(room_id.to_owned())))
155
        .await
156
        .unwrap();
157
    let mut reset_received = false;
158
    tokio::time::timeout(Duration::from_secs(2), async {
159
        loop {
160
            tokio::select! {
161
                biased;
162
                event = events.recv() => {
163
                    if let Event::TimelineReset { timeline_id, .. } = event.expect("event stream closed")
164
                        && timeline_id.room_id() == room_id
165
                    {
166
                        reset_received = true;
167
                    }
168
                }
169
                result = &mut pending => {
170
                    result.expect("opening room failed");
171
                    break;
172
                }
173
            }
174
        }
175
    })
176
    .await
177
    .expect("room did not open");
178
    assert!(reset_received, "open completed before its reset");
179
}