WIP: improve move and link handling around 'root paths', currently very incorrect and in need of a deeper rework

This commit is contained in:
Mikayla 2023-09-10 22:48:25 -07:00
parent cda54b8b5f
commit 7fa68a9aa4
No known key found for this signature in database
6 changed files with 362 additions and 167 deletions

View File

@ -1,10 +1,11 @@
use std::{sync::Arc, ops::Deref}; use std::{ops::Deref, sync::Arc};
use collections::HashMap; use collections::HashMap;
use rpc::proto; use rpc::proto;
use serde_derive::{Serialize, Deserialize}; use serde_derive::{Deserialize, Serialize};
use crate::{ChannelId, Channel};
use crate::{Channel, ChannelId};
pub type ChannelsById = HashMap<ChannelId, Arc<Channel>>; pub type ChannelsById = HashMap<ChannelId, Arc<Channel>>;
@ -21,9 +22,7 @@ impl Deref for ChannelPath {
impl ChannelPath { impl ChannelPath {
pub fn parent_id(&self) -> Option<ChannelId> { pub fn parent_id(&self) -> Option<ChannelId> {
self.0.len().checked_sub(2).map(|i| { self.0.len().checked_sub(2).map(|i| self.0[i])
self.0[i]
})
} }
} }
@ -39,7 +38,6 @@ pub struct ChannelIndex {
channels_by_id: ChannelsById, channels_by_id: ChannelsById,
} }
impl ChannelIndex { impl ChannelIndex {
pub fn by_id(&self) -> &ChannelsById { pub fn by_id(&self) -> &ChannelsById {
&self.channels_by_id &self.channels_by_id
@ -62,24 +60,53 @@ impl ChannelIndex {
self.paths.iter() self.paths.iter()
} }
/// Remove the given edge from this index. This will not remove the channel /// Remove the given edge from this index. This will not remove the channel.
/// and may result in dangling channels. /// If this operation would result in a dangling edge, re-insert it.
pub fn delete_edge(&mut self, parent_id: ChannelId, channel_id: ChannelId) { pub fn delete_edge(&mut self, parent_id: Option<ChannelId>, channel_id: ChannelId) {
self.paths.retain(|path| { if let Some(parent_id) = parent_id {
!path self.paths.retain(|path| {
.windows(2) !path
.any(|window| window == [parent_id, channel_id]) .windows(2)
}); .any(|window| window == [parent_id, channel_id])
});
} else {
self.paths.retain(|path| path.first() != Some(&channel_id));
}
// Ensure that there is at least one channel path in the index
if !self
.paths
.iter()
.any(|path| path.iter().any(|id| id == &channel_id))
{
let path = ChannelPath(Arc::from([channel_id]));
let current_item: Vec<_> =
channel_path_sorting_key(&path, &self.channels_by_id).collect();
match self.paths.binary_search_by(|channel_path| {
current_item
.iter()
.copied()
.cmp(channel_path_sorting_key(channel_path, &self.channels_by_id))
}) {
Ok(ix) => self.paths.insert(ix, path),
Err(ix) => self.paths.insert(ix, path),
}
}
} }
/// Delete the given channels from this index. /// Delete the given channels from this index.
pub fn delete_channels(&mut self, channels: &[ChannelId]) { pub fn delete_channels(&mut self, channels: &[ChannelId]) {
self.channels_by_id.retain(|channel_id, _| !channels.contains(channel_id)); self.channels_by_id
self.paths.retain(|channel_path| !channel_path.iter().any(|channel_id| {channels.contains(channel_id)})) .retain(|channel_id, _| !channels.contains(channel_id));
self.paths.retain(|channel_path| {
!channel_path
.iter()
.any(|channel_id| channels.contains(channel_id))
})
} }
/// Upsert one or more channels into this index. /// Upsert one or more channels into this index.
pub fn start_upsert(& mut self) -> ChannelPathsUpsertGuard { pub fn start_upsert(&mut self) -> ChannelPathsUpsertGuard {
ChannelPathsUpsertGuard { ChannelPathsUpsertGuard {
paths: &mut self.paths, paths: &mut self.paths,
channels_by_id: &mut self.channels_by_id, channels_by_id: &mut self.channels_by_id,
@ -90,7 +117,7 @@ impl ChannelIndex {
/// A guard for ensuring that the paths index maintains its sort and uniqueness /// A guard for ensuring that the paths index maintains its sort and uniqueness
/// invariants after a series of insertions /// invariants after a series of insertions
pub struct ChannelPathsUpsertGuard<'a> { pub struct ChannelPathsUpsertGuard<'a> {
paths: &'a mut Vec<ChannelPath>, paths: &'a mut Vec<ChannelPath>,
channels_by_id: &'a mut ChannelsById, channels_by_id: &'a mut ChannelsById,
} }
@ -151,7 +178,6 @@ impl<'a> Drop for ChannelPathsUpsertGuard<'a> {
} }
} }
fn channel_path_sorting_key<'a>( fn channel_path_sorting_key<'a>(
path: &'a [ChannelId], path: &'a [ChannelId],
channels_by_id: &'a ChannelsById, channels_by_id: &'a ChannelsById,

View File

@ -870,36 +870,22 @@ impl Database {
&self, &self,
user: UserId, user: UserId,
channel: ChannelId, channel: ChannelId,
from: Option<ChannelId>, from: ChannelId,
tx: &DatabaseTransaction, tx: &DatabaseTransaction,
) -> Result<()> { ) -> Result<()> {
if let Some(from) = from { self.check_user_is_channel_admin(from, user, &*tx).await?;
self.check_user_is_channel_admin(from, user, &*tx).await?;
let sql = r#" let sql = r#"
DELETE FROM channel_paths DELETE FROM channel_paths
WHERE WHERE
id_path LIKE '%' || $1 || '/' || $2 || '%' id_path LIKE '%' || $1 || '/' || $2 || '%'
"#; "#;
let channel_paths_stmt = Statement::from_sql_and_values( let channel_paths_stmt = Statement::from_sql_and_values(
self.pool.get_database_backend(), self.pool.get_database_backend(),
sql, sql,
[from.to_proto().into(), channel.to_proto().into()], [from.to_proto().into(), channel.to_proto().into()],
); );
tx.execute(channel_paths_stmt).await?; tx.execute(channel_paths_stmt).await?;
} else {
let sql = r#"
DELETE FROM channel_paths
WHERE
id_path = '/' || $1 || '/'
"#;
let channel_paths_stmt = Statement::from_sql_and_values(
self.pool.get_database_backend(),
sql,
[channel.to_proto().into()],
);
tx.execute(channel_paths_stmt).await?;
}
// Make sure that there is always at least one path to the channel // Make sure that there is always at least one path to the channel
let sql = r#" let sql = r#"
@ -929,7 +915,7 @@ impl Database {
&self, &self,
user: UserId, user: UserId,
channel: ChannelId, channel: ChannelId,
from: Option<ChannelId>, from: ChannelId,
to: ChannelId, to: ChannelId,
) -> Result<Vec<Channel>> { ) -> Result<Vec<Channel>> {
self.transaction(|tx| async move { self.transaction(|tx| async move {
@ -941,6 +927,10 @@ impl Database {
self.unlink_channel_internal(user, channel, from, &*tx) self.unlink_channel_internal(user, channel, from, &*tx)
.await?; .await?;
dbg!(channel_path::Entity::find().all(&*tx).await);
dbg!(&moved_channels);
Ok(moved_channels) Ok(moved_channels)
}) })
.await .await

View File

@ -2435,22 +2435,23 @@ async fn unlink_channel(
let db = session.db().await; let db = session.db().await;
let channel_id = ChannelId::from_proto(request.channel_id); let channel_id = ChannelId::from_proto(request.channel_id);
let from = request.from.map(ChannelId::from_proto); let from = request.from.map(ChannelId::from_proto);
// Get the members before we remove it, so we know who to notify
let members = db.get_channel_members(channel_id).await?;
db.unlink_channel(session.user_id, channel_id, from).await?; db.unlink_channel(session.user_id, channel_id, from).await?;
if let Some(from_parent) = from { let update = proto::UpdateChannels {
let members = db.get_channel_members(from_parent).await?; delete_channel_edge: vec![proto::ChannelEdge {
let update = proto::UpdateChannels { channel_id: channel_id.to_proto(),
delete_channel_edge: vec![proto::ChannelEdge { parent_id: from.map(ChannelId::to_proto),
channel_id: channel_id.to_proto(), }],
parent_id: from_parent.to_proto(), ..Default::default()
}], };
..Default::default() let connection_pool = session.connection_pool().await;
}; for member_id in members {
let connection_pool = session.connection_pool().await; for connection_id in connection_pool.user_connection_ids(member_id) {
for member_id in members { session.peer.send(connection_id, update.clone())?;
for connection_id in connection_pool.user_connection_ids(member_id) {
session.peer.send(connection_id, update.clone())?;
}
} }
} }
@ -2468,16 +2469,24 @@ async fn move_channel(
let channel_id = ChannelId::from_proto(request.channel_id); let channel_id = ChannelId::from_proto(request.channel_id);
let from_parent = request.from.map(ChannelId::from_proto); let from_parent = request.from.map(ChannelId::from_proto);
let to = ChannelId::from_proto(request.to); let to = ChannelId::from_proto(request.to);
let mut members = db.get_channel_members(channel_id).await?;
let channels_to_send: Vec<Channel> = db let channels_to_send: Vec<Channel> = db
.move_channel(session.user_id, channel_id, from_parent, to) .move_channel(session.user_id, channel_id, from_parent, to)
.await?; .await?;
let members_after = db.get_channel_members(channel_id).await?;
members.extend(members_after);
members.sort();
members.dedup();
if let Some(from_parent) = from_parent { if let Some(from_parent) = from_parent {
let members = db.get_channel_members(from_parent).await?;
let update = proto::UpdateChannels { let update = proto::UpdateChannels {
delete_channel_edge: vec![proto::ChannelEdge { delete_channel_edge: vec![proto::ChannelEdge {
channel_id: channel_id.to_proto(), channel_id: channel_id.to_proto(),
parent_id: from_parent.to_proto(), parent_id: Some(from_parent.to_proto()),
}], }],
..Default::default() ..Default::default()
}; };
@ -2489,7 +2498,6 @@ async fn move_channel(
} }
} }
let members = db.get_channel_members(to).await?;
let connection_pool = session.connection_pool().await; let connection_pool = session.connection_pool().await;
let update = proto::UpdateChannels { let update = proto::UpdateChannels {
channels: channels_to_send channels: channels_to_send

View File

@ -25,7 +25,7 @@ async fn test_core_channel_buffers(
let client_b = server.create_client(cx_b, "user_b").await; let client_b = server.create_client(cx_b, "user_b").await;
let channel_id = server let channel_id = server
.make_channel("zed", (&client_a, cx_a), &mut [(&client_b, cx_b)]) .make_channel("zed", None, (&client_a, cx_a), &mut [(&client_b, cx_b)])
.await; .await;
// Client A joins the channel buffer // Client A joins the channel buffer
@ -135,6 +135,7 @@ async fn test_channel_buffer_replica_ids(
let channel_id = server let channel_id = server
.make_channel( .make_channel(
"the-channel", "the-channel",
None,
(&client_a, cx_a), (&client_a, cx_a),
&mut [(&client_b, cx_b), (&client_c, cx_c)], &mut [(&client_b, cx_b), (&client_c, cx_c)],
) )
@ -279,7 +280,7 @@ async fn test_reopen_channel_buffer(deterministic: Arc<Deterministic>, cx_a: &mu
let client_a = server.create_client(cx_a, "user_a").await; let client_a = server.create_client(cx_a, "user_a").await;
let channel_id = server let channel_id = server
.make_channel("the-channel", (&client_a, cx_a), &mut []) .make_channel("the-channel", None, (&client_a, cx_a), &mut [])
.await; .await;
let channel_buffer_1 = client_a let channel_buffer_1 = client_a
@ -341,7 +342,7 @@ async fn test_channel_buffer_disconnect(
let client_b = server.create_client(cx_b, "user_b").await; let client_b = server.create_client(cx_b, "user_b").await;
let channel_id = server let channel_id = server
.make_channel("the-channel", (&client_a, cx_a), &mut [(&client_b, cx_b)]) .make_channel("the-channel", None, (&client_a, cx_a), &mut [(&client_b, cx_b)])
.await; .await;
let channel_buffer_a = client_a let channel_buffer_a = client_a
@ -411,7 +412,7 @@ async fn test_rejoin_channel_buffer(
let client_b = server.create_client(cx_b, "user_b").await; let client_b = server.create_client(cx_b, "user_b").await;
let channel_id = server let channel_id = server
.make_channel("the-channel", (&client_a, cx_a), &mut [(&client_b, cx_b)]) .make_channel("the-channel", None, (&client_a, cx_a), &mut [(&client_b, cx_b)])
.await; .await;
let channel_buffer_a = client_a let channel_buffer_a = client_a
@ -491,6 +492,7 @@ async fn test_channel_buffers_and_server_restarts(
let channel_id = server let channel_id = server
.make_channel( .make_channel(
"the-channel", "the-channel",
None,
(&client_a, cx_a), (&client_a, cx_a),
&mut [(&client_b, cx_b), (&client_c, cx_c)], &mut [(&client_b, cx_b), (&client_c, cx_c)],
) )

View File

@ -326,7 +326,7 @@ async fn test_joining_channel_ancestor_member(
let client_b = server.create_client(cx_b, "user_b").await; let client_b = server.create_client(cx_b, "user_b").await;
let parent_id = server let parent_id = server
.make_channel("parent", (&client_a, cx_a), &mut [(&client_b, cx_b)]) .make_channel("parent", None, (&client_a, cx_a), &mut [(&client_b, cx_b)])
.await; .await;
let sub_id = client_a let sub_id = client_a
@ -361,6 +361,7 @@ async fn test_channel_room(
let zed_id = server let zed_id = server
.make_channel( .make_channel(
"zed", "zed",
None,
(&client_a, cx_a), (&client_a, cx_a),
&mut [(&client_b, cx_b), (&client_c, cx_c)], &mut [(&client_b, cx_b), (&client_c, cx_c)],
) )
@ -544,9 +545,11 @@ async fn test_channel_jumping(deterministic: Arc<Deterministic>, cx_a: &mut Test
let mut server = TestServer::start(&deterministic).await; let mut server = TestServer::start(&deterministic).await;
let client_a = server.create_client(cx_a, "user_a").await; let client_a = server.create_client(cx_a, "user_a").await;
let zed_id = server.make_channel("zed", (&client_a, cx_a), &mut []).await; let zed_id = server
.make_channel("zed", None, (&client_a, cx_a), &mut [])
.await;
let rust_id = server let rust_id = server
.make_channel("rust", (&client_a, cx_a), &mut []) .make_channel("rust", None, (&client_a, cx_a), &mut [])
.await; .await;
let active_call_a = cx_a.read(ActiveCall::global); let active_call_a = cx_a.read(ActiveCall::global);
@ -597,7 +600,7 @@ async fn test_permissions_update_while_invited(
let client_b = server.create_client(cx_b, "user_b").await; let client_b = server.create_client(cx_b, "user_b").await;
let rust_id = server let rust_id = server
.make_channel("rust", (&client_a, cx_a), &mut []) .make_channel("rust", None, (&client_a, cx_a), &mut [])
.await; .await;
client_a client_a
@ -658,7 +661,7 @@ async fn test_channel_rename(
let client_b = server.create_client(cx_b, "user_b").await; let client_b = server.create_client(cx_b, "user_b").await;
let rust_id = server let rust_id = server
.make_channel("rust", (&client_a, cx_a), &mut [(&client_b, cx_b)]) .make_channel("rust", None, (&client_a, cx_a), &mut [(&client_b, cx_b)])
.await; .await;
// Rename the channel // Rename the channel
@ -716,6 +719,7 @@ async fn test_call_from_channel(
let channel_id = server let channel_id = server
.make_channel( .make_channel(
"x", "x",
None,
(&client_a, cx_a), (&client_a, cx_a),
&mut [(&client_b, cx_b), (&client_c, cx_c)], &mut [(&client_b, cx_b), (&client_c, cx_c)],
) )
@ -776,6 +780,7 @@ async fn test_lost_channel_creation(
deterministic: Arc<Deterministic>, deterministic: Arc<Deterministic>,
cx_a: &mut TestAppContext, cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext, cx_b: &mut TestAppContext,
cx_c: &mut TestAppContext,
) { ) {
deterministic.forbid_parking(); deterministic.forbid_parking();
let mut server = TestServer::start(&deterministic).await; let mut server = TestServer::start(&deterministic).await;
@ -786,7 +791,9 @@ async fn test_lost_channel_creation(
.make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)])
.await; .await;
let channel_id = server.make_channel("x", (&client_a, cx_a), &mut []).await; let channel_id = server
.make_channel("x", None, (&client_a, cx_a), &mut [])
.await;
// Invite a member // Invite a member
client_a client_a
@ -875,140 +882,216 @@ async fn test_lost_channel_creation(
} }
#[gpui::test] #[gpui::test]
async fn test_channel_moving(deterministic: Arc<Deterministic>, cx_a: &mut TestAppContext) { async fn test_channel_moving(
deterministic: Arc<Deterministic>,
cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext,
cx_c: &mut TestAppContext,
) {
deterministic.forbid_parking(); deterministic.forbid_parking();
let mut server = TestServer::start(&deterministic).await; let mut server = TestServer::start(&deterministic).await;
let client_a = server.create_client(cx_a, "user_a").await; let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
let client_c = server.create_client(cx_c, "user_c").await;
let channel_a_id = client_a let channels = server
.channel_store() .make_channel_tree(
.update(cx_a, |channel_store, cx| { &[
channel_store.create_channel("channel-a", None, cx) ("channel-a", None),
}) ("channel-b", Some("channel-a")),
.await ("channel-c", Some("channel-b")),
.unwrap(); ("channel-d", Some("channel-c")),
let channel_b_id = client_a ],
.channel_store() (&client_a, cx_a),
.update(cx_a, |channel_store, cx| { )
channel_store.create_channel("channel-b", Some(channel_a_id), cx) .await;
}) let channel_a_a_id = channels[0];
.await let channel_a_b_id = channels[1];
.unwrap(); let channel_a_c_id = channels[2];
let channel_c_id = client_a let channel_a_d_id = channels[3];
.channel_store()
.update(cx_a, |channel_store, cx| {
channel_store.create_channel("channel-c", Some(channel_b_id), cx)
})
.await
.unwrap();
// Current shape: // Current shape:
// a - b - c // a - b - c - d
deterministic.run_until_parked(); assert_channels_list_shape(
assert_channels(
client_a.channel_store(), client_a.channel_store(),
cx_a, cx_a,
&[ &[
ExpectedChannel { (channel_a_a_id, 0),
id: channel_a_id, (channel_a_b_id, 1),
name: "channel-a".to_string(), (channel_a_c_id, 2),
depth: 0, (channel_a_d_id, 3),
user_is_admin: true,
},
ExpectedChannel {
id: channel_b_id,
name: "channel-b".to_string(),
depth: 1,
user_is_admin: true,
},
ExpectedChannel {
id: channel_c_id,
name: "channel-c".to_string(),
depth: 2,
user_is_admin: true,
},
], ],
); );
client_a client_a
.channel_store() .channel_store()
.update(cx_a, |channel_store, cx| { .update(cx_a, |channel_store, cx| {
channel_store.move_channel(channel_c_id, Some(channel_b_id), channel_a_id, cx) channel_store.move_channel(channel_a_d_id, Some(channel_a_c_id), channel_a_b_id, cx)
}) })
.await .await
.unwrap(); .unwrap();
// Current shape: // Current shape:
// /- c // /- d
// a -- b // a - b -- c
deterministic.run_until_parked(); assert_channels_list_shape(
assert_channels(
client_a.channel_store(), client_a.channel_store(),
cx_a, cx_a,
&[ &[
ExpectedChannel { (channel_a_a_id, 0),
id: channel_a_id, (channel_a_b_id, 1),
name: "channel-a".to_string(), (channel_a_c_id, 2),
depth: 0, (channel_a_d_id, 2),
user_is_admin: true,
},
ExpectedChannel {
id: channel_b_id,
name: "channel-b".to_string(),
depth: 1,
user_is_admin: true,
},
ExpectedChannel {
id: channel_c_id,
name: "channel-c".to_string(),
depth: 1,
user_is_admin: true,
},
], ],
); );
client_a client_a
.channel_store() .channel_store()
.update(cx_a, |channel_store, cx| { .update(cx_a, |channel_store, cx| {
channel_store.link_channel(channel_c_id, channel_b_id, cx) channel_store.link_channel(channel_a_d_id, channel_a_c_id, cx)
}) })
.await .await
.unwrap(); .unwrap();
// Current shape: // Current shape:
// /------\ // /------\
// a -- b -- c // a - b -- c -- d
deterministic.run_until_parked(); assert_channels_list_shape(
assert_channels(
client_a.channel_store(), client_a.channel_store(),
cx_a, cx_a,
&[ &[
ExpectedChannel { (channel_a_a_id, 0),
id: channel_a_id, (channel_a_b_id, 1),
name: "channel-a".to_string(), (channel_a_c_id, 2),
depth: 0, (channel_a_d_id, 3),
user_is_admin: true, (channel_a_d_id, 2),
},
ExpectedChannel {
id: channel_b_id,
name: "channel-b".to_string(),
depth: 1,
user_is_admin: true,
},
ExpectedChannel {
id: channel_c_id,
name: "channel-c".to_string(),
depth: 2,
user_is_admin: true,
},
ExpectedChannel {
id: channel_c_id,
name: "channel-c".to_string(),
depth: 1,
user_is_admin: true,
},
], ],
); );
let b_channels = server
.make_channel_tree(
&[
("channel-mu", None),
("channel-gamma", Some("channel-mu")),
("channel-epsilon", Some("channel-mu")),
],
(&client_b, cx_b),
)
.await;
let channel_b_mu_id = b_channels[0];
let channel_b_gamma_id = b_channels[1];
let channel_b_epsilon_id = b_channels[2];
// Current shape for B:
// /- ep
// mu -- ga
assert_channels_list_shape(
client_b.channel_store(),
cx_b,
&[
(channel_b_mu_id, 0),
(channel_b_gamma_id, 1),
(channel_b_epsilon_id, 1)
],
);
client_a.add_admin_to_channel((&client_b, cx_b), channel_a_b_id, cx_a).await;
// Current shape for B:
// /- ep
// mu -- ga
// /---------\
// b -- c -- d
assert_channels_list_shape(
client_b.channel_store(),
cx_b,
&[
// B's old channels
(channel_b_mu_id, 0),
(channel_b_gamma_id, 1),
(channel_b_epsilon_id, 1),
// New channels from a
(channel_a_b_id, 0),
(channel_a_c_id, 1),
(channel_a_d_id, 1),
(channel_a_d_id, 2),
],
);
client_b
.channel_store()
.update(cx_a, |channel_store, cx| {
channel_store.move_channel(channel_a_b_id, None, channel_b_epsilon_id, cx)
})
.await
.unwrap();
// Current shape for B:
// /---------\
// /- ep -- b -- c -- d
// mu -- ga
assert_channels_list_shape(
client_b.channel_store(),
cx_b,
&[
// B's old channels
(channel_b_mu_id, 0),
(channel_b_gamma_id, 1),
(channel_b_epsilon_id, 1),
// New channels from a, now under epsilon
(channel_a_b_id, 2),
(channel_a_c_id, 3),
(channel_a_d_id, 3),
(channel_a_d_id, 4),
],
);
client_b
.channel_store()
.update(cx_a, |channel_store, cx| {
channel_store.link_channel(channel_b_gamma_id, channel_a_b_id, cx)
})
.await
.unwrap();
// Current shape for B:
// /---------\
// /- ep -- b -- c -- d
// / \
// mu ---------- ga
assert_channels_list_shape(
client_b.channel_store(),
cx_b,
&[
// B's old channels
(channel_b_mu_id, 0),
(channel_b_gamma_id, 1),
(channel_b_epsilon_id, 1),
// New channels from a, now under epsilon, with gamma
(channel_a_b_id, 2),
(channel_b_gamma_id, 3),
(channel_a_c_id, 3),
(channel_a_d_id, 3),
(channel_a_d_id, 4),
],
);
// Current shape for A:
assert_channels_list_shape(
client_a.channel_store(),
cx_a,
&[
(channel_a_a_id, 0),
(channel_a_b_id, 1),
(channel_b_gamma_id, 1),
(channel_a_c_id, 2),
(channel_a_d_id, 3),
(channel_a_d_id, 2),
],
);
// TODO: Make sure to test that non-local root removing problem I was thinking about
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@ -1059,3 +1142,20 @@ fn assert_channels(
}); });
pretty_assertions::assert_eq!(actual, expected_channels); pretty_assertions::assert_eq!(actual, expected_channels);
} }
#[track_caller]
fn assert_channels_list_shape(
channel_store: &ModelHandle<ChannelStore>,
cx: &TestAppContext,
expected_channels: &[(u64, usize)],
) {
cx.foreground().run_until_parked();
let actual = channel_store.read_with(cx, |store, _| {
store
.channels()
.map(|(depth, channel)| (channel.id, depth))
.collect::<Vec<_>>()
});
pretty_assertions::assert_eq!(actual, expected_channels);
}

View File

@ -288,6 +288,7 @@ impl TestServer {
pub async fn make_channel( pub async fn make_channel(
&self, &self,
channel: &str, channel: &str,
parent: Option<u64>,
admin: (&TestClient, &mut TestAppContext), admin: (&TestClient, &mut TestAppContext),
members: &mut [(&TestClient, &mut TestAppContext)], members: &mut [(&TestClient, &mut TestAppContext)],
) -> u64 { ) -> u64 {
@ -296,7 +297,7 @@ impl TestServer {
.app_state .app_state
.channel_store .channel_store
.update(admin_cx, |channel_store, cx| { .update(admin_cx, |channel_store, cx| {
channel_store.create_channel(channel, None, cx) channel_store.create_channel(channel, parent, cx)
}) })
.await .await
.unwrap(); .unwrap();
@ -331,6 +332,39 @@ impl TestServer {
channel_id channel_id
} }
pub async fn make_channel_tree(
&self,
channels: &[(&str, Option<&str>)],
creator: (&TestClient, &mut TestAppContext),
) -> Vec<u64> {
let mut observed_channels = HashMap::default();
let mut result = Vec::new();
for (channel, parent) in channels {
let id;
if let Some(parent) = parent {
if let Some(parent_id) = observed_channels.get(parent) {
id = self
.make_channel(channel, Some(*parent_id), (creator.0, creator.1), &mut [])
.await;
} else {
panic!(
"Edge {}->{} referenced before {} was created",
parent, channel, parent
)
}
} else {
id = self
.make_channel(channel, None, (creator.0, creator.1), &mut [])
.await;
}
observed_channels.insert(channel, id);
result.push(id);
}
result
}
pub async fn create_room(&self, clients: &mut [(&TestClient, &mut TestAppContext)]) { pub async fn create_room(&self, clients: &mut [(&TestClient, &mut TestAppContext)]) {
self.make_contacts(clients).await; self.make_contacts(clients).await;
@ -549,6 +583,41 @@ impl TestClient {
) -> WindowHandle<Workspace> { ) -> WindowHandle<Workspace> {
cx.add_window(|cx| Workspace::new(0, project.clone(), self.app_state.clone(), cx)) cx.add_window(|cx| Workspace::new(0, project.clone(), self.app_state.clone(), cx))
} }
pub async fn add_admin_to_channel(
&self,
user: (&TestClient, &mut TestAppContext),
channel: u64,
cx_self: &mut TestAppContext,
) {
let (other_client, other_cx) = user;
self
.app_state
.channel_store
.update(cx_self, |channel_store, cx| {
channel_store.invite_member(
channel,
other_client.user_id().unwrap(),
true,
cx,
)
})
.await
.unwrap();
cx_self.foreground().run_until_parked();
other_client
.app_state
.channel_store
.update(other_cx, |channels, _| {
channels.respond_to_channel_invite(channel, true)
})
.await
.unwrap();
}
} }
impl Drop for TestClient { impl Drop for TestClient {