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