channel_guest_tests.rs

  1use crate::{
  2    db::ChannelId,
  3    tests::{test_server::join_channel_call, TestServer},
  4};
  5use call::ActiveCall;
  6use editor::Editor;
  7use gpui::{BackgroundExecutor, TestAppContext};
  8use rpc::proto;
  9
 10#[gpui::test]
 11async fn test_channel_guests(
 12    executor: BackgroundExecutor,
 13    cx_a: &mut TestAppContext,
 14    cx_b: &mut TestAppContext,
 15) {
 16    let mut server = TestServer::start(executor.clone()).await;
 17    let client_a = server.create_client(cx_a, "user_a").await;
 18    let client_b = server.create_client(cx_b, "user_b").await;
 19    let active_call_a = cx_a.read(ActiveCall::global);
 20
 21    let channel_id = server
 22        .make_public_channel("the-channel", &client_a, cx_a)
 23        .await;
 24
 25    // Client A shares a project in the channel
 26    let project_a = client_a.build_test_project(cx_a).await;
 27    active_call_a
 28        .update(cx_a, |call, cx| call.join_channel(channel_id, cx))
 29        .await
 30        .unwrap();
 31    let project_id = active_call_a
 32        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 33        .await
 34        .unwrap();
 35    cx_a.executor().run_until_parked();
 36
 37    // Client B joins channel A as a guest
 38    cx_b.update(|cx| workspace::open_channel(channel_id, client_b.app_state.clone(), None, cx))
 39        .await
 40        .unwrap();
 41
 42    // b should be following a in the shared project.
 43    // B is a guest,
 44    executor.run_until_parked();
 45
 46    let active_call_b = cx_b.read(ActiveCall::global);
 47    let project_b =
 48        active_call_b.read_with(cx_b, |call, _| call.location().unwrap().upgrade().unwrap());
 49    let room_b = active_call_b.update(cx_b, |call, _| call.room().unwrap().clone());
 50
 51    assert_eq!(
 52        project_b.read_with(cx_b, |project, _| project.remote_id()),
 53        Some(project_id),
 54    );
 55    assert!(project_b.read_with(cx_b, |project, _| project.is_read_only()));
 56    assert!(project_b
 57        .update(cx_b, |project, cx| {
 58            let worktree_id = project.worktrees().next().unwrap().read(cx).id();
 59            project.create_entry((worktree_id, "b.txt"), false, cx)
 60        })
 61        .await
 62        .is_err());
 63    assert!(room_b.read_with(cx_b, |room, _| room.is_muted()));
 64}
 65
 66#[gpui::test]
 67async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
 68    let mut server = TestServer::start(cx_a.executor()).await;
 69    let client_a = server.create_client(cx_a, "user_a").await;
 70    let client_b = server.create_client(cx_b, "user_b").await;
 71    let active_call_a = cx_a.read(ActiveCall::global);
 72
 73    let channel_id = server
 74        .make_public_channel("the-channel", &client_a, cx_a)
 75        .await;
 76
 77    let project_a = client_a.build_test_project(cx_a).await;
 78    cx_a.update(|cx| workspace::open_channel(channel_id, client_a.app_state.clone(), None, cx))
 79        .await
 80        .unwrap();
 81
 82    // Client A shares a project in the channel
 83    active_call_a
 84        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 85        .await
 86        .unwrap();
 87    cx_a.run_until_parked();
 88
 89    // Client B joins channel A as a guest
 90    cx_b.update(|cx| workspace::open_channel(channel_id, client_b.app_state.clone(), None, cx))
 91        .await
 92        .unwrap();
 93    cx_a.run_until_parked();
 94
 95    join_channel_call(cx_b).await.unwrap();
 96
 97    // client B opens 1.txt as a guest
 98    let (workspace_b, cx_b) = client_b.active_workspace(cx_b);
 99    let room_b = cx_b
