use app_error::ErrorCode; use appflowy_collaborate::collab::cache::mem_cache::CollabMemCache; use appflowy_collaborate::CollabMetrics; use client_api_test::*; use collab::core::transaction::DocTransactionExtension; use collab::entity::EncodedCollab; use collab::preclude::{Doc, Transact}; use collab_entity::CollabType; use database::collab::CollabMetadata; use database_entity::dto::{ CreateCollabParams, DeleteCollabParams, QueryCollab, QueryCollabParams, QueryCollabResult, }; use sqlx::types::Uuid; use std::collections::HashMap; use workspace_template::document::getting_started::GettingStartedTemplate; use workspace_template::WorkspaceTemplateBuilder; use crate::collab::util::{redis_connection_manager, test_encode_collab_v1}; #[tokio::test] async fn success_insert_collab_test() { let (c, _user) = generate_unique_registered_user_client().await; let workspace_id = workspace_id_from_client(&c).await; let object_id = Uuid::new_v4().to_string(); let encode_collab = test_encode_collab_v1(&object_id, "title", "hello world"); c.create_collab(CreateCollabParams { object_id: object_id.clone(), collab_type: CollabType::Unknown, workspace_id: workspace_id.clone(), encoded_collab_v1: encode_collab.encode_to_bytes().unwrap(), }) .await .unwrap(); let doc_state = c .get_collab(QueryCollabParams::new( &object_id, CollabType::Document, &workspace_id, )) .await .unwrap() .encode_collab .doc_state; assert_eq!(doc_state, encode_collab.doc_state); } #[tokio::test] async fn success_batch_get_collab_test() { let (c, _user) = generate_unique_registered_user_client().await; let workspace_id = workspace_id_from_client(&c).await; let queries = vec![ QueryCollab { object_id: Uuid::new_v4().to_string(), collab_type: CollabType::Unknown, }, QueryCollab { object_id: Uuid::new_v4().to_string(), collab_type: CollabType::Unknown, }, QueryCollab { object_id: Uuid::new_v4().to_string(), collab_type: CollabType::Unknown, }, ]; let mut expected_results = HashMap::new(); for query in queries.iter() { let object_id = query.object_id.clone(); let encode_collab = test_encode_collab_v1(&object_id, "title", "hello world") .encode_to_bytes() .unwrap(); let collab_type = query.collab_type.clone(); expected_results.insert( object_id.clone(), QueryCollabResult::Success { encode_collab_v1: encode_collab.clone(), }, ); c.create_collab(CreateCollabParams { object_id: object_id.clone(), encoded_collab_v1: encode_collab.clone(), collab_type: collab_type.clone(), workspace_id: workspace_id.clone(), }) .await .unwrap(); } let results = c.batch_get_collab(&workspace_id, queries).await.unwrap().0; for (object_id, result) in expected_results.iter() { assert_eq!(result, results.get(object_id).unwrap()); } } #[tokio::test] async fn success_part_batch_get_collab_test() { let (c, _user) = generate_unique_registered_user_client().await; let workspace_id = workspace_id_from_client(&c).await; let queries = vec![ QueryCollab { object_id: Uuid::new_v4().to_string(), collab_type: CollabType::Unknown, }, QueryCollab { object_id: Uuid::new_v4().to_string(), collab_type: CollabType::Unknown, }, QueryCollab { object_id: Uuid::new_v4().to_string(), collab_type: CollabType::Unknown, }, ]; let mut expected_results = HashMap::new(); for (index, query) in queries.iter().enumerate() { let object_id = query.object_id.clone(); let collab_type = query.collab_type.clone(); let encode_collab = test_encode_collab_v1(&object_id, "title", "hello world") .encode_to_bytes() .unwrap(); if index == 1 { expected_results.insert( object_id.clone(), QueryCollabResult::Failed { error: "Record not found".to_string(), }, ); } else { expected_results.insert( object_id.clone(), QueryCollabResult::Success { encode_collab_v1: encode_collab.clone(), }, ); c.create_collab(CreateCollabParams { object_id: object_id.clone(), encoded_collab_v1: encode_collab.clone(), collab_type: collab_type.clone(), workspace_id: workspace_id.clone(), }) .await .unwrap(); } } let results = c.batch_get_collab(&workspace_id, queries).await.unwrap().0; for (object_id, result) in expected_results.iter() { assert_eq!(result, results.get(object_id).unwrap()); } } #[tokio::test] async fn success_delete_collab_test() { let (c, _user) = generate_unique_registered_user_client().await; let workspace_id = workspace_id_from_client(&c).await; let object_id = Uuid::new_v4().to_string(); let encode_collab = test_encode_collab_v1(&object_id, "title", "hello world") .encode_to_bytes() .unwrap(); c.create_collab(CreateCollabParams { object_id: object_id.clone(), encoded_collab_v1: encode_collab, collab_type: CollabType::Unknown, workspace_id: workspace_id.clone(), }) .await .unwrap(); c.delete_collab(DeleteCollabParams { object_id: object_id.clone(), workspace_id: workspace_id.clone(), }) .await .unwrap(); let error = c .get_collab(QueryCollabParams::new( &object_id, CollabType::Document, &workspace_id, )) .await .unwrap_err(); assert_eq!(error.code, ErrorCode::RecordNotFound); } #[tokio::test] async fn fail_insert_collab_with_empty_payload_test() { let (c, _user) = generate_unique_registered_user_client().await; let workspace_id = workspace_id_from_client(&c).await; let error = c .create_collab(CreateCollabParams { object_id: Uuid::new_v4().to_string(), encoded_collab_v1: vec![], collab_type: CollabType::Document, workspace_id: workspace_id.clone(), }) .await .unwrap_err(); assert_eq!(error.code, ErrorCode::NoRequiredData); } #[tokio::test] async fn fail_insert_collab_with_invalid_workspace_id_test() { let (c, _user) = generate_unique_registered_user_client().await; let workspace_id = Uuid::new_v4().to_string(); let object_id = Uuid::new_v4().to_string(); let encode_collab = test_encode_collab_v1(&object_id, "title", "hello world") .encode_to_bytes() .unwrap(); let error = c .create_collab(CreateCollabParams { object_id, encoded_collab_v1: encode_collab, collab_type: CollabType::Unknown, workspace_id: workspace_id.clone(), }) .await .unwrap_err(); assert_eq!(error.code, ErrorCode::RecordNotFound); } #[tokio::test] async fn collab_mem_cache_read_write_test() { let conn = redis_connection_manager().await; let mem_cache = CollabMemCache::new(conn, CollabMetrics::default().into()); let encode_collab = EncodedCollab::new_v1(vec![1, 2, 3], vec![4, 5, 6]); let object_id = uuid::Uuid::new_v4().to_string(); let timestamp = chrono::Utc::now().timestamp(); mem_cache .insert_encode_collab_data( &object_id, &encode_collab.encode_to_bytes().unwrap(), timestamp, None, ) .await .unwrap(); let encode_collab_from_cache = mem_cache.get_encode_collab(&object_id).await.unwrap(); assert_eq!(encode_collab_from_cache.state_vector, vec![1, 2, 3]); assert_eq!(encode_collab_from_cache.doc_state, vec![4, 5, 6]); } #[tokio::test] async fn collab_mem_cache_insert_override_test() { let conn = redis_connection_manager().await; let mem_cache = CollabMemCache::new(conn, CollabMetrics::default().into()); let object_id = uuid::Uuid::new_v4().to_string(); let encode_collab = EncodedCollab::new_v1(vec![1, 2, 3], vec![4, 5, 6]); let mut timestamp = chrono::Utc::now().timestamp(); mem_cache .insert_encode_collab_data( &object_id, &encode_collab.encode_to_bytes().unwrap(), timestamp, None, ) .await .unwrap(); // the following insert should not override the previous one because the timestamp is older // than the previous one timestamp -= 100; mem_cache .insert_encode_collab_data( &object_id, &EncodedCollab::new_v1(vec![6, 7, 8], vec![9, 10, 11]) .encode_to_bytes() .unwrap(), timestamp, None, ) .await .unwrap(); // check that the previous insert is still in the cache let encode_collab_from_cache = mem_cache.get_encode_collab(&object_id).await.unwrap(); assert_eq!(encode_collab_from_cache.doc_state, encode_collab.doc_state); assert_eq!(encode_collab_from_cache.state_vector, vec![1, 2, 3]); assert_eq!(encode_collab_from_cache.doc_state, vec![4, 5, 6]); // the following insert should override the previous one because the timestamp is newer timestamp += 500; mem_cache .insert_encode_collab_data( &object_id, &EncodedCollab::new_v1(vec![12, 13, 14], vec![15, 16, 17]) .encode_to_bytes() .unwrap(), timestamp, None, ) .await .unwrap(); // check that the previous insert is overridden let encode_collab_from_cache = mem_cache.get_encode_collab(&object_id).await.unwrap(); assert_eq!(encode_collab_from_cache.doc_state, vec![15, 16, 17]); assert_eq!(encode_collab_from_cache.state_vector, vec![12, 13, 14]); } #[tokio::test] async fn collab_meta_redis_cache_test() { let conn = redis_connection_manager().await; let mem_cache = CollabMemCache::new(conn, CollabMetrics::default().into()); mem_cache.get_collab_meta("1").await.unwrap_err(); let object_id = uuid::Uuid::new_v4().to_string(); let meta = CollabMetadata { object_id: object_id.clone(), workspace_id: "w1".to_string(), }; mem_cache.insert_collab_meta(meta.clone()).await.unwrap(); let meta_from_cache = mem_cache.get_collab_meta(&object_id).await.unwrap(); assert_eq!(meta.workspace_id, meta_from_cache.workspace_id); assert_eq!(meta.object_id, meta_from_cache.object_id); } #[tokio::test] async fn insert_empty_data_test() { let test_client = TestClient::new_user().await; let workspace_id = test_client.workspace_id().await; let object_id = uuid::Uuid::new_v4().to_string(); // test all collab type for collab_type in [ CollabType::Folder, CollabType::Document, CollabType::UserAwareness, CollabType::WorkspaceDatabase, CollabType::Database, CollabType::DatabaseRow, ] { let params = CreateCollabParams { workspace_id: workspace_id.clone(), object_id: object_id.clone(), encoded_collab_v1: vec![], collab_type, }; let error = test_client .api_client .create_collab(params) .await .unwrap_err(); assert_eq!(error.code, ErrorCode::NoRequiredData); } } #[tokio::test] async fn insert_invalid_data_test() { let test_client = TestClient::new_user().await; let workspace_id = test_client.workspace_id().await; let object_id = uuid::Uuid::new_v4().to_string(); let doc = Doc::new(); let encoded_collab_v1 = doc .transact() .get_encoded_collab_v1() .encode_to_bytes() .unwrap(); for collab_type in [ CollabType::Folder, CollabType::Document, CollabType::UserAwareness, CollabType::WorkspaceDatabase, CollabType::Database, CollabType::DatabaseRow, ] { let params = CreateCollabParams { workspace_id: workspace_id.clone(), object_id: object_id.clone(), encoded_collab_v1: encoded_collab_v1.clone(), collab_type: collab_type.clone(), }; let error = test_client .api_client .create_collab(params) .await .unwrap_err(); assert_eq!( error.code, ErrorCode::NoRequiredData, "collab_type: {:?}", collab_type ); } } #[tokio::test] async fn insert_folder_data_success_test() { let test_client = TestClient::new_user().await; let workspace_id = test_client.workspace_id().await; let object_id = uuid::Uuid::new_v4().to_string(); let uid = test_client.uid().await; let templates = WorkspaceTemplateBuilder::new(uid, &workspace_id) .with_templates(vec![GettingStartedTemplate]) .build() .await .unwrap(); // 2 spaces, 4 documents, 2 databases, 5 rows assert_eq!(templates.len(), 13); for template in templates.into_iter() { let data = template.encoded_collab.encode_to_bytes().unwrap(); let params = CreateCollabParams { workspace_id: workspace_id.clone(), object_id: object_id.clone(), encoded_collab_v1: data, collab_type: template.collab_type, }; test_client.api_client.create_collab(params).await.unwrap(); } }