AppFlowy-Cloud/tests/collab/permission_test.rs

507 lines
14 KiB
Rust

use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use assert_json_diff::{assert_json_eq, assert_json_include};
use collab_entity::CollabType;
use serde_json::json;
use tokio::time::sleep;
use uuid::Uuid;
use client_api_test::{
assert_client_collab_include_value, assert_client_collab_within_secs, assert_server_collab,
TestClient,
};
use database_entity::dto::AFRole;
use crate::collab::util::generate_random_string;
#[tokio::test]
async fn recv_updates_without_permission_test() {
let collab_type = CollabType::Unknown;
let mut client_1 = TestClient::new_user().await;
let mut client_2 = TestClient::new_user().await;
let workspace_id = client_1.workspace_id().await;
let object_id = client_1
.create_and_edit_collab(&workspace_id, collab_type.clone())
.await;
client_2
.open_collab(&workspace_id, &object_id, collab_type.clone())
.await;
// Edit the collab from client 1 and then the server will broadcast to client 2. But the client 2
// is not the member of the collab, so the client 2 will not receive the update.
client_1.insert_into(&object_id, "name", "AppFlowy").await;
client_1
.wait_object_sync_complete(&object_id)
.await
.unwrap();
assert_client_collab_within_secs(&mut client_2, &object_id, "name", json!({}), 60).await;
}
// #[tokio::test]
// async fn recv_remote_updates_with_readonly_permission_test() {
// let collab_type = CollabType::Unknown;
// let mut client_1 = TestClient::new_user().await;
// let mut client_2 = TestClient::new_user().await;
//
// let workspace_id = client_1.workspace_id().await;
// let object_id = client_1
// .create_and_edit_collab(&workspace_id, collab_type.clone())
// .await;
//
// // Add client 2 as the member of the collab then the client 2 will receive the update.
// client_1
// .add_collab_member(
// &workspace_id,
// &object_id,
// &client_2,
// AFAccessLevel::ReadOnly,
// )
// .await;
//
// client_2
// .open_collab(&workspace_id, &object_id, collab_type.clone())
// .await;
//
// // Edit the collab from client 1 and then the server will broadcast to client 2
// client_1
// .collabs
// .get_mut(&object_id)
// .unwrap()
// .collab
// .write().await
// .insert("name", "AppFlowy");
// client_1
// .wait_object_sync_complete(&object_id)
// .await
// .unwrap();
//
// let expected = json!({
// "name": "AppFlowy"
// });
// assert_client_collab_within_secs(&mut client_2, &object_id, "name", expected.clone(), 60).await;
// assert_server_collab(
// &workspace_id,
// &mut client_1.api_client,
// &object_id,
// &collab_type,
// 10,
// expected,
// )
// .await
// .unwrap();
// }
// #[tokio::test]
// async fn init_sync_with_readonly_permission_test() {
// let collab_type = CollabType::Unknown;
// let mut client_1 = TestClient::new_user().await;
// let mut client_2 = TestClient::new_user().await;
//
// let workspace_id = client_1.workspace_id().await;
// let object_id = client_1
// .create_and_edit_collab(&workspace_id, collab_type.clone())
// .await;
// client_1
// .collabs
// .get_mut(&object_id)
// .unwrap()
// .collab
// .write().await
// .insert("name", "AppFlowy");
// client_1
// .wait_object_sync_complete(&object_id)
// .await
// .unwrap();
// sleep(Duration::from_secs(2)).await;
//
// //
// let expected = json!({
// "name": "AppFlowy"
// });
// assert_server_collab(
// &workspace_id,
// &mut client_1.api_client,
// &object_id,
// &collab_type,
// 10,
// expected.clone(),
// )
// .await
// .unwrap();
//
// // Add client 2 as the member of the collab with readonly permission.
// // client 2 can pull the latest updates via the init sync. But it's not allowed to send local changes.
// client_1
// .add_collab_member(
// &workspace_id,
// &object_id,
// &client_2,
// AFAccessLevel::ReadOnly,
// )
// .await;
// client_2
// .open_collab(&workspace_id, &object_id, collab_type.clone())
// .await;
// assert_client_collab_include_value(&mut client_2, &object_id, expected)
// .await
// .unwrap();
// }
#[tokio::test]
async fn edit_collab_with_readonly_permission_test() {
let collab_type = CollabType::Unknown;
let mut client_1 = TestClient::new_user().await;
let mut client_2 = TestClient::new_user().await;
let workspace_id = client_1.workspace_id().await;
let object_id = client_1
.create_and_edit_collab(&workspace_id, collab_type.clone())
.await;
// Add client 2 as the member of the collab then the client 2 will receive the update.
client_1
.invite_and_accepted_workspace_member(&workspace_id, &client_2, AFRole::Guest)
.await
.unwrap();
client_2
.open_collab(&workspace_id, &object_id, collab_type.clone())
.await;
// client 2 edit the collab and then the server will reject the update which mean the
// collab in the server will not be updated.
client_2.insert_into(&object_id, "name", "AppFlowy").await;
assert_client_collab_include_value(
&mut client_2,
&object_id,
json!({
"name": "AppFlowy"
}),
)
.await
.unwrap();
assert_server_collab(
&workspace_id,
&mut client_1.api_client,
&object_id,
&collab_type,
5,
json!({}),
)
.await
.unwrap();
}
#[tokio::test]
async fn edit_collab_with_read_and_write_permission_test() {
let collab_type = CollabType::Unknown;
let mut client_1 = TestClient::new_user().await;
let mut client_2 = TestClient::new_user().await;
let workspace_id = client_1.workspace_id().await;
let object_id = client_1
.create_and_edit_collab(&workspace_id, collab_type.clone())
.await;
// Add client 2 as the member of the collab then the client 2 will receive the update.
client_1
.invite_and_accepted_workspace_member(&workspace_id, &client_2, AFRole::Member)
.await
.unwrap();
client_2
.open_collab(&workspace_id, &object_id, collab_type.clone())
.await;
// client 2 edit the collab and then the server will broadcast the update
client_2.insert_into(&object_id, "name", "AppFlowy").await;
client_2
.wait_object_sync_complete(&object_id)
.await
.unwrap();
let expected = json!({
"name": "AppFlowy"
});
assert_client_collab_include_value(&mut client_2, &object_id, expected.clone())
.await
.unwrap();
assert_server_collab(
&workspace_id,
&mut client_1.api_client,
&object_id,
&collab_type,
5,
expected,
)
.await
.unwrap();
}
#[tokio::test]
async fn edit_collab_with_full_access_permission_test() {
let collab_type = CollabType::Unknown;
let mut client_1 = TestClient::new_user().await;
let mut client_2 = TestClient::new_user().await;
let workspace_id = client_1.workspace_id().await;
let object_id = client_1
.create_and_edit_collab(&workspace_id, collab_type.clone())
.await;
// Add client 2 as the member of the collab then the client 2 will receive the update.
client_1
.invite_and_accepted_workspace_member(&workspace_id, &client_2, AFRole::Member)
.await
.unwrap();
client_2
.open_collab(&workspace_id, &object_id, collab_type.clone())
.await;
// client 2 edit the collab and then the server will broadcast the update
client_2.insert_into(&object_id, "name", "AppFlowy").await;
let expected = json!({
"name": "AppFlowy"
});
client_2
.wait_object_sync_complete(&object_id)
.await
.unwrap();
assert_client_collab_within_secs(&mut client_2, &object_id, "name", expected.clone(), 30).await;
assert_server_collab(
&workspace_id,
&mut client_1.api_client,
&object_id,
&collab_type,
5,
expected,
)
.await
.unwrap();
}
#[tokio::test]
async fn edit_collab_with_full_access_then_readonly_permission() {
let collab_type = CollabType::Unknown;
let mut client_1 = TestClient::new_user().await;
let mut client_2 = TestClient::new_user().await;
let workspace_id = client_1.workspace_id().await;
let object_id = client_1
.create_and_edit_collab(&workspace_id, collab_type.clone())
.await;
// Add client 2 as the member of the collab then the client 2 will receive the update.
client_1
.invite_and_accepted_workspace_member(&workspace_id, &client_2, AFRole::Member)
.await
.unwrap();
// client 2 edit the collab and then the server will broadcast the update
{
client_2
.open_collab(&workspace_id, &object_id, collab_type.clone())
.await;
client_2
.insert_into(&object_id, "title", "hello world")
.await;
client_2
.wait_object_sync_complete(&object_id)
.await
.unwrap();
}
// update the permission from full access to readonly, then the server will reject the subsequent
// updates generated by client 2
{
client_1
.try_update_workspace_member(&workspace_id, &client_2, AFRole::Guest)
.await
.unwrap();
client_2
.insert_into(&object_id, "subtitle", "Writing Rust, fun")
.await;
}
assert_client_collab_include_value(
&mut client_2,
&object_id,
json!({
"title": "hello world",
"subtitle": "Writing Rust, fun"
}),
)
.await
.unwrap();
assert_server_collab(
&workspace_id,
&mut client_1.api_client,
&object_id,
&collab_type,
5,
json!({
"title": "hello world"
}),
)
.await
.unwrap();
}
#[tokio::test]
async fn multiple_user_with_read_and_write_permission_edit_same_collab_test() {
let mut tasks = Vec::new();
let mut owner = TestClient::new_user().await;
let object_id = Uuid::new_v4().to_string();
let collab_type = CollabType::Unknown;
let workspace_id = owner.workspace_id().await;
owner
.create_and_edit_collab_with_data(&object_id, &workspace_id, collab_type.clone(), None)
.await;
let arc_owner = Arc::new(owner);
// simulate multiple users edit the same collab. All of them have read and write permission
for i in 0..3 {
let owner = arc_owner.clone();
let object_id = object_id.clone();
let collab_type = collab_type.clone();
let workspace_id = workspace_id.clone();
let task = tokio::spawn(async move {
let mut new_member = TestClient::new_user().await;
// sleep 2 secs to make sure it do not trigger register user too fast in gotrue
sleep(Duration::from_secs(i % 3)).await;
owner
.invite_and_accepted_workspace_member(&workspace_id, &new_member, AFRole::Member)
.await
.unwrap();
new_member
.open_collab(&workspace_id, &object_id, collab_type.clone())
.await;
// generate random string and insert it to the collab
let random_str = generate_random_string(200);
new_member
.insert_into(&object_id, &i.to_string(), random_str.clone())
.await;
new_member
.wait_object_sync_complete(&object_id)
.await
.unwrap();
(random_str, new_member)
});
tasks.push(task);
}
let results = futures::future::join_all(tasks).await;
let mut expected_json = HashMap::new();
let mut clients = vec![];
for (index, result) in results.into_iter().enumerate() {
let (s, client) = result.unwrap();
clients.push(client);
expected_json.insert(index.to_string(), s);
}
// wait 5 seconds to make sure all the server broadcast the updates to all the clients
sleep(Duration::from_secs(5)).await;
// all the clients should have the same collab object
assert_json_eq!(
json!(expected_json),
(*arc_owner
.collabs
.get(&object_id)
.unwrap()
.collab
.write()
.await)
.borrow()
.to_json_value()
);
for client in clients {
let expected = (*client.collabs.get(&object_id).unwrap().collab.read().await)
.borrow()
.to_json_value();
assert_json_include!(
actual: json!(expected_json),
expected: expected
);
}
}
#[tokio::test]
async fn multiple_user_with_read_only_permission_edit_same_collab_test() {
let mut tasks = Vec::new();
let mut owner = TestClient::new_user().await;
let collab_type = CollabType::Unknown;
let workspace_id = owner.workspace_id().await;
let object_id = owner
.create_and_edit_collab(&workspace_id, collab_type.clone())
.await;
let arc_owner = Arc::new(owner);
for i in 0..5 {
let owner = arc_owner.clone();
let object_id = object_id.clone();
let collab_type = collab_type.clone();
let workspace_id = workspace_id.clone();
let task = tokio::spawn(async move {
let mut new_user = TestClient::new_user().await;
// sleep 2 secs to make sure it do not trigger register user too fast in gotrue
sleep(Duration::from_secs(i % 2)).await;
owner
.invite_and_accepted_workspace_member(&workspace_id, &new_user, AFRole::Guest)
.await
.unwrap();
new_user
.open_collab(&workspace_id, &object_id, collab_type.clone())
.await;
let random_str = generate_random_string(200);
new_user
.insert_into(&object_id, &i.to_string(), random_str.clone())
.await;
// wait 3 seconds to let the client try to send the update to the server
// can't use want_object_sync_complete because the client do not have permission to send the update
sleep(Duration::from_secs(3)).await;
(random_str, new_user)
});
tasks.push(task);
}
let results = futures::future::join_all(tasks).await;
for (index, result) in results.into_iter().enumerate() {
let (s, client) = result.unwrap();
let value = (*client.collabs.get(&object_id).unwrap().collab.read().await)
.borrow()
.to_json_value();
assert_json_eq!(json!({index.to_string(): s}), value,);
}
// all the clients should have the same collab object
let expected = (*arc_owner
.collabs
.get(&object_id)
.unwrap()
.collab
.read()
.await)
.borrow()
.to_json_value();
assert_json_eq!(json!({}), expected);
}