1use ruma::OwnedUserId;
16
17use super::Room;
18
19impl Room {
20 pub fn has_active_room_call(&self) -> bool {
23 self.inner.read().has_active_room_call()
24 }
25
26 pub fn active_room_call_participants(&self) -> Vec<OwnedUserId> {
35 self.inner.read().active_room_call_participants()
36 }
37}
38
39#[cfg(test)]
40mod tests {
41 use std::{ops::Sub, sync::Arc, time::Duration};
42
43 use assign::assign;
44 use matrix_sdk_test::{ALICE, BOB, CAROL};
45 use ruma::{
46 device_id, event_id,
47 events::{
48 call::member::{
49 ActiveFocus, ActiveLivekitFocus, Application, CallApplicationContent,
50 CallMemberEventContent, CallMemberStateKey, Focus, LegacyMembershipData,
51 LegacyMembershipDataInit, LivekitFocus, OriginalSyncCallMemberEvent,
52 },
53 AnySyncStateEvent, StateUnsigned, SyncStateEvent,
54 },
55 room_id,
56 time::SystemTime,
57 user_id, DeviceId, EventId, MilliSecondsSinceUnixEpoch, OwnedUserId, UserId,
58 };
59 use similar_asserts::assert_eq;
60
61 use super::super::{Room, RoomState};
62 use crate::store::MemoryStore;
63
64 fn make_room_test_helper(room_type: RoomState) -> (Arc<MemoryStore>, Room) {
65 let store = Arc::new(MemoryStore::new());
66 let user_id = user_id!("@me:example.org");
67 let room_id = room_id!("!test:localhost");
68 let (sender, _receiver) = tokio::sync::broadcast::channel(1);
69
70 (store.clone(), Room::new(user_id, store, room_id, room_type, sender))
71 }
72
73 fn timestamp(minutes_ago: u32) -> MilliSecondsSinceUnixEpoch {
74 MilliSecondsSinceUnixEpoch::from_system_time(
75 SystemTime::now().sub(Duration::from_secs((60 * minutes_ago).into())),
76 )
77 .expect("date out of range")
78 }
79
80 fn legacy_membership_for_my_call(
81 device_id: &DeviceId,
82 membership_id: &str,
83 minutes_ago: u32,
84 ) -> LegacyMembershipData {
85 let (application, foci) = foci_and_application();
86 assign!(
87 LegacyMembershipData::from(LegacyMembershipDataInit {
88 application,
89 device_id: device_id.to_owned(),
90 expires: Duration::from_millis(3_600_000),
91 foci_active: foci,
92 membership_id: membership_id.to_owned(),
93 }),
94 { created_ts: Some(timestamp(minutes_ago)) }
95 )
96 }
97
98 fn legacy_member_state_event(
99 memberships: Vec<LegacyMembershipData>,
100 ev_id: &EventId,
101 user_id: &UserId,
102 ) -> AnySyncStateEvent {
103 let content = CallMemberEventContent::new_legacy(memberships);
104
105 AnySyncStateEvent::CallMember(SyncStateEvent::Original(OriginalSyncCallMemberEvent {
106 content,
107 event_id: ev_id.to_owned(),
108 sender: user_id.to_owned(),
109 origin_server_ts: timestamp(0),
112 state_key: CallMemberStateKey::new(user_id.to_owned(), None, false),
113 unsigned: StateUnsigned::new(),
114 }))
115 }
116
117 struct InitData<'a> {
118 device_id: &'a DeviceId,
119 minutes_ago: u32,
120 }
121
122 fn session_member_state_event(
123 ev_id: &EventId,
124 user_id: &UserId,
125 init_data: Option<InitData<'_>>,
126 ) -> AnySyncStateEvent {
127 let application = Application::Call(CallApplicationContent::new(
128 "my_call_id_1".to_owned(),
129 ruma::events::call::member::CallScope::Room,
130 ));
131 let foci_preferred = vec![Focus::Livekit(LivekitFocus::new(
132 "my_call_foci_alias".to_owned(),
133 "https://lk.org".to_owned(),
134 ))];
135 let focus_active = ActiveFocus::Livekit(ActiveLivekitFocus::new());
136 let (content, state_key) = match init_data {
137 Some(InitData { device_id, minutes_ago }) => (
138 CallMemberEventContent::new(
139 application,
140 device_id.to_owned(),
141 focus_active,
142 foci_preferred,
143 Some(timestamp(minutes_ago)),
144 ),
145 CallMemberStateKey::new(user_id.to_owned(), Some(device_id.to_owned()), false),
146 ),
147 None => (
148 CallMemberEventContent::new_empty(None),
149 CallMemberStateKey::new(user_id.to_owned(), None, false),
150 ),
151 };
152
153 AnySyncStateEvent::CallMember(SyncStateEvent::Original(OriginalSyncCallMemberEvent {
154 content,
155 event_id: ev_id.to_owned(),
156 sender: user_id.to_owned(),
157 origin_server_ts: timestamp(0),
160 state_key,
161 unsigned: StateUnsigned::new(),
162 }))
163 }
164
165 fn foci_and_application() -> (Application, Vec<Focus>) {
166 (
167 Application::Call(CallApplicationContent::new(
168 "my_call_id_1".to_owned(),
169 ruma::events::call::member::CallScope::Room,
170 )),
171 vec![Focus::Livekit(LivekitFocus::new(
172 "my_call_foci_alias".to_owned(),
173 "https://lk.org".to_owned(),
174 ))],
175 )
176 }
177
178 fn receive_state_events(room: &Room, events: Vec<&AnySyncStateEvent>) {
179 room.inner.update_if(|info| {
180 let mut res = false;
181 for ev in events {
182 res |= info.handle_state_event(ev);
183 }
184 res
185 });
186 }
187
188 fn legacy_create_call_with_member_events_for_user(a: &UserId, b: &UserId, c: &UserId) -> Room {
192 let (_, room) = make_room_test_helper(RoomState::Joined);
193
194 let a_empty = legacy_member_state_event(Vec::new(), event_id!("$1234"), a);
195
196 let m_init_b = legacy_membership_for_my_call(device_id!("DEVICE_0"), "0", 1);
198 let b_one = legacy_member_state_event(vec![m_init_b], event_id!("$12345"), b);
199
200 let m_init_c1 = legacy_membership_for_my_call(device_id!("DEVICE_0"), "0", 10);
202 let m_init_c2 = legacy_membership_for_my_call(device_id!("DEVICE_1"), "0", 20);
204 let c_two = legacy_member_state_event(vec![m_init_c1, m_init_c2], event_id!("$123456"), c);
205
206 receive_state_events(&room, vec![&c_two, &a_empty, &b_one]);
208
209 room
210 }
211
212 fn session_create_call_with_member_events_for_user(a: &UserId, b: &UserId, c: &UserId) -> Room {
216 let (_, room) = make_room_test_helper(RoomState::Joined);
217
218 let a_empty = session_member_state_event(event_id!("$1234"), a, None);
219
220 let b_one = session_member_state_event(
222 event_id!("$12345"),
223 b,
224 Some(InitData { device_id: "DEVICE_0".into(), minutes_ago: 1 }),
225 );
226
227 let m_c1 = session_member_state_event(
228 event_id!("$123456_0"),
229 c,
230 Some(InitData { device_id: "DEVICE_0".into(), minutes_ago: 10 }),
231 );
232 let m_c2 = session_member_state_event(
233 event_id!("$123456_1"),
234 c,
235 Some(InitData { device_id: "DEVICE_1".into(), minutes_ago: 20 }),
236 );
237 receive_state_events(&room, vec![&m_c1, &m_c2, &a_empty, &b_one]);
239
240 room
241 }
242
243 #[test]
244 fn test_show_correct_active_call_state() {
245 let room_legacy = legacy_create_call_with_member_events_for_user(&ALICE, &BOB, &CAROL);
246
247 assert_eq!(
251 vec![CAROL.to_owned(), CAROL.to_owned(), BOB.to_owned()],
252 room_legacy.active_room_call_participants()
253 );
254 assert!(room_legacy.has_active_room_call());
255
256 let room_session = session_create_call_with_member_events_for_user(&ALICE, &BOB, &CAROL);
257 assert_eq!(
258 vec![CAROL.to_owned(), CAROL.to_owned(), BOB.to_owned()],
259 room_session.active_room_call_participants()
260 );
261 assert!(room_session.has_active_room_call());
262 }
263
264 #[test]
265 fn test_active_call_is_false_when_everyone_left() {
266 let room = legacy_create_call_with_member_events_for_user(&ALICE, &BOB, &CAROL);
267
268 let b_empty_membership = legacy_member_state_event(Vec::new(), event_id!("$1234_1"), &BOB);
269 let c_empty_membership =
270 legacy_member_state_event(Vec::new(), event_id!("$12345_1"), &CAROL);
271
272 receive_state_events(&room, vec![&b_empty_membership, &c_empty_membership]);
273
274 assert_eq!(Vec::<OwnedUserId>::new(), room.active_room_call_participants());
276 assert!(!room.has_active_room_call());
277 }
278}