Merge branch 'main' into following-tests
This commit is contained in:
commit
204ef451d0
224 changed files with 12664 additions and 23906 deletions
|
@ -132,6 +132,14 @@ impl ChannelRole {
|
|||
Admin | Member | Banned => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn can_share_projects(&self) -> bool {
|
||||
use ChannelRole::*;
|
||||
match self {
|
||||
Admin | Member => true,
|
||||
Guest | Banned => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<proto::ChannelRole> for ChannelRole {
|
||||
|
|
|
@ -129,51 +129,52 @@ impl Database {
|
|||
.await?,
|
||||
);
|
||||
|
||||
debug_assert!(
|
||||
self.channel_role_for_user(&channel, user_id, &*tx).await? == role
|
||||
);
|
||||
} else if channel.visibility == ChannelVisibility::Public {
|
||||
role = Some(ChannelRole::Guest);
|
||||
let channel_to_join = self
|
||||
.public_ancestors_including_self(&channel, &*tx)
|
||||
.await?
|
||||
.first()
|
||||
.cloned()
|
||||
.unwrap_or(channel.clone());
|
||||
|
||||
channel_member::Entity::insert(channel_member::ActiveModel {
|
||||
id: ActiveValue::NotSet,
|
||||
channel_id: ActiveValue::Set(channel_to_join.id),
|
||||
user_id: ActiveValue::Set(user_id),
|
||||
accepted: ActiveValue::Set(true),
|
||||
role: ActiveValue::Set(ChannelRole::Guest),
|
||||
})
|
||||
.exec(&*tx)
|
||||
.await?;
|
||||
|
||||
accept_invite_result = Some(
|
||||
self.calculate_membership_updated(&channel_to_join, user_id, &*tx)
|
||||
.await?,
|
||||
);
|
||||
|
||||
debug_assert!(
|
||||
self.channel_role_for_user(&channel, user_id, &*tx).await? == role
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if channel.visibility == ChannelVisibility::Public {
|
||||
role = Some(ChannelRole::Guest);
|
||||
let channel_to_join = self
|
||||
.public_ancestors_including_self(&channel, &*tx)
|
||||
.await?
|
||||
.first()
|
||||
.cloned()
|
||||
.unwrap_or(channel.clone());
|
||||
|
||||
channel_member::Entity::insert(channel_member::ActiveModel {
|
||||
id: ActiveValue::NotSet,
|
||||
channel_id: ActiveValue::Set(channel_to_join.id),
|
||||
user_id: ActiveValue::Set(user_id),
|
||||
accepted: ActiveValue::Set(true),
|
||||
role: ActiveValue::Set(ChannelRole::Guest),
|
||||
})
|
||||
.exec(&*tx)
|
||||
.await?;
|
||||
|
||||
accept_invite_result = Some(
|
||||
self.calculate_membership_updated(&channel_to_join, user_id, &*tx)
|
||||
.await?,
|
||||
);
|
||||
|
||||
debug_assert!(self.channel_role_for_user(&channel, user_id, &*tx).await? == role);
|
||||
}
|
||||
|
||||
if role.is_none() || role == Some(ChannelRole::Banned) {
|
||||
Err(anyhow!("not allowed"))?
|
||||
}
|
||||
let role = role.unwrap();
|
||||
|
||||
let live_kit_room = format!("channel-{}", nanoid::nanoid!(30));
|
||||
let room_id = self
|
||||
.get_or_create_channel_room(channel_id, &live_kit_room, environment, &*tx)
|
||||
.await?;
|
||||
|
||||
self.join_channel_room_internal(room_id, user_id, connection, &*tx)
|
||||
self.join_channel_room_internal(room_id, user_id, connection, role, &*tx)
|
||||
.await
|
||||
.map(|jr| (jr, accept_invite_result, role.unwrap()))
|
||||
.map(|jr| (jr, accept_invite_result, role))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
|
|
@ -46,6 +46,13 @@ impl Database {
|
|||
if participant.room_id != room_id {
|
||||
return Err(anyhow!("shared project on unexpected room"))?;
|
||||
}
|
||||
if !participant
|
||||
.role
|
||||
.unwrap_or(ChannelRole::Member)
|
||||
.can_share_projects()
|
||||
{
|
||||
return Err(anyhow!("guests cannot share projects"))?;
|
||||
}
|
||||
|
||||
let project = project::ActiveModel {
|
||||
room_id: ActiveValue::set(participant.room_id),
|
||||
|
|
|
@ -131,7 +131,12 @@ impl Database {
|
|||
connection.owner_id as i32,
|
||||
))),
|
||||
participant_index: ActiveValue::set(Some(0)),
|
||||
..Default::default()
|
||||
role: ActiveValue::set(Some(ChannelRole::Admin)),
|
||||
|
||||
id: ActiveValue::NotSet,
|
||||
location_kind: ActiveValue::NotSet,
|
||||
location_project_id: ActiveValue::NotSet,
|
||||
initial_project_id: ActiveValue::NotSet,
|
||||
}
|
||||
.insert(&*tx)
|
||||
.await?;
|
||||
|
@ -151,6 +156,22 @@ impl Database {
|
|||
initial_project_id: Option<ProjectId>,
|
||||
) -> Result<RoomGuard<(proto::Room, proto::IncomingCall)>> {
|
||||
self.room_transaction(room_id, |tx| async move {
|
||||
let caller = room_participant::Entity::find()
|
||||
.filter(
|
||||
room_participant::Column::UserId
|
||||
.eq(calling_user_id)
|
||||
.and(room_participant::Column::RoomId.eq(room_id)),
|
||||
)
|
||||
.one(&*tx)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("user is not in the room"))?;
|
||||
|
||||
let called_user_role = match caller.role.unwrap_or(ChannelRole::Member) {
|
||||
ChannelRole::Admin | ChannelRole::Member => ChannelRole::Member,
|
||||
ChannelRole::Guest => ChannelRole::Guest,
|
||||
ChannelRole::Banned => return Err(anyhow!("banned users cannot invite").into()),
|
||||
};
|
||||
|
||||
room_participant::ActiveModel {
|
||||
room_id: ActiveValue::set(room_id),
|
||||
user_id: ActiveValue::set(called_user_id),
|
||||
|
@ -162,7 +183,13 @@ impl Database {
|
|||
calling_connection.owner_id as i32,
|
||||
))),
|
||||
initial_project_id: ActiveValue::set(initial_project_id),
|
||||
..Default::default()
|
||||
role: ActiveValue::set(Some(called_user_role)),
|
||||
|
||||
id: ActiveValue::NotSet,
|
||||
answering_connection_id: ActiveValue::NotSet,
|
||||
answering_connection_server_id: ActiveValue::NotSet,
|
||||
location_kind: ActiveValue::NotSet,
|
||||
location_project_id: ActiveValue::NotSet,
|
||||
}
|
||||
.insert(&*tx)
|
||||
.await?;
|
||||
|
@ -384,6 +411,7 @@ impl Database {
|
|||
room_id: RoomId,
|
||||
user_id: UserId,
|
||||
connection: ConnectionId,
|
||||
role: ChannelRole,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<JoinRoom> {
|
||||
let participant_index = self
|
||||
|
@ -404,7 +432,11 @@ impl Database {
|
|||
connection.owner_id as i32,
|
||||
))),
|
||||
participant_index: ActiveValue::Set(Some(participant_index)),
|
||||
..Default::default()
|
||||
role: ActiveValue::set(Some(role)),
|
||||
id: ActiveValue::NotSet,
|
||||
location_kind: ActiveValue::NotSet,
|
||||
location_project_id: ActiveValue::NotSet,
|
||||
initial_project_id: ActiveValue::NotSet,
|
||||
}])
|
||||
.on_conflict(
|
||||
OnConflict::columns([room_participant::Column::UserId])
|
||||
|
@ -413,6 +445,7 @@ impl Database {
|
|||
room_participant::Column::AnsweringConnectionServerId,
|
||||
room_participant::Column::AnsweringConnectionLost,
|
||||
room_participant::Column::ParticipantIndex,
|
||||
room_participant::Column::Role,
|
||||
])
|
||||
.to_owned(),
|
||||
)
|
||||
|
@ -1134,6 +1167,7 @@ impl Database {
|
|||
projects: Default::default(),
|
||||
location: Some(proto::ParticipantLocation { variant: location }),
|
||||
participant_index: participant_index as u32,
|
||||
role: db_participant.role.unwrap_or(ChannelRole::Member).into(),
|
||||
},
|
||||
);
|
||||
} else {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::db::{ProjectId, RoomId, RoomParticipantId, ServerId, UserId};
|
||||
use crate::db::{ChannelRole, ProjectId, RoomId, RoomParticipantId, ServerId, UserId};
|
||||
use rpc::ConnectionId;
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
|
@ -19,6 +19,7 @@ pub struct Model {
|
|||
pub calling_connection_id: i32,
|
||||
pub calling_connection_server_id: Option<ServerId>,
|
||||
pub participant_index: Option<i32>,
|
||||
pub role: Option<ChannelRole>,
|
||||
}
|
||||
|
||||
impl Model {
|
||||
|
|
|
@ -2,6 +2,7 @@ use call::Room;
|
|||
use gpui::{Model, TestAppContext};
|
||||
|
||||
mod channel_buffer_tests;
|
||||
mod channel_guest_tests;
|
||||
mod channel_message_tests;
|
||||
mod channel_tests;
|
||||
mod editor_tests;
|
||||
|
|
86
crates/collab/src/tests/channel_guest_tests.rs
Normal file
86
crates/collab/src/tests/channel_guest_tests.rs
Normal file
|
@ -0,0 +1,86 @@
|
|||
use crate::tests::TestServer;
|
||||
use call::ActiveCall;
|
||||
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
||||
use rpc::proto;
|
||||
use workspace::Workspace;
|
||||
|
||||
#[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 channel_id = server
|
||||
.make_channel("the-channel", None, (&client_a, cx_a), &mut [])
|
||||
.await;
|
||||
|
||||
client_a
|
||||
.channel_store()
|
||||
.update(cx_a, |channel_store, cx| {
|
||||
channel_store.set_channel_visibility(channel_id, proto::ChannelVisibility::Public, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
client_a
|
||||
.fs()
|
||||
.insert_tree(
|
||||
"/a",
|
||||
serde_json::json!({
|
||||
"a.txt": "a-contents",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
|
||||
// Client A shares a project in the channel
|
||||
active_call_a
|
||||
.update(cx_a, |call, cx| call.join_channel(channel_id, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
|
||||
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,
|
||||
cx_a.executor().run_until_parked();
|
||||
|
||||
// todo!() the test window does not call activation handlers
|
||||
// correctly yet, so this API does not work.
|
||||
// let project_b = active_call_b.read_with(cx_b, |call, _| {
|
||||
// call.location()
|
||||
// .unwrap()
|
||||
// .upgrade()
|
||||
// .expect("should not be weak")
|
||||
// });
|
||||
|
||||
let window_b = cx_b.update(|cx| cx.active_window().unwrap());
|
||||
let cx_b = &mut VisualTestContext::from_window(window_b, cx_b);
|
||||
|
||||
let workspace_b = window_b
|
||||
.downcast::<Workspace>()
|
||||
.unwrap()
|
||||
.root_view(cx_b)
|
||||
.unwrap();
|
||||
let project_b = workspace_b.update(cx_b, |workspace, _| workspace.project().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()))
|
||||
}
|
|
@ -19,6 +19,7 @@ use project::{
|
|||
search::SearchQuery, DiagnosticSummary, FormatTrigger, HoverBlockKind, Project, ProjectPath,
|
||||
};
|
||||
use rand::prelude::*;
|
||||
use rpc::proto::ChannelRole;
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use std::{
|
||||
|
@ -1380,7 +1381,7 @@ async fn test_unshare_project(
|
|||
.unwrap();
|
||||
executor.run_until_parked();
|
||||
|
||||
assert!(project_b.read_with(cx_b, |project, _| project.is_read_only()));
|
||||
assert!(project_b.read_with(cx_b, |project, _| project.is_disconnected()));
|
||||
|
||||
// Client C opens the project.
|
||||
let project_c = client_c.build_remote_project(project_id, cx_c).await;
|
||||
|
@ -1393,7 +1394,7 @@ async fn test_unshare_project(
|
|||
|
||||
assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
|
||||
|
||||
assert!(project_c.read_with(cx_c, |project, _| project.is_read_only()));
|
||||
assert!(project_c.read_with(cx_c, |project, _| project.is_disconnected()));
|
||||
|
||||
// Client C can open the project again after client A re-shares.
|
||||
let project_id = active_call_a
|
||||
|
@ -1419,7 +1420,7 @@ async fn test_unshare_project(
|
|||
project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
|
||||
|
||||
project_c2.read_with(cx_c, |project, _| {
|
||||
assert!(project.is_read_only());
|
||||
assert!(project.is_disconnected());
|
||||
assert!(project.collaborators().is_empty());
|
||||
});
|
||||
}
|
||||
|
@ -1551,7 +1552,7 @@ async fn test_project_reconnect(
|
|||
});
|
||||
|
||||
project_b1.read_with(cx_b, |project, _| {
|
||||
assert!(!project.is_read_only());
|
||||
assert!(!project.is_disconnected());
|
||||
assert_eq!(project.collaborators().len(), 1);
|
||||
});
|
||||
|
||||
|
@ -1653,7 +1654,7 @@ async fn test_project_reconnect(
|
|||
});
|
||||
|
||||
project_b1.read_with(cx_b, |project, cx| {
|
||||
assert!(!project.is_read_only());
|
||||
assert!(!project.is_disconnected());
|
||||
assert_eq!(
|
||||
project
|
||||
.worktree_for_id(worktree1_id, cx)
|
||||
|
@ -1687,9 +1688,9 @@ async fn test_project_reconnect(
|
|||
);
|
||||
});
|
||||
|
||||
project_b2.read_with(cx_b, |project, _| assert!(project.is_read_only()));
|
||||
project_b2.read_with(cx_b, |project, _| assert!(project.is_disconnected()));
|
||||
|
||||
project_b3.read_with(cx_b, |project, _| assert!(!project.is_read_only()));
|
||||
project_b3.read_with(cx_b, |project, _| assert!(!project.is_disconnected()));
|
||||
|
||||
buffer_a1.read_with(cx_a, |buffer, _| assert_eq!(buffer.text(), "WaZ"));
|
||||
|
||||
|
@ -1746,7 +1747,7 @@ async fn test_project_reconnect(
|
|||
executor.run_until_parked();
|
||||
|
||||
project_b1.read_with(cx_b, |project, cx| {
|
||||
assert!(!project.is_read_only());
|
||||
assert!(!project.is_disconnected());
|
||||
assert_eq!(
|
||||
project
|
||||
.worktree_for_id(worktree1_id, cx)
|
||||
|
@ -1780,7 +1781,7 @@ async fn test_project_reconnect(
|
|||
);
|
||||
});
|
||||
|
||||
project_b3.read_with(cx_b, |project, _| assert!(project.is_read_only()));
|
||||
project_b3.read_with(cx_b, |project, _| assert!(project.is_disconnected()));
|
||||
|
||||
buffer_a1.read_with(cx_a, |buffer, _| assert_eq!(buffer.text(), "WXaYZ"));
|
||||
|
||||
|
@ -3535,7 +3536,7 @@ async fn test_leaving_project(
|
|||
});
|
||||
|
||||
project_b2.read_with(cx_b, |project, _| {
|
||||
assert!(project.is_read_only());
|
||||
assert!(project.is_disconnected());
|
||||
});
|
||||
|
||||
project_c.read_with(cx_c, |project, _| {
|
||||
|
@ -3550,6 +3551,7 @@ async fn test_leaving_project(
|
|||
client_b.user_store().clone(),
|
||||
client_b.language_registry().clone(),
|
||||
FakeFs::new(cx.background_executor().clone()),
|
||||
ChannelRole::Member,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
|
@ -3568,11 +3570,11 @@ async fn test_leaving_project(
|
|||
});
|
||||
|
||||
project_b2.read_with(cx_b, |project, _| {
|
||||
assert!(project.is_read_only());
|
||||
assert!(project.is_disconnected());
|
||||
});
|
||||
|
||||
project_c.read_with(cx_c, |project, _| {
|
||||
assert!(project.is_read_only());
|
||||
assert!(project.is_disconnected());
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1149,7 +1149,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
|||
Some((project, cx))
|
||||
});
|
||||
|
||||
if !guest_project.is_read_only() {
|
||||
if !guest_project.is_disconnected() {
|
||||
if let Some((host_project, host_cx)) = host_project {
|
||||
let host_worktree_snapshots =
|
||||
host_project.read_with(host_cx, |host_project, cx| {
|
||||
|
@ -1236,7 +1236,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
|||
let buffers = client.buffers().clone();
|
||||
for (guest_project, guest_buffers) in &buffers {
|
||||
let project_id = if guest_project.read_with(client_cx, |project, _| {
|
||||
project.is_local() || project.is_read_only()
|
||||
project.is_local() || project.is_disconnected()
|
||||
}) {
|
||||
continue;
|
||||
} else {
|
||||
|
|
|
@ -518,7 +518,7 @@ impl<T: RandomizedTest> TestPlan<T> {
|
|||
for project in client.remote_projects().iter() {
|
||||
project.read_with(&client_cx, |project, _| {
|
||||
assert!(
|
||||
project.is_read_only(),
|
||||
project.is_disconnected(),
|
||||
"project {:?} should be read only",
|
||||
project.remote_id()
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue