Get collab2 green
This commit is contained in:
@@ -0,0 +1,506 @@
|
||||
use super::*;
|
||||
use crate::test_both_dbs;
|
||||
use language::proto::{self, serialize_version};
|
||||
use text::Buffer;
|
||||
|
||||
test_both_dbs!(
|
||||
test_channel_buffers,
|
||||
test_channel_buffers_postgres,
|
||||
test_channel_buffers_sqlite
|
||||
);
|
||||
|
||||
async fn test_channel_buffers(db: &Arc<Database>) {
|
||||
let a_id = db
|
||||
.create_user(
|
||||
"user_a@example.com",
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: "user_a".into(),
|
||||
github_user_id: 101,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.user_id;
|
||||
let b_id = db
|
||||
.create_user(
|
||||
"user_b@example.com",
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: "user_b".into(),
|
||||
github_user_id: 102,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.user_id;
|
||||
|
||||
// This user will not be a part of the channel
|
||||
let c_id = db
|
||||
.create_user(
|
||||
"user_c@example.com",
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: "user_c".into(),
|
||||
github_user_id: 102,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.user_id;
|
||||
|
||||
let owner_id = db.create_server("production").await.unwrap().0 as u32;
|
||||
|
||||
let zed_id = db.create_root_channel("zed", a_id).await.unwrap();
|
||||
|
||||
db.invite_channel_member(zed_id, b_id, a_id, ChannelRole::Member)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.respond_to_channel_invite(zed_id, b_id, true)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let connection_id_a = ConnectionId { owner_id, id: 1 };
|
||||
let _ = db
|
||||
.join_channel_buffer(zed_id, a_id, connection_id_a)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut buffer_a = Buffer::new(0, 0, "".to_string());
|
||||
let mut operations = Vec::new();
|
||||
operations.push(buffer_a.edit([(0..0, "hello world")]));
|
||||
operations.push(buffer_a.edit([(5..5, ", cruel")]));
|
||||
operations.push(buffer_a.edit([(0..5, "goodbye")]));
|
||||
operations.push(buffer_a.undo().unwrap().1);
|
||||
assert_eq!(buffer_a.text(), "hello, cruel world");
|
||||
|
||||
let operations = operations
|
||||
.into_iter()
|
||||
.map(|op| proto::serialize_operation(&language::Operation::Buffer(op)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
db.update_channel_buffer(zed_id, a_id, &operations)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let connection_id_b = ConnectionId { owner_id, id: 2 };
|
||||
let buffer_response_b = db
|
||||
.join_channel_buffer(zed_id, b_id, connection_id_b)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut buffer_b = Buffer::new(0, 0, buffer_response_b.base_text);
|
||||
buffer_b
|
||||
.apply_ops(buffer_response_b.operations.into_iter().map(|operation| {
|
||||
let operation = proto::deserialize_operation(operation).unwrap();
|
||||
if let language::Operation::Buffer(operation) = operation {
|
||||
operation
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(buffer_b.text(), "hello, cruel world");
|
||||
|
||||
// Ensure that C fails to open the buffer
|
||||
assert!(db
|
||||
.join_channel_buffer(zed_id, c_id, ConnectionId { owner_id, id: 3 })
|
||||
.await
|
||||
.is_err());
|
||||
|
||||
// Ensure that both collaborators have shown up
|
||||
assert_eq!(
|
||||
buffer_response_b.collaborators,
|
||||
&[
|
||||
rpc::proto::Collaborator {
|
||||
user_id: a_id.to_proto(),
|
||||
peer_id: Some(rpc::proto::PeerId { id: 1, owner_id }),
|
||||
replica_id: 0,
|
||||
},
|
||||
rpc::proto::Collaborator {
|
||||
user_id: b_id.to_proto(),
|
||||
peer_id: Some(rpc::proto::PeerId { id: 2, owner_id }),
|
||||
replica_id: 1,
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
// Ensure that get_channel_buffer_collaborators works
|
||||
let zed_collaborats = db.get_channel_buffer_collaborators(zed_id).await.unwrap();
|
||||
assert_eq!(zed_collaborats, &[a_id, b_id]);
|
||||
|
||||
let left_buffer = db
|
||||
.leave_channel_buffer(zed_id, connection_id_b)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(left_buffer.connections, &[connection_id_a],);
|
||||
|
||||
let cargo_id = db.create_root_channel("cargo", a_id).await.unwrap();
|
||||
let _ = db
|
||||
.join_channel_buffer(cargo_id, a_id, connection_id_a)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.leave_channel_buffers(connection_id_a).await.unwrap();
|
||||
|
||||
let zed_collaborators = db.get_channel_buffer_collaborators(zed_id).await.unwrap();
|
||||
let cargo_collaborators = db.get_channel_buffer_collaborators(cargo_id).await.unwrap();
|
||||
assert_eq!(zed_collaborators, &[]);
|
||||
assert_eq!(cargo_collaborators, &[]);
|
||||
|
||||
// When everyone has left the channel, the operations are collapsed into
|
||||
// a new base text.
|
||||
let buffer_response_b = db
|
||||
.join_channel_buffer(zed_id, b_id, connection_id_b)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(buffer_response_b.base_text, "hello, cruel world");
|
||||
assert_eq!(buffer_response_b.operations, &[]);
|
||||
}
|
||||
|
||||
test_both_dbs!(
|
||||
test_channel_buffers_last_operations,
|
||||
test_channel_buffers_last_operations_postgres,
|
||||
test_channel_buffers_last_operations_sqlite
|
||||
);
|
||||
|
||||
async fn test_channel_buffers_last_operations(db: &Database) {
|
||||
let user_id = db
|
||||
.create_user(
|
||||
"user_a@example.com",
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: "user_a".into(),
|
||||
github_user_id: 101,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.user_id;
|
||||
let observer_id = db
|
||||
.create_user(
|
||||
"user_b@example.com",
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: "user_b".into(),
|
||||
github_user_id: 102,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.user_id;
|
||||
let owner_id = db.create_server("production").await.unwrap().0 as u32;
|
||||
let connection_id = ConnectionId {
|
||||
owner_id,
|
||||
id: user_id.0 as u32,
|
||||
};
|
||||
|
||||
let mut buffers = Vec::new();
|
||||
let mut text_buffers = Vec::new();
|
||||
for i in 0..3 {
|
||||
let channel = db
|
||||
.create_root_channel(&format!("channel-{i}"), user_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.invite_channel_member(channel, observer_id, user_id, ChannelRole::Member)
|
||||
.await
|
||||
.unwrap();
|
||||
db.respond_to_channel_invite(channel, observer_id, true)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.join_channel_buffer(channel, user_id, connection_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
buffers.push(
|
||||
db.transaction(|tx| async move { db.get_channel_buffer(channel, &*tx).await })
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
text_buffers.push(Buffer::new(0, 0, "".to_string()));
|
||||
}
|
||||
|
||||
let operations = db
|
||||
.transaction(|tx| {
|
||||
let buffers = &buffers;
|
||||
async move {
|
||||
db.get_latest_operations_for_buffers([buffers[0].id, buffers[2].id], &*tx)
|
||||
.await
|
||||
}
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(operations.is_empty());
|
||||
|
||||
update_buffer(
|
||||
buffers[0].channel_id,
|
||||
user_id,
|
||||
db,
|
||||
vec![
|
||||
text_buffers[0].edit([(0..0, "a")]),
|
||||
text_buffers[0].edit([(0..0, "b")]),
|
||||
text_buffers[0].edit([(0..0, "c")]),
|
||||
],
|
||||
)
|
||||
.await;
|
||||
|
||||
update_buffer(
|
||||
buffers[1].channel_id,
|
||||
user_id,
|
||||
db,
|
||||
vec![
|
||||
text_buffers[1].edit([(0..0, "d")]),
|
||||
text_buffers[1].edit([(1..1, "e")]),
|
||||
text_buffers[1].edit([(2..2, "f")]),
|
||||
],
|
||||
)
|
||||
.await;
|
||||
|
||||
// cause buffer 1's epoch to increment.
|
||||
db.leave_channel_buffer(buffers[1].channel_id, connection_id)
|
||||
.await
|
||||
.unwrap();
|
||||
db.join_channel_buffer(buffers[1].channel_id, user_id, connection_id)
|
||||
.await
|
||||
.unwrap();
|
||||
text_buffers[1] = Buffer::new(1, 0, "def".to_string());
|
||||
update_buffer(
|
||||
buffers[1].channel_id,
|
||||
user_id,
|
||||
db,
|
||||
vec![
|
||||
text_buffers[1].edit([(0..0, "g")]),
|
||||
text_buffers[1].edit([(0..0, "h")]),
|
||||
],
|
||||
)
|
||||
.await;
|
||||
|
||||
update_buffer(
|
||||
buffers[2].channel_id,
|
||||
user_id,
|
||||
db,
|
||||
vec![text_buffers[2].edit([(0..0, "i")])],
|
||||
)
|
||||
.await;
|
||||
|
||||
let operations = db
|
||||
.transaction(|tx| {
|
||||
let buffers = &buffers;
|
||||
async move {
|
||||
db.get_latest_operations_for_buffers([buffers[1].id, buffers[2].id], &*tx)
|
||||
.await
|
||||
}
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_operations(
|
||||
&operations,
|
||||
&[
|
||||
(buffers[1].id, 1, &text_buffers[1]),
|
||||
(buffers[2].id, 0, &text_buffers[2]),
|
||||
],
|
||||
);
|
||||
|
||||
let operations = db
|
||||
.transaction(|tx| {
|
||||
let buffers = &buffers;
|
||||
async move {
|
||||
db.get_latest_operations_for_buffers([buffers[0].id, buffers[1].id], &*tx)
|
||||
.await
|
||||
}
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_operations(
|
||||
&operations,
|
||||
&[
|
||||
(buffers[0].id, 0, &text_buffers[0]),
|
||||
(buffers[1].id, 1, &text_buffers[1]),
|
||||
],
|
||||
);
|
||||
|
||||
let buffer_changes = db
|
||||
.transaction(|tx| {
|
||||
let buffers = &buffers;
|
||||
async move {
|
||||
db.unseen_channel_buffer_changes(
|
||||
observer_id,
|
||||
&[
|
||||
buffers[0].channel_id,
|
||||
buffers[1].channel_id,
|
||||
buffers[2].channel_id,
|
||||
],
|
||||
&*tx,
|
||||
)
|
||||
.await
|
||||
}
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
pretty_assertions::assert_eq!(
|
||||
buffer_changes,
|
||||
[
|
||||
rpc::proto::UnseenChannelBufferChange {
|
||||
channel_id: buffers[0].channel_id.to_proto(),
|
||||
epoch: 0,
|
||||
version: serialize_version(&text_buffers[0].version()),
|
||||
},
|
||||
rpc::proto::UnseenChannelBufferChange {
|
||||
channel_id: buffers[1].channel_id.to_proto(),
|
||||
epoch: 1,
|
||||
version: serialize_version(&text_buffers[1].version())
|
||||
.into_iter()
|
||||
.filter(|vector| vector.replica_id
|
||||
== buffer_changes[1].version.first().unwrap().replica_id)
|
||||
.collect::<Vec<_>>(),
|
||||
},
|
||||
rpc::proto::UnseenChannelBufferChange {
|
||||
channel_id: buffers[2].channel_id.to_proto(),
|
||||
epoch: 0,
|
||||
version: serialize_version(&text_buffers[2].version()),
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
db.observe_buffer_version(
|
||||
buffers[1].id,
|
||||
observer_id,
|
||||
1,
|
||||
serialize_version(&text_buffers[1].version()).as_slice(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let buffer_changes = db
|
||||
.transaction(|tx| {
|
||||
let buffers = &buffers;
|
||||
async move {
|
||||
db.unseen_channel_buffer_changes(
|
||||
observer_id,
|
||||
&[
|
||||
buffers[0].channel_id,
|
||||
buffers[1].channel_id,
|
||||
buffers[2].channel_id,
|
||||
],
|
||||
&*tx,
|
||||
)
|
||||
.await
|
||||
}
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
buffer_changes,
|
||||
[
|
||||
rpc::proto::UnseenChannelBufferChange {
|
||||
channel_id: buffers[0].channel_id.to_proto(),
|
||||
epoch: 0,
|
||||
version: serialize_version(&text_buffers[0].version()),
|
||||
},
|
||||
rpc::proto::UnseenChannelBufferChange {
|
||||
channel_id: buffers[2].channel_id.to_proto(),
|
||||
epoch: 0,
|
||||
version: serialize_version(&text_buffers[2].version()),
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
// Observe an earlier version of the buffer.
|
||||
db.observe_buffer_version(
|
||||
buffers[1].id,
|
||||
observer_id,
|
||||
1,
|
||||
&[rpc::proto::VectorClockEntry {
|
||||
replica_id: 0,
|
||||
timestamp: 0,
|
||||
}],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let buffer_changes = db
|
||||
.transaction(|tx| {
|
||||
let buffers = &buffers;
|
||||
async move {
|
||||
db.unseen_channel_buffer_changes(
|
||||
observer_id,
|
||||
&[
|
||||
buffers[0].channel_id,
|
||||
buffers[1].channel_id,
|
||||
buffers[2].channel_id,
|
||||
],
|
||||
&*tx,
|
||||
)
|
||||
.await
|
||||
}
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
buffer_changes,
|
||||
[
|
||||
rpc::proto::UnseenChannelBufferChange {
|
||||
channel_id: buffers[0].channel_id.to_proto(),
|
||||
epoch: 0,
|
||||
version: serialize_version(&text_buffers[0].version()),
|
||||
},
|
||||
rpc::proto::UnseenChannelBufferChange {
|
||||
channel_id: buffers[2].channel_id.to_proto(),
|
||||
epoch: 0,
|
||||
version: serialize_version(&text_buffers[2].version()),
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
async fn update_buffer(
|
||||
channel_id: ChannelId,
|
||||
user_id: UserId,
|
||||
db: &Database,
|
||||
operations: Vec<text::Operation>,
|
||||
) {
|
||||
let operations = operations
|
||||
.into_iter()
|
||||
.map(|op| proto::serialize_operation(&language::Operation::Buffer(op)))
|
||||
.collect::<Vec<_>>();
|
||||
db.update_channel_buffer(channel_id, user_id, &operations)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn assert_operations(
|
||||
operations: &[buffer_operation::Model],
|
||||
expected: &[(BufferId, i32, &text::Buffer)],
|
||||
) {
|
||||
let actual = operations
|
||||
.iter()
|
||||
.map(|op| buffer_operation::Model {
|
||||
buffer_id: op.buffer_id,
|
||||
epoch: op.epoch,
|
||||
lamport_timestamp: op.lamport_timestamp,
|
||||
replica_id: op.replica_id,
|
||||
value: vec![],
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let expected = expected
|
||||
.iter()
|
||||
.map(|(buffer_id, epoch, buffer)| buffer_operation::Model {
|
||||
buffer_id: *buffer_id,
|
||||
epoch: *epoch,
|
||||
lamport_timestamp: buffer.lamport_clock.value as i32 - 1,
|
||||
replica_id: buffer.replica_id() as i32,
|
||||
value: vec![],
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(actual, expected, "unexpected operations")
|
||||
}
|
||||
@@ -0,0 +1,819 @@
|
||||
use crate::{
|
||||
db::{
|
||||
tests::{channel_tree, new_test_connection, new_test_user, TEST_RELEASE_CHANNEL},
|
||||
Channel, ChannelId, ChannelRole, Database, NewUserParams, RoomId,
|
||||
},
|
||||
test_both_dbs,
|
||||
};
|
||||
use rpc::{
|
||||
proto::{self},
|
||||
ConnectionId,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
test_both_dbs!(test_channels, test_channels_postgres, test_channels_sqlite);
|
||||
|
||||
async fn test_channels(db: &Arc<Database>) {
|
||||
let a_id = new_test_user(db, "user1@example.com").await;
|
||||
let b_id = new_test_user(db, "user2@example.com").await;
|
||||
|
||||
let zed_id = db.create_root_channel("zed", a_id).await.unwrap();
|
||||
|
||||
// Make sure that people cannot read channels they haven't been invited to
|
||||
assert!(db.get_channel(zed_id, b_id).await.is_err());
|
||||
|
||||
db.invite_channel_member(zed_id, b_id, a_id, ChannelRole::Member)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.respond_to_channel_invite(zed_id, b_id, true)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let crdb_id = db.create_sub_channel("crdb", zed_id, a_id).await.unwrap();
|
||||
let livestreaming_id = db
|
||||
.create_sub_channel("livestreaming", zed_id, a_id)
|
||||
.await
|
||||
.unwrap();
|
||||
let replace_id = db
|
||||
.create_sub_channel("replace", zed_id, a_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut members = db
|
||||
.transaction(|tx| async move {
|
||||
let channel = db.get_channel_internal(replace_id, &*tx).await?;
|
||||
Ok(db.get_channel_participants(&channel, &*tx).await?)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
members.sort();
|
||||
assert_eq!(members, &[a_id, b_id]);
|
||||
|
||||
let rust_id = db.create_root_channel("rust", a_id).await.unwrap();
|
||||
let cargo_id = db.create_sub_channel("cargo", rust_id, a_id).await.unwrap();
|
||||
|
||||
let cargo_ra_id = db
|
||||
.create_sub_channel("cargo-ra", cargo_id, a_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let result = db.get_channels_for_user(a_id).await.unwrap();
|
||||
assert_eq!(
|
||||
result.channels,
|
||||
channel_tree(&[
|
||||
(zed_id, &[], "zed", ChannelRole::Admin),
|
||||
(crdb_id, &[zed_id], "crdb", ChannelRole::Admin),
|
||||
(
|
||||
livestreaming_id,
|
||||
&[zed_id],
|
||||
"livestreaming",
|
||||
ChannelRole::Admin
|
||||
),
|
||||
(replace_id, &[zed_id], "replace", ChannelRole::Admin),
|
||||
(rust_id, &[], "rust", ChannelRole::Admin),
|
||||
(cargo_id, &[rust_id], "cargo", ChannelRole::Admin),
|
||||
(
|
||||
cargo_ra_id,
|
||||
&[rust_id, cargo_id],
|
||||
"cargo-ra",
|
||||
ChannelRole::Admin
|
||||
)
|
||||
],)
|
||||
);
|
||||
|
||||
let result = db.get_channels_for_user(b_id).await.unwrap();
|
||||
assert_eq!(
|
||||
result.channels,
|
||||
channel_tree(&[
|
||||
(zed_id, &[], "zed", ChannelRole::Member),
|
||||
(crdb_id, &[zed_id], "crdb", ChannelRole::Member),
|
||||
(
|
||||
livestreaming_id,
|
||||
&[zed_id],
|
||||
"livestreaming",
|
||||
ChannelRole::Member
|
||||
),
|
||||
(replace_id, &[zed_id], "replace", ChannelRole::Member)
|
||||
],)
|
||||
);
|
||||
|
||||
// Update member permissions
|
||||
let set_subchannel_admin = db
|
||||
.set_channel_member_role(crdb_id, a_id, b_id, ChannelRole::Admin)
|
||||
.await;
|
||||
assert!(set_subchannel_admin.is_err());
|
||||
let set_channel_admin = db
|
||||
.set_channel_member_role(zed_id, a_id, b_id, ChannelRole::Admin)
|
||||
.await;
|
||||
assert!(set_channel_admin.is_ok());
|
||||
|
||||
let result = db.get_channels_for_user(b_id).await.unwrap();
|
||||
assert_eq!(
|
||||
result.channels,
|
||||
channel_tree(&[
|
||||
(zed_id, &[], "zed", ChannelRole::Admin),
|
||||
(crdb_id, &[zed_id], "crdb", ChannelRole::Admin),
|
||||
(
|
||||
livestreaming_id,
|
||||
&[zed_id],
|
||||
"livestreaming",
|
||||
ChannelRole::Admin
|
||||
),
|
||||
(replace_id, &[zed_id], "replace", ChannelRole::Admin)
|
||||
],)
|
||||
);
|
||||
|
||||
// Remove a single channel
|
||||
db.delete_channel(crdb_id, a_id).await.unwrap();
|
||||
assert!(db.get_channel(crdb_id, a_id).await.is_err());
|
||||
|
||||
// Remove a channel tree
|
||||
let (mut channel_ids, user_ids) = db.delete_channel(rust_id, a_id).await.unwrap();
|
||||
channel_ids.sort();
|
||||
assert_eq!(channel_ids, &[rust_id, cargo_id, cargo_ra_id]);
|
||||
assert_eq!(user_ids, &[a_id]);
|
||||
|
||||
assert!(db.get_channel(rust_id, a_id).await.is_err());
|
||||
assert!(db.get_channel(cargo_id, a_id).await.is_err());
|
||||
assert!(db.get_channel(cargo_ra_id, a_id).await.is_err());
|
||||
}
|
||||
|
||||
test_both_dbs!(
|
||||
test_joining_channels,
|
||||
test_joining_channels_postgres,
|
||||
test_joining_channels_sqlite
|
||||
);
|
||||
|
||||
async fn test_joining_channels(db: &Arc<Database>) {
|
||||
let owner_id = db.create_server("test").await.unwrap().0 as u32;
|
||||
|
||||
let user_1 = new_test_user(db, "user1@example.com").await;
|
||||
let user_2 = new_test_user(db, "user2@example.com").await;
|
||||
|
||||
let channel_1 = db.create_root_channel("channel_1", user_1).await.unwrap();
|
||||
|
||||
// can join a room with membership to its channel
|
||||
let (joined_room, _, _) = db
|
||||
.join_channel(
|
||||
channel_1,
|
||||
user_1,
|
||||
ConnectionId { owner_id, id: 1 },
|
||||
TEST_RELEASE_CHANNEL,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(joined_room.room.participants.len(), 1);
|
||||
|
||||
let room_id = RoomId::from_proto(joined_room.room.id);
|
||||
drop(joined_room);
|
||||
// cannot join a room without membership to its channel
|
||||
assert!(db
|
||||
.join_room(
|
||||
room_id,
|
||||
user_2,
|
||||
ConnectionId { owner_id, id: 1 },
|
||||
TEST_RELEASE_CHANNEL
|
||||
)
|
||||
.await
|
||||
.is_err());
|
||||
}
|
||||
|
||||
test_both_dbs!(
|
||||
test_channel_invites,
|
||||
test_channel_invites_postgres,
|
||||
test_channel_invites_sqlite
|
||||
);
|
||||
|
||||
async fn test_channel_invites(db: &Arc<Database>) {
|
||||
db.create_server("test").await.unwrap();
|
||||
|
||||
let user_1 = new_test_user(db, "user1@example.com").await;
|
||||
let user_2 = new_test_user(db, "user2@example.com").await;
|
||||
let user_3 = new_test_user(db, "user3@example.com").await;
|
||||
|
||||
let channel_1_1 = db.create_root_channel("channel_1", user_1).await.unwrap();
|
||||
|
||||
let channel_1_2 = db.create_root_channel("channel_2", user_1).await.unwrap();
|
||||
|
||||
db.invite_channel_member(channel_1_1, user_2, user_1, ChannelRole::Member)
|
||||
.await
|
||||
.unwrap();
|
||||
db.invite_channel_member(channel_1_2, user_2, user_1, ChannelRole::Member)
|
||||
.await
|
||||
.unwrap();
|
||||
db.invite_channel_member(channel_1_1, user_3, user_1, ChannelRole::Admin)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let user_2_invites = db
|
||||
.get_channel_invites_for_user(user_2) // -> [channel_1_1, channel_1_2]
|
||||
.await
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|channel| channel.id)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(user_2_invites, &[channel_1_1, channel_1_2]);
|
||||
|
||||
let user_3_invites = db
|
||||
.get_channel_invites_for_user(user_3) // -> [channel_1_1]
|
||||
.await
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|channel| channel.id)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(user_3_invites, &[channel_1_1]);
|
||||
|
||||
let mut members = db
|
||||
.get_channel_participant_details(channel_1_1, user_1)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
members.sort_by_key(|member| member.user_id);
|
||||
assert_eq!(
|
||||
members,
|
||||
&[
|
||||
proto::ChannelMember {
|
||||
user_id: user_1.to_proto(),
|
||||
kind: proto::channel_member::Kind::Member.into(),
|
||||
role: proto::ChannelRole::Admin.into(),
|
||||
},
|
||||
proto::ChannelMember {
|
||||
user_id: user_2.to_proto(),
|
||||
kind: proto::channel_member::Kind::Invitee.into(),
|
||||
role: proto::ChannelRole::Member.into(),
|
||||
},
|
||||
proto::ChannelMember {
|
||||
user_id: user_3.to_proto(),
|
||||
kind: proto::channel_member::Kind::Invitee.into(),
|
||||
role: proto::ChannelRole::Admin.into(),
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
db.respond_to_channel_invite(channel_1_1, user_2, true)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let channel_1_3 = db
|
||||
.create_sub_channel("channel_3", channel_1_1, user_1)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let members = db
|
||||
.get_channel_participant_details(channel_1_3, user_1)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
members,
|
||||
&[
|
||||
proto::ChannelMember {
|
||||
user_id: user_1.to_proto(),
|
||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
||||
role: proto::ChannelRole::Admin.into(),
|
||||
},
|
||||
proto::ChannelMember {
|
||||
user_id: user_2.to_proto(),
|
||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
||||
role: proto::ChannelRole::Member.into(),
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
test_both_dbs!(
|
||||
test_channel_renames,
|
||||
test_channel_renames_postgres,
|
||||
test_channel_renames_sqlite
|
||||
);
|
||||
|
||||
async fn test_channel_renames(db: &Arc<Database>) {
|
||||
db.create_server("test").await.unwrap();
|
||||
|
||||
let user_1 = db
|
||||
.create_user(
|
||||
"user1@example.com",
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: "user1".into(),
|
||||
github_user_id: 5,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.user_id;
|
||||
|
||||
let user_2 = db
|
||||
.create_user(
|
||||
"user2@example.com",
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: "user2".into(),
|
||||
github_user_id: 6,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.user_id;
|
||||
|
||||
let zed_id = db.create_root_channel("zed", user_1).await.unwrap();
|
||||
|
||||
db.rename_channel(zed_id, user_1, "#zed-archive")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let channel = db.get_channel(zed_id, user_1).await.unwrap();
|
||||
assert_eq!(channel.name, "zed-archive");
|
||||
|
||||
let non_permissioned_rename = db.rename_channel(zed_id, user_2, "hacked-lol").await;
|
||||
assert!(non_permissioned_rename.is_err());
|
||||
|
||||
let bad_name_rename = db.rename_channel(zed_id, user_1, "#").await;
|
||||
assert!(bad_name_rename.is_err())
|
||||
}
|
||||
|
||||
test_both_dbs!(
|
||||
test_db_channel_moving,
|
||||
test_channels_moving_postgres,
|
||||
test_channels_moving_sqlite
|
||||
);
|
||||
|
||||
async fn test_db_channel_moving(db: &Arc<Database>) {
|
||||
let a_id = db
|
||||
.create_user(
|
||||
"user1@example.com",
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: "user1".into(),
|
||||
github_user_id: 5,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.user_id;
|
||||
|
||||
let zed_id = db.create_root_channel("zed", a_id).await.unwrap();
|
||||
|
||||
let crdb_id = db.create_sub_channel("crdb", zed_id, a_id).await.unwrap();
|
||||
|
||||
let gpui2_id = db.create_sub_channel("gpui2", zed_id, a_id).await.unwrap();
|
||||
|
||||
let livestreaming_id = db
|
||||
.create_sub_channel("livestreaming", crdb_id, a_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let livestreaming_dag_id = db
|
||||
.create_sub_channel("livestreaming_dag", livestreaming_id, a_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// ========================================================================
|
||||
// sanity check
|
||||
// Initial DAG:
|
||||
// /- gpui2
|
||||
// zed -- crdb - livestreaming - livestreaming_dag
|
||||
let result = db.get_channels_for_user(a_id).await.unwrap();
|
||||
assert_channel_tree(
|
||||
result.channels,
|
||||
&[
|
||||
(zed_id, &[]),
|
||||
(crdb_id, &[zed_id]),
|
||||
(livestreaming_id, &[zed_id, crdb_id]),
|
||||
(livestreaming_dag_id, &[zed_id, crdb_id, livestreaming_id]),
|
||||
(gpui2_id, &[zed_id]),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
test_both_dbs!(
|
||||
test_db_channel_moving_bugs,
|
||||
test_db_channel_moving_bugs_postgres,
|
||||
test_db_channel_moving_bugs_sqlite
|
||||
);
|
||||
|
||||
async fn test_db_channel_moving_bugs(db: &Arc<Database>) {
|
||||
let user_id = db
|
||||
.create_user(
|
||||
"user1@example.com",
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: "user1".into(),
|
||||
github_user_id: 5,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.user_id;
|
||||
|
||||
let zed_id = db.create_root_channel("zed", user_id).await.unwrap();
|
||||
|
||||
let projects_id = db
|
||||
.create_sub_channel("projects", zed_id, user_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let livestreaming_id = db
|
||||
.create_sub_channel("livestreaming", projects_id, user_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Dag is: zed - projects - livestreaming
|
||||
|
||||
// Move to same parent should be a no-op
|
||||
assert!(db
|
||||
.move_channel(projects_id, Some(zed_id), user_id)
|
||||
.await
|
||||
.unwrap()
|
||||
.is_none());
|
||||
|
||||
let result = db.get_channels_for_user(user_id).await.unwrap();
|
||||
assert_channel_tree(
|
||||
result.channels,
|
||||
&[
|
||||
(zed_id, &[]),
|
||||
(projects_id, &[zed_id]),
|
||||
(livestreaming_id, &[zed_id, projects_id]),
|
||||
],
|
||||
);
|
||||
|
||||
// Move the project channel to the root
|
||||
db.move_channel(projects_id, None, user_id).await.unwrap();
|
||||
let result = db.get_channels_for_user(user_id).await.unwrap();
|
||||
assert_channel_tree(
|
||||
result.channels,
|
||||
&[
|
||||
(zed_id, &[]),
|
||||
(projects_id, &[]),
|
||||
(livestreaming_id, &[projects_id]),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
test_both_dbs!(
|
||||
test_user_is_channel_participant,
|
||||
test_user_is_channel_participant_postgres,
|
||||
test_user_is_channel_participant_sqlite
|
||||
);
|
||||
|
||||
async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
||||
let admin = new_test_user(db, "admin@example.com").await;
|
||||
let member = new_test_user(db, "member@example.com").await;
|
||||
let guest = new_test_user(db, "guest@example.com").await;
|
||||
|
||||
let zed_channel = db.create_root_channel("zed", admin).await.unwrap();
|
||||
let active_channel_id = db
|
||||
.create_sub_channel("active", zed_channel, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
let vim_channel_id = db
|
||||
.create_sub_channel("vim", active_channel_id, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.set_channel_visibility(vim_channel_id, crate::db::ChannelVisibility::Public, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
db.invite_channel_member(active_channel_id, member, admin, ChannelRole::Member)
|
||||
.await
|
||||
.unwrap();
|
||||
db.invite_channel_member(vim_channel_id, guest, admin, ChannelRole::Guest)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.respond_to_channel_invite(active_channel_id, member, true)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.transaction(|tx| async move {
|
||||
db.check_user_is_channel_participant(
|
||||
&db.get_channel_internal(vim_channel_id, &*tx).await?,
|
||||
admin,
|
||||
&*tx,
|
||||
)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
db.transaction(|tx| async move {
|
||||
db.check_user_is_channel_participant(
|
||||
&db.get_channel_internal(vim_channel_id, &*tx).await?,
|
||||
member,
|
||||
&*tx,
|
||||
)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut members = db
|
||||
.get_channel_participant_details(vim_channel_id, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
members.sort_by_key(|member| member.user_id);
|
||||
|
||||
assert_eq!(
|
||||
members,
|
||||
&[
|
||||
proto::ChannelMember {
|
||||
user_id: admin.to_proto(),
|
||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
||||
role: proto::ChannelRole::Admin.into(),
|
||||
},
|
||||
proto::ChannelMember {
|
||||
user_id: member.to_proto(),
|
||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
||||
role: proto::ChannelRole::Member.into(),
|
||||
},
|
||||
proto::ChannelMember {
|
||||
user_id: guest.to_proto(),
|
||||
kind: proto::channel_member::Kind::Invitee.into(),
|
||||
role: proto::ChannelRole::Guest.into(),
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
db.respond_to_channel_invite(vim_channel_id, guest, true)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.transaction(|tx| async move {
|
||||
db.check_user_is_channel_participant(
|
||||
&db.get_channel_internal(vim_channel_id, &*tx).await?,
|
||||
guest,
|
||||
&*tx,
|
||||
)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let channels = db.get_channels_for_user(guest).await.unwrap().channels;
|
||||
assert_channel_tree(channels, &[(vim_channel_id, &[])]);
|
||||
let channels = db.get_channels_for_user(member).await.unwrap().channels;
|
||||
assert_channel_tree(
|
||||
channels,
|
||||
&[
|
||||
(active_channel_id, &[]),
|
||||
(vim_channel_id, &[active_channel_id]),
|
||||
],
|
||||
);
|
||||
|
||||
db.set_channel_member_role(vim_channel_id, admin, guest, ChannelRole::Banned)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(db
|
||||
.transaction(|tx| async move {
|
||||
db.check_user_is_channel_participant(
|
||||
&db.get_channel_internal(vim_channel_id, &*tx).await.unwrap(),
|
||||
guest,
|
||||
&*tx,
|
||||
)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
.is_err());
|
||||
|
||||
let mut members = db
|
||||
.get_channel_participant_details(vim_channel_id, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
members.sort_by_key(|member| member.user_id);
|
||||
|
||||
assert_eq!(
|
||||
members,
|
||||
&[
|
||||
proto::ChannelMember {
|
||||
user_id: admin.to_proto(),
|
||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
||||
role: proto::ChannelRole::Admin.into(),
|
||||
},
|
||||
proto::ChannelMember {
|
||||
user_id: member.to_proto(),
|
||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
||||
role: proto::ChannelRole::Member.into(),
|
||||
},
|
||||
proto::ChannelMember {
|
||||
user_id: guest.to_proto(),
|
||||
kind: proto::channel_member::Kind::Member.into(),
|
||||
role: proto::ChannelRole::Banned.into(),
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
db.remove_channel_member(vim_channel_id, guest, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.set_channel_visibility(zed_channel, crate::db::ChannelVisibility::Public, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.invite_channel_member(zed_channel, guest, admin, ChannelRole::Guest)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// currently people invited to parent channels are not shown here
|
||||
let mut members = db
|
||||
.get_channel_participant_details(vim_channel_id, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
members.sort_by_key(|member| member.user_id);
|
||||
|
||||
assert_eq!(
|
||||
members,
|
||||
&[
|
||||
proto::ChannelMember {
|
||||
user_id: admin.to_proto(),
|
||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
||||
role: proto::ChannelRole::Admin.into(),
|
||||
},
|
||||
proto::ChannelMember {
|
||||
user_id: member.to_proto(),
|
||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
||||
role: proto::ChannelRole::Member.into(),
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
db.respond_to_channel_invite(zed_channel, guest, true)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.transaction(|tx| async move {
|
||||
db.check_user_is_channel_participant(
|
||||
&db.get_channel_internal(zed_channel, &*tx).await.unwrap(),
|
||||
guest,
|
||||
&*tx,
|
||||
)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(db
|
||||
.transaction(|tx| async move {
|
||||
db.check_user_is_channel_participant(
|
||||
&db.get_channel_internal(active_channel_id, &*tx)
|
||||
.await
|
||||
.unwrap(),
|
||||
guest,
|
||||
&*tx,
|
||||
)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
.is_err(),);
|
||||
|
||||
db.transaction(|tx| async move {
|
||||
db.check_user_is_channel_participant(
|
||||
&db.get_channel_internal(vim_channel_id, &*tx).await.unwrap(),
|
||||
guest,
|
||||
&*tx,
|
||||
)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut members = db
|
||||
.get_channel_participant_details(vim_channel_id, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
members.sort_by_key(|member| member.user_id);
|
||||
|
||||
assert_eq!(
|
||||
members,
|
||||
&[
|
||||
proto::ChannelMember {
|
||||
user_id: admin.to_proto(),
|
||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
||||
role: proto::ChannelRole::Admin.into(),
|
||||
},
|
||||
proto::ChannelMember {
|
||||
user_id: member.to_proto(),
|
||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
||||
role: proto::ChannelRole::Member.into(),
|
||||
},
|
||||
proto::ChannelMember {
|
||||
user_id: guest.to_proto(),
|
||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
||||
role: proto::ChannelRole::Guest.into(),
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
let channels = db.get_channels_for_user(guest).await.unwrap().channels;
|
||||
assert_channel_tree(
|
||||
channels,
|
||||
&[(zed_channel, &[]), (vim_channel_id, &[zed_channel])],
|
||||
)
|
||||
}
|
||||
|
||||
test_both_dbs!(
|
||||
test_user_joins_correct_channel,
|
||||
test_user_joins_correct_channel_postgres,
|
||||
test_user_joins_correct_channel_sqlite
|
||||
);
|
||||
|
||||
async fn test_user_joins_correct_channel(db: &Arc<Database>) {
|
||||
let admin = new_test_user(db, "admin@example.com").await;
|
||||
|
||||
let zed_channel = db.create_root_channel("zed", admin).await.unwrap();
|
||||
|
||||
let active_channel = db
|
||||
.create_sub_channel("active", zed_channel, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let vim_channel = db
|
||||
.create_sub_channel("vim", active_channel, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let vim2_channel = db
|
||||
.create_sub_channel("vim2", vim_channel, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.set_channel_visibility(zed_channel, crate::db::ChannelVisibility::Public, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.set_channel_visibility(vim_channel, crate::db::ChannelVisibility::Public, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.set_channel_visibility(vim2_channel, crate::db::ChannelVisibility::Public, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let most_public = db
|
||||
.transaction(|tx| async move {
|
||||
Ok(db
|
||||
.public_ancestors_including_self(
|
||||
&db.get_channel_internal(vim_channel, &*tx).await.unwrap(),
|
||||
&tx,
|
||||
)
|
||||
.await?
|
||||
.first()
|
||||
.cloned())
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.id;
|
||||
|
||||
assert_eq!(most_public, zed_channel)
|
||||
}
|
||||
|
||||
test_both_dbs!(
|
||||
test_guest_access,
|
||||
test_guest_access_postgres,
|
||||
test_guest_access_sqlite
|
||||
);
|
||||
|
||||
async fn test_guest_access(db: &Arc<Database>) {
|
||||
let server = db.create_server("test").await.unwrap();
|
||||
|
||||
let admin = new_test_user(db, "admin@example.com").await;
|
||||
let guest = new_test_user(db, "guest@example.com").await;
|
||||
let guest_connection = new_test_connection(server);
|
||||
|
||||
let zed_channel = db.create_root_channel("zed", admin).await.unwrap();
|
||||
db.set_channel_visibility(zed_channel, crate::db::ChannelVisibility::Public, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(db
|
||||
.join_channel_chat(zed_channel, guest_connection, guest)
|
||||
.await
|
||||
.is_err());
|
||||
|
||||
db.join_channel(zed_channel, guest, guest_connection, TEST_RELEASE_CHANNEL)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(db
|
||||
.join_channel_chat(zed_channel, guest_connection, guest)
|
||||
.await
|
||||
.is_ok())
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_channel_tree(actual: Vec<Channel>, expected: &[(ChannelId, &[ChannelId])]) {
|
||||
let actual = actual
|
||||
.iter()
|
||||
.map(|channel| (channel.id, channel.parent_path.as_slice()))
|
||||
.collect::<Vec<_>>();
|
||||
pretty_assertions::assert_eq!(
|
||||
actual,
|
||||
expected.to_vec(),
|
||||
"wrong channel ids and parent paths"
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,633 @@
|
||||
use super::*;
|
||||
use crate::test_both_dbs;
|
||||
use gpui::TestAppContext;
|
||||
use pretty_assertions::{assert_eq, assert_ne};
|
||||
use std::sync::Arc;
|
||||
use tests::TestDb;
|
||||
|
||||
test_both_dbs!(
|
||||
test_get_users,
|
||||
test_get_users_by_ids_postgres,
|
||||
test_get_users_by_ids_sqlite
|
||||
);
|
||||
|
||||
async fn test_get_users(db: &Arc<Database>) {
|
||||
let mut user_ids = Vec::new();
|
||||
let mut user_metric_ids = Vec::new();
|
||||
for i in 1..=4 {
|
||||
let user = db
|
||||
.create_user(
|
||||
&format!("user{i}@example.com"),
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: format!("user{i}"),
|
||||
github_user_id: i,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
user_ids.push(user.user_id);
|
||||
user_metric_ids.push(user.metrics_id);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
db.get_users_by_ids(user_ids.clone()).await.unwrap(),
|
||||
vec![
|
||||
User {
|
||||
id: user_ids[0],
|
||||
github_login: "user1".to_string(),
|
||||
github_user_id: Some(1),
|
||||
email_address: Some("user1@example.com".to_string()),
|
||||
admin: false,
|
||||
metrics_id: user_metric_ids[0].parse().unwrap(),
|
||||
..Default::default()
|
||||
},
|
||||
User {
|
||||
id: user_ids[1],
|
||||
github_login: "user2".to_string(),
|
||||
github_user_id: Some(2),
|
||||
email_address: Some("user2@example.com".to_string()),
|
||||
admin: false,
|
||||
metrics_id: user_metric_ids[1].parse().unwrap(),
|
||||
..Default::default()
|
||||
},
|
||||
User {
|
||||
id: user_ids[2],
|
||||
github_login: "user3".to_string(),
|
||||
github_user_id: Some(3),
|
||||
email_address: Some("user3@example.com".to_string()),
|
||||
admin: false,
|
||||
metrics_id: user_metric_ids[2].parse().unwrap(),
|
||||
..Default::default()
|
||||
},
|
||||
User {
|
||||
id: user_ids[3],
|
||||
github_login: "user4".to_string(),
|
||||
github_user_id: Some(4),
|
||||
email_address: Some("user4@example.com".to_string()),
|
||||
admin: false,
|
||||
metrics_id: user_metric_ids[3].parse().unwrap(),
|
||||
..Default::default()
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
test_both_dbs!(
|
||||
test_get_or_create_user_by_github_account,
|
||||
test_get_or_create_user_by_github_account_postgres,
|
||||
test_get_or_create_user_by_github_account_sqlite
|
||||
);
|
||||
|
||||
async fn test_get_or_create_user_by_github_account(db: &Arc<Database>) {
|
||||
let user_id1 = db
|
||||
.create_user(
|
||||
"user1@example.com",
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: "login1".into(),
|
||||
github_user_id: 101,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.user_id;
|
||||
let user_id2 = db
|
||||
.create_user(
|
||||
"user2@example.com",
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: "login2".into(),
|
||||
github_user_id: 102,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.user_id;
|
||||
|
||||
let user = db
|
||||
.get_or_create_user_by_github_account("login1", None, None)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(user.id, user_id1);
|
||||
assert_eq!(&user.github_login, "login1");
|
||||
assert_eq!(user.github_user_id, Some(101));
|
||||
|
||||
assert!(db
|
||||
.get_or_create_user_by_github_account("non-existent-login", None, None)
|
||||
.await
|
||||
.unwrap()
|
||||
.is_none());
|
||||
|
||||
let user = db
|
||||
.get_or_create_user_by_github_account("the-new-login2", Some(102), None)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(user.id, user_id2);
|
||||
assert_eq!(&user.github_login, "the-new-login2");
|
||||
assert_eq!(user.github_user_id, Some(102));
|
||||
|
||||
let user = db
|
||||
.get_or_create_user_by_github_account("login3", Some(103), Some("user3@example.com"))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(&user.github_login, "login3");
|
||||
assert_eq!(user.github_user_id, Some(103));
|
||||
assert_eq!(user.email_address, Some("user3@example.com".into()));
|
||||
}
|
||||
|
||||
test_both_dbs!(
|
||||
test_create_access_tokens,
|
||||
test_create_access_tokens_postgres,
|
||||
test_create_access_tokens_sqlite
|
||||
);
|
||||
|
||||
async fn test_create_access_tokens(db: &Arc<Database>) {
|
||||
let user = db
|
||||
.create_user(
|
||||
"u1@example.com",
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: "u1".into(),
|
||||
github_user_id: 1,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.user_id;
|
||||
|
||||
let token_1 = db.create_access_token(user, "h1", 2).await.unwrap();
|
||||
let token_2 = db.create_access_token(user, "h2", 2).await.unwrap();
|
||||
assert_eq!(
|
||||
db.get_access_token(token_1).await.unwrap(),
|
||||
access_token::Model {
|
||||
id: token_1,
|
||||
user_id: user,
|
||||
hash: "h1".into(),
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
db.get_access_token(token_2).await.unwrap(),
|
||||
access_token::Model {
|
||||
id: token_2,
|
||||
user_id: user,
|
||||
hash: "h2".into()
|
||||
}
|
||||
);
|
||||
|
||||
let token_3 = db.create_access_token(user, "h3", 2).await.unwrap();
|
||||
assert_eq!(
|
||||
db.get_access_token(token_3).await.unwrap(),
|
||||
access_token::Model {
|
||||
id: token_3,
|
||||
user_id: user,
|
||||
hash: "h3".into()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
db.get_access_token(token_2).await.unwrap(),
|
||||
access_token::Model {
|
||||
id: token_2,
|
||||
user_id: user,
|
||||
hash: "h2".into()
|
||||
}
|
||||
);
|
||||
assert!(db.get_access_token(token_1).await.is_err());
|
||||
|
||||
let token_4 = db.create_access_token(user, "h4", 2).await.unwrap();
|
||||
assert_eq!(
|
||||
db.get_access_token(token_4).await.unwrap(),
|
||||
access_token::Model {
|
||||
id: token_4,
|
||||
user_id: user,
|
||||
hash: "h4".into()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
db.get_access_token(token_3).await.unwrap(),
|
||||
access_token::Model {
|
||||
id: token_3,
|
||||
user_id: user,
|
||||
hash: "h3".into()
|
||||
}
|
||||
);
|
||||
assert!(db.get_access_token(token_2).await.is_err());
|
||||
assert!(db.get_access_token(token_1).await.is_err());
|
||||
}
|
||||
|
||||
test_both_dbs!(
|
||||
test_add_contacts,
|
||||
test_add_contacts_postgres,
|
||||
test_add_contacts_sqlite
|
||||
);
|
||||
|
||||
async fn test_add_contacts(db: &Arc<Database>) {
|
||||
let mut user_ids = Vec::new();
|
||||
for i in 0..3 {
|
||||
user_ids.push(
|
||||
db.create_user(
|
||||
&format!("user{i}@example.com"),
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: format!("user{i}"),
|
||||
github_user_id: i,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.user_id,
|
||||
);
|
||||
}
|
||||
|
||||
let user_1 = user_ids[0];
|
||||
let user_2 = user_ids[1];
|
||||
let user_3 = user_ids[2];
|
||||
|
||||
// User starts with no contacts
|
||||
assert_eq!(db.get_contacts(user_1).await.unwrap(), &[]);
|
||||
|
||||
// User requests a contact. Both users see the pending request.
|
||||
db.send_contact_request(user_1, user_2).await.unwrap();
|
||||
assert!(!db.has_contact(user_1, user_2).await.unwrap());
|
||||
assert!(!db.has_contact(user_2, user_1).await.unwrap());
|
||||
assert_eq!(
|
||||
db.get_contacts(user_1).await.unwrap(),
|
||||
&[Contact::Outgoing { user_id: user_2 }],
|
||||
);
|
||||
assert_eq!(
|
||||
db.get_contacts(user_2).await.unwrap(),
|
||||
&[Contact::Incoming { user_id: user_1 }]
|
||||
);
|
||||
|
||||
// User 2 dismisses the contact request notification without accepting or rejecting.
|
||||
// We shouldn't notify them again.
|
||||
db.dismiss_contact_notification(user_1, user_2)
|
||||
.await
|
||||
.unwrap_err();
|
||||
db.dismiss_contact_notification(user_2, user_1)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
db.get_contacts(user_2).await.unwrap(),
|
||||
&[Contact::Incoming { user_id: user_1 }]
|
||||
);
|
||||
|
||||
// User can't accept their own contact request
|
||||
db.respond_to_contact_request(user_1, user_2, true)
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
// User accepts a contact request. Both users see the contact.
|
||||
db.respond_to_contact_request(user_2, user_1, true)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
db.get_contacts(user_1).await.unwrap(),
|
||||
&[Contact::Accepted {
|
||||
user_id: user_2,
|
||||
busy: false,
|
||||
}],
|
||||
);
|
||||
assert!(db.has_contact(user_1, user_2).await.unwrap());
|
||||
assert!(db.has_contact(user_2, user_1).await.unwrap());
|
||||
assert_eq!(
|
||||
db.get_contacts(user_2).await.unwrap(),
|
||||
&[Contact::Accepted {
|
||||
user_id: user_1,
|
||||
busy: false,
|
||||
}]
|
||||
);
|
||||
|
||||
// Users cannot re-request existing contacts.
|
||||
db.send_contact_request(user_1, user_2).await.unwrap_err();
|
||||
db.send_contact_request(user_2, user_1).await.unwrap_err();
|
||||
|
||||
// Users can't dismiss notifications of them accepting other users' requests.
|
||||
db.dismiss_contact_notification(user_2, user_1)
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
db.get_contacts(user_1).await.unwrap(),
|
||||
&[Contact::Accepted {
|
||||
user_id: user_2,
|
||||
busy: false,
|
||||
}]
|
||||
);
|
||||
|
||||
// Users can dismiss notifications of other users accepting their requests.
|
||||
db.dismiss_contact_notification(user_1, user_2)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
db.get_contacts(user_1).await.unwrap(),
|
||||
&[Contact::Accepted {
|
||||
user_id: user_2,
|
||||
busy: false,
|
||||
}]
|
||||
);
|
||||
|
||||
// Users send each other concurrent contact requests and
|
||||
// see that they are immediately accepted.
|
||||
db.send_contact_request(user_1, user_3).await.unwrap();
|
||||
db.send_contact_request(user_3, user_1).await.unwrap();
|
||||
assert_eq!(
|
||||
db.get_contacts(user_1).await.unwrap(),
|
||||
&[
|
||||
Contact::Accepted {
|
||||
user_id: user_2,
|
||||
busy: false,
|
||||
},
|
||||
Contact::Accepted {
|
||||
user_id: user_3,
|
||||
busy: false,
|
||||
}
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
db.get_contacts(user_3).await.unwrap(),
|
||||
&[Contact::Accepted {
|
||||
user_id: user_1,
|
||||
busy: false,
|
||||
}],
|
||||
);
|
||||
|
||||
// User declines a contact request. Both users see that it is gone.
|
||||
db.send_contact_request(user_2, user_3).await.unwrap();
|
||||
db.respond_to_contact_request(user_3, user_2, false)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(!db.has_contact(user_2, user_3).await.unwrap());
|
||||
assert!(!db.has_contact(user_3, user_2).await.unwrap());
|
||||
assert_eq!(
|
||||
db.get_contacts(user_2).await.unwrap(),
|
||||
&[Contact::Accepted {
|
||||
user_id: user_1,
|
||||
busy: false,
|
||||
}]
|
||||
);
|
||||
assert_eq!(
|
||||
db.get_contacts(user_3).await.unwrap(),
|
||||
&[Contact::Accepted {
|
||||
user_id: user_1,
|
||||
busy: false,
|
||||
}],
|
||||
);
|
||||
}
|
||||
|
||||
test_both_dbs!(
|
||||
test_metrics_id,
|
||||
test_metrics_id_postgres,
|
||||
test_metrics_id_sqlite
|
||||
);
|
||||
|
||||
async fn test_metrics_id(db: &Arc<Database>) {
|
||||
let NewUserResult {
|
||||
user_id: user1,
|
||||
metrics_id: metrics_id1,
|
||||
..
|
||||
} = db
|
||||
.create_user(
|
||||
"person1@example.com",
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: "person1".into(),
|
||||
github_user_id: 101,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let NewUserResult {
|
||||
user_id: user2,
|
||||
metrics_id: metrics_id2,
|
||||
..
|
||||
} = db
|
||||
.create_user(
|
||||
"person2@example.com",
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: "person2".into(),
|
||||
github_user_id: 102,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(db.get_user_metrics_id(user1).await.unwrap(), metrics_id1);
|
||||
assert_eq!(db.get_user_metrics_id(user2).await.unwrap(), metrics_id2);
|
||||
assert_eq!(metrics_id1.len(), 36);
|
||||
assert_eq!(metrics_id2.len(), 36);
|
||||
assert_ne!(metrics_id1, metrics_id2);
|
||||
}
|
||||
|
||||
test_both_dbs!(
|
||||
test_project_count,
|
||||
test_project_count_postgres,
|
||||
test_project_count_sqlite
|
||||
);
|
||||
|
||||
async fn test_project_count(db: &Arc<Database>) {
|
||||
let owner_id = db.create_server("test").await.unwrap().0 as u32;
|
||||
|
||||
let user1 = db
|
||||
.create_user(
|
||||
&format!("admin@example.com"),
|
||||
true,
|
||||
NewUserParams {
|
||||
github_login: "admin".into(),
|
||||
github_user_id: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let user2 = db
|
||||
.create_user(
|
||||
&format!("user@example.com"),
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: "user".into(),
|
||||
github_user_id: 1,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let room_id = RoomId::from_proto(
|
||||
db.create_room(user1.user_id, ConnectionId { owner_id, id: 0 }, "", "dev")
|
||||
.await
|
||||
.unwrap()
|
||||
.id,
|
||||
);
|
||||
db.call(
|
||||
room_id,
|
||||
user1.user_id,
|
||||
ConnectionId { owner_id, id: 0 },
|
||||
user2.user_id,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
db.join_room(
|
||||
room_id,
|
||||
user2.user_id,
|
||||
ConnectionId { owner_id, id: 1 },
|
||||
"dev",
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
|
||||
|
||||
db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[])
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 1);
|
||||
|
||||
db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[])
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
|
||||
|
||||
// Projects shared by admins aren't counted.
|
||||
db.share_project(room_id, ConnectionId { owner_id, id: 0 }, &[])
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
|
||||
|
||||
db.leave_room(ConnectionId { owner_id, id: 1 })
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fuzzy_like_string() {
|
||||
assert_eq!(Database::fuzzy_like_string("abcd"), "%a%b%c%d%");
|
||||
assert_eq!(Database::fuzzy_like_string("x y"), "%x%y%");
|
||||
assert_eq!(Database::fuzzy_like_string(" z "), "%z%");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_fuzzy_search_users(cx: &mut TestAppContext) {
|
||||
let test_db = TestDb::postgres(cx.executor().clone());
|
||||
let db = test_db.db();
|
||||
for (i, github_login) in [
|
||||
"California",
|
||||
"colorado",
|
||||
"oregon",
|
||||
"washington",
|
||||
"florida",
|
||||
"delaware",
|
||||
"rhode-island",
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
db.create_user(
|
||||
&format!("{github_login}@example.com"),
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: github_login.into(),
|
||||
github_user_id: i as i32,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
fuzzy_search_user_names(db, "clr").await,
|
||||
&["colorado", "California"]
|
||||
);
|
||||
assert_eq!(
|
||||
fuzzy_search_user_names(db, "ro").await,
|
||||
&["rhode-island", "colorado", "oregon"],
|
||||
);
|
||||
|
||||
async fn fuzzy_search_user_names(db: &Database, query: &str) -> Vec<String> {
|
||||
db.fuzzy_search_users(query, 10)
|
||||
.await
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|user| user.github_login)
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
}
|
||||
|
||||
test_both_dbs!(
|
||||
test_non_matching_release_channels,
|
||||
test_non_matching_release_channels_postgres,
|
||||
test_non_matching_release_channels_sqlite
|
||||
);
|
||||
|
||||
async fn test_non_matching_release_channels(db: &Arc<Database>) {
|
||||
let owner_id = db.create_server("test").await.unwrap().0 as u32;
|
||||
|
||||
let user1 = db
|
||||
.create_user(
|
||||
&format!("admin@example.com"),
|
||||
true,
|
||||
NewUserParams {
|
||||
github_login: "admin".into(),
|
||||
github_user_id: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let user2 = db
|
||||
.create_user(
|
||||
&format!("user@example.com"),
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: "user".into(),
|
||||
github_user_id: 1,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let room = db
|
||||
.create_room(
|
||||
user1.user_id,
|
||||
ConnectionId { owner_id, id: 0 },
|
||||
"",
|
||||
"stable",
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.call(
|
||||
RoomId::from_proto(room.id),
|
||||
user1.user_id,
|
||||
ConnectionId { owner_id, id: 0 },
|
||||
user2.user_id,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// User attempts to join from preview
|
||||
let result = db
|
||||
.join_room(
|
||||
RoomId::from_proto(room.id),
|
||||
user2.user_id,
|
||||
ConnectionId { owner_id, id: 1 },
|
||||
"preview",
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(result.is_err());
|
||||
|
||||
// User switches to stable
|
||||
let result = db
|
||||
.join_room(
|
||||
RoomId::from_proto(room.id),
|
||||
user2.user_id,
|
||||
ConnectionId { owner_id, id: 1 },
|
||||
"stable",
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok())
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
use crate::{
|
||||
db::{Database, NewUserParams},
|
||||
test_both_dbs,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
test_both_dbs!(
|
||||
test_get_user_flags,
|
||||
test_get_user_flags_postgres,
|
||||
test_get_user_flags_sqlite
|
||||
);
|
||||
|
||||
async fn test_get_user_flags(db: &Arc<Database>) {
|
||||
let user_1 = db
|
||||
.create_user(
|
||||
&format!("user1@example.com"),
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: format!("user1"),
|
||||
github_user_id: 1,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.user_id;
|
||||
|
||||
let user_2 = db
|
||||
.create_user(
|
||||
&format!("user2@example.com"),
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: format!("user2"),
|
||||
github_user_id: 2,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.user_id;
|
||||
|
||||
const CHANNELS_ALPHA: &'static str = "channels-alpha";
|
||||
const NEW_SEARCH: &'static str = "new-search";
|
||||
|
||||
let channels_flag = db.create_user_flag(CHANNELS_ALPHA).await.unwrap();
|
||||
let search_flag = db.create_user_flag(NEW_SEARCH).await.unwrap();
|
||||
|
||||
db.add_user_flag(user_1, channels_flag).await.unwrap();
|
||||
db.add_user_flag(user_1, search_flag).await.unwrap();
|
||||
|
||||
db.add_user_flag(user_2, channels_flag).await.unwrap();
|
||||
|
||||
let mut user_1_flags = db.get_user_flags(user_1).await.unwrap();
|
||||
user_1_flags.sort();
|
||||
assert_eq!(user_1_flags, &[CHANNELS_ALPHA, NEW_SEARCH]);
|
||||
|
||||
let mut user_2_flags = db.get_user_flags(user_2).await.unwrap();
|
||||
user_2_flags.sort();
|
||||
assert_eq!(user_2_flags, &[CHANNELS_ALPHA]);
|
||||
}
|
||||
@@ -0,0 +1,454 @@
|
||||
use super::new_test_user;
|
||||
use crate::{
|
||||
db::{ChannelRole, Database, MessageId},
|
||||
test_both_dbs,
|
||||
};
|
||||
use channel::mentions_to_proto;
|
||||
use std::sync::Arc;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
test_both_dbs!(
|
||||
test_channel_message_retrieval,
|
||||
test_channel_message_retrieval_postgres,
|
||||
test_channel_message_retrieval_sqlite
|
||||
);
|
||||
|
||||
async fn test_channel_message_retrieval(db: &Arc<Database>) {
|
||||
let user = new_test_user(db, "user@example.com").await;
|
||||
let result = db.create_channel("channel", None, user).await.unwrap();
|
||||
|
||||
let owner_id = db.create_server("test").await.unwrap().0 as u32;
|
||||
db.join_channel_chat(
|
||||
result.channel.id,
|
||||
rpc::ConnectionId { owner_id, id: 0 },
|
||||
user,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut all_messages = Vec::new();
|
||||
for i in 0..10 {
|
||||
all_messages.push(
|
||||
db.create_channel_message(
|
||||
result.channel.id,
|
||||
user,
|
||||
&i.to_string(),
|
||||
&[],
|
||||
OffsetDateTime::now_utc(),
|
||||
i,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.message_id
|
||||
.to_proto(),
|
||||
);
|
||||
}
|
||||
|
||||
let messages = db
|
||||
.get_channel_messages(result.channel.id, user, 3, None)
|
||||
.await
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|message| message.id)
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(messages, &all_messages[7..10]);
|
||||
|
||||
let messages = db
|
||||
.get_channel_messages(
|
||||
result.channel.id,
|
||||
user,
|
||||
4,
|
||||
Some(MessageId::from_proto(all_messages[6])),
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|message| message.id)
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(messages, &all_messages[2..6]);
|
||||
}
|
||||
|
||||
test_both_dbs!(
|
||||
test_channel_message_nonces,
|
||||
test_channel_message_nonces_postgres,
|
||||
test_channel_message_nonces_sqlite
|
||||
);
|
||||
|
||||
async fn test_channel_message_nonces(db: &Arc<Database>) {
|
||||
let user_a = new_test_user(db, "user_a@example.com").await;
|
||||
let user_b = new_test_user(db, "user_b@example.com").await;
|
||||
let user_c = new_test_user(db, "user_c@example.com").await;
|
||||
let channel = db.create_root_channel("channel", user_a).await.unwrap();
|
||||
db.invite_channel_member(channel, user_b, user_a, ChannelRole::Member)
|
||||
.await
|
||||
.unwrap();
|
||||
db.invite_channel_member(channel, user_c, user_a, ChannelRole::Member)
|
||||
.await
|
||||
.unwrap();
|
||||
db.respond_to_channel_invite(channel, user_b, true)
|
||||
.await
|
||||
.unwrap();
|
||||
db.respond_to_channel_invite(channel, user_c, true)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let owner_id = db.create_server("test").await.unwrap().0 as u32;
|
||||
db.join_channel_chat(channel, rpc::ConnectionId { owner_id, id: 0 }, user_a)
|
||||
.await
|
||||
.unwrap();
|
||||
db.join_channel_chat(channel, rpc::ConnectionId { owner_id, id: 1 }, user_b)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// As user A, create messages that re-use the same nonces. The requests
|
||||
// succeed, but return the same ids.
|
||||
let id1 = db
|
||||
.create_channel_message(
|
||||
channel,
|
||||
user_a,
|
||||
"hi @user_b",
|
||||
&mentions_to_proto(&[(3..10, user_b.to_proto())]),
|
||||
OffsetDateTime::now_utc(),
|
||||
100,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.message_id;
|
||||
let id2 = db
|
||||
.create_channel_message(
|
||||
channel,
|
||||
user_a,
|
||||
"hello, fellow users",
|
||||
&mentions_to_proto(&[]),
|
||||
OffsetDateTime::now_utc(),
|
||||
200,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.message_id;
|
||||
let id3 = db
|
||||
.create_channel_message(
|
||||
channel,
|
||||
user_a,
|
||||
"bye @user_c (same nonce as first message)",
|
||||
&mentions_to_proto(&[(4..11, user_c.to_proto())]),
|
||||
OffsetDateTime::now_utc(),
|
||||
100,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.message_id;
|
||||
let id4 = db
|
||||
.create_channel_message(
|
||||
channel,
|
||||
user_a,
|
||||
"omg (same nonce as second message)",
|
||||
&mentions_to_proto(&[]),
|
||||
OffsetDateTime::now_utc(),
|
||||
200,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.message_id;
|
||||
|
||||
// As a different user, reuse one of the same nonces. This request succeeds
|
||||
// and returns a different id.
|
||||
let id5 = db
|
||||
.create_channel_message(
|
||||
channel,
|
||||
user_b,
|
||||
"omg @user_a (same nonce as user_a's first message)",
|
||||
&mentions_to_proto(&[(4..11, user_a.to_proto())]),
|
||||
OffsetDateTime::now_utc(),
|
||||
100,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.message_id;
|
||||
|
||||
assert_ne!(id1, id2);
|
||||
assert_eq!(id1, id3);
|
||||
assert_eq!(id2, id4);
|
||||
assert_ne!(id5, id1);
|
||||
|
||||
let messages = db
|
||||
.get_channel_messages(channel, user_a, 5, None)
|
||||
.await
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|m| (m.id, m.body, m.mentions))
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
messages,
|
||||
&[
|
||||
(
|
||||
id1.to_proto(),
|
||||
"hi @user_b".into(),
|
||||
mentions_to_proto(&[(3..10, user_b.to_proto())]),
|
||||
),
|
||||
(
|
||||
id2.to_proto(),
|
||||
"hello, fellow users".into(),
|
||||
mentions_to_proto(&[])
|
||||
),
|
||||
(
|
||||
id5.to_proto(),
|
||||
"omg @user_a (same nonce as user_a's first message)".into(),
|
||||
mentions_to_proto(&[(4..11, user_a.to_proto())]),
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
test_both_dbs!(
|
||||
test_unseen_channel_messages,
|
||||
test_unseen_channel_messages_postgres,
|
||||
test_unseen_channel_messages_sqlite
|
||||
);
|
||||
|
||||
async fn test_unseen_channel_messages(db: &Arc<Database>) {
|
||||
let user = new_test_user(db, "user_a@example.com").await;
|
||||
let observer = new_test_user(db, "user_b@example.com").await;
|
||||
|
||||
let channel_1 = db.create_root_channel("channel", user).await.unwrap();
|
||||
let channel_2 = db.create_root_channel("channel-2", user).await.unwrap();
|
||||
|
||||
db.invite_channel_member(channel_1, observer, user, ChannelRole::Member)
|
||||
.await
|
||||
.unwrap();
|
||||
db.invite_channel_member(channel_2, observer, user, ChannelRole::Member)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.respond_to_channel_invite(channel_1, observer, true)
|
||||
.await
|
||||
.unwrap();
|
||||
db.respond_to_channel_invite(channel_2, observer, true)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let owner_id = db.create_server("test").await.unwrap().0 as u32;
|
||||
let user_connection_id = rpc::ConnectionId { owner_id, id: 0 };
|
||||
|
||||
db.join_channel_chat(channel_1, user_connection_id, user)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let _ = db
|
||||
.create_channel_message(channel_1, user, "1_1", &[], OffsetDateTime::now_utc(), 1)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let second_message = db
|
||||
.create_channel_message(channel_1, user, "1_2", &[], OffsetDateTime::now_utc(), 2)
|
||||
.await
|
||||
.unwrap()
|
||||
.message_id;
|
||||
|
||||
let third_message = db
|
||||
.create_channel_message(channel_1, user, "1_3", &[], OffsetDateTime::now_utc(), 3)
|
||||
.await
|
||||
.unwrap()
|
||||
.message_id;
|
||||
|
||||
db.join_channel_chat(channel_2, user_connection_id, user)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let fourth_message = db
|
||||
.create_channel_message(channel_2, user, "2_1", &[], OffsetDateTime::now_utc(), 4)
|
||||
.await
|
||||
.unwrap()
|
||||
.message_id;
|
||||
|
||||
// Check that observer has new messages
|
||||
let unseen_messages = db
|
||||
.transaction(|tx| async move {
|
||||
db.unseen_channel_messages(observer, &[channel_1, channel_2], &*tx)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
unseen_messages,
|
||||
[
|
||||
rpc::proto::UnseenChannelMessage {
|
||||
channel_id: channel_1.to_proto(),
|
||||
message_id: third_message.to_proto(),
|
||||
},
|
||||
rpc::proto::UnseenChannelMessage {
|
||||
channel_id: channel_2.to_proto(),
|
||||
message_id: fourth_message.to_proto(),
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
// Observe the second message
|
||||
db.observe_channel_message(channel_1, observer, second_message)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Make sure the observer still has a new message
|
||||
let unseen_messages = db
|
||||
.transaction(|tx| async move {
|
||||
db.unseen_channel_messages(observer, &[channel_1, channel_2], &*tx)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
unseen_messages,
|
||||
[
|
||||
rpc::proto::UnseenChannelMessage {
|
||||
channel_id: channel_1.to_proto(),
|
||||
message_id: third_message.to_proto(),
|
||||
},
|
||||
rpc::proto::UnseenChannelMessage {
|
||||
channel_id: channel_2.to_proto(),
|
||||
message_id: fourth_message.to_proto(),
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
// Observe the third message,
|
||||
db.observe_channel_message(channel_1, observer, third_message)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Make sure the observer does not have a new method
|
||||
let unseen_messages = db
|
||||
.transaction(|tx| async move {
|
||||
db.unseen_channel_messages(observer, &[channel_1, channel_2], &*tx)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
unseen_messages,
|
||||
[rpc::proto::UnseenChannelMessage {
|
||||
channel_id: channel_2.to_proto(),
|
||||
message_id: fourth_message.to_proto(),
|
||||
}]
|
||||
);
|
||||
|
||||
// Observe the second message again, should not regress our observed state
|
||||
db.observe_channel_message(channel_1, observer, second_message)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Make sure the observer does not have a new message
|
||||
let unseen_messages = db
|
||||
.transaction(|tx| async move {
|
||||
db.unseen_channel_messages(observer, &[channel_1, channel_2], &*tx)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
unseen_messages,
|
||||
[rpc::proto::UnseenChannelMessage {
|
||||
channel_id: channel_2.to_proto(),
|
||||
message_id: fourth_message.to_proto(),
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
test_both_dbs!(
|
||||
test_channel_message_mentions,
|
||||
test_channel_message_mentions_postgres,
|
||||
test_channel_message_mentions_sqlite
|
||||
);
|
||||
|
||||
async fn test_channel_message_mentions(db: &Arc<Database>) {
|
||||
let user_a = new_test_user(db, "user_a@example.com").await;
|
||||
let user_b = new_test_user(db, "user_b@example.com").await;
|
||||
let user_c = new_test_user(db, "user_c@example.com").await;
|
||||
|
||||
let channel = db
|
||||
.create_channel("channel", None, user_a)
|
||||
.await
|
||||
.unwrap()
|
||||
.channel
|
||||
.id;
|
||||
db.invite_channel_member(channel, user_b, user_a, ChannelRole::Member)
|
||||
.await
|
||||
.unwrap();
|
||||
db.respond_to_channel_invite(channel, user_b, true)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let owner_id = db.create_server("test").await.unwrap().0 as u32;
|
||||
let connection_id = rpc::ConnectionId { owner_id, id: 0 };
|
||||
db.join_channel_chat(channel, connection_id, user_a)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.create_channel_message(
|
||||
channel,
|
||||
user_a,
|
||||
"hi @user_b and @user_c",
|
||||
&mentions_to_proto(&[(3..10, user_b.to_proto()), (15..22, user_c.to_proto())]),
|
||||
OffsetDateTime::now_utc(),
|
||||
1,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
db.create_channel_message(
|
||||
channel,
|
||||
user_a,
|
||||
"bye @user_c",
|
||||
&mentions_to_proto(&[(4..11, user_c.to_proto())]),
|
||||
OffsetDateTime::now_utc(),
|
||||
2,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
db.create_channel_message(
|
||||
channel,
|
||||
user_a,
|
||||
"umm",
|
||||
&mentions_to_proto(&[]),
|
||||
OffsetDateTime::now_utc(),
|
||||
3,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
db.create_channel_message(
|
||||
channel,
|
||||
user_a,
|
||||
"@user_b, stop.",
|
||||
&mentions_to_proto(&[(0..7, user_b.to_proto())]),
|
||||
OffsetDateTime::now_utc(),
|
||||
4,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let messages = db
|
||||
.get_channel_messages(channel, user_b, 5, None)
|
||||
.await
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|m| (m.body, m.mentions))
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
&messages,
|
||||
&[
|
||||
(
|
||||
"hi @user_b and @user_c".into(),
|
||||
mentions_to_proto(&[(3..10, user_b.to_proto()), (15..22, user_c.to_proto())]),
|
||||
),
|
||||
(
|
||||
"bye @user_c".into(),
|
||||
mentions_to_proto(&[(4..11, user_c.to_proto())]),
|
||||
),
|
||||
("umm".into(), mentions_to_proto(&[]),),
|
||||
(
|
||||
"@user_b, stop.".into(),
|
||||
mentions_to_proto(&[(0..7, user_b.to_proto())]),
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user