395 lines
13 KiB
Rust
395 lines
13 KiB
Rust
use std::{collections::HashMap, panic};
|
|
|
|
use http::Response;
|
|
use matrix_sdk_common::{
|
|
api::r0::sync::sync_events::Response as SyncResponse,
|
|
events::{
|
|
presence::PresenceEvent, AnyGlobalAccountDataEvent, AnySyncEphemeralRoomEvent,
|
|
AnySyncRoomEvent, AnySyncStateEvent,
|
|
},
|
|
identifiers::{room_id, RoomId},
|
|
IncomingResponse,
|
|
};
|
|
pub use matrix_sdk_test_macros::async_test;
|
|
use serde_json::Value as JsonValue;
|
|
|
|
pub mod test_json;
|
|
|
|
/// Embedded event files
|
|
#[derive(Debug)]
|
|
pub enum EventsJson {
|
|
Alias,
|
|
Aliases,
|
|
Create,
|
|
FullyRead,
|
|
HistoryVisibility,
|
|
JoinRules,
|
|
Member,
|
|
MemberNameChange,
|
|
MessageEmote,
|
|
MessageNotice,
|
|
MessageText,
|
|
Name,
|
|
PowerLevels,
|
|
Presence,
|
|
RedactedInvalid,
|
|
RedactedState,
|
|
Redacted,
|
|
Redaction,
|
|
RoomAvatar,
|
|
Tag,
|
|
Topic,
|
|
Typing,
|
|
}
|
|
|
|
/// The `EventBuilder` 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.
|
|
///
|
|
/// **Important** You *must* use the *same* builder when sending multiple sync
|
|
/// responses to a single client. Otherwise, the subsequent responses will be
|
|
/// *ignored* by the client because the `next_batch` sync token will not be
|
|
/// rotated properly.
|
|
///
|
|
/// # Example usage
|
|
///
|
|
/// ```rust
|
|
/// use matrix_sdk_test::{EventBuilder, EventsJson};
|
|
///
|
|
/// let mut builder = EventBuilder::new();
|
|
///
|
|
/// // response1 now contains events that add an example member to the room and change their power
|
|
/// // level
|
|
/// let response1 = builder
|
|
/// .add_room_event(EventsJson::Member)
|
|
/// .add_room_event(EventsJson::PowerLevels)
|
|
/// .build_sync_response();
|
|
///
|
|
/// // response2 is now empty (nothing changed)
|
|
/// let response2 = builder.build_sync_response();
|
|
///
|
|
/// // response3 contains a display name change for member example
|
|
/// let response3 = builder
|
|
/// .add_room_event(EventsJson::MemberNameChange)
|
|
/// .build_sync_response();
|
|
/// ```
|
|
|
|
#[derive(Default)]
|
|
pub struct EventBuilder {
|
|
/// The events that determine the state of a `Room`.
|
|
joined_room_events: HashMap<RoomId, Vec<AnySyncRoomEvent>>,
|
|
/// The events that determine the state of a `Room`.
|
|
invited_room_events: HashMap<RoomId, Vec<AnySyncStateEvent>>,
|
|
/// The events that determine the state of a `Room`.
|
|
left_room_events: HashMap<RoomId, Vec<AnySyncRoomEvent>>,
|
|
/// The presence events that determine the presence state of a `RoomMember`.
|
|
presence_events: Vec<PresenceEvent>,
|
|
/// The state events that determine the state of a `Room`.
|
|
state_events: Vec<AnySyncStateEvent>,
|
|
/// The ephemeral room events that determine the state of a `Room`.
|
|
ephemeral: Vec<AnySyncEphemeralRoomEvent>,
|
|
/// The account data events that determine the state of a `Room`.
|
|
account_data: Vec<AnyGlobalAccountDataEvent>,
|
|
/// Internal counter to enable the `prev_batch` and `next_batch` of each
|
|
/// sync response to vary.
|
|
batch_counter: i64,
|
|
}
|
|
|
|
impl EventBuilder {
|
|
pub fn new() -> Self {
|
|
let builder: EventBuilder = Default::default();
|
|
builder
|
|
}
|
|
|
|
/// Add an event to the room events `Vec`.
|
|
pub fn add_ephemeral(&mut self, json: EventsJson) -> &mut Self {
|
|
let val: &JsonValue = match json {
|
|
EventsJson::Typing => &test_json::TYPING,
|
|
_ => panic!("unknown ephemeral event {:?}", json),
|
|
};
|
|
|
|
let event = serde_json::from_value::<AnySyncEphemeralRoomEvent>(val.clone()).unwrap();
|
|
self.ephemeral.push(event);
|
|
self
|
|
}
|
|
|
|
/// Add an event to the room events `Vec`.
|
|
#[allow(clippy::match_single_binding, unused)]
|
|
pub fn add_account(&mut self, json: EventsJson) -> &mut Self {
|
|
let val: &JsonValue = match json {
|
|
_ => panic!("unknown account event {:?}", json),
|
|
};
|
|
|
|
let event = serde_json::from_value::<AnyGlobalAccountDataEvent>(val.clone()).unwrap();
|
|
self.account_data.push(event);
|
|
self
|
|
}
|
|
|
|
/// Add an event to the room events `Vec`.
|
|
pub fn add_room_event(&mut self, json: EventsJson) -> &mut Self {
|
|
let val: &JsonValue = match json {
|
|
EventsJson::Member => &test_json::MEMBER,
|
|
EventsJson::MemberNameChange => &test_json::MEMBER_NAME_CHANGE,
|
|
EventsJson::PowerLevels => &test_json::POWER_LEVELS,
|
|
_ => panic!("unknown room event json {:?}", json),
|
|
};
|
|
|
|
let event = serde_json::from_value::<AnySyncRoomEvent>(val.clone()).unwrap();
|
|
|
|
self.add_joined_event(&room_id!("!SVkFJHzfwvuaIEawgC:localhost"), event);
|
|
self
|
|
}
|
|
|
|
pub fn add_custom_joined_event(
|
|
&mut self,
|
|
room_id: &RoomId,
|
|
event: serde_json::Value,
|
|
) -> &mut Self {
|
|
let event = serde_json::from_value::<AnySyncRoomEvent>(event).unwrap();
|
|
self.add_joined_event(room_id, event);
|
|
self
|
|
}
|
|
|
|
fn add_joined_event(&mut self, room_id: &RoomId, event: AnySyncRoomEvent) {
|
|
self.joined_room_events.entry(room_id.clone()).or_insert_with(Vec::new).push(event);
|
|
}
|
|
|
|
pub fn add_custom_invited_event(
|
|
&mut self,
|
|
room_id: &RoomId,
|
|
event: serde_json::Value,
|
|
) -> &mut Self {
|
|
let event = serde_json::from_value::<AnySyncStateEvent>(event).unwrap();
|
|
self.invited_room_events.entry(room_id.clone()).or_insert_with(Vec::new).push(event);
|
|
self
|
|
}
|
|
|
|
pub fn add_custom_left_event(
|
|
&mut self,
|
|
room_id: &RoomId,
|
|
event: serde_json::Value,
|
|
) -> &mut Self {
|
|
let event = serde_json::from_value::<AnySyncRoomEvent>(event).unwrap();
|
|
self.left_room_events.entry(room_id.clone()).or_insert_with(Vec::new).push(event);
|
|
self
|
|
}
|
|
|
|
/// Add a state event to the state events `Vec`.
|
|
pub fn add_state_event(&mut self, json: EventsJson) -> &mut Self {
|
|
let val: &JsonValue = match json {
|
|
EventsJson::Alias => &test_json::ALIAS,
|
|
EventsJson::Aliases => &test_json::ALIASES,
|
|
EventsJson::Name => &test_json::NAME,
|
|
EventsJson::Member => &test_json::MEMBER,
|
|
EventsJson::PowerLevels => &test_json::POWER_LEVELS,
|
|
_ => panic!("unknown state event {:?}", json),
|
|
};
|
|
|
|
let event = serde_json::from_value::<AnySyncStateEvent>(val.clone()).unwrap();
|
|
self.state_events.push(event);
|
|
self
|
|
}
|
|
|
|
/// Add an presence event to the presence events `Vec`.
|
|
pub fn add_presence_event(&mut self, json: EventsJson) -> &mut Self {
|
|
let val: &JsonValue = match json {
|
|
EventsJson::Presence => &test_json::PRESENCE,
|
|
_ => panic!("unknown presence event {:?}", json),
|
|
};
|
|
|
|
let event = serde_json::from_value::<PresenceEvent>(val.clone()).unwrap();
|
|
self.presence_events.push(event);
|
|
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 {
|
|
let main_room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost");
|
|
|
|
// First time building a sync response, so initialize the `prev_batch` to a
|
|
// default one.
|
|
let prev_batch = self.generate_sync_token();
|
|
self.batch_counter += 1;
|
|
let next_batch = self.generate_sync_token();
|
|
|
|
// TODO generalize this.
|
|
let joined_room = serde_json::json!({
|
|
"summary": {},
|
|
"account_data": {
|
|
"events": self.account_data
|
|
},
|
|
"ephemeral": {
|
|
"events": self.ephemeral
|
|
},
|
|
"state": {
|
|
"events": self.state_events
|
|
},
|
|
"timeline": {
|
|
"events": self.joined_room_events.remove(&main_room_id).unwrap_or_default(),
|
|
"limited": true,
|
|
"prev_batch": prev_batch
|
|
},
|
|
"unread_notifications": {
|
|
"highlight_count": 0,
|
|
"notification_count": 11
|
|
}
|
|
});
|
|
|
|
let mut joined_rooms: HashMap<RoomId, serde_json::Value> = HashMap::new();
|
|
|
|
joined_rooms.insert(main_room_id, joined_room);
|
|
|
|
for (room_id, events) in self.joined_room_events.drain() {
|
|
let joined_room = serde_json::json!({
|
|
"summary": {},
|
|
"account_data": {
|
|
"events": [],
|
|
},
|
|
"ephemeral": {
|
|
"events": [],
|
|
},
|
|
"state": {
|
|
"events": [],
|
|
},
|
|
"timeline": {
|
|
"events": events,
|
|
"limited": true,
|
|
"prev_batch": prev_batch
|
|
},
|
|
"unread_notifications": {
|
|
"highlight_count": 0,
|
|
"notification_count": 11
|
|
}
|
|
});
|
|
joined_rooms.insert(room_id, joined_room);
|
|
}
|
|
|
|
let mut left_rooms: HashMap<RoomId, serde_json::Value> = HashMap::new();
|
|
|
|
for (room_id, events) in self.left_room_events.drain() {
|
|
let room = serde_json::json!({
|
|
"state": {
|
|
"events": [],
|
|
},
|
|
"timeline": {
|
|
"events": events,
|
|
"limited": false,
|
|
"prev_batch": prev_batch
|
|
},
|
|
});
|
|
left_rooms.insert(room_id, room);
|
|
}
|
|
|
|
let mut invited_rooms: HashMap<RoomId, serde_json::Value> = HashMap::new();
|
|
|
|
for (room_id, events) in self.invited_room_events.drain() {
|
|
let room = serde_json::json!({
|
|
"invite_state": {
|
|
"events": events,
|
|
},
|
|
});
|
|
invited_rooms.insert(room_id, room);
|
|
}
|
|
|
|
let body = serde_json::json! {
|
|
{
|
|
"device_one_time_keys_count": {},
|
|
"next_batch": next_batch,
|
|
"device_lists": {
|
|
"changed": [],
|
|
"left": []
|
|
},
|
|
"rooms": {
|
|
"invite": invited_rooms,
|
|
"join": joined_rooms,
|
|
"leave": left_rooms,
|
|
},
|
|
"to_device": {
|
|
"events": []
|
|
},
|
|
"presence": {
|
|
"events": []
|
|
}
|
|
}
|
|
};
|
|
|
|
// 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.ephemeral.clear();
|
|
self.invited_room_events.clear();
|
|
self.joined_room_events.clear();
|
|
self.left_room_events.clear();
|
|
self.presence_events.clear();
|
|
self.state_events.clear();
|
|
}
|
|
}
|
|
|
|
/// Embedded sync reponse files
|
|
pub enum SyncResponseFile {
|
|
All,
|
|
Default,
|
|
DefaultWithSummary,
|
|
Invite,
|
|
Leave,
|
|
Voip,
|
|
}
|
|
|
|
/// Get specific API responses for testing
|
|
pub fn sync_response(kind: SyncResponseFile) -> SyncResponse {
|
|
let data: &JsonValue = match kind {
|
|
SyncResponseFile::All => &test_json::MORE_SYNC,
|
|
SyncResponseFile::Default => &test_json::SYNC,
|
|
SyncResponseFile::DefaultWithSummary => &test_json::DEFAULT_SYNC_SUMMARY,
|
|
SyncResponseFile::Invite => &test_json::INVITE_SYNC,
|
|
SyncResponseFile::Leave => &test_json::LEAVE_SYNC,
|
|
SyncResponseFile::Voip => &test_json::VOIP_SYNC,
|
|
};
|
|
|
|
let response = Response::builder().body(data.to_string().as_bytes().to_vec()).unwrap();
|
|
SyncResponse::try_from_http_response(response).unwrap()
|
|
}
|
|
|
|
pub fn response_from_file(json: &serde_json::Value) -> Response<Vec<u8>> {
|
|
Response::builder().status(200).body(json.to_string().as_bytes().to_vec()).unwrap()
|
|
}
|