channel_guest_tests.rs

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