channel_guest_tests.rs

  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}