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