1use crate::TestServer;
2use call::ActiveCall;
3use chrono::Utc;
4use collab::db::ChannelId;
5use editor::Editor;
6use gpui::{BackgroundExecutor, TestAppContext};
7use rpc::proto;
8use util::rel_path::rel_path;
9#[gpui::test]
10async fn test_channel_guests(
11 executor: BackgroundExecutor,
12 cx_a: &mut TestAppContext,
13 cx_b: &mut TestAppContext,
14) {
15 let mut server = TestServer::start(executor.clone()).await;
16 let client_a = server.create_client(cx_a, "user_a").await;
17 let client_b = server.create_client(cx_b, "user_b").await;
18 let active_call_a = cx_a.read(ActiveCall::global);
19
20 let channel_id = server
21 .make_public_channel("the-channel", &client_a, cx_a)
22 .await;
23
24 // Client A shares a project in the channel
25 let project_a = client_a.build_test_project(cx_a).await;
26 active_call_a
27 .update(cx_a, |call, cx| call.join_channel(channel_id, cx))
28 .await
29 .unwrap();
30 let project_id = active_call_a
31 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
32 .await
33 .unwrap();
34 cx_a.executor().run_until_parked();
35
36 // Client B joins channel A as a guest
37 cx_b.update(|cx| {
38 workspace::join_channel(channel_id, client_b.app_state.clone(), None, None, cx)
39 })
40 .await
41 .unwrap();
42
43 // b should be following a in the shared project.
44 // B is a guest,
45 executor.run_until_parked();
46
47 let active_call_b = cx_b.read(ActiveCall::global);
48 let project_b =
49 active_call_b.read_with(cx_b, |call, _| call.location().unwrap().upgrade().unwrap());
50 let room_b = active_call_b.update(cx_b, |call, _| call.room().unwrap().clone());
51
52 assert_eq!(
53 project_b.read_with(cx_b, |project, _| project.remote_id()),
54 Some(project_id),
55 );
56 assert!(project_b.read_with(cx_b, |project, cx| project.is_read_only(cx)));
57 assert!(
58 project_b
59 .update(cx_b, |project, cx| {
60 let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id();
61 project.create_entry((worktree_id, rel_path("b.txt")), false, cx)
62 })
63 .await
64 .is_err()
65 );
66 assert!(room_b.read_with(cx_b, |room, _| room.is_muted()));
67}
68
69#[gpui::test]
70async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
71 let mut server = TestServer::start(cx_a.executor()).await;
72 let client_a = server.create_client(cx_a, "user_a").await;
73 let client_b = server.create_client(cx_b, "user_b").await;
74 let active_call_a = cx_a.read(ActiveCall::global);
75
76 let channel_id = server
77 .make_public_channel("the-channel", &client_a, cx_a)
78 .await;
79
80 let project_a = client_a.build_test_project(cx_a).await;
81 cx_a.update(|cx| {
82 workspace::join_channel(channel_id, client_a.app_state.clone(), None, None, cx)
83 })
84 .await
85 .unwrap();
86
87 // Client A shares a project in the channel
88 active_call_a
89 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
90 .await
91 .unwrap();
92 cx_a.run_until_parked();
93
94 // Client B joins channel A as a guest
95 cx_b.update(|cx| {
96 workspace::join_channel(channel_id, client_b.app_state.clone(), None, None, cx)
97 })
98 .await
99 .unwrap();
100 cx_a.run_until_parked();
101
102 // client B opens 1.txt as a guest
103 let (workspace_b, cx_b) = client_b.active_workspace(cx_b);
104 let room_b = cx_b
105 .read(ActiveCall::global)
106 .update(cx_b, |call, _| call.room().unwrap().clone());
107 cx_b.simulate_keystrokes("cmd-p");
108 cx_a.run_until_parked();
109 cx_b.simulate_keystrokes("1 enter");
110
111 let (project_b, editor_b) = workspace_b.update(cx_b, |workspace, cx| {
112 (
113 workspace.project().clone(),
114 workspace.active_item_as::<Editor>(cx).unwrap(),
115 )
116 });
117 assert!(project_b.read_with(cx_b, |project, cx| project.is_read_only(cx)));
118 assert!(editor_b.update(cx_b, |e, cx| e.read_only(cx)));
119 cx_b.update(|_window, cx_b| {
120 assert!(room_b.read_with(cx_b, |room, _| !room.can_use_microphone()));
121 });
122 assert!(
123 room_b
124 .update(cx_b, |room, cx| room.share_microphone(cx))
125 .await
126 .is_err()
127 );
128
129 // B is promoted
130 active_call_a
131 .update(cx_a, |call, cx| {
132 call.room().unwrap().update(cx, |room, cx| {
133 room.set_participant_role(
134 client_b.user_id().unwrap(),
135 proto::ChannelRole::Member,
136 cx,
137 )
138 })
139 })
140 .await
141 .unwrap();
142 cx_a.run_until_parked();
143
144 // project and buffers are now editable
145 assert!(project_b.read_with(cx_b, |project, cx| !project.is_read_only(cx)));
146 assert!(editor_b.update(cx_b, |editor, cx| !editor.read_only(cx)));
147
148 // B sees themselves as muted, and can unmute.
149 cx_b.update(|_window, cx_b| {
150 assert!(room_b.read_with(cx_b, |room, _| room.can_use_microphone()));
151 });
152 room_b.read_with(cx_b, |room, _| assert!(room.is_muted()));
153 room_b.update(cx_b, |room, cx| room.toggle_mute(cx));
154 cx_a.run_until_parked();
155 room_b.read_with(cx_b, |room, _| assert!(!room.is_muted()));
156
157 // B is demoted
158 active_call_a
159 .update(cx_a, |call, cx| {
160 call.room().unwrap().update(cx, |room, cx| {
161 room.set_participant_role(
162 client_b.user_id().unwrap(),
163 proto::ChannelRole::Guest,
164 cx,
165 )
166 })
167 })
168 .await
169 .unwrap();
170 cx_a.run_until_parked();
171
172 // project and buffers are no longer editable
173 assert!(project_b.read_with(cx_b, |project, cx| project.is_read_only(cx)));
174 assert!(editor_b.update(cx_b, |editor, cx| editor.read_only(cx)));
175 assert!(
176 room_b
177 .update(cx_b, |room, cx| room.share_microphone(cx))
178 .await
179 .is_err()
180 );
181}
182
183#[gpui::test]
184async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
185 let mut server = TestServer::start(cx_a.executor()).await;
186
187 server
188 .app_state
189 .db
190 .update_or_create_user_by_github_account("user_b", 100, None, None, Utc::now(), None)
191 .await
192 .unwrap();
193
194 let client_a = server.create_client(cx_a, "user_a").await;
195 let client_b = server.create_client(cx_b, "user_b").await;
196 let active_call_a = cx_a.read(ActiveCall::global);
197 let active_call_b = cx_b.read(ActiveCall::global);
198
199 // Create a parent channel that requires the Zed CLA
200 let parent_channel_id = server
201 .make_channel("the-channel", None, (&client_a, cx_a), &mut [])
202 .await;
203 server
204 .app_state
205 .db
206 .set_channel_requires_zed_cla(ChannelId::from_proto(parent_channel_id.0), true)
207 .await
208 .unwrap();
209
210 // Create a public channel that is a child of the parent channel.
211 let channel_id = client_a
212 .channel_store()
213 .update(cx_a, |store, cx| {
214 store.create_channel("the-sub-channel", Some(parent_channel_id), cx)
215 })
216 .await
217 .unwrap();
218 client_a
219 .channel_store()
220 .update(cx_a, |store, cx| {
221 store.set_channel_visibility(parent_channel_id, proto::ChannelVisibility::Public, cx)
222 })
223 .await
224 .unwrap();
225 client_a
226 .channel_store()
227 .update(cx_a, |store, cx| {
228 store.set_channel_visibility(channel_id, proto::ChannelVisibility::Public, cx)
229 })
230 .await
231 .unwrap();
232
233 // Users A and B join the channel. B is a guest.
234 active_call_a
235 .update(cx_a, |call, cx| call.join_channel(channel_id, cx))
236 .await
237 .unwrap();
238 active_call_b
239 .update(cx_b, |call, cx| call.join_channel(channel_id, cx))
240 .await
241 .unwrap();
242 cx_a.run_until_parked();
243 let room_b = cx_b
244 .read(ActiveCall::global)
245 .update(cx_b, |call, _| call.room().unwrap().clone());
246 cx_b.update(|cx_b| {
247 assert!(room_b.read_with(cx_b, |room, _| !room.can_use_microphone()));
248 });
249
250 // A tries to grant write access to B, but cannot because B has not
251 // yet signed the zed CLA.
252 active_call_a
253 .update(cx_a, |call, cx| {
254 call.room().unwrap().update(cx, |room, cx| {
255 room.set_participant_role(
256 client_b.user_id().unwrap(),
257 proto::ChannelRole::Member,
258 cx,
259 )
260 })
261 })
262 .await
263 .unwrap_err();
264 cx_a.run_until_parked();
265 assert!(room_b.read_with(cx_b, |room, _| !room.can_share_projects()));
266 cx_b.update(|cx_b| {
267 assert!(room_b.read_with(cx_b, |room, _| !room.can_use_microphone()));
268 });
269
270 // A tries to grant write access to B, but cannot because B has not
271 // yet signed the zed CLA.
272 active_call_a
273 .update(cx_a, |call, cx| {
274 call.room().unwrap().update(cx, |room, cx| {
275 room.set_participant_role(
276 client_b.user_id().unwrap(),
277 proto::ChannelRole::Talker,
278 cx,
279 )
280 })
281 })
282 .await
283 .unwrap();
284 cx_a.run_until_parked();
285 assert!(room_b.read_with(cx_b, |room, _| !room.can_share_projects()));
286 cx_b.update(|cx_b| {
287 assert!(room_b.read_with(cx_b, |room, _| room.can_use_microphone()));
288 });
289
290 // User B signs the zed CLA.
291 server
292 .app_state
293 .db
294 .add_contributor("user_b", 100, None, None, Utc::now(), None)
295 .await
296 .unwrap();
297
298 // A can now grant write access to B.
299 active_call_a
300 .update(cx_a, |call, cx| {
301 call.room().unwrap().update(cx, |room, cx| {
302 room.set_participant_role(
303 client_b.user_id().unwrap(),
304 proto::ChannelRole::Member,
305 cx,
306 )
307 })
308 })
309 .await
310 .unwrap();
311 cx_a.run_until_parked();
312 assert!(room_b.read_with(cx_b, |room, _| room.can_share_projects()));
313 cx_b.update(|cx_b| {
314 assert!(room_b.read_with(cx_b, |room, _| room.can_use_microphone()));
315 });
316}