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