AppFlowy-Cloud/tests/collab/single_device_edit.rs

529 lines
13 KiB
Rust

use crate::collab::util::{generate_random_string, make_big_collab_doc_state};
use assert_json_diff::assert_json_eq;
use client_api_test_util::*;
use collab_entity::CollabType;
use database_entity::dto::AFAccessLevel;
use serde_json::json;
use std::time::Duration;
use tokio::time::sleep;
use uuid::Uuid;
#[tokio::test]
async fn collab_write_small_chunk_of_data_test() {
let collab_type = CollabType::Document;
let mut test_client = TestClient::new_user().await;
let workspace_id = test_client.workspace_id().await;
let object_id = Uuid::new_v4().to_string();
// Calling the open_collab function directly will create the collab object in the plugin.
// The [CollabStoragePlugin] plugin try to get the collab object from the database, but it doesn't exist.
// So the plugin will create the collab object.
test_client
.open_collab(&workspace_id, &object_id, collab_type.clone())
.await;
// Edit the collab
for i in 0..=5 {
test_client
.collab_by_object_id
.get_mut(&object_id)
.unwrap()
.collab
.lock()
.insert(&i.to_string(), i.to_string());
}
test_client.wait_object_sync_complete(&object_id).await;
test_client.disconnect().await;
assert_server_collab(
&workspace_id,
&mut test_client.api_client,
&object_id,
&collab_type,
10,
json!( {
"0": "0",
"1": "1",
"2": "2",
"3": "3",
"4": "4",
"5": "5",
}),
)
.await;
}
#[tokio::test]
async fn collab_write_big_chunk_of_data_test() {
let collab_type = CollabType::Document;
let mut test_client = TestClient::new_user().await;
let workspace_id = test_client.workspace_id().await;
let object_id = Uuid::new_v4().to_string();
test_client
.open_collab(&workspace_id, &object_id, collab_type.clone())
.await;
let s = generate_random_string(10000);
test_client
.collab_by_object_id
.get_mut(&object_id)
.unwrap()
.collab
.lock()
.insert("big_text", s.clone());
test_client.wait_object_sync_complete(&object_id).await;
assert_server_collab(
&workspace_id,
&mut test_client.api_client,
&object_id,
&collab_type,
10,
json!({
"big_text": s
}),
)
.await;
}
#[tokio::test]
async fn write_big_chunk_data_init_sync_test() {
let mut test_client = TestClient::new_user().await;
let workspace_id = test_client.workspace_id().await;
let object_id = Uuid::new_v4().to_string();
let big_text = generate_random_string(1024 * 1024 * 2);
let collab_type = CollabType::Document;
let doc_state = make_big_collab_doc_state(&object_id, "big_text", big_text.clone());
// the big doc_state will force the init_sync using the http request.
// It will trigger the POST_REALTIME_MESSAGE_STREAM_HANDLER to handle the request.
test_client
.open_collab_with_doc_state(&workspace_id, &object_id, collab_type.clone(), doc_state)
.await;
test_client.wait_object_sync_complete(&object_id).await;
assert_server_collab(
&workspace_id,
&mut test_client.api_client,
&object_id,
&collab_type,
10,
json!({
"big_text": big_text
}),
)
.await;
}
#[tokio::test]
async fn realtime_write_single_collab_test() {
let collab_type = CollabType::Document;
let mut test_client = TestClient::new_user().await;
let workspace_id = test_client.workspace_id().await;
let object_id = test_client
.create_and_edit_collab(&workspace_id, collab_type.clone())
.await;
test_client
.open_collab(&workspace_id, &object_id, collab_type.clone())
.await;
// Edit the collab
for i in 0..=5 {
test_client
.collab_by_object_id
.get_mut(&object_id)
.unwrap()
.collab
.lock()
.insert(&i.to_string(), i.to_string());
}
let expected_json = json!( {
"0": "0",
"1": "1",
"2": "2",
"3": "3",
"4": "4",
"5": "5",
});
test_client.wait_object_sync_complete(&object_id).await;
assert_server_collab(
&workspace_id,
&mut test_client.api_client,
&object_id,
&collab_type,
10,
expected_json,
)
.await;
}
#[tokio::test]
async fn realtime_write_multiple_collab_test() {
let mut test_client = TestClient::new_user().await;
let workspace_id = test_client.workspace_id().await;
let mut object_ids = vec![];
for _ in 0..5 {
let collab_type = CollabType::Document;
let object_id = test_client
.create_and_edit_collab(&workspace_id, collab_type.clone())
.await;
test_client
.open_collab(&workspace_id, &object_id, collab_type.clone())
.await;
for i in 0..=5 {
test_client
.collab_by_object_id
.get_mut(&object_id)
.unwrap()
.collab
.lock()
.insert(&i.to_string(), i.to_string());
}
test_client.wait_object_sync_complete(&object_id).await;
object_ids.push(object_id);
}
// Wait for the messages to be sent
for object_id in object_ids {
assert_server_collab(
&workspace_id,
&mut test_client.api_client,
&object_id,
&CollabType::Document,
10,
json!( {
"0": "0",
"1": "1",
"2": "2",
"3": "3",
"4": "4",
"5": "5",
}),
)
.await;
}
}
//
#[tokio::test]
async fn user_with_duplicate_devices_connect_edit_test() {
let collab_type = CollabType::Document;
let mut old_client = TestClient::new_user().await;
let workspace_id = old_client.workspace_id().await;
let object_id = old_client
.create_and_edit_collab(&workspace_id, collab_type.clone())
.await;
old_client
.collab_by_object_id
.get_mut(&object_id)
.unwrap()
.collab
.lock()
.insert("1", "a");
old_client
.collab_by_object_id
.get_mut(&object_id)
.unwrap()
.collab
.lock()
.insert("3", "c");
old_client.wait_object_sync_complete(&object_id).await;
// The new_client will receive the old_client's edit
// The doc will be json!({
// "1": "a",
// "3": "c"
// })
let mut new_client =
TestClient::new_with_device_id(&old_client.device_id, old_client.user.clone(), true).await;
new_client
.open_collab(&workspace_id, &object_id, collab_type.clone())
.await;
new_client
.collab_by_object_id
.get_mut(&object_id)
.unwrap()
.collab
.lock()
.insert("2", "b");
new_client.wait_object_sync_complete(&object_id).await;
// Old client shouldn't receive the new client's edit
assert_client_collab_include_value(
&mut old_client,
&object_id,
json!({
"1": "a",
"3": "c"
}),
)
.await;
assert_client_collab_include_value(
&mut new_client,
&object_id,
json!({
"1": "a",
"3": "c",
"2": "b"
}),
)
.await;
assert_server_collab(
&workspace_id,
&mut new_client.api_client,
&object_id,
&collab_type,
10,
json!({
"1": "a",
"2": "b",
"3": "c"
}),
)
.await;
}
#[tokio::test]
async fn two_direction_peer_sync_test() {
let collab_type = CollabType::Document;
let mut client_1 = 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;
let mut client_2 = TestClient::new_user().await;
// Before the client_2 want to edit the collab object, it needs to become a member of the collab
// Otherwise, the server will reject the edit request
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_1
.collab_by_object_id
.get_mut(&object_id)
.unwrap()
.collab
.lock()
.insert("name", "AppFlowy");
client_1.wait_object_sync_complete(&object_id).await;
client_2
.collab_by_object_id
.get_mut(&object_id)
.unwrap()
.collab
.lock()
.insert("support platform", "macOS, Windows, Linux, iOS, Android");
client_2.wait_object_sync_complete(&object_id).await;
let expected_json = json!({
"name": "AppFlowy",
"support platform": "macOS, Windows, Linux, iOS, Android"
});
assert_client_collab_include_value(&mut client_1, &object_id, expected_json.clone()).await;
assert_client_collab_include_value(&mut client_2, &object_id, expected_json.clone()).await;
}
#[tokio::test]
async fn multiple_collab_edit_test() {
let collab_type = CollabType::Document;
let mut client_1 = TestClient::new_user().await;
let workspace_id_1 = client_1.workspace_id().await;
let object_id_1 = client_1
.create_and_edit_collab(&workspace_id_1, collab_type.clone())
.await;
client_1
.open_collab(&workspace_id_1, &object_id_1, collab_type.clone())
.await;
let mut client_2 = TestClient::new_user().await;
let workspace_id_2 = client_2.workspace_id().await;
let object_id_2 = client_2
.create_and_edit_collab(&workspace_id_2, collab_type.clone())
.await;
client_2
.open_collab(&workspace_id_2, &object_id_2, collab_type.clone())
.await;
client_1
.collab_by_object_id
.get_mut(&object_id_1)
.unwrap()
.collab
.lock()
.insert("title", "I am client 1");
client_1.wait_object_sync_complete(&object_id_1).await;
client_2
.collab_by_object_id
.get_mut(&object_id_2)
.unwrap()
.collab
.lock()
.insert("title", "I am client 2");
client_2.wait_object_sync_complete(&object_id_2).await;
assert_server_collab(
&workspace_id_1,
&mut client_1.api_client,
&object_id_1,
&collab_type,
10,
json!( {
"title": "I am client 1"
}),
)
.await;
assert_server_collab(
&workspace_id_2,
&mut client_2.api_client,
&object_id_2,
&collab_type,
10,
json!( {
"title": "I am client 2"
}),
)
.await;
}
#[tokio::test]
async fn simulate_multiple_user_edit_collab_test() {
let mut tasks = Vec::new();
for _i in 0..20 {
let task = tokio::spawn(async move {
let mut new_user = TestClient::new_user().await;
let collab_type = CollabType::Document;
let workspace_id = new_user.workspace_id().await;
let object_id = Uuid::new_v4().to_string();
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("string", random_str.clone());
let expected_json = json!({
"string": random_str
});
new_user.wait_object_sync_complete(&object_id).await;
(
expected_json,
new_user
.collab_by_object_id
.get(&object_id)
.unwrap()
.collab
.to_json_value(),
)
});
tasks.push(task);
}
let results = futures::future::join_all(tasks).await;
for result in results {
let (expected_json, json) = result.unwrap();
assert_json_eq!(expected_json, json);
}
}
#[tokio::test]
async fn post_realtime_message_test() {
let mut tasks = Vec::new();
let big_text = generate_random_string(1024 * 1024 * 3);
for i in 0..20 {
let cloned_text = big_text.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;
let object_id = Uuid::new_v4().to_string();
let workspace_id = new_user.workspace_id().await;
let doc_state = make_big_collab_doc_state(&object_id, "text", cloned_text);
// the big doc_state will force the init_sync using the http request.
// It will trigger the POST_REALTIME_MESSAGE_STREAM_HANDLER to handle the request.
new_user
.open_collab_with_doc_state(&workspace_id, &object_id, CollabType::Document, doc_state)
.await;
new_user.wait_object_sync_complete(&object_id).await;
(new_user, object_id, workspace_id)
});
tasks.push(task);
}
let results = futures::future::join_all(tasks).await;
for result in results.into_iter() {
let (mut client, object_id, workspace_id) = result.unwrap();
assert_server_collab(
&workspace_id,
&mut client.api_client,
&object_id,
&CollabType::Document,
10,
json!({
"text": big_text
}),
)
.await;
drop(client);
}
}
#[tokio::test]
async fn collab_flush_test() {
let mut new_user = TestClient::new_user().await;
let object_id = Uuid::new_v4().to_string();
let workspace_id = new_user.workspace_id().await;
new_user
.open_collab(&workspace_id, &object_id, CollabType::Document)
.await;
// the default flush_per_update is 100 that defined in [WriteConfig]
// so we need to write 200 times to trigger the flush
for i in 0..200 {
new_user
.collab_by_object_id
.get_mut(&object_id)
.unwrap()
.collab
.lock()
.insert(&i.to_string(), i.to_string());
sleep(Duration::from_millis(300)).await;
}
// TODO(nathan): assert the collab content in disk
}