Quellcode durchsuchen

tests: add supabase tests (#3084)

Nathan.fooo vor 1 Jahr
Ursprung
Commit
e68d3f1c71

+ 1 - 13
.github/workflows/rust_ci.yaml

@@ -61,16 +61,4 @@ jobs:
 
       - name: clippy rust-lib
         run: cargo clippy --features="rev-sqlite"
-        working-directory: frontend/rust-lib
-
-      - name: rustfmt shared-lib
-        run: cargo fmt --all -- --check
-        working-directory: shared-lib
-
-      - name: clippy shared-lib
-        run: cargo clippy -- -D warnings
-        working-directory: shared-lib
-
-      - name: Run shared-lib tests
-        working-directory: shared-lib
-        run: RUST_LOG=info cargo test --no-default-features
+        working-directory: frontend/rust-lib

+ 0 - 7
frontend/flowy-server-config/Cargo.lock

@@ -1,7 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 3
-
-[[package]]
-name = "flowy-server-config"
-version = "0.1.0"

+ 13 - 12
frontend/rust-lib/Cargo.lock

@@ -85,7 +85,7 @@ checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
 [[package]]
 name = "appflowy-integrate"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12811d#12811d26a96330f6c1acaa8815f1d8d61ca3aa61"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5783a5#5783a5ba6416125b669814d4089ed9afaf3469b5"
 dependencies = [
  "anyhow",
  "collab",
@@ -888,7 +888,7 @@ dependencies = [
 [[package]]
 name = "collab"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12811d#12811d26a96330f6c1acaa8815f1d8d61ca3aa61"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5783a5#5783a5ba6416125b669814d4089ed9afaf3469b5"
 dependencies = [
  "anyhow",
  "bytes",
@@ -906,7 +906,7 @@ dependencies = [
 [[package]]
 name = "collab-client-ws"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12811d#12811d26a96330f6c1acaa8815f1d8d61ca3aa61"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5783a5#5783a5ba6416125b669814d4089ed9afaf3469b5"
 dependencies = [
  "bytes",
  "collab-sync",
@@ -924,7 +924,7 @@ dependencies = [
 [[package]]
 name = "collab-database"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12811d#12811d26a96330f6c1acaa8815f1d8d61ca3aa61"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5783a5#5783a5ba6416125b669814d4089ed9afaf3469b5"
 dependencies = [
  "anyhow",
  "async-trait",
@@ -951,7 +951,7 @@ dependencies = [
 [[package]]
 name = "collab-derive"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12811d#12811d26a96330f6c1acaa8815f1d8d61ca3aa61"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5783a5#5783a5ba6416125b669814d4089ed9afaf3469b5"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -963,7 +963,7 @@ dependencies = [
 [[package]]
 name = "collab-document"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12811d#12811d26a96330f6c1acaa8815f1d8d61ca3aa61"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5783a5#5783a5ba6416125b669814d4089ed9afaf3469b5"
 dependencies = [
  "anyhow",
  "collab",
@@ -982,7 +982,7 @@ dependencies = [
 [[package]]
 name = "collab-folder"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12811d#12811d26a96330f6c1acaa8815f1d8d61ca3aa61"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5783a5#5783a5ba6416125b669814d4089ed9afaf3469b5"
 dependencies = [
  "anyhow",
  "chrono",
@@ -1002,7 +1002,7 @@ dependencies = [
 [[package]]
 name = "collab-persistence"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12811d#12811d26a96330f6c1acaa8815f1d8d61ca3aa61"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5783a5#5783a5ba6416125b669814d4089ed9afaf3469b5"
 dependencies = [
  "bincode",
  "chrono",
@@ -1022,7 +1022,7 @@ dependencies = [
 [[package]]
 name = "collab-plugins"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12811d#12811d26a96330f6c1acaa8815f1d8d61ca3aa61"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5783a5#5783a5ba6416125b669814d4089ed9afaf3469b5"
 dependencies = [
  "anyhow",
  "async-trait",
@@ -1052,7 +1052,7 @@ dependencies = [
 [[package]]
 name = "collab-sync"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12811d#12811d26a96330f6c1acaa8815f1d8d61ca3aa61"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5783a5#5783a5ba6416125b669814d4089ed9afaf3469b5"
 dependencies = [
  "bytes",
  "collab",
@@ -1834,6 +1834,7 @@ name = "flowy-server"
 version = "0.1.0"
 dependencies = [
  "anyhow",
+ "assert-json-diff",
  "bytes",
  "chrono",
  "collab",
@@ -4187,9 +4188,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.103"
+version = "1.0.104"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b"
+checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c"
 dependencies = [
  "itoa",
  "ryu",

+ 2 - 1
frontend/rust-lib/flowy-server/Cargo.toml

@@ -42,4 +42,5 @@ uuid = { version = "1.3.3", features = ["v4"] }
 tracing-subscriber = { version = "0.3.3", features = ["env-filter"] }
 dotenv = "0.15.0"
 yrs = "0.16.5"
-
+assert-json-diff = "2.0.2"
+serde_json = "1.0.104"

+ 193 - 13
frontend/rust-lib/flowy-server/tests/supabase_test/folder_test.rs

@@ -1,14 +1,19 @@
-use crate::supabase_test::util::{
-  collab_service, folder_service, get_supabase_config, sign_up_param, user_auth_service,
-};
+use assert_json_diff::assert_json_eq;
 use collab_plugins::cloud_storage::{CollabObject, CollabType};
-use flowy_user_deps::entities::SignUpResponse;
 use futures::future::join_all;
-use lib_infra::box_any::BoxAny;
-
+use serde_json::json;
 use tokio::task;
 use uuid::Uuid;
-use yrs::{Doc, Map, ReadTxn, StateVector, Transact};
+use yrs::types::ToJson;
+use yrs::updates::decoder::Decode;
+use yrs::{merge_updates_v1, Array, Doc, Map, MapPrelim, ReadTxn, StateVector, Transact, Update};
+
+use flowy_user_deps::entities::SignUpResponse;
+use lib_infra::box_any::BoxAny;
+
+use crate::supabase_test::util::{
+  collab_service, folder_service, get_supabase_config, sign_up_param, user_auth_service,
+};
 
 #[tokio::test]
 async fn supabase_create_workspace_test() {
@@ -70,7 +75,8 @@ async fn supabase_get_folder_test() {
     .unwrap();
   assert_eq!(updates.len(), 2);
 
-  // The init sync will try to merge the updates into one.
+  // The init sync will try to merge the updates into one. Spawn 5 tasks to simulate
+  // multiple clients trying to init sync at the same time.
   let mut handles = Vec::new();
   for _ in 0..5 {
     let cloned_collab_service = collab_service.clone();
@@ -84,13 +90,14 @@ async fn supabase_get_folder_test() {
     handles.push(handle);
   }
   let _results: Vec<_> = join_all(handles).await;
-  let remote_update = folder_service
+  // after the init sync, the updates should be merged into one.
+  let updates: Vec<Vec<u8>> = folder_service
     .get_folder_updates(&user.latest_workspace.id, user.user_id)
     .await
-    .unwrap()
-    .first()
-    .unwrap()
-    .clone();
+    .unwrap();
+  assert_eq!(updates.len(), 1);
+  // Other the init sync, try to get the updates from the server.
+  let remote_update = updates.first().unwrap().clone();
   let expected_update = doc
     .transact_mut()
     .encode_state_as_update_v1(&StateVector::default());
@@ -98,3 +105,176 @@ async fn supabase_get_folder_test() {
   // check the update is the same as local document update.
   assert_eq!(remote_update, expected_update);
 }
+
+/// This async test function checks the behavior of updates duplication in Supabase.
+/// It creates a new user and simulates two updates to the user's workspace with different values.
+/// Then, it merges these updates and sends an initial synchronization request to test duplication handling.
+/// Finally, it asserts that the duplicated updates don't affect the overall data consistency in Supabase.
+#[tokio::test]
+async fn supabase_duplicate_updates_test() {
+  if get_supabase_config().is_none() {
+    return;
+  }
+
+  let folder_service = folder_service();
+  let user_service = user_auth_service();
+  let collab_service = collab_service();
+  let uuid = Uuid::new_v4().to_string();
+  let params = sign_up_param(uuid);
+  let user: SignUpResponse = user_service.sign_up(BoxAny::new(params)).await.unwrap();
+
+  let collab_object = CollabObject {
+    id: user.latest_workspace.id.clone(),
+    uid: user.user_id,
+    ty: CollabType::Folder,
+    meta: Default::default(),
+  }
+  .with_workspace_id(user.latest_workspace.id.clone());
+  let doc = Doc::with_client_id(1);
+  let map = { doc.get_or_insert_map("map") };
+  let mut duplicated_updates = vec![];
+  {
+    let mut txn = doc.transact_mut();
+    map.insert(&mut txn, "1", "a");
+    let update = txn.encode_update_v1();
+    duplicated_updates.push(update.clone());
+    collab_service
+      .send_update(&collab_object, 0, update)
+      .await
+      .unwrap();
+  };
+  {
+    let mut txn = doc.transact_mut();
+    map.insert(&mut txn, "2", "b");
+    let update = txn.encode_update_v1();
+    duplicated_updates.push(update.clone());
+    collab_service
+      .send_update(&collab_object, 1, update)
+      .await
+      .unwrap();
+  };
+  // send init sync
+  collab_service
+    .send_init_sync(&collab_object, 3, vec![])
+    .await
+    .unwrap();
+  let first_init_sync_update: Vec<u8> = folder_service
+    .get_folder_updates(&user.latest_workspace.id, user.user_id)
+    .await
+    .unwrap()
+    .first()
+    .unwrap()
+    .clone();
+
+  // simulate the duplicated updates.
+  let merged_update = merge_updates_v1(
+    &duplicated_updates
+      .iter()
+      .map(|update| update.as_ref())
+      .collect::<Vec<&[u8]>>(),
+  )
+  .unwrap();
+  collab_service
+    .send_init_sync(&collab_object, 4, merged_update)
+    .await
+    .unwrap();
+  let second_init_sync_update: Vec<u8> = folder_service
+    .get_folder_updates(&user.latest_workspace.id, user.user_id)
+    .await
+    .unwrap()
+    .first()
+    .unwrap()
+    .clone();
+  let doc_2 = Doc::new();
+  assert_eq!(first_init_sync_update.len(), second_init_sync_update.len());
+  let map = { doc_2.get_or_insert_map("map") };
+  {
+    let mut txn = doc_2.transact_mut();
+    let update = Update::decode_v1(&second_init_sync_update).unwrap();
+    txn.apply_update(update);
+  }
+  {
+    let txn = doc_2.transact();
+    let json = map.to_json(&txn);
+    assert_json_eq!(
+      json,
+      json!({
+        "1": "a",
+        "2": "b"
+      })
+    );
+  }
+}
+
+#[tokio::test]
+async fn supabase_diff_state_vec_test() {
+  if get_supabase_config().is_none() {
+    return;
+  }
+
+  let folder_service = folder_service();
+  let user_service = user_auth_service();
+  let collab_service = collab_service();
+  let uuid = Uuid::new_v4().to_string();
+  let params = sign_up_param(uuid);
+  let user: SignUpResponse = user_service.sign_up(BoxAny::new(params)).await.unwrap();
+
+  let collab_object = CollabObject {
+    id: user.latest_workspace.id.clone(),
+    uid: user.user_id,
+    ty: CollabType::Folder,
+    meta: Default::default(),
+  }
+  .with_workspace_id(user.latest_workspace.id.clone());
+  let doc = Doc::with_client_id(1);
+  let map = { doc.get_or_insert_map("map") };
+  let array = { doc.get_or_insert_array("array") };
+
+  {
+    let mut txn = doc.transact_mut();
+    map.insert(&mut txn, "1", "a");
+    map.insert(&mut txn, "inner_map", MapPrelim::<String>::new());
+
+    array.push_back(&mut txn, "element 1");
+    let update = txn.encode_update_v1();
+    collab_service
+      .send_update(&collab_object, 0, update)
+      .await
+      .unwrap();
+  };
+  {
+    let mut txn = doc.transact_mut();
+    map.insert(&mut txn, "2", "b");
+    array.push_back(&mut txn, "element 2");
+    let update = txn.encode_update_v1();
+    collab_service
+      .send_update(&collab_object, 1, update)
+      .await
+      .unwrap();
+  };
+
+  // restore the doc with given updates.
+  let old_version_doc = Doc::new();
+  let map = { old_version_doc.get_or_insert_map("map") };
+  let updates: Vec<Vec<u8>> = folder_service
+    .get_folder_updates(&user.latest_workspace.id, user.user_id)
+    .await
+    .unwrap();
+  {
+    let mut txn = old_version_doc.transact_mut();
+    for update in updates {
+      let update = Update::decode_v1(&update).unwrap();
+      txn.apply_update(update);
+    }
+  }
+  let txn = old_version_doc.transact();
+  let json = map.to_json(&txn);
+  assert_json_eq!(
+    json,
+    json!({
+      "1": "a",
+      "2": "b",
+      "inner_map": {}
+    })
+  );
+}