539 lines
14 KiB
Rust
539 lines
14 KiB
Rust
use crate::collab::util::generate_random_string;
|
|
use assert_json_diff::{assert_json_eq, assert_json_include};
|
|
use client_api_test_util::{
|
|
assert_client_collab, assert_client_collab_include_value, assert_server_collab, TestClient,
|
|
};
|
|
use collab_entity::CollabType;
|
|
use database_entity::dto::{AFAccessLevel, AFRole};
|
|
use serde_json::json;
|
|
use std::collections::HashMap;
|
|
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
use tokio::time::sleep;
|
|
use uuid::Uuid;
|
|
|
|
#[tokio::test]
|
|
async fn recv_updates_without_permission_test() {
|
|
let collab_type = CollabType::Document;
|
|
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
|
|
.collab_by_object_id
|
|
.get_mut(&object_id)
|
|
.unwrap()
|
|
.collab
|
|
.lock()
|
|
.insert("name", "AppFlowy");
|
|
client_1.wait_object_sync_complete(&object_id).await;
|
|
assert_client_collab(&mut client_2, &object_id, "name", json!({}), 3).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn recv_remote_updates_with_readonly_permission_test() {
|
|
let collab_type = CollabType::Document;
|
|
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_client_as_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
|
|
.collab_by_object_id
|
|
.get_mut(&object_id)
|
|
.unwrap()
|
|
.collab
|
|
.lock()
|
|
.insert("name", "AppFlowy");
|
|
client_1.wait_object_sync_complete(&object_id).await;
|
|
|
|
let expected = json!({
|
|
"name": "AppFlowy"
|
|
});
|
|
assert_client_collab(&mut client_2, &object_id, "name", expected.clone(), 10).await;
|
|
assert_server_collab(
|
|
&workspace_id,
|
|
&mut client_1.api_client,
|
|
&object_id,
|
|
&collab_type,
|
|
10,
|
|
expected,
|
|
)
|
|
.await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn init_sync_with_readonly_permission_test() {
|
|
let collab_type = CollabType::Document;
|
|
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
|
|
.collab_by_object_id
|
|
.get_mut(&object_id)
|
|
.unwrap()
|
|
.collab
|
|
.lock()
|
|
.insert("name", "AppFlowy");
|
|
client_1.wait_object_sync_complete(&object_id).await;
|
|
|
|
//
|
|
let expected = json!({
|
|
"name": "AppFlowy"
|
|
});
|
|
assert_server_collab(
|
|
&workspace_id,
|
|
&mut client_1.api_client,
|
|
&object_id,
|
|
&collab_type,
|
|
10,
|
|
expected.clone(),
|
|
)
|
|
.await;
|
|
|
|
// 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_client_as_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;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn edit_collab_with_readonly_permission_test() {
|
|
let collab_type = CollabType::Document;
|
|
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_client_as_collab_member(
|
|
&workspace_id,
|
|
&object_id,
|
|
&client_2,
|
|
AFAccessLevel::ReadOnly,
|
|
)
|
|
.await;
|
|
|
|
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
|
|
.collab_by_object_id
|
|
.get_mut(&object_id)
|
|
.unwrap()
|
|
.collab
|
|
.lock()
|
|
.insert("name", "AppFlowy");
|
|
assert_client_collab_include_value(
|
|
&mut client_2,
|
|
&object_id,
|
|
json!({
|
|
"name": "AppFlowy"
|
|
}),
|
|
)
|
|
.await;
|
|
|
|
assert_server_collab(
|
|
&workspace_id,
|
|
&mut client_1.api_client,
|
|
&object_id,
|
|
&collab_type,
|
|
5,
|
|
json!({}),
|
|
)
|
|
.await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn edit_collab_with_read_and_write_permission_test() {
|
|
let collab_type = CollabType::Document;
|
|
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_client_as_collab_member(
|
|
&workspace_id,
|
|
&object_id,
|
|
&client_2,
|
|
AFAccessLevel::ReadAndWrite,
|
|
)
|
|
.await;
|
|
|
|
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
|
|
.collab_by_object_id
|
|
.get_mut(&object_id)
|
|
.unwrap()
|
|
.collab
|
|
.lock()
|
|
.insert("name", "AppFlowy");
|
|
|
|
let expected = json!({
|
|
"name": "AppFlowy"
|
|
});
|
|
assert_client_collab_include_value(&mut client_2, &object_id, expected.clone()).await;
|
|
|
|
assert_server_collab(
|
|
&workspace_id,
|
|
&mut client_1.api_client,
|
|
&object_id,
|
|
&collab_type,
|
|
5,
|
|
expected,
|
|
)
|
|
.await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn edit_collab_with_full_access_permission_test() {
|
|
let collab_type = CollabType::Document;
|
|
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_client_as_collab_member(
|
|
&workspace_id,
|
|
&object_id,
|
|
&client_2,
|
|
AFAccessLevel::FullAccess,
|
|
)
|
|
.await;
|
|
|
|
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
|
|
.collab_by_object_id
|
|
.get_mut(&object_id)
|
|
.unwrap()
|
|
.collab
|
|
.lock()
|
|
.insert("name", "AppFlowy");
|
|
|
|
let expected = json!({
|
|
"name": "AppFlowy"
|
|
});
|
|
assert_client_collab(&mut client_2, &object_id, "name", expected.clone(), 5).await;
|
|
|
|
assert_server_collab(
|
|
&workspace_id,
|
|
&mut client_1.api_client,
|
|
&object_id,
|
|
&collab_type,
|
|
5,
|
|
expected,
|
|
)
|
|
.await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn edit_collab_with_full_access_then_readonly_permission() {
|
|
let collab_type = CollabType::Document;
|
|
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_client_as_collab_member(
|
|
&workspace_id,
|
|
&object_id,
|
|
&client_2,
|
|
AFAccessLevel::FullAccess,
|
|
)
|
|
.await;
|
|
|
|
// 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
|
|
.collab_by_object_id
|
|
.get_mut(&object_id)
|
|
.unwrap()
|
|
.collab
|
|
.lock()
|
|
.insert("title", "hello world");
|
|
client_2.wait_object_sync_complete(&object_id).await;
|
|
}
|
|
|
|
// update the permission from full access to readonly, then the server will reject the subsequent
|
|
// updates generated by client 2
|
|
{
|
|
client_1
|
|
.update_collab_member_access_level(
|
|
&workspace_id,
|
|
&object_id,
|
|
&client_2,
|
|
AFAccessLevel::ReadOnly,
|
|
)
|
|
.await;
|
|
client_2
|
|
.collab_by_object_id
|
|
.get_mut(&object_id)
|
|
.unwrap()
|
|
.collab
|
|
.lock()
|
|
.insert("subtitle", "Writing Rust, fun");
|
|
}
|
|
|
|
assert_client_collab_include_value(
|
|
&mut client_2,
|
|
&object_id,
|
|
json!({
|
|
"title": "hello world",
|
|
"subtitle": "Writing Rust, fun"
|
|
}),
|
|
)
|
|
.await;
|
|
assert_server_collab(
|
|
&workspace_id,
|
|
&mut client_1.api_client,
|
|
&object_id,
|
|
&collab_type,
|
|
5,
|
|
json!({
|
|
"title": "hello world"
|
|
}),
|
|
)
|
|
.await;
|
|
}
|
|
|
|
#[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::Document;
|
|
let workspace_id = owner.workspace_id().await;
|
|
owner
|
|
.open_collab(&workspace_id, &object_id, collab_type.clone())
|
|
.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..10 {
|
|
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 % 3)).await;
|
|
|
|
owner
|
|
.add_workspace_member(&workspace_id, &new_user, AFRole::Member)
|
|
.await;
|
|
owner
|
|
.add_client_as_collab_member(
|
|
&workspace_id,
|
|
&object_id,
|
|
&new_user,
|
|
AFAccessLevel::ReadAndWrite,
|
|
)
|
|
.await;
|
|
|
|
new_user
|
|
.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_user
|
|
.collab_by_object_id
|
|
.get_mut(&object_id)
|
|
.unwrap()
|
|
.collab
|
|
.lock()
|
|
.insert(&i.to_string(), random_str.clone());
|
|
new_user.wait_object_sync_complete(&object_id).await;
|
|
(random_str, new_user)
|
|
});
|
|
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_include!(
|
|
actual: json!(expected_json),
|
|
expected: arc_owner
|
|
.collab_by_object_id
|
|
.get(&object_id)
|
|
.unwrap()
|
|
.collab
|
|
.to_json_value()
|
|
);
|
|
|
|
for client in clients {
|
|
assert_json_include!(
|
|
expected: json!(expected_json),
|
|
actual: client
|
|
.collab_by_object_id
|
|
.get(&object_id)
|
|
.unwrap()
|
|
.collab
|
|
.to_json_value()
|
|
);
|
|
}
|
|
}
|
|
|
|
#[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 object_id = Uuid::new_v4().to_string();
|
|
let collab_type = CollabType::Document;
|
|
let workspace_id = owner.workspace_id().await;
|
|
owner
|
|
.open_collab(&workspace_id, &object_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
|
|
.add_client_as_collab_member(
|
|
&workspace_id,
|
|
&object_id,
|
|
&new_user,
|
|
AFAccessLevel::ReadOnly,
|
|
)
|
|
.await;
|
|
|
|
new_user
|
|
.open_collab(&workspace_id, &object_id, collab_type.clone())
|
|
.await;
|
|
|
|
let random_str = generate_random_string(200);
|
|
new_user
|
|
.collab_by_object_id
|
|
.get_mut(&object_id)
|
|
.unwrap()
|
|
.collab
|
|
.lock()
|
|
.insert(&i.to_string(), random_str.clone());
|
|
|
|
// 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();
|
|
assert_json_eq!(
|
|
json!({index.to_string(): s}),
|
|
client
|
|
.collab_by_object_id
|
|
.get(&object_id)
|
|
.unwrap()
|
|
.collab
|
|
.to_json_value(),
|
|
);
|
|
}
|
|
// all the clients should have the same collab object
|
|
assert_json_eq!(
|
|
json!({}),
|
|
arc_owner
|
|
.collab_by_object_id
|
|
.get(&object_id)
|
|
.unwrap()
|
|
.collab
|
|
.to_json_value(),
|
|
);
|
|
}
|