matrix_sdk_test/sync_builder/
mod.rs

1use std::collections::HashMap;
2
3use http::Response;
4use ruma::{
5    api::{
6        client::sync::sync_events::v3::{
7            InvitedRoom, JoinedRoom, KnockedRoom, LeftRoom, Response as SyncResponse,
8        },
9        IncomingResponse,
10    },
11    events::{presence::PresenceEvent, AnyGlobalAccountDataEvent, AnyToDeviceEvent},
12    serde::Raw,
13    OwnedRoomId, OwnedUserId, UserId,
14};
15use serde_json::{from_value as from_json_value, json, Value as JsonValue};
16
17use super::test_json;
18
19mod bulk;
20mod invited_room;
21mod joined_room;
22mod knocked_room;
23mod left_room;
24mod test_event;
25
26pub use bulk::bulk_room_members;
27pub use invited_room::InvitedRoomBuilder;
28pub use joined_room::JoinedRoomBuilder;
29pub use knocked_room::KnockedRoomBuilder;
30pub use left_room::LeftRoomBuilder;
31pub use test_event::{
32    GlobalAccountDataTestEvent, PresenceTestEvent, RoomAccountDataTestEvent, StateTestEvent,
33    StrippedStateTestEvent,
34};
35
36/// The `SyncResponseBuilder` struct can be used to easily generate valid sync
37/// responses for testing. These can be then fed into either `Client` or `Room`.
38///
39/// It supports generated a number of canned events, such as a member entering a
40/// room, his power level and display name changing and similar. It also
41/// supports insertion of custom events in the form of `EventsJson` values.
42#[derive(Default)]
43pub struct SyncResponseBuilder {
44    /// Updates to joined `Room`s.
45    joined_rooms: HashMap<OwnedRoomId, JoinedRoom>,
46    /// Updates to invited `Room`s.
47    invited_rooms: HashMap<OwnedRoomId, InvitedRoom>,
48    /// Updates to left `Room`s.
49    left_rooms: HashMap<OwnedRoomId, LeftRoom>,
50    /// Updates to knocked `Room`s.
51    knocked_rooms: HashMap<OwnedRoomId, KnockedRoom>,
52    /// Events that determine the presence state of a user.
53    presence: Vec<Raw<PresenceEvent>>,
54    /// Global account data events.
55    account_data: Vec<Raw<AnyGlobalAccountDataEvent>>,
56    /// Internal counter to enable the `prev_batch` and `next_batch` of each
57    /// sync response to vary.
58    batch_counter: i64,
59    /// The device lists of the user.
60    changed_device_lists: Vec<OwnedUserId>,
61    to_device_events: Vec<Raw<AnyToDeviceEvent>>,
62}
63
64impl SyncResponseBuilder {
65    pub fn new() -> Self {
66        Self::default()
67    }
68
69    /// Add a joined room to the next sync response.
70    ///
71    /// If a room with the same room ID already exists, it is replaced by this
72    /// one.
73    pub fn add_joined_room(&mut self, room: JoinedRoomBuilder) -> &mut Self {
74        self.invited_rooms.remove(&room.room_id);
75        self.left_rooms.remove(&room.room_id);
76        self.knocked_rooms.remove(&room.room_id);
77        self.joined_rooms.insert(room.room_id, room.inner);
78        self
79    }
80
81    /// Add an invited room to the next sync response.
82    ///
83    /// If a room with the same room ID already exists, it is replaced by this
84    /// one.
85    pub fn add_invited_room(&mut self, room: InvitedRoomBuilder) -> &mut Self {
86        self.joined_rooms.remove(&room.room_id);
87        self.left_rooms.remove(&room.room_id);
88        self.knocked_rooms.remove(&room.room_id);
89        self.invited_rooms.insert(room.room_id, room.inner);
90        self
91    }
92
93    /// Add a left room to the next sync response.
94    ///
95    /// If a room with the same room ID already exists, it is replaced by this
96    /// one.
97    pub fn add_left_room(&mut self, room: LeftRoomBuilder) -> &mut Self {
98        self.joined_rooms.remove(&room.room_id);
99        self.invited_rooms.remove(&room.room_id);
100        self.knocked_rooms.remove(&room.room_id);
101        self.left_rooms.insert(room.room_id, room.inner);
102        self
103    }
104
105    /// Add a knocked room to the next sync response.
106    ///
107    /// If a room with the same room ID already exists, it is replaced by this
108    /// one.
109    pub fn add_knocked_room(&mut self, room: KnockedRoomBuilder) -> &mut Self {
110        self.joined_rooms.remove(&room.room_id);
111        self.invited_rooms.remove(&room.room_id);
112        self.left_rooms.remove(&room.room_id);
113        self.knocked_rooms.insert(room.room_id, room.inner);
114        self
115    }
116
117    /// Add a presence event.
118    pub fn add_presence_event(&mut self, event: PresenceTestEvent) -> &mut Self {
119        let val = match event {
120            PresenceTestEvent::Presence => test_json::PRESENCE.to_owned(),
121            PresenceTestEvent::Custom(json) => json,
122        };
123
124        self.presence.push(from_json_value(val).unwrap());
125        self
126    }
127
128    /// Add presence in bulk.
129    pub fn add_presence_bulk<I>(&mut self, events: I) -> &mut Self
130    where
131        I: IntoIterator<Item = Raw<PresenceEvent>>,
132    {
133        self.presence.extend(events);
134        self
135    }
136
137    /// Add global account data.
138    pub fn add_global_account_data_event(
139        &mut self,
140        event: GlobalAccountDataTestEvent,
141    ) -> &mut Self {
142        let val = match event {
143            GlobalAccountDataTestEvent::Direct => test_json::DIRECT.to_owned(),
144            GlobalAccountDataTestEvent::PushRules => test_json::PUSH_RULES.to_owned(),
145            GlobalAccountDataTestEvent::IgnoredUserList => test_json::IGNORED_USER_LIST.to_owned(),
146            GlobalAccountDataTestEvent::Custom(json) => json,
147        };
148
149        self.account_data.push(from_json_value(val).unwrap());
150        self
151    }
152
153    /// Add global account data in bulk.
154    pub fn add_global_account_data_bulk<I>(&mut self, events: I) -> &mut Self
155    where
156        I: IntoIterator<Item = Raw<AnyGlobalAccountDataEvent>>,
157    {
158        self.account_data.extend(events);
159        self
160    }
161
162    pub fn add_change_device(&mut self, user_id: &UserId) -> &mut Self {
163        self.changed_device_lists.push(user_id.to_owned());
164        self
165    }
166
167    /// Add a to device event.
168    pub fn add_to_device_event(&mut self, event: JsonValue) -> &mut Self {
169        self.to_device_events.push(from_json_value(event).unwrap());
170        self
171    }
172
173    /// Builds a sync response as a JSON Value containing the events we queued
174    /// so far.
175    ///
176    /// The next response returned by `build_sync_response` will then be empty
177    /// if no further events were queued.
178    ///
179    /// This method is raw JSON equivalent to
180    /// [build_sync_response()](#method.build_sync_response), use
181    /// [build_sync_response()](#method.build_sync_response) if you need a typed
182    /// response.
183    pub fn build_json_sync_response(&mut self) -> JsonValue {
184        self.batch_counter += 1;
185        let next_batch = self.generate_sync_token();
186
187        let body = json! {
188            {
189                "device_one_time_keys_count": {},
190                "next_batch": next_batch,
191                "device_lists": {
192                    "changed": self.changed_device_lists,
193                    "left": [],
194                },
195                "rooms": {
196                    "invite": self.invited_rooms,
197                    "join": self.joined_rooms,
198                    "leave": self.left_rooms,
199                    "knock": self.knocked_rooms,
200                },
201                "to_device": {
202                    "events": self.to_device_events,
203                },
204                "presence": {
205                    "events": self.presence,
206                },
207                "account_data": {
208                    "events": self.account_data,
209                },
210            }
211        };
212
213        // Clear state so that the next sync response will be empty if nothing
214        // was added.
215        self.clear();
216
217        body
218    }
219
220    /// Builds a `SyncResponse` containing the events we queued so far.
221    ///
222    /// The next response returned by `build_sync_response` will then be empty
223    /// if no further events were queued.
224    ///
225    /// This method is high level and typed equivalent to
226    /// [build_json_sync_response()](#method.build_json_sync_response), use
227    /// [build_json_sync_response()](#method.build_json_sync_response) if you
228    /// need an untyped response.
229    pub fn build_sync_response(&mut self) -> SyncResponse {
230        let body = self.build_json_sync_response();
231
232        let response = Response::builder().body(serde_json::to_vec(&body).unwrap()).unwrap();
233
234        SyncResponse::try_from_http_response(response).unwrap()
235    }
236
237    fn generate_sync_token(&self) -> String {
238        format!("t392-516_47314_0_7_1_1_1_11444_{}", self.batch_counter)
239    }
240
241    pub fn clear(&mut self) {
242        self.account_data.clear();
243        self.invited_rooms.clear();
244        self.joined_rooms.clear();
245        self.left_rooms.clear();
246        self.knocked_rooms.clear();
247        self.presence.clear();
248    }
249}