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