channel_guest_tests.rs

  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}