atagen

macltok

public
Star0
Watch0
9f95c6462d606393c70c65d478107c0fe4496177
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
tests/mentions_checkpoint.rsRust155 lines5.2 KB
1
use std::{sync::Arc, time::Duration};
2
3
use matrix_sdk::{
4
    Client, Room,
5
    ruma::{api::client::sync::sync_events::v5, events::AnySyncTimelineEvent, room_id, serde::Raw},
6
    sliding_sync::{SlidingSyncList, SlidingSyncMode},
7
    test_utils::mocks::MatrixMockServer,
8
};
9
use serde_json::{Value, json};
10
use tokio::sync::{Notify, Semaphore};
11
12
async fn saved_position(client: &Client) -> Option<String> {
13
    let machine = client.olm_machine_for_testing().await;
14
    let key = format!(
15
        "sliding_sync_store::mentions-spike::{}::instance",
16
        client.user_id().unwrap()
17
    );
18
    let saved = machine
19
        .as_ref()
20
        .unwrap()
21
        .store()
22
        .get_custom_value(&key)
23
        .await
24
        .unwrap()?;
25
    serde_json::from_slice::<Value>(&saved).unwrap()["pos"]
26
        .as_str()
27
        .map(ToOwned::to_owned)
28
}
29
30
async fn server_with_mention() -> (MatrixMockServer, Client) {
31
    let server = MatrixMockServer::new().await;
32
    let client = server.client_builder().build().await;
33
    let mut response = v5::Response::new("position-1".to_owned());
34
    response.rooms.insert(
35
        room_id!("!checkpoint:localhost").to_owned(),
36
        serde_json::from_value(json!({
37
                "initial": true,
38
                "required_state": [],
39
                "timeline": [{
40
                    "type": "m.room.message",
41
                    "event_id": "$checkpoint-mention",
42
                    "sender": "@sender:localhost",
43
                    "origin_server_ts": 1,
44
                    "content": {
45
                        "msgtype": "m.text",
46
                        "body": "checkpoint candidate",
47
                        "m.mentions": { "user_ids": [client.user_id().unwrap()] }
48
                    }
49
                }]
50
        }))
51
        .unwrap(),
52
    );
53
    server.mock_sliding_sync().ok(response).mount().await;
54
    (server, client)
55
}
56
57
async fn new_sync(client: &Client) -> matrix_sdk::sliding_sync::SlidingSync {
58
    client
59
        .sliding_sync("mentions-spike")
60
        .unwrap()
61
        .share_pos()
62
        .add_list(
63
            SlidingSyncList::builder("mentions")
64
                .sync_mode(SlidingSyncMode::new_selective().add_range(0..=0)),
65
        )
66
        .build()
67
        .await
68
        .unwrap()
69
}
70
71
#[tokio::test]
72
async fn awaited_event_handler_fences_position_even_after_caller_cancellation() {
73
    let (server, client) = server_with_mention().await;
74
75
    let entered = Arc::new(Notify::new());
76
    let committed = Arc::new(Semaphore::new(0));
77
    let handler = client.add_event_handler({
78
        let entered = entered.clone();
79
        let committed = committed.clone();
80
        move |event: Raw<AnySyncTimelineEvent>, room: Room| {
81
            let entered = entered.clone();
82
            let committed = committed.clone();
83
            async move {
84
                assert_eq!(room.room_id().as_str(), "!checkpoint:localhost");
85
                assert_eq!(
86
                    event.get_field::<String>("event_id").unwrap().as_deref(),
87
                    Some("$checkpoint-mention")
88
                );
89
                entered.notify_one();
90
                committed.acquire().await.unwrap().forget();
91
            }
92
        }
93
    });
94
    let sync = new_sync(&client).await;
95
    let processing = tokio::spawn(async move { sync.sync_once().await });
96
    tokio::time::timeout(Duration::from_secs(5), entered.notified())
97
        .await
98
        .unwrap();
99
    assert!(!processing.is_finished());
100
    assert!(saved_position(&client).await.is_none());
101
    processing.abort();
102
    assert!(processing.await.unwrap_err().is_cancelled());
103
    assert!(saved_position(&client).await.is_none());
104
105
    committed.add_permits(1);
106
    tokio::time::timeout(Duration::from_secs(5), async {
107
        while saved_position(&client).await.as_deref() != Some("position-1") {
108
            tokio::task::yield_now().await;
109
        }
110
    })
111
    .await
112
    .unwrap();
113
    client.remove_event_handler(handler);
114
115
    let restored = new_sync(&client).await;
116
    tokio::time::timeout(Duration::from_secs(5), restored.sync_once())
117
        .await
118
        .unwrap()
119
        .unwrap();
120
    let requests: Vec<_> = server
121
        .received_requests()
122
        .await
123
        .unwrap()
124
        .into_iter()
125
        .filter_map(|request| {
126
            let body = serde_json::from_slice::<Value>(&request.body).ok()?;
127
            let pos = request
128
                .url
129
                .query_pairs()
130
                .find(|(key, _)| key == "pos")
131
                .map(|(_, value)| value.into_owned());
132
            Some((body, pos))
133
        })
134
        .filter(|(body, _)| body["conn_id"] == "mentions-spike")
135
        .collect();
136
    assert_eq!(requests.len(), 2);
137
    assert!(requests[0].1.is_none());
138
    assert_eq!(requests[1].1.as_deref(), Some("position-1"));
139
}
140
141
#[tokio::test]
142
async fn event_handler_error_does_not_prevent_position_persistence() {
143
    let (_server, client) = server_with_mention().await;
144
    let handler =
145
        client.add_event_handler(|_event: Raw<AnySyncTimelineEvent>, _room: Room| async {
146
            Err::<(), _>("the candidate transaction failed")
147
        });
148
    let sync = new_sync(&client).await;
149
    tokio::time::timeout(Duration::from_secs(5), sync.sync_once())
150
        .await
151
        .unwrap()
152
        .unwrap();
153
    assert_eq!(saved_position(&client).await.as_deref(), Some("position-1"));
154
    client.remove_event_handler(handler);
155
}