1use std::collections::HashMap;
23use 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},
12 serde::Raw,
13 OwnedRoomId, OwnedUserId, UserId,
14};
15use serde_json::{from_value as from_json_value, json, Value as JsonValue};
1617use super::test_json;
1819mod bulk;
20mod invited_room;
21mod joined_room;
22mod knocked_room;
23mod left_room;
24mod test_event;
2526pub 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};
3536/// 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.
45joined_rooms: HashMap<OwnedRoomId, JoinedRoom>,
46/// Updates to invited `Room`s.
47invited_rooms: HashMap<OwnedRoomId, InvitedRoom>,
48/// Updates to left `Room`s.
49left_rooms: HashMap<OwnedRoomId, LeftRoom>,
50/// Updates to knocked `Room`s.
51knocked_rooms: HashMap<OwnedRoomId, KnockedRoom>,
52/// Events that determine the presence state of a user.
53presence: Vec<Raw<PresenceEvent>>,
54/// Global account data events.
55account_data: Vec<Raw<AnyGlobalAccountDataEvent>>,
56/// Internal counter to enable the `prev_batch` and `next_batch` of each
57 /// sync response to vary.
58batch_counter: i64,
59/// The device lists of the user.
60changed_device_lists: Vec<OwnedUserId>,
61}
6263impl SyncResponseBuilder {
64pub fn new() -> Self {
65Self::default()
66 }
6768/// Add a joined room to the next sync response.
69 ///
70 /// If a room with the same room ID already exists, it is replaced by this
71 /// one.
72pub fn add_joined_room(&mut self, room: JoinedRoomBuilder) -> &mut Self {
73self.invited_rooms.remove(&room.room_id);
74self.left_rooms.remove(&room.room_id);
75self.knocked_rooms.remove(&room.room_id);
76self.joined_rooms.insert(room.room_id, room.inner);
77self
78}
7980/// Add an invited room to the next sync response.
81 ///
82 /// If a room with the same room ID already exists, it is replaced by this
83 /// one.
84pub fn add_invited_room(&mut self, room: InvitedRoomBuilder) -> &mut Self {
85self.joined_rooms.remove(&room.room_id);
86self.left_rooms.remove(&room.room_id);
87self.knocked_rooms.remove(&room.room_id);
88self.invited_rooms.insert(room.room_id, room.inner);
89self
90}
9192/// Add a left room to the next sync response.
93 ///
94 /// If a room with the same room ID already exists, it is replaced by this
95 /// one.
96pub fn add_left_room(&mut self, room: LeftRoomBuilder) -> &mut Self {
97self.joined_rooms.remove(&room.room_id);
98self.invited_rooms.remove(&room.room_id);
99self.knocked_rooms.remove(&room.room_id);
100self.left_rooms.insert(room.room_id, room.inner);
101self
102}
103104/// Add a knocked room to the next sync response.
105 ///
106 /// If a room with the same room ID already exists, it is replaced by this
107 /// one.
108pub fn add_knocked_room(&mut self, room: KnockedRoomBuilder) -> &mut Self {
109self.joined_rooms.remove(&room.room_id);
110self.invited_rooms.remove(&room.room_id);
111self.left_rooms.remove(&room.room_id);
112self.knocked_rooms.insert(room.room_id, room.inner);
113self
114}
115116/// Add a presence event.
117pub fn add_presence_event(&mut self, event: PresenceTestEvent) -> &mut Self {
118let val = match event {
119 PresenceTestEvent::Presence => test_json::PRESENCE.to_owned(),
120 PresenceTestEvent::Custom(json) => json,
121 };
122123self.presence.push(from_json_value(val).unwrap());
124self
125}
126127/// Add presence in bulk.
128pub fn add_presence_bulk<I>(&mut self, events: I) -> &mut Self
129where
130I: IntoIterator<Item = Raw<PresenceEvent>>,
131 {
132self.presence.extend(events);
133self
134}
135136/// Add global account data.
137pub fn add_global_account_data_event(
138&mut self,
139 event: GlobalAccountDataTestEvent,
140 ) -> &mut Self {
141let val = match event {
142 GlobalAccountDataTestEvent::Direct => test_json::DIRECT.to_owned(),
143 GlobalAccountDataTestEvent::PushRules => test_json::PUSH_RULES.to_owned(),
144 GlobalAccountDataTestEvent::Custom(json) => json,
145 };
146147self.account_data.push(from_json_value(val).unwrap());
148self
149}
150151/// Add global account data in bulk.
152pub fn add_global_account_data_bulk<I>(&mut self, events: I) -> &mut Self
153where
154I: IntoIterator<Item = Raw<AnyGlobalAccountDataEvent>>,
155 {
156self.account_data.extend(events);
157self
158}
159160pub fn add_change_device(&mut self, user_id: &UserId) -> &mut Self {
161self.changed_device_lists.push(user_id.to_owned());
162self
163}
164165/// Builds a sync response as a JSON Value containing the events we queued
166 /// so far.
167 ///
168 /// The next response returned by `build_sync_response` will then be empty
169 /// if no further events were queued.
170 ///
171 /// This method is raw JSON equivalent to
172 /// [build_sync_response()](#method.build_sync_response), use
173 /// [build_sync_response()](#method.build_sync_response) if you need a typed
174 /// response.
175pub fn build_json_sync_response(&mut self) -> JsonValue {
176self.batch_counter += 1;
177let next_batch = self.generate_sync_token();
178179let body = json! {
180 {
181"device_one_time_keys_count": {},
182"next_batch": next_batch,
183"device_lists": {
184"changed": self.changed_device_lists,
185"left": [],
186 },
187"rooms": {
188"invite": self.invited_rooms,
189"join": self.joined_rooms,
190"leave": self.left_rooms,
191"knock": self.knocked_rooms,
192 },
193"to_device": {
194"events": []
195 },
196"presence": {
197"events": self.presence,
198 },
199"account_data": {
200"events": self.account_data,
201 },
202 }
203 };
204205// Clear state so that the next sync response will be empty if nothing
206 // was added.
207self.clear();
208209 body
210 }
211212/// Builds a `SyncResponse` containing the events we queued so far.
213 ///
214 /// The next response returned by `build_sync_response` will then be empty
215 /// if no further events were queued.
216 ///
217 /// This method is high level and typed equivalent to
218 /// [build_json_sync_response()](#method.build_json_sync_response), use
219 /// [build_json_sync_response()](#method.build_json_sync_response) if you
220 /// need an untyped response.
221pub fn build_sync_response(&mut self) -> SyncResponse {
222let body = self.build_json_sync_response();
223224let response = Response::builder().body(serde_json::to_vec(&body).unwrap()).unwrap();
225226 SyncResponse::try_from_http_response(response).unwrap()
227 }
228229fn generate_sync_token(&self) -> String {
230format!("t392-516_47314_0_7_1_1_1_11444_{}", self.batch_counter)
231 }
232233pub fn clear(&mut self) {
234self.account_data.clear();
235self.invited_rooms.clear();
236self.joined_rooms.clear();
237self.left_rooms.clear();
238self.knocked_rooms.clear();
239self.presence.clear();
240 }
241}