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