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 1 enter");
 99
100    let (project_b, editor_b) = workspace_b.update(cx_b, |workspace, cx| {
101        (
102            workspace.project().clone(),
103            workspace.active_item_as::<Editor>(cx).unwrap(),
104        )
105    });
106    assert!(project_b.read_with(cx_b, |project, cx| project.is_read_only(cx)));
107    assert!(editor_b.update(cx_b, |e, cx| e.read_only(cx)));
108    assert!(room_b.read_with(cx_b, |room, _| !room.can_use_microphone()));
109    assert!(room_b
110        .update(cx_b, |room, cx| room.share_microphone(cx))
111        .await
112        .is_err());
113
114    // B is promoted
115    active_call_a
116        .update(cx_a, |call, cx| {
117            call.room().unwrap().update(cx, |room, cx| {
118                room.set_participant_role(
119                    client_b.user_id().unwrap(),
120                    proto::ChannelRole::Member,
121                    cx,
122                )
123            })
124        })
125        .await
126        .unwrap();
127    cx_a.run_until_parked();
128
129    // project and buffers are now editable
130    assert!(project_b.read_with(cx_b, |project, cx| !project.is_read_only(cx)));
131    assert!(editor_b.update(cx_b, |editor, cx| !editor.read_only(cx)));
132
133    // B sees themselves as muted, and can unmute.
134    assert!(room_b.read_with(cx_b, |room, _| room.can_use_microphone()));
135    room_b.read_with(cx_b, |room, _| assert!(room.is_muted()));
136    room_b.update(cx_b, |room, cx| room.toggle_mute(cx));
137    cx_a.run_until_parked();
138    room_b.read_with(cx_b, |room, _| assert!(!room.is_muted()));
139
140    // B is demoted
141    active_call_a
142        .update(cx_a, |call, cx| {
143            call.room().unwrap().update(cx, |room, cx| {
144                room.set_participant_role(
145                    client_b.user_id().unwrap(),
146                    proto::ChannelRole::Guest,
147                    cx,
148                )
149            })
150        })
151        .await
152        .unwrap();
153    cx_a.run_until_parked();
154
155    // project and buffers are no longer editable
156    assert!(project_b.read_with(cx_b, |project, cx| project.is_read_only(cx)));
157    assert!(editor_b.update(cx_b, |editor, cx| editor.read_only(cx)));
158    assert!(room_b
159        .update(cx_b, |room, cx| room.share_microphone(cx))
160        .await
161        .is_err());
162}
163
164#[gpui::test]
165async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
166    let mut server = TestServer::start(cx_a.executor()).await;
167
168    server
169        .app_state
170        .db
171        .get_or_create_user_by_github_account("user_b", 100, None, Utc::now(), None)
172        .await
173        .unwrap();
174
175    let client_a = server.create_client(cx_a, "user_a").await;
176    let client_b = server.create_client(cx_b, "user_b").await;
177    let active_call_a = cx_a.read(ActiveCall::global);
178    let active_call_b = cx_b.read(ActiveCall::global);
179
180    // Create a parent channel that requires the Zed CLA
181    let parent_channel_id = server
182        .make_channel("the-channel", None, (&client_a, cx_a), &mut [])
183        .await;
184    server
185        .app_state
186        .db
187        .set_channel_requires_zed_cla(ChannelId::from_proto(parent_channel_id.0), true)
188        .await
189        .unwrap();
190
191    // Create a public channel that is a child of the parent channel.
192    let channel_id = client_a
193        .channel_store()
194        .update(cx_a, |store, cx| {
195            store.create_channel("the-sub-channel", Some(parent_channel_id), cx)
196        })
197        .await
198        .unwrap();
199    client_a
200        .channel_store()
201        .update(cx_a, |store, cx| {
202            store.set_channel_visibility(parent_channel_id, proto::ChannelVisibility::Public, cx)
203        })
204        .await
205        .unwrap();
206    client_a
207        .channel_store()
208        .update(cx_a, |store, cx| {
209            store.set_channel_visibility(channel_id, proto::ChannelVisibility::Public, cx)
210        })
211        .await
212        .unwrap();
213
214    // Users A and B join the channel. B is a guest.
215    active_call_a
216        .update(cx_a, |call, cx| call.join_channel(channel_id, cx))
217        .await
218        .unwrap();
219    active_call_b
220        .update(cx_b, |call, cx| call.join_channel(channel_id, cx))
221        .await
222        .unwrap();
223    cx_a.run_until_parked();
224    let room_b = cx_b
225        .read(ActiveCall::global)
226        .update(cx_b, |call, _| call.room().unwrap().clone());
227    assert!(room_b.read_with(cx_b, |room, _| !room.can_use_microphone()));
228
229    // A tries to grant write access to B, but cannot because B has not
230    // yet signed the zed CLA.
231    active_call_a
232        .update(cx_a, |call, cx| {
233            call.room().unwrap().update(cx, |room, cx| {
234                room.set_participant_role(
235                    client_b.user_id().unwrap(),
236                    proto::ChannelRole::Member,
237                    cx,
238                )
239            })
240        })
241        .await
242        .unwrap_err();
243    cx_a.run_until_parked();
244    assert!(room_b.read_with(cx_b, |room, _| !room.can_share_projects()));
245    assert!(room_b.read_with(cx_b, |room, _| !room.can_use_microphone()));
246
247    // A tries to grant write access to B, but cannot because B has not
248    // yet signed the zed CLA.
249    active_call_a
250        .update(cx_a, |call, cx| {
251            call.room().unwrap().update(cx, |room, cx| {
252                room.set_participant_role(
253                    client_b.user_id().unwrap(),
254                    proto::ChannelRole::Talker,
255                    cx,
256                )
257            })
258        })
259        .await
260        .unwrap();
261    cx_a.run_until_parked();
262    assert!(room_b.read_with(cx_b, |room, _| !room.can_share_projects()));
263    assert!(room_b.read_with(cx_b, |room, _| room.can_use_microphone()));
264
265    // User B signs the zed CLA.
266    server
267        .app_state
268        .db
269        .add_contributor("user_b", 100, None, Utc::now(), None)
270        .await
271        .unwrap();
272
273    // A can now grant write access to B.
274    active_call_a
275        .update(cx_a, |call, cx| {
276            call.room().unwrap().update(cx, |room, cx| {
277                room.set_participant_role(
278                    client_b.user_id().unwrap(),
279                    proto::ChannelRole::Member,
280                    cx,
281                )
282            })
283        })
284        .await
285        .unwrap();
286    cx_a.run_until_parked();
287    assert!(room_b.read_with(cx_b, |room, _| room.can_share_projects()));
288    assert!(room_b.read_with(cx_b, |room, _| room.can_use_microphone()));
289}