100        .read(ActiveCall::global)
101        .update(cx_b, |call, _| call.room().unwrap().clone());
102    cx_b.simulate_keystrokes("cmd-p 1 enter");
103
104    let (project_b, editor_b) = workspace_b.update(cx_b, |workspace, cx| {
105        (
106            workspace.project().clone(),
107            workspace.active_item_as::<Editor>(cx).unwrap(),
108        )
109    });
110    assert!(project_b.read_with(cx_b, |project, _| project.is_read_only()));
111    assert!(editor_b.update(cx_b, |e, cx| e.read_only(cx)));
112    assert!(room_b.read_with(cx_b, |room, _| room.read_only()));
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, _| !project.is_read_only()));
135    assert!(editor_b.update(cx_b, |editor, cx| !editor.read_only(cx)));
136
137    // B sees themselves as muted, and can unmute.
138    assert!(room_b.read_with(cx_b, |room, _| !room.read_only()));
139    room_b.read_with(cx_b, |room, _| assert!(room.is_muted()));
140    room_b.update(cx_b, |room, cx| room.toggle_mute(cx));
141    cx_a.run_until_parked();
142    room_b.read_with(cx_b, |room, _| assert!(!room.is_muted()));
143
144    // B is demoted
145    active_call_a
146        .update(cx_a, |call, cx| {
147            call.room().unwrap().update(cx, |room, cx| {
148                room.set_participant_role(
149                    client_b.user_id().unwrap(),
150                    proto::ChannelRole::Guest,
151                    cx,
152                )
153            })
154        })
155        .await
156        .unwrap();
157    cx_a.run_until_parked();
158
159    // project and buffers are no longer editable
160    assert!(project_b.read_with(cx_b, |project, _| project.is_read_only()));
161    assert!(editor_b.update(cx_b, |editor, cx| editor.read_only(cx)));
162    assert!(room_b
163        .update(cx_b, |room, cx| room.share_microphone(cx))
164        .await
165        .is_err());
166}
167
168#[gpui::test]
169async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
170    let mut server = TestServer::start(cx_a.executor()).await;
171
172    server
173        .app_state
174        .db
175        .get_or_create_user_by_github_account("user_b", Some(100), None)
176        .await
177        .unwrap();
178
179    let client_a = server.create_client(cx_a, "user_a").await;
180    let client_b = server.create_client(cx_b, "user_b").await;
181    let active_call_a = cx_a.read(ActiveCall::global);
182    let active_call_b = cx_b.read(ActiveCall::global);
183
184    // Create a parent channel that requires the Zed CLA
185    let parent_channel_id = server
186        .make_channel("the-channel", None, (&client_a, cx_a), &mut [])
187        .await;
188    server
189        .app_state
190        .db
191        .set_channel_requires_zed_cla(ChannelId::from_proto(parent_channel_id), true)
192        .await
193        .unwrap();
194
195    // Create a public channel that is a child of the parent channel.
196    let channel_id = client_a
197        .channel_store()
198        .update(cx_a, |store, cx| {
199            store.create_channel("the-sub-channel", Some(parent_channel_id), cx)
200        })
201        .await
202        .unwrap();
203    client_a
204        .channel_store()
205        .update(cx_a, |store, cx| {
206            store.set_channel_visibility(parent_channel_id, proto::ChannelVisibility::Public, cx)
207        })
208        .await
209        .unwrap();
210    client_a
211        .channel_store()
212        .update(cx_a, |store, cx| {
213            store.set_channel_visibility(channel_id, proto::ChannelVisibility::Public, cx)
214        })
215        .await
216        .unwrap();
217
218    // Users A and B join the channel. B is a guest.
219    active_call_a
220        .update(cx_a, |call, cx| call.join_channel(channel_id, cx))
221        .await
222        .unwrap();
223    active_call_b
224        .update(cx_b, |call, cx| call.join_channel(channel_id, cx))
225        .await
226        .unwrap();
227    cx_a.run_until_parked();
228    let room_b = cx_b
229        .read(ActiveCall::global)
230        .update(cx_b, |call, _| call.room().unwrap().clone());
231    assert!(room_b.read_with(cx_b, |room, _| room.read_only()));
232
233    // A tries to grant write access to B, but cannot because B has not
234    // yet signed the zed CLA.
235    active_call_a
236        .update(cx_a, |call, cx| {
237            call.room().unwrap().update(cx, |room, cx| {
238                room.set_participant_role(
239                    client_b.user_id().unwrap(),
240                    proto::ChannelRole::Member,
241                    cx,
242                )
243            })
244        })
245        .await
246        .unwrap_err();
247    cx_a.run_until_parked();
248    assert!(room_b.read_with(cx_b, |room, _| room.read_only()));
249
250    // User B signs the zed CLA.
251    server
252        .app_state
253        .db
254        .add_contributor("user_b", Some(100), None)
255        .await
256        .unwrap();
257
258    // A can now grant write access to B.
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::Member,
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.read_only()));
273}