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();
    }
}