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