use crate::{db::ChannelId, tests::TestServer}; use call::ActiveCall; use chrono::Utc; use editor::Editor; use gpui::{BackgroundExecutor, TestAppContext}; use rpc::proto; #[gpui::test] async fn test_channel_guests( executor: BackgroundExecutor, cx_a: &mut TestAppContext, cx_b: &mut TestAppContext, ) { let mut server = TestServer::start(executor.clone()).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; let active_call_a = cx_a.read(ActiveCall::global); let channel_id = server .make_public_channel("the-channel", &client_a, cx_a) .await; // Client A shares a project in the channel let project_a = client_a.build_test_project(cx_a).await; active_call_a .update(cx_a, |call, cx| call.join_channel(channel_id, cx)) .await .unwrap(); let project_id = active_call_a .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); cx_a.executor().run_until_parked(); // Client B joins channel A as a guest cx_b.update(|cx| workspace::join_channel(channel_id, client_b.app_state.clone(), None, cx)) .await .unwrap(); // b should be following a in the shared project. // B is a guest, executor.run_until_parked(); let active_call_b = cx_b.read(ActiveCall::global); let project_b = active_call_b.read_with(cx_b, |call, _| call.location().unwrap().upgrade().unwrap()); let room_b = active_call_b.update(cx_b, |call, _| call.room().unwrap().clone()); assert_eq!( project_b.read_with(cx_b, |project, _| project.remote_id()), Some(project_id), ); assert!(project_b.read_with(cx_b, |project, _| project.is_read_only())); assert!(project_b .update(cx_b, |project, cx| { let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id(); project.create_entry((worktree_id, "b.txt"), false, cx) }) .await .is_err()); assert!(room_b.read_with(cx_b, |room, _| room.is_muted())); } #[gpui::test] async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { let mut server = TestServer::start(cx_a.executor()).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; let active_call_a = cx_a.read(ActiveCall::global); let channel_id = server .make_public_channel("the-channel", &client_a, cx_a) .await; let project_a = client_a.build_test_project(cx_a).await; cx_a.update(|cx| workspace::join_channel(channel_id, client_a.app_state.clone(), None, cx)) .await .unwrap(); // Client A shares a project in the channel active_call_a .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); cx_a.run_until_parked(); // Client B joins channel A as a guest cx_b.update(|cx| workspace::join_channel(channel_id, client_b.app_state.clone(), None, cx)) .await .unwrap(); cx_a.run_until_parked(); // client B opens 1.txt as a guest let (workspace_b, cx_b) = client_b.active_workspace(cx_b); let room_b = cx_b .read(ActiveCall::global) .update(cx_b, |call, _| call.room().unwrap().clone()); cx_b.simulate_keystrokes("cmd-p 1 enter"); let (project_b, editor_b) = workspace_b.update(cx_b, |workspace, cx| { ( workspace.project().clone(), workspace.active_item_as::(cx).unwrap(), ) }); assert!(project_b.read_with(cx_b, |project, _| project.is_read_only())); assert!(editor_b.update(cx_b, |e, cx| e.read_only(cx))); assert!(room_b.read_with(cx_b, |room, _| !room.can_use_microphone())); assert!(room_b .update(cx_b, |room, cx| room.share_microphone(cx)) .await .is_err()); // B is promoted active_call_a .update(cx_a, |call, cx| { call.room().unwrap().update(cx, |room, cx| { room.set_participant_role( client_b.user_id().unwrap(), proto::ChannelRole::Member, cx, ) }) }) .await .unwrap(); cx_a.run_until_parked(); // project and buffers are now editable assert!(project_b.read_with(cx_b, |project, _| !project.is_read_only())); assert!(editor_b.update(cx_b, |editor, cx| !editor.read_only(cx))); // B sees themselves as muted, and can unmute. assert!(room_b.read_with(cx_b, |room, _| room.can_use_microphone())); room_b.read_with(cx_b, |room, _| assert!(room.is_muted())); room_b.update(cx_b, |room, cx| room.toggle_mute(cx)); cx_a.run_until_parked(); room_b.read_with(cx_b, |room, _| assert!(!room.is_muted())); // B is demoted active_call_a .update(cx_a, |call, cx| { call.room().unwrap().update(cx, |room, cx| { room.set_participant_role( client_b.user_id().unwrap(), proto::ChannelRole::Guest, cx, ) }) }) .await .unwrap(); cx_a.run_until_parked(); // project and buffers are no longer editable assert!(project_b.read_with(cx_b, |project, _| project.is_read_only())); assert!(editor_b.update(cx_b, |editor, cx| editor.read_only(cx))); assert!(room_b .update(cx_b, |room, cx| room.share_microphone(cx)) .await .is_err()); } #[gpui::test] async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { let mut server = TestServer::start(cx_a.executor()).await; server .app_state .db .get_or_create_user_by_github_account("user_b", Some(100), None, Some(Utc::now()), None) .await .unwrap(); let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; let active_call_a = cx_a.read(ActiveCall::global); let active_call_b = cx_b.read(ActiveCall::global); // Create a parent channel that requires the Zed CLA let parent_channel_id = server .make_channel("the-channel", None, (&client_a, cx_a), &mut []) .await; server .app_state .db .set_channel_requires_zed_cla(ChannelId::from_proto(parent_channel_id.0), true) .await .unwrap(); // Create a public channel that is a child of the parent channel. let channel_id = client_a .channel_store() .update(cx_a, |store, cx| { store.create_channel("the-sub-channel", Some(parent_channel_id), cx) }) .await .unwrap(); client_a .channel_store() .update(cx_a, |store, cx| { store.set_channel_visibility(parent_channel_id, proto::ChannelVisibility::Public, cx) }) .await .unwrap(); client_a .channel_store() .update(cx_a, |store, cx| { store.set_channel_visibility(channel_id, proto::ChannelVisibility::Public, cx) }) .await .unwrap(); // Users A and B join the channel. B is a guest. active_call_a .update(cx_a, |call, cx| call.join_channel(channel_id, cx)) .await .unwrap(); active_call_b .update(cx_b, |call, cx| call.join_channel(channel_id, cx)) .await .unwrap(); cx_a.run_until_parked(); let room_b = cx_b .read(ActiveCall::global) .update(cx_b, |call, _| call.room().unwrap().clone()); assert!(room_b.read_with(cx_b, |room, _| !room.can_use_microphone())); // A tries to grant write access to B, but cannot because B has not // yet signed the zed CLA. active_call_a .update(cx_a, |call, cx| { call.room().unwrap().update(cx, |room, cx| { room.set_participant_role( client_b.user_id().unwrap(), proto::ChannelRole::Member, cx, ) }) }) .await .unwrap_err(); cx_a.run_until_parked(); assert!(room_b.read_with(cx_b, |room, _| !room.can_share_projects())); assert!(room_b.read_with(cx_b, |room, _| !room.can_use_microphone())); // A tries to grant write access to B, but cannot because B has not // yet signed the zed CLA. active_call_a .update(cx_a, |call, cx| { call.room().unwrap().update(cx, |room, cx| { room.set_participant_role( client_b.user_id().unwrap(), proto::ChannelRole::Talker, cx, ) }) }) .await .unwrap(); cx_a.run_until_parked(); assert!(room_b.read_with(cx_b, |room, _| !room.can_share_projects())); assert!(room_b.read_with(cx_b, |room, _| room.can_use_microphone())); // User B signs the zed CLA. server .app_state .db .add_contributor("user_b", Some(100), None, Some(Utc::now()), None) .await .unwrap(); // A can now grant write access to B. active_call_a .update(cx_a, |call, cx| { call.room().unwrap().update(cx, |room, cx| { room.set_participant_role( client_b.user_id().unwrap(), proto::ChannelRole::Member, cx, ) }) }) .await .unwrap(); cx_a.run_until_parked(); assert!(room_b.read_with(cx_b, |room, _| room.can_share_projects())); assert!(room_b.read_with(cx_b, |room, _| room.can_use_microphone())); }