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