소스 검색

fix multithread test issue of database init

appflowy 3 년 전
부모
커밋
d27e2b9475

+ 4 - 4
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/errors.pbenum.dart

@@ -12,8 +12,8 @@ import 'package:protobuf/protobuf.dart' as $pb;
 class ErrorCode extends $pb.ProtobufEnum {
   static const ErrorCode Unknown = ErrorCode._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Unknown');
   static const ErrorCode UserDatabaseInitFailed = ErrorCode._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserDatabaseInitFailed');
-  static const ErrorCode UserDatabaseWriteLocked = ErrorCode._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserDatabaseWriteLocked');
-  static const ErrorCode UserDatabaseReadLocked = ErrorCode._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserDatabaseReadLocked');
+  static const ErrorCode AcquireWriteLockedFailed = ErrorCode._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'AcquireWriteLockedFailed');
+  static const ErrorCode AcquireReadLockedFailed = ErrorCode._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'AcquireReadLockedFailed');
   static const ErrorCode UserDatabaseDidNotMatch = ErrorCode._(4, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserDatabaseDidNotMatch');
   static const ErrorCode UserDatabaseInternalError = ErrorCode._(5, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserDatabaseInternalError');
   static const ErrorCode SqlInternalError = ErrorCode._(6, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'SqlInternalError');
@@ -41,8 +41,8 @@ class ErrorCode extends $pb.ProtobufEnum {
   static const $core.List<ErrorCode> values = <ErrorCode> [
     Unknown,
     UserDatabaseInitFailed,
-    UserDatabaseWriteLocked,
-    UserDatabaseReadLocked,
+    AcquireWriteLockedFailed,
+    AcquireReadLockedFailed,
     UserDatabaseDidNotMatch,
     UserDatabaseInternalError,
     SqlInternalError,

+ 3 - 3
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/errors.pbjson.dart

@@ -14,8 +14,8 @@ const ErrorCode$json = const {
   '2': const [
     const {'1': 'Unknown', '2': 0},
     const {'1': 'UserDatabaseInitFailed', '2': 1},
-    const {'1': 'UserDatabaseWriteLocked', '2': 2},
-    const {'1': 'UserDatabaseReadLocked', '2': 3},
+    const {'1': 'AcquireWriteLockedFailed', '2': 2},
+    const {'1': 'AcquireReadLockedFailed', '2': 3},
     const {'1': 'UserDatabaseDidNotMatch', '2': 4},
     const {'1': 'UserDatabaseInternalError', '2': 5},
     const {'1': 'SqlInternalError', '2': 6},
@@ -43,7 +43,7 @@ const ErrorCode$json = const {
 };
 
 /// Descriptor for `ErrorCode`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode('CglFcnJvckNvZGUSCwoHVW5rbm93bhAAEhoKFlVzZXJEYXRhYmFzZUluaXRGYWlsZWQQARIbChdVc2VyRGF0YWJhc2VXcml0ZUxvY2tlZBACEhoKFlVzZXJEYXRhYmFzZVJlYWRMb2NrZWQQAxIbChdVc2VyRGF0YWJhc2VEaWROb3RNYXRjaBAEEh0KGVVzZXJEYXRhYmFzZUludGVybmFsRXJyb3IQBRIUChBTcWxJbnRlcm5hbEVycm9yEAYSGAoURGF0YWJhc2VDb25uZWN0RXJyb3IQBxITCg9Vc2VyTm90TG9naW5ZZXQQChIXChNSZWFkQ3VycmVudElkRmFpbGVkEAsSGAoUV3JpdGVDdXJyZW50SWRGYWlsZWQQDBIQCgxFbWFpbElzRW1wdHkQFBIWChJFbWFpbEZvcm1hdEludmFsaWQQFRIWChJFbWFpbEFscmVhZHlFeGlzdHMQFhITCg9QYXNzd29yZElzRW1wdHkQHhITCg9QYXNzd29yZFRvb0xvbmcQHxIkCiBQYXNzd29yZENvbnRhaW5zRm9yYmlkQ2hhcmFjdGVycxAgEhkKFVBhc3N3b3JkRm9ybWF0SW52YWxpZBAhEhQKEFBhc3N3b3JkTm90TWF0Y2gQIhITCg9Vc2VyTmFtZVRvb0xvbmcQKBInCiNVc2VyTmFtZUNvbnRhaW5zRm9yYmlkZGVuQ2hhcmFjdGVycxApEhMKD1VzZXJOYW1lSXNFbXB0eRAqEhgKFFVzZXJXb3Jrc3BhY2VJbnZhbGlkEDISEQoNVXNlcklkSW52YWxpZBAzEiAKHENyZWF0ZURlZmF1bHRXb3Jrc3BhY2VGYWlsZWQQNBIgChxEZWZhdWx0V29ya3NwYWNlQWxyZWFkeUV4aXN0EDUSDwoLU2VydmVyRXJyb3IQZA==');
+final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode('CglFcnJvckNvZGUSCwoHVW5rbm93bhAAEhoKFlVzZXJEYXRhYmFzZUluaXRGYWlsZWQQARIcChhBY3F1aXJlV3JpdGVMb2NrZWRGYWlsZWQQAhIbChdBY3F1aXJlUmVhZExvY2tlZEZhaWxlZBADEhsKF1VzZXJEYXRhYmFzZURpZE5vdE1hdGNoEAQSHQoZVXNlckRhdGFiYXNlSW50ZXJuYWxFcnJvchAFEhQKEFNxbEludGVybmFsRXJyb3IQBhIYChREYXRhYmFzZUNvbm5lY3RFcnJvchAHEhMKD1VzZXJOb3RMb2dpbllldBAKEhcKE1JlYWRDdXJyZW50SWRGYWlsZWQQCxIYChRXcml0ZUN1cnJlbnRJZEZhaWxlZBAMEhAKDEVtYWlsSXNFbXB0eRAUEhYKEkVtYWlsRm9ybWF0SW52YWxpZBAVEhYKEkVtYWlsQWxyZWFkeUV4aXN0cxAWEhMKD1Bhc3N3b3JkSXNFbXB0eRAeEhMKD1Bhc3N3b3JkVG9vTG9uZxAfEiQKIFBhc3N3b3JkQ29udGFpbnNGb3JiaWRDaGFyYWN0ZXJzECASGQoVUGFzc3dvcmRGb3JtYXRJbnZhbGlkECESFAoQUGFzc3dvcmROb3RNYXRjaBAiEhMKD1VzZXJOYW1lVG9vTG9uZxAoEicKI1VzZXJOYW1lQ29udGFpbnNGb3JiaWRkZW5DaGFyYWN0ZXJzECkSEwoPVXNlck5hbWVJc0VtcHR5ECoSGAoUVXNlcldvcmtzcGFjZUludmFsaWQQMhIRCg1Vc2VySWRJbnZhbGlkEDMSIAocQ3JlYXRlRGVmYXVsdFdvcmtzcGFjZUZhaWxlZBA0EiAKHERlZmF1bHRXb3Jrc3BhY2VBbHJlYWR5RXhpc3QQNRIPCgtTZXJ2ZXJFcnJvchBk');
 @$core.Deprecated('Use userErrorDescriptor instead')
 const UserError$json = const {
   '1': 'UserError',

+ 1 - 0
rust-lib/flowy-dispatch/Cargo.toml

@@ -24,6 +24,7 @@ dyn-clone = "1.0"
 derivative = "2.2.0"
 serde_json = {version = "1.0"}
 serde = { version = "1.0", features = ["derive"] }
+dashmap = "4.0"
 
 #optional crate
 bincode = { version = "1.3", optional = true}

+ 6 - 18
rust-lib/flowy-dispatch/src/dispatch.rs

@@ -14,7 +14,7 @@ use std::{future::Future, sync::RwLock};
 use tokio::macros::support::{Pin, Poll};
 
 lazy_static! {
-    pub static ref EVENT_DISPATCH: RwLock<Option<EventDispatch>> = RwLock::new(None);
+    static ref EVENT_DISPATCH: RwLock<Option<EventDispatch>> = RwLock::new(None);
 }
 
 pub struct EventDispatch {
@@ -31,10 +31,7 @@ impl EventDispatch {
         log::trace!("{}", module_info(&modules));
         let module_map = as_module_map(modules);
         let runtime = tokio_default_runtime().unwrap();
-        let dispatch = EventDispatch {
-            module_map,
-            runtime,
-        };
+        let dispatch = EventDispatch { module_map, runtime };
         *(EVENT_DISPATCH.write().unwrap()) = Some(dispatch);
     }
 
@@ -45,10 +42,7 @@ impl EventDispatch {
         EventDispatch::async_send_with_callback(request, |_| Box::pin(async {}))
     }
 
-    pub fn async_send_with_callback<Req, Callback>(
-        request: Req,
-        callback: Callback,
-    ) -> DispatchFuture<EventResponse>
+    pub fn async_send_with_callback<Req, Callback>(request: Req, callback: Callback) -> DispatchFuture<EventResponse>
     where
         Req: std::convert::Into<ModuleRequest>,
         Callback: FnOnce(EventResponse) -> BoxFuture<'static, ()> + 'static + Send + Sync,
@@ -74,10 +68,7 @@ impl EventDispatch {
                 DispatchFuture {
                     fut: Box::pin(async move {
                         join_handle.await.unwrap_or_else(|e| {
-                            let error = InternalError::JoinError(format!(
-                                "EVENT_DISPATCH join error: {:?}",
-                                e
-                            ));
+                            let error = InternalError::JoinError(format!("EVENT_DISPATCH join error: {:?}", e));
                             error.as_response()
                         })
                     }),
@@ -94,9 +85,7 @@ impl EventDispatch {
     }
 
     pub fn sync_send(request: ModuleRequest) -> EventResponse {
-        futures::executor::block_on(async {
-            EventDispatch::async_send_with_callback(request, |_| Box::pin(async {})).await
-        })
+        futures::executor::block_on(async { EventDispatch::async_send_with_callback(request, |_| Box::pin(async {})).await })
     }
 }
 
@@ -120,8 +109,7 @@ where
     }
 }
 
-pub type BoxFutureCallback =
-    Box<dyn FnOnce(EventResponse) -> BoxFuture<'static, ()> + 'static + Send + Sync>;
+pub type BoxFutureCallback = Box<dyn FnOnce(EventResponse) -> BoxFuture<'static, ()> + 'static + Send + Sync>;
 
 #[derive(Derivative)]
 #[derivative(Debug)]

+ 3 - 0
rust-lib/flowy-document/tests/editor/doc_test.rs

@@ -1,7 +1,9 @@
 use crate::helper::*;
+use flowy_test::builder::{TestBuilder, UserTestBuilder};
 
 #[test]
 fn file_create_test() {
+    let _ = UserTestBuilder::new().sign_up();
     let doc_desc = create_doc("hello world", "flutter ❤️ rust", "123");
     dbg!(&doc_desc);
 
@@ -11,6 +13,7 @@ fn file_create_test() {
 
 #[test]
 fn file_update_text_test() {
+    let _ = UserTestBuilder::new().sign_up();
     let doc_desc = create_doc("hello world", "flutter ❤️ rust", "");
     dbg!(&doc_desc);
 

+ 21 - 25
rust-lib/flowy-document/tests/editor/helper.rs

@@ -1,4 +1,4 @@
-use flowy_test::builder::AnnieTestBuilder;
+use flowy_test::builder::{AnnieTestBuilder, DocTestBuilder, TestBuilder};
 
 use flowy_document::{entities::doc::*, event::EditorEvent::*};
 use flowy_infra::uuid;
@@ -11,13 +11,12 @@ pub fn create_doc(name: &str, desc: &str, text: &str) -> DocInfo {
         text: text.to_owned(),
     };
 
-    let doc_desc = AnnieTestBuilder::new()
+    let doc = DocTestBuilder::new()
         .event(CreateDoc)
         .request(request)
         .sync_send()
         .parse::<DocInfo>();
-
-    doc_desc
+    doc
 }
 
 pub fn save_doc(desc: &DocInfo, content: &str) {
@@ -28,34 +27,31 @@ pub fn save_doc(desc: &DocInfo, content: &str) {
         text: Some(content.to_owned()),
     };
 
-    let _ = AnnieTestBuilder::new()
-        .event(UpdateDoc)
-        .request(request)
-        .sync_send();
-}
-
-#[allow(dead_code)]
-pub fn read_doc(doc_id: &str) -> DocInfo {
-    let request = QueryDocRequest {
-        doc_id: doc_id.to_string(),
-    };
-
-    let doc = AnnieTestBuilder::new()
-        .event(ReadDocInfo)
-        .request(request)
-        .sync_send()
-        .parse::<DocInfo>();
-
-    doc
+    let _ = DocTestBuilder::new().event(UpdateDoc).request(request).sync_send();
 }
 
-pub fn read_doc_data(doc_id: &str, path: &str) -> DocData {
+// #[allow(dead_code)]
+// pub fn read_doc(doc_id: &str) -> DocInfo {
+//     let request = QueryDocRequest {
+//         doc_id: doc_id.to_string(),
+//     };
+//
+//     let doc = AnnieTestBuilder::new()
+//         .event(ReadDocInfo)
+//         .request(request)
+//         .sync_send()
+//         .parse::<DocInfo>();
+//
+//     doc
+// }
+
+pub(crate) fn read_doc_data(doc_id: &str, path: &str) -> DocData {
     let request = QueryDocDataRequest {
         doc_id: doc_id.to_string(),
         path: path.to_string(),
     };
 
-    let doc = AnnieTestBuilder::new()
+    let doc = DocTestBuilder::new()
         .event(ReadDocData)
         .request(request)
         .sync_send()

+ 1 - 0
rust-lib/flowy-test/Cargo.toml

@@ -11,6 +11,7 @@ flowy-dispatch = { path = "../flowy-dispatch"}
 flowy-user = { path = "../flowy-user"}
 flowy-workspace = { path = "../flowy-workspace"}
 flowy-infra = { path = "../flowy-infra"}
+flowy-document = { path = "../flowy-document"}
 
 serde = { version = "1.0", features = ["derive"] }
 bincode = { version = "1.3"}

+ 31 - 55
rust-lib/flowy-test/src/builder.rs

@@ -6,45 +6,32 @@ use std::{
 };
 
 use crate::{
-    helper::{create_default_workspace_if_need, valid_email},
+    helper::{create_default_workspace_if_need, login_email, login_password},
+    init_test_sdk,
     tester::{TesterContext, TesterTrait},
 };
+use flowy_document::errors::DocError;
 use flowy_user::errors::UserError;
 use flowy_workspace::errors::WorkspaceError;
 use std::marker::PhantomData;
 
-pub type AnnieTestBuilder = Builder<FlowyAnnie<WorkspaceError>>;
-impl AnnieTestBuilder {
-    pub fn new() -> Self {
-        let mut builder = Builder::test(Box::new(FlowyAnnie::<WorkspaceError>::new()));
-        builder.setup_default_workspace();
-        builder
-    }
-
-    pub fn setup_default_workspace(&mut self) {
-        self.login_if_need();
-        let user_id = self.user_detail.as_ref().unwrap().id.clone();
-        let _ = create_default_workspace_if_need(&user_id);
-    }
-}
-pub type TestBuilder = Builder<RandomUserTester<UserError>>;
-impl TestBuilder {
-    pub fn new() -> Self { Builder::test(Box::new(RandomUserTester::<UserError>::new())) }
+pub type WorkspaceTestBuilder = Builder<RandomUserTester<WorkspaceError>>;
+impl WorkspaceTestBuilder {
+    pub fn new() -> Self { Builder::test(Box::new(RandomUserTester::<WorkspaceError>::new())) }
 }
 
-pub struct Builder<T: TesterTrait> {
-    pub tester: Box<T>,
-    pub user_detail: Option<UserDetail>,
+pub type DocTestBuilder = Builder<RandomUserTester<DocError>>;
+impl DocTestBuilder {
+    pub fn new() -> Self { Builder::test(Box::new(RandomUserTester::<DocError>::new())) }
 }
 
-impl<T> Builder<T>
-where
-    T: TesterTrait,
-{
-    fn test(tester: Box<T>) -> Self { Self { tester, user_detail: None } }
+pub type UserTestBuilder = Builder<RandomUserTester<UserError>>;
+impl UserTestBuilder {
+    pub fn new() -> Self { Builder::test(Box::new(RandomUserTester::<UserError>::new())) }
 
-    pub fn sign_up(self) -> SignUpContext {
+    pub fn sign_up(mut self) -> SignUpContext {
         let (user_detail, password) = self.tester.sign_up();
+        let _ = create_default_workspace_if_need(&user_detail.id);
         SignUpContext { user_detail, password }
     }
 
@@ -59,6 +46,23 @@ where
         self.user_detail = Some(user_detail);
     }
 
+    pub fn get_user_detail(&self) -> &Option<UserDetail> { &self.user_detail }
+}
+
+pub struct Builder<T: TesterTrait> {
+    pub tester: Box<T>,
+    user_detail: Option<UserDetail>,
+}
+
+impl<T> Builder<T>
+where
+    T: TesterTrait,
+{
+    fn test(tester: Box<T>) -> Self {
+        init_test_sdk();
+        Self { tester, user_detail: None }
+    }
+
     pub fn request<P>(mut self, request: P) -> Self
     where
         P: ToBytes,
@@ -128,34 +132,6 @@ where
     fn context(&self) -> &TesterContext { &self.context }
 }
 
-pub struct FlowyAnnie<Error> {
-    context: TesterContext,
-    err_phantom: PhantomData<Error>,
-}
-
-impl<Error> FlowyAnnie<Error>
-where
-    Error: FromBytes + Debug,
-{
-    pub fn new() -> Self {
-        Self {
-            context: TesterContext::new(valid_email()),
-            err_phantom: PhantomData,
-        }
-    }
-}
-
-impl<Error> TesterTrait for FlowyAnnie<Error>
-where
-    Error: FromBytes + Debug,
-{
-    type Error = Error;
-
-    fn mut_context(&mut self) -> &mut TesterContext { &mut self.context }
-
-    fn context(&self) -> &TesterContext { &self.context }
-}
-
 pub struct SignUpContext {
     pub user_detail: UserDetail,
     pub password: String,

+ 5 - 4
rust-lib/flowy-test/src/helper.rs

@@ -4,6 +4,7 @@ use flowy_infra::{kv::KV, uuid};
 use flowy_user::errors::{ErrorBuilder, ErrorCode, UserError};
 use flowy_workspace::{
     entities::workspace::{CreateWorkspaceRequest, QueryWorkspaceRequest, Workspace},
+    errors::WorkspaceError,
     event::WorkspaceEvent::{CreateWorkspace, OpenWorkspace},
 };
 use std::{fs, path::PathBuf};
@@ -26,9 +27,9 @@ pub fn root_dir() -> String {
 
 pub fn random_email() -> String { format!("{}@appflowy.io", uuid()) }
 
-pub fn valid_email() -> String { "[email protected]".to_string() }
+pub fn login_email() -> String { "[email protected]".to_string() }
 
-pub fn valid_password() -> String { "HelloWorld!123".to_string() }
+pub fn login_password() -> String { "HelloWorld!123".to_string() }
 
 const DEFAULT_WORKSPACE_NAME: &'static str = "My workspace";
 const DEFAULT_WORKSPACE_DESC: &'static str = "This is your first workspace";
@@ -50,7 +51,7 @@ pub(crate) fn create_default_workspace_if_need(user_id: &str) -> Result<(), User
 
     let request = ModuleRequest::new(CreateWorkspace).payload(payload);
     let result = EventDispatch::sync_send(request)
-        .parse::<Workspace, DispatchError>()
+        .parse::<Workspace, WorkspaceError>()
         .map_err(|e| ErrorBuilder::new(ErrorCode::CreateDefaultWorkspaceFailed).error(e).build())?;
 
     let workspace = result.map_err(|e| ErrorBuilder::new(ErrorCode::CreateDefaultWorkspaceFailed).error(e).build())?;
@@ -63,7 +64,7 @@ pub(crate) fn create_default_workspace_if_need(user_id: &str) -> Result<(), User
 
     let request = ModuleRequest::new(OpenWorkspace).payload(query);
     let _result = EventDispatch::sync_send(request)
-        .parse::<Workspace, DispatchError>()
+        .parse::<Workspace, WorkspaceError>()
         .unwrap()
         .unwrap();
 

+ 1 - 4
rust-lib/flowy-test/src/lib.rs

@@ -7,10 +7,7 @@ use flowy_sdk::FlowySDK;
 use std::sync::Once;
 
 pub mod prelude {
-    pub use crate::{
-        builder::{TestBuilder, *},
-        helper::*,
-    };
+    pub use crate::{builder::*, helper::*};
     pub use flowy_dispatch::prelude::*;
 }
 

+ 11 - 31
rust-lib/flowy-test/src/tester.rs

@@ -1,5 +1,5 @@
 use crate::{
-    helper::{random_email, valid_password},
+    helper::{login_password, random_email},
     init_test_sdk,
 };
 use flowy_dispatch::prelude::*;
@@ -10,6 +10,7 @@ use flowy_user::{
     prelude::*,
 };
 
+use crate::helper::login_email;
 use flowy_user::event::UserEvent::SignIn;
 use std::{
     convert::TryFrom,
@@ -22,15 +23,10 @@ pub struct TesterContext {
     request: Option<ModuleRequest>,
     response: Option<EventResponse>,
     status_code: StatusCode,
-    user_email: String,
 }
 
 impl TesterContext {
-    pub fn new(email: String) -> Self {
-        let mut ctx = TesterContext::default();
-        ctx.user_email = email;
-        ctx
-    }
+    pub fn new(email: String) -> Self { TesterContext::default() }
 }
 
 impl std::default::Default for TesterContext {
@@ -39,7 +35,6 @@ impl std::default::Default for TesterContext {
             request: None,
             status_code: StatusCode::Ok,
             response: None,
-            user_email: random_email(),
         }
     }
 }
@@ -59,7 +54,6 @@ pub trait TesterTrait {
     where
         E: Eq + Hash + Debug + Clone + Display,
     {
-        init_test_sdk();
         self.mut_context().request = Some(ModuleRequest::new(event));
     }
 
@@ -101,16 +95,13 @@ pub trait TesterTrait {
     fn error(&mut self) -> Self::Error {
         let response = self.mut_context().response.clone().unwrap();
         assert_eq!(response.status_code, StatusCode::Err);
-        <Data<Self::Error>>::try_from(response.payload)
-            .unwrap()
-            .into_inner()
+        <Data<Self::Error>>::try_from(response.payload).unwrap().into_inner()
     }
 
     fn sign_up(&self) -> (UserDetail, String) {
-        init_test_sdk();
-        let password = valid_password();
+        let password = login_password();
         let payload = SignUpRequest {
-            email: self.context().user_email.clone(),
+            email: random_email(),
             name: "app flowy".to_string(),
             password: password.clone(),
         }
@@ -118,34 +109,26 @@ pub trait TesterTrait {
         .unwrap();
 
         let request = ModuleRequest::new(SignUp).payload(payload);
-        let user_detail = EventDispatch::sync_send(request)
-            .parse::<UserDetail, UserError>()
-            .unwrap()
-            .unwrap();
+        let user_detail = EventDispatch::sync_send(request).parse::<UserDetail, UserError>().unwrap().unwrap();
 
         (user_detail, password)
     }
 
     fn sign_in(&self) -> UserDetail {
-        init_test_sdk();
         let payload = SignInRequest {
-            email: self.context().user_email.clone(),
-            password: valid_password(),
+            email: login_email(),
+            password: login_password(),
         }
         .into_bytes()
         .unwrap();
 
         let request = ModuleRequest::new(SignIn).payload(payload);
-        let user_detail = EventDispatch::sync_send(request)
-            .parse::<UserDetail, UserError>()
-            .unwrap()
-            .unwrap();
+        let user_detail = EventDispatch::sync_send(request).parse::<UserDetail, UserError>().unwrap().unwrap();
 
         user_detail
     }
 
     fn login_if_need(&self) -> UserDetail {
-        init_test_sdk();
         match EventDispatch::sync_send(ModuleRequest::new(GetUserProfile))
             .parse::<UserDetail, UserError>()
             .unwrap()
@@ -155,8 +138,5 @@ pub trait TesterTrait {
         }
     }
 
-    fn logout(&self) {
-        init_test_sdk();
-        let _ = EventDispatch::sync_send(ModuleRequest::new(SignOut));
-    }
+    fn logout(&self) { let _ = EventDispatch::sync_send(ModuleRequest::new(SignOut)); }
 }

+ 4 - 4
rust-lib/flowy-user/src/errors.rs

@@ -27,10 +27,10 @@ pub enum ErrorCode {
     Unknown              = 0,
     #[display(fmt = "Database init failed")]
     UserDatabaseInitFailed = 1,
-    #[display(fmt = "Get database write lock failed")]
-    UserDatabaseWriteLocked = 2,
-    #[display(fmt = "Get database read lock failed")]
-    UserDatabaseReadLocked = 3,
+    #[display(fmt = "Acquire database write lock failed")]
+    AcquireWriteLockedFailed = 2,
+    #[display(fmt = "Acquire database read lock failed")]
+    AcquireReadLockedFailed = 3,
     #[display(fmt = "Opening database is not belonging to the current user")]
     UserDatabaseDidNotMatch = 4,
     #[display(fmt = "Database internal error")]

+ 5 - 12
rust-lib/flowy-user/src/handlers/auth_handler.rs

@@ -4,13 +4,9 @@ use std::{convert::TryInto, sync::Arc};
 
 // tracing instrument 👉🏻 https://docs.rs/tracing/0.1.26/tracing/attr.instrument.html
 #[tracing::instrument(name = "sign_in", skip(data, session), fields(email = %data.email))]
-pub async fn sign_in(
-    data: Data<SignInRequest>,
-    session: Unit<Arc<UserSession>>,
-) -> DataResult<UserDetail, UserError> {
+pub async fn sign_in(data: Data<SignInRequest>, session: Unit<Arc<UserSession>>) -> DataResult<UserDetail, UserError> {
     let params: SignInParams = data.into_inner().try_into()?;
-    let user = session.sign_in(params).await?;
-    let user_detail = UserDetail::from(user);
+    let user_detail = session.sign_in(params).await?;
     data_result(user_detail)
 }
 
@@ -22,12 +18,9 @@ pub async fn sign_in(
         name = %data.name,
     )
 )]
-pub async fn sign_up(
-    data: Data<SignUpRequest>,
-    session: Unit<Arc<UserSession>>,
-) -> DataResult<UserDetail, UserError> {
+pub async fn sign_up(data: Data<SignUpRequest>, session: Unit<Arc<UserSession>>) -> DataResult<UserDetail, UserError> {
     let params: SignUpParams = data.into_inner().try_into()?;
-    let user = session.sign_up(params).await?;
-    let user_detail = UserDetail::from(user);
+    let user_detail = session.sign_up(params).await?;
+
     data_result(user_detail)
 }

+ 14 - 14
rust-lib/flowy-user/src/protobuf/model/errors.rs

@@ -217,8 +217,8 @@ impl ::protobuf::reflect::ProtobufValue for UserError {
 pub enum ErrorCode {
     Unknown = 0,
     UserDatabaseInitFailed = 1,
-    UserDatabaseWriteLocked = 2,
-    UserDatabaseReadLocked = 3,
+    AcquireWriteLockedFailed = 2,
+    AcquireReadLockedFailed = 3,
     UserDatabaseDidNotMatch = 4,
     UserDatabaseInternalError = 5,
     SqlInternalError = 6,
@@ -253,8 +253,8 @@ impl ::protobuf::ProtobufEnum for ErrorCode {
         match value {
             0 => ::std::option::Option::Some(ErrorCode::Unknown),
             1 => ::std::option::Option::Some(ErrorCode::UserDatabaseInitFailed),
-            2 => ::std::option::Option::Some(ErrorCode::UserDatabaseWriteLocked),
-            3 => ::std::option::Option::Some(ErrorCode::UserDatabaseReadLocked),
+            2 => ::std::option::Option::Some(ErrorCode::AcquireWriteLockedFailed),
+            3 => ::std::option::Option::Some(ErrorCode::AcquireReadLockedFailed),
             4 => ::std::option::Option::Some(ErrorCode::UserDatabaseDidNotMatch),
             5 => ::std::option::Option::Some(ErrorCode::UserDatabaseInternalError),
             6 => ::std::option::Option::Some(ErrorCode::SqlInternalError),
@@ -286,8 +286,8 @@ impl ::protobuf::ProtobufEnum for ErrorCode {
         static values: &'static [ErrorCode] = &[
             ErrorCode::Unknown,
             ErrorCode::UserDatabaseInitFailed,
-            ErrorCode::UserDatabaseWriteLocked,
-            ErrorCode::UserDatabaseReadLocked,
+            ErrorCode::AcquireWriteLockedFailed,
+            ErrorCode::AcquireReadLockedFailed,
             ErrorCode::UserDatabaseDidNotMatch,
             ErrorCode::UserDatabaseInternalError,
             ErrorCode::SqlInternalError,
@@ -341,10 +341,10 @@ impl ::protobuf::reflect::ProtobufValue for ErrorCode {
 static file_descriptor_proto_data: &'static [u8] = b"\
     \n\x0cerrors.proto\"=\n\tUserError\x12\x1e\n\x04code\x18\x01\x20\x01(\
     \x0e2\n.ErrorCodeR\x04code\x12\x10\n\x03msg\x18\x02\x20\x01(\tR\x03msg*\
-    \xb9\x05\n\tErrorCode\x12\x0b\n\x07Unknown\x10\0\x12\x1a\n\x16UserDataba\
-    seInitFailed\x10\x01\x12\x1b\n\x17UserDatabaseWriteLocked\x10\x02\x12\
-    \x1a\n\x16UserDatabaseReadLocked\x10\x03\x12\x1b\n\x17UserDatabaseDidNot\
-    Match\x10\x04\x12\x1d\n\x19UserDatabaseInternalError\x10\x05\x12\x14\n\
+    \xbb\x05\n\tErrorCode\x12\x0b\n\x07Unknown\x10\0\x12\x1a\n\x16UserDataba\
+    seInitFailed\x10\x01\x12\x1c\n\x18AcquireWriteLockedFailed\x10\x02\x12\
+    \x1b\n\x17AcquireReadLockedFailed\x10\x03\x12\x1b\n\x17UserDatabaseDidNo\
+    tMatch\x10\x04\x12\x1d\n\x19UserDatabaseInternalError\x10\x05\x12\x14\n\
     \x10SqlInternalError\x10\x06\x12\x18\n\x14DatabaseConnectError\x10\x07\
     \x12\x13\n\x0fUserNotLoginYet\x10\n\x12\x17\n\x13ReadCurrentIdFailed\x10\
     \x0b\x12\x18\n\x14WriteCurrentIdFailed\x10\x0c\x12\x10\n\x0cEmailIsEmpty\
@@ -369,10 +369,10 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \x04\x0b\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\x07\x0e\x0f\n\x0b\n\x04\x05\
     \0\x02\x01\x12\x03\x08\x04\x1f\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x08\
     \x04\x1a\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x08\x1d\x1e\n\x0b\n\x04\
-    \x05\0\x02\x02\x12\x03\t\x04\x20\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\t\
-    \x04\x1b\n\x0c\n\x05\x05\0\x02\x02\x02\x12\x03\t\x1e\x1f\n\x0b\n\x04\x05\
-    \0\x02\x03\x12\x03\n\x04\x1f\n\x0c\n\x05\x05\0\x02\x03\x01\x12\x03\n\x04\
-    \x1a\n\x0c\n\x05\x05\0\x02\x03\x02\x12\x03\n\x1d\x1e\n\x0b\n\x04\x05\0\
+    \x05\0\x02\x02\x12\x03\t\x04!\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\t\
+    \x04\x1c\n\x0c\n\x05\x05\0\x02\x02\x02\x12\x03\t\x1f\x20\n\x0b\n\x04\x05\
+    \0\x02\x03\x12\x03\n\x04\x20\n\x0c\n\x05\x05\0\x02\x03\x01\x12\x03\n\x04\
+    \x1b\n\x0c\n\x05\x05\0\x02\x03\x02\x12\x03\n\x1e\x1f\n\x0b\n\x04\x05\0\
     \x02\x04\x12\x03\x0b\x04\x20\n\x0c\n\x05\x05\0\x02\x04\x01\x12\x03\x0b\
     \x04\x1b\n\x0c\n\x05\x05\0\x02\x04\x02\x12\x03\x0b\x1e\x1f\n\x0b\n\x04\
     \x05\0\x02\x05\x12\x03\x0c\x04\"\n\x0c\n\x05\x05\0\x02\x05\x01\x12\x03\

+ 2 - 2
rust-lib/flowy-user/src/protobuf/proto/errors.proto

@@ -7,8 +7,8 @@ message UserError {
 enum ErrorCode {
     Unknown = 0;
     UserDatabaseInitFailed = 1;
-    UserDatabaseWriteLocked = 2;
-    UserDatabaseReadLocked = 3;
+    AcquireWriteLockedFailed = 2;
+    AcquireReadLockedFailed = 3;
     UserDatabaseDidNotMatch = 4;
     UserDatabaseInternalError = 5;
     SqlInternalError = 6;

+ 5 - 4
rust-lib/flowy-user/src/services/server/server_api_mock.rs

@@ -15,21 +15,22 @@ impl UserServerAPI for UserServerMock {
         let uid = uuid();
         ResultFuture::new(async move {
             Ok(SignUpResponse {
-                user_id: uid,
+                user_id: uid.clone(),
                 name: params.name,
                 email: params.email,
-                token: "fake token".to_owned(),
+                token: uid,
             })
         })
     }
 
     fn sign_in(&self, params: SignInParams) -> ResultFuture<SignInResponse, UserError> {
+        let user_id = uuid();
         ResultFuture::new(async {
             Ok(SignInResponse {
-                uid: uuid(),
+                uid: user_id.clone(),
                 name: "fake name".to_owned(),
                 email: params.email,
-                token: "fake token".to_string(),
+                token: user_id,
             })
         })
     }

+ 50 - 51
rust-lib/flowy-user/src/services/user/database.rs

@@ -3,12 +3,8 @@ use flowy_database::{DBConnection, Database};
 use flowy_sqlite::ConnectionPool;
 use lazy_static::lazy_static;
 use once_cell::sync::Lazy;
-use parking_lot::Mutex;
-use std::{
-    collections::HashMap,
-    sync::{Arc, RwLock},
-};
-
+use parking_lot::{lock_api::RwLockReadGuard, Mutex, RawRwLock, RwLock};
+use std::{collections::HashMap, sync::Arc, time::Duration};
 lazy_static! {
     static ref DB: RwLock<Option<Database>> = RwLock::new(None);
 }
@@ -18,46 +14,42 @@ pub(crate) struct UserDB {
 }
 
 impl UserDB {
-    pub(crate) fn new(db_dir: &str) -> Self {
-        Self {
-            db_dir: db_dir.to_owned(),
-        }
-    }
+    pub(crate) fn new(db_dir: &str) -> Self { Self { db_dir: db_dir.to_owned() } }
 
     fn open_user_db(&self, user_id: &str) -> Result<(), UserError> {
         if user_id.is_empty() {
-            return Err(ErrorBuilder::new(ErrorCode::UserDatabaseInitFailed)
-                .msg("user id is empty")
-                .build());
+            return Err(ErrorBuilder::new(ErrorCode::UserDatabaseInitFailed).msg("user id is empty").build());
         }
 
+        log::info!("open user db {}", user_id);
         let dir = format!("{}/{}", self.db_dir, user_id);
         let db = flowy_database::init(&dir).map_err(|e| {
-            log::error!("flowy_database::init failed, {:?}", e);
-            ErrorBuilder::new(ErrorCode::UserDatabaseInitFailed)
-                .error(e)
-                .build()
+            log::error!("init user db failed, {:?}, user_id: {}", e, user_id);
+            ErrorBuilder::new(ErrorCode::UserDatabaseInitFailed).error(e).build()
         })?;
 
-        let mut db_map = DB_MAP.write().map_err(|e| {
-            ErrorBuilder::new(ErrorCode::UserDatabaseWriteLocked)
-                .error(e)
-                .build()
-        })?;
-
-        db_map.insert(user_id.to_owned(), db);
-        Ok(())
+        match DB_MAP.try_write_for(Duration::from_millis(300)) {
+            None => Err(ErrorBuilder::new(ErrorCode::AcquireWriteLockedFailed)
+                .msg(format!("Open user db failed"))
+                .build()),
+            Some(mut write_guard) => {
+                write_guard.insert(user_id.to_owned(), db);
+                Ok(())
+            },
+        }
     }
 
     pub(crate) fn close_user_db(&self, user_id: &str) -> Result<(), UserError> {
-        let mut db_map = DB_MAP.write().map_err(|e| {
-            ErrorBuilder::new(ErrorCode::UserDatabaseWriteLocked)
-                .msg(format!("Close user db failed. {:?}", e))
-                .build()
-        })?;
-        set_user_db_init(false, user_id);
-        db_map.remove(user_id);
-        Ok(())
+        match DB_MAP.try_write_for(Duration::from_millis(300)) {
+            None => Err(ErrorBuilder::new(ErrorCode::AcquireWriteLockedFailed)
+                .msg(format!("Close user db failed"))
+                .build()),
+            Some(mut write_guard) => {
+                set_user_db_init(false, user_id);
+                write_guard.remove(user_id);
+                Ok(())
+            },
+        }
     }
 
     pub(crate) fn get_connection(&self, user_id: &str) -> Result<DBConnection, UserError> {
@@ -66,22 +58,28 @@ impl UserDB {
     }
 
     pub(crate) fn get_pool(&self, user_id: &str) -> Result<Arc<ConnectionPool>, UserError> {
-        if !is_user_db_init(user_id) {
-            let _ = self.open_user_db(user_id)?;
-            set_user_db_init(true, user_id);
+        // Opti: INIT_LOCK try to lock the INIT_RECORD accesses. Because the write guard
+        // can not nested in the read guard that will cause the deadlock.
+        match INIT_LOCK.try_lock_for(Duration::from_millis(300)) {
+            None => log::error!("get_pool fail"),
+            Some(_) => {
+                if !is_user_db_init(user_id) {
+                    let _ = self.open_user_db(user_id)?;
+                    set_user_db_init(true, user_id);
+                }
+            },
         }
 
-        let db_map = DB_MAP.read().map_err(|e| {
-            ErrorBuilder::new(ErrorCode::UserDatabaseReadLocked)
-                .error(e)
-                .build()
-        })?;
-
-        match db_map.get(user_id) {
-            None => Err(ErrorBuilder::new(ErrorCode::UserDatabaseInitFailed)
-                .msg("Get connection failed. The database is not initialization")
+        match DB_MAP.try_read_for(Duration::from_millis(300)) {
+            None => Err(ErrorBuilder::new(ErrorCode::AcquireReadLockedFailed)
+                .msg(format!("Read user db failed"))
                 .build()),
-            Some(database) => Ok(database.get_pool()),
+            Some(read_guard) => match read_guard.get(user_id) {
+                None => Err(ErrorBuilder::new(ErrorCode::UserDatabaseInitFailed)
+                    .msg("Get connection failed. The database is not initialization")
+                    .build()),
+                Some(database) => Ok(database.get_pool()),
+            },
         }
     }
 }
@@ -90,14 +88,15 @@ lazy_static! {
     static ref DB_MAP: RwLock<HashMap<String, Database>> = RwLock::new(HashMap::new());
 }
 
-static INIT_FLAG_MAP: Lazy<Mutex<HashMap<String, bool>>> = Lazy::new(|| Mutex::new(HashMap::new()));
+static INIT_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
+static INIT_RECORD: Lazy<Mutex<HashMap<String, bool>>> = Lazy::new(|| Mutex::new(HashMap::new()));
 fn set_user_db_init(is_init: bool, user_id: &str) {
-    let mut flag_map = INIT_FLAG_MAP.lock();
-    flag_map.insert(user_id.to_owned(), is_init);
+    let mut record = INIT_RECORD.lock();
+    record.insert(user_id.to_owned(), is_init);
 }
 
 fn is_user_db_init(user_id: &str) -> bool {
-    match INIT_FLAG_MAP.lock().get(user_id) {
+    match INIT_RECORD.lock().get(user_id) {
         None => false,
         Some(flag) => flag.clone(),
     }

+ 36 - 12
rust-lib/flowy-user/src/services/user/user_session.rs

@@ -69,22 +69,36 @@ impl UserSession {
         self.database.get_pool(&user_id)
     }
 
-    pub async fn sign_in(&self, params: SignInParams) -> Result<UserTable, UserError> {
-        let resp = self.server.sign_in(params).await?;
-        let session = Session::new(&resp.uid, &resp.token);
-        let _ = self.set_session(Some(session))?;
-        let user_table = self.save_user(resp.into()).await?;
-
-        Ok(user_table)
+    pub async fn sign_in(&self, params: SignInParams) -> Result<UserDetail, UserError> {
+        if self.is_login(&params.email) {
+            self.user_detail().await
+        } else {
+            let resp = self.server.sign_in(params).await?;
+            let session = Session::new(&resp.uid, &resp.token, &resp.email);
+            let _ = self.set_session(Some(session))?;
+            let user_table = self.save_user(resp.into()).await?;
+            let user_detail = UserDetail::from(user_table);
+            Ok(user_detail)
+        }
     }
 
-    pub async fn sign_up(&self, params: SignUpParams) -> Result<UserTable, UserError> {
+    pub async fn sign_up(&self, params: SignUpParams) -> Result<UserDetail, UserError> {
+        // if self.is_login(&params.email) {
+        //     self.user_detail().await
+        // } else {
+        //     let resp = self.server.sign_up(params).await?;
+        //     let session = Session::new(&resp.user_id, &resp.token, &resp.email);
+        //     let _ = self.set_session(Some(session))?;
+        //     let user_table = self.save_user(resp.into()).await?;
+        //     let user_detail = UserDetail::from(user_table);
+        //     Ok(user_detail)
+        // }
         let resp = self.server.sign_up(params).await?;
-        let session = Session::new(&resp.user_id, &resp.token);
+        let session = Session::new(&resp.user_id, &resp.token, &resp.email);
         let _ = self.set_session(Some(session))?;
         let user_table = self.save_user(resp.into()).await?;
-
-        Ok(user_table)
+        let user_detail = UserDetail::from(user_table);
+        Ok(user_detail)
     }
 
     pub async fn sign_out(&self) -> Result<(), UserError> {
@@ -170,6 +184,7 @@ impl UserSession {
         *self.session.write() = session;
         Ok(())
     }
+
     fn get_session(&self) -> Result<Session, UserError> {
         let mut session = { (*self.session.read()).clone() };
         if session.is_none() {
@@ -187,6 +202,13 @@ impl UserSession {
             Some(session) => Ok(session),
         }
     }
+
+    fn is_login(&self, email: &str) -> bool {
+        match self.get_session() {
+            Ok(session) => session.email == email,
+            Err(_) => false,
+        }
+    }
 }
 
 pub async fn update_user(_server: Server, pool: Arc<ConnectionPool>, params: UpdateUserParams) -> Result<(), UserError> {
@@ -213,13 +235,15 @@ const SESSION_CACHE_KEY: &str = "session_cache_key";
 struct Session {
     user_id: String,
     token: String,
+    email: String,
 }
 
 impl Session {
-    pub fn new(user_id: &str, token: &str) -> Self {
+    pub fn new(user_id: &str, token: &str, email: &str) -> Self {
         Self {
             user_id: user_id.to_owned(),
             token: token.to_owned(),
+            email: email.to_owned(),
         }
     }
 }

+ 11 - 28
rust-lib/flowy-user/tests/event/auth_test.rs

@@ -1,11 +1,12 @@
 use crate::helper::*;
+use flowy_test::builder::UserTestBuilder;
 use flowy_user::{errors::ErrorCode, event::UserEvent::*, prelude::*};
 use serial_test::*;
 
 #[test]
 #[serial]
 fn sign_up_success() {
-    let user_detail = TestBuilder::new().sign_up().user_detail;
+    let user_detail = UserTestBuilder::new().sign_up().user_detail;
     log::info!("{:?}", user_detail);
 }
 
@@ -16,16 +17,11 @@ fn sign_up_with_invalid_email() {
         let request = SignUpRequest {
             email: email.to_string(),
             name: valid_name(),
-            password: valid_password(),
+            password: login_password(),
         };
 
         assert_eq!(
-            TestBuilder::new()
-                .event(SignUp)
-                .request(request)
-                .sync_send()
-                .error()
-                .code,
+            UserTestBuilder::new().event(SignUp).request(request).sync_send().error().code,
             ErrorCode::EmailFormatInvalid
         );
     }
@@ -40,27 +36,23 @@ fn sign_up_with_invalid_password() {
             password,
         };
 
-        TestBuilder::new()
-            .event(SignUp)
-            .request(request)
-            .sync_send()
-            .assert_error();
+        UserTestBuilder::new().event(SignUp).request(request).sync_send().assert_error();
     }
 }
 
 #[test]
 #[serial]
 fn sign_in_success() {
-    let context = TestBuilder::new().sign_up();
+    let context = UserTestBuilder::new().sign_up();
 
-    let _ = TestBuilder::new().event(SignOut).sync_send();
+    let _ = UserTestBuilder::new().event(SignOut).sync_send();
 
     let request = SignInRequest {
         email: context.user_detail.email,
         password: context.password,
     };
 
-    let response = TestBuilder::new()
+    let response = UserTestBuilder::new()
         .event(SignIn)
         .request(request)
         .sync_send()
@@ -74,16 +66,11 @@ fn sign_in_with_invalid_email() {
     for email in invalid_email_test_case() {
         let request = SignInRequest {
             email: email.to_string(),
-            password: valid_password(),
+            password: login_password(),
         };
 
         assert_eq!(
-            TestBuilder::new()
-                .event(SignIn)
-                .request(request)
-                .sync_send()
-                .error()
-                .code,
+            UserTestBuilder::new().event(SignIn).request(request).sync_send().error().code,
             ErrorCode::EmailFormatInvalid
         );
     }
@@ -98,10 +85,6 @@ fn sign_in_with_invalid_password() {
             password,
         };
 
-        TestBuilder::new()
-            .event(SignIn)
-            .request(request)
-            .sync_send()
-            .assert_error();
+        UserTestBuilder::new().event(SignIn).request(request).sync_send().assert_error();
     }
 }

+ 4 - 3
rust-lib/flowy-user/tests/event/helper.rs

@@ -1,6 +1,7 @@
-pub use flowy_test::builder::TestBuilder;
-
-pub use flowy_test::prelude::{random_email, valid_password};
+pub use flowy_test::{
+    builder::*,
+    prelude::{login_password, random_email},
+};
 
 pub(crate) fn invalid_email_test_case() -> Vec<String> {
     // https://gist.github.com/cjaoude/fd9910626629b53c4d25

+ 19 - 44
rust-lib/flowy-user/tests/event/user_profile_test.rs

@@ -1,28 +1,22 @@
 use crate::helper::*;
 use flowy_infra::uuid;
+use flowy_test::builder::UserTestBuilder;
 use flowy_user::{errors::ErrorCode, event::UserEvent::*, prelude::*};
 use serial_test::*;
 
 #[test]
 #[serial]
 fn user_status_get_failed() {
-    let user_detail = TestBuilder::new()
-        .event(GetUserProfile)
-        .assert_error()
-        .sync_send()
-        .user_detail;
-    assert!(user_detail.is_none())
+    let tester = UserTestBuilder::new().event(GetUserProfile).assert_error().sync_send();
+    assert!(tester.get_user_detail().is_none())
 }
 
 #[test]
 #[serial]
 fn user_detail_get() {
-    let user_detail = TestBuilder::new().sign_up().user_detail;
+    let user_detail = UserTestBuilder::new().sign_up().user_detail;
 
-    let user_detail2 = TestBuilder::new()
-        .event(GetUserProfile)
-        .sync_send()
-        .parse::<UserDetail>();
+    let user_detail2 = UserTestBuilder::new().event(GetUserProfile).sync_send().parse::<UserDetail>();
 
     assert_eq!(user_detail, user_detail2);
 }
@@ -30,15 +24,12 @@ fn user_detail_get() {
 #[test]
 #[serial]
 fn user_update_with_name() {
-    let user_detail = TestBuilder::new().sign_up().user_detail;
+    let user_detail = UserTestBuilder::new().sign_up().user_detail;
     let new_name = "hello_world".to_owned();
     let request = UpdateUserRequest::new(&user_detail.id).name(&new_name);
-    let _ = TestBuilder::new()
-        .event(UpdateUser)
-        .request(request)
-        .sync_send();
+    let _ = UserTestBuilder::new().event(UpdateUser).request(request).sync_send();
 
-    let user_detail = TestBuilder::new()
+    let user_detail = UserTestBuilder::new()
         .event(GetUserProfile)
         .assert_error()
         .sync_send()
@@ -50,16 +41,13 @@ fn user_update_with_name() {
 #[test]
 #[serial]
 fn user_update_with_email() {
-    let user_detail = TestBuilder::new().sign_up().user_detail;
+    let user_detail = UserTestBuilder::new().sign_up().user_detail;
     let new_email = format!("{}@gmai.com", uuid());
     let request = UpdateUserRequest::new(&user_detail.id).email(&new_email);
 
-    let _ = TestBuilder::new()
-        .event(UpdateUser)
-        .request(request)
-        .sync_send();
+    let _ = UserTestBuilder::new().event(UpdateUser).request(request).sync_send();
 
-    let user_detail = TestBuilder::new()
+    let user_detail = UserTestBuilder::new()
         .event(GetUserProfile)
         .assert_error()
         .sync_send()
@@ -71,11 +59,11 @@ fn user_update_with_email() {
 #[test]
 #[serial]
 fn user_update_with_password() {
-    let user_detail = TestBuilder::new().sign_up().user_detail;
+    let user_detail = UserTestBuilder::new().sign_up().user_detail;
     let new_password = "H123world!".to_owned();
     let request = UpdateUserRequest::new(&user_detail.id).password(&new_password);
 
-    let _ = TestBuilder::new()
+    let _ = UserTestBuilder::new()
         .event(UpdateUser)
         .request(request)
         .sync_send()
@@ -85,16 +73,11 @@ fn user_update_with_password() {
 #[test]
 #[serial]
 fn user_update_with_invalid_email() {
-    let user_detail = TestBuilder::new().sign_up().user_detail;
+    let user_detail = UserTestBuilder::new().sign_up().user_detail;
     for email in invalid_email_test_case() {
         let request = UpdateUserRequest::new(&user_detail.id).email(&email);
         assert_eq!(
-            TestBuilder::new()
-                .event(UpdateUser)
-                .request(request)
-                .sync_send()
-                .error()
-                .code,
+            UserTestBuilder::new().event(UpdateUser).request(request).sync_send().error().code,
             ErrorCode::EmailFormatInvalid
         );
     }
@@ -103,27 +86,19 @@ fn user_update_with_invalid_email() {
 #[test]
 #[serial]
 fn user_update_with_invalid_password() {
-    let user_detail = TestBuilder::new().sign_up().user_detail;
+    let user_detail = UserTestBuilder::new().sign_up().user_detail;
     for password in invalid_password_test_case() {
         let request = UpdateUserRequest::new(&user_detail.id).password(&password);
 
-        TestBuilder::new()
-            .event(UpdateUser)
-            .request(request)
-            .sync_send()
-            .assert_error();
+        UserTestBuilder::new().event(UpdateUser).request(request).sync_send().assert_error();
     }
 }
 
 #[test]
 #[serial]
 fn user_update_with_invalid_name() {
-    let user_detail = TestBuilder::new().sign_up().user_detail;
+    let user_detail = UserTestBuilder::new().sign_up().user_detail;
     let request = UpdateUserRequest::new(&user_detail.id).name("");
 
-    TestBuilder::new()
-        .event(UpdateUser)
-        .request(request)
-        .sync_send()
-        .assert_error();
+    UserTestBuilder::new().event(UpdateUser).request(request).sync_send().assert_error();
 }

+ 9 - 0
rust-lib/flowy-workspace/tests/event/app_test.rs

@@ -1,5 +1,6 @@
 use crate::helper::*;
 
+use flowy_test::builder::UserTestBuilder;
 use flowy_workspace::entities::{
     app::{QueryAppRequest, UpdateAppRequest},
     view::*,
@@ -7,6 +8,7 @@ use flowy_workspace::entities::{
 
 #[test]
 fn app_create() {
+    let _ = UserTestBuilder::new().sign_up();
     let workspace = create_workspace("Workspace", "");
     let app = create_app("App A", "AppFlowy Github Project", &workspace.id);
     dbg!(&app);
@@ -15,6 +17,8 @@ fn app_create() {
 #[test]
 #[should_panic]
 fn app_delete() {
+    let _ = UserTestBuilder::new().sign_up();
+
     let workspace = create_workspace("Workspace", "");
     let app = create_app("App A", "AppFlowy Github Project", &workspace.id);
     delete_app(&app.id);
@@ -24,6 +28,8 @@ fn app_delete() {
 
 #[test]
 fn app_read() {
+    let _ = UserTestBuilder::new().sign_up();
+
     let workspace = create_workspace("Workspace", "");
     let app = create_app("App A", "AppFlowy Github Project", &workspace.id);
     let query = QueryAppRequest::new(&app.id);
@@ -33,6 +39,7 @@ fn app_read() {
 
 #[test]
 fn app_create_with_view() {
+    let _a = UserTestBuilder::new().sign_up();
     let workspace = create_workspace("Workspace", "");
     let app = create_app("App A", "AppFlowy Github Project", &workspace.id);
     let request_a = CreateViewRequest {
@@ -63,6 +70,7 @@ fn app_create_with_view() {
 
 #[test]
 fn app_set_trash_flag() {
+    let _ = UserTestBuilder::new().sign_up();
     let app_id = create_app_with_trash_flag();
     let query = QueryAppRequest::new(&app_id).set_is_trash(true);
     let _ = read_app(query);
@@ -71,6 +79,7 @@ fn app_set_trash_flag() {
 #[test]
 #[should_panic]
 fn app_set_trash_flag_2() {
+    let _ = UserTestBuilder::new().sign_up();
     let app_id = create_app_with_trash_flag();
     let query = QueryAppRequest::new(&app_id);
     let _ = read_app(query);

+ 23 - 14
rust-lib/flowy-workspace/tests/event/helper.rs

@@ -1,4 +1,4 @@
-pub use flowy_test::builder::AnnieTestBuilder;
+use flowy_test::builder::{UserTestBuilder, WorkspaceTestBuilder};
 use flowy_workspace::{
     entities::{app::*, view::*, workspace::*},
     event::WorkspaceEvent::*,
@@ -17,7 +17,7 @@ pub fn create_workspace(name: &str, desc: &str) -> Workspace {
         desc: desc.to_owned(),
     };
 
-    let workspace = AnnieTestBuilder::new()
+    let workspace = WorkspaceTestBuilder::new()
         .event(CreateWorkspace)
         .request(request)
         .sync_send()
@@ -26,13 +26,13 @@ pub fn create_workspace(name: &str, desc: &str) -> Workspace {
 }
 
 pub fn read_workspaces(request: QueryWorkspaceRequest) -> Option<Workspace> {
-    let mut repeated_workspace = AnnieTestBuilder::new()
+    let mut repeated_workspace = WorkspaceTestBuilder::new()
         .event(ReadWorkspaces)
         .request(request)
         .sync_send()
         .parse::<RepeatedWorkspace>();
 
-    debug_assert_eq!(repeated_workspace.len(), 1);
+    debug_assert_eq!(repeated_workspace.len(), 1, "Default workspace not found");
     repeated_workspace.drain(..1).collect::<Vec<Workspace>>().pop()
 }
 
@@ -44,7 +44,7 @@ pub fn create_app(name: &str, desc: &str, workspace_id: &str) -> App {
         color_style: Default::default(),
     };
 
-    let app = AnnieTestBuilder::new()
+    let app = WorkspaceTestBuilder::new()
         .event(CreateApp)
         .request(create_app_request)
         .sync_send()
@@ -57,19 +57,23 @@ pub fn delete_app(app_id: &str) {
         app_id: app_id.to_string(),
     };
 
-    AnnieTestBuilder::new().event(DeleteApp).request(delete_app_request).sync_send();
+    WorkspaceTestBuilder::new().event(DeleteApp).request(delete_app_request).sync_send();
 }
 
-pub fn update_app(request: UpdateAppRequest) { AnnieTestBuilder::new().event(UpdateApp).request(request).sync_send(); }
+pub fn update_app(request: UpdateAppRequest) { WorkspaceTestBuilder::new().event(UpdateApp).request(request).sync_send(); }
 
 pub fn read_app(request: QueryAppRequest) -> App {
-    let app = AnnieTestBuilder::new().event(ReadApp).request(request).sync_send().parse::<App>();
+    let app = WorkspaceTestBuilder::new()
+        .event(ReadApp)
+        .request(request)
+        .sync_send()
+        .parse::<App>();
 
     app
 }
 
 pub fn create_view_with_request(request: CreateViewRequest) -> View {
-    let view = AnnieTestBuilder::new()
+    let view = WorkspaceTestBuilder::new()
         .event(CreateView)
         .request(request)
         .sync_send()
@@ -78,9 +82,8 @@ pub fn create_view_with_request(request: CreateViewRequest) -> View {
     view
 }
 
-pub fn create_view() -> View {
-    let workspace = create_workspace("Workspace", "");
-    let app = create_app("App A", "AppFlowy Github Project", &workspace.id);
+pub fn create_view(workspace_id: &str) -> View {
+    let app = create_app("App A", "AppFlowy Github Project", workspace_id);
     let request = CreateViewRequest {
         belong_to_id: app.id.clone(),
         name: "View A".to_string(),
@@ -92,6 +95,12 @@ pub fn create_view() -> View {
     create_view_with_request(request)
 }
 
-pub fn update_view(request: UpdateViewRequest) { AnnieTestBuilder::new().event(UpdateView).request(request).sync_send(); }
+pub fn update_view(request: UpdateViewRequest) { WorkspaceTestBuilder::new().event(UpdateView).request(request).sync_send(); }
 
-pub fn read_view(request: QueryViewRequest) -> View { AnnieTestBuilder::new().event(ReadView).request(request).sync_send().parse::<View>() }
+pub fn read_view(request: QueryViewRequest) -> View {
+    WorkspaceTestBuilder::new()
+        .event(ReadView)
+        .request(request)
+        .sync_send()
+        .parse::<View>()
+}

+ 12 - 2
rust-lib/flowy-workspace/tests/event/view_test.rs

@@ -1,12 +1,19 @@
 use crate::helper::*;
 
+use flowy_test::builder::UserTestBuilder;
 use flowy_workspace::entities::view::*;
 
 #[test]
-fn view_create() { let _ = create_view(); }
+fn view_create() {
+    let _ = UserTestBuilder::new().sign_up();
+
+    let workspace = create_workspace("Workspace", "");
+    let _ = create_view(&workspace.id);
+}
 
 #[test]
 fn view_set_trash_flag() {
+    let _ = UserTestBuilder::new().sign_up();
     let view_id = create_view_with_trash_flag();
     let query = QueryViewRequest::new(&view_id).set_is_trash(true);
     let _ = read_view(query);
@@ -15,13 +22,16 @@ fn view_set_trash_flag() {
 #[test]
 #[should_panic]
 fn view_set_trash_flag2() {
+    let _ = UserTestBuilder::new().sign_up();
+
     let view_id = create_view_with_trash_flag();
     let query = QueryViewRequest::new(&view_id);
     let _ = read_view(query);
 }
 
 fn create_view_with_trash_flag() -> String {
-    let view = create_view();
+    let workspace = create_workspace("Workspace", "");
+    let view = create_view(&workspace.id);
     let request = UpdateViewRequest {
         view_id: view.id.clone(),
         name: None,

+ 19 - 9
rust-lib/flowy-workspace/tests/event/workspace_test.rs

@@ -1,4 +1,5 @@
 use crate::helper::*;
+use flowy_test::builder::*;
 use flowy_workspace::{
     entities::workspace::{CreateWorkspaceRequest, QueryWorkspaceRequest, RepeatedWorkspace},
     event::WorkspaceEvent::*,
@@ -10,11 +11,12 @@ fn workspace_create_success() { let _ = create_workspace("First workspace", "");
 
 #[test]
 fn workspace_read_all() {
+    let _ = UserTestBuilder::new().sign_up();
     let _ = create_workspace("Workspace A", "workspace_create_and_then_get_workspace_success");
-    let request = QueryWorkspaceRequest::new();
-    let workspaces = AnnieTestBuilder::new()
+
+    let workspaces = WorkspaceTestBuilder::new()
         .event(ReadWorkspaces)
-        .request(request)
+        .request(QueryWorkspaceRequest::new())
         .sync_send()
         .parse::<RepeatedWorkspace>();
 
@@ -42,11 +44,15 @@ fn workspace_create_with_apps() {
 #[test]
 fn workspace_create_with_invalid_name() {
     for name in invalid_workspace_name_test_case() {
-        let builder = AnnieTestBuilder::new();
+        let _ = UserTestBuilder::new().sign_up();
         let request = CreateWorkspaceRequest { name, desc: "".to_owned() };
-
         assert_eq!(
-            builder.event(CreateWorkspace).request(request).sync_send().error().code,
+            WorkspaceTestBuilder::new()
+                .event(CreateWorkspace)
+                .request(request)
+                .sync_send()
+                .error()
+                .code,
             ErrorCode::WorkspaceNameInvalid
         )
     }
@@ -54,12 +60,16 @@ fn workspace_create_with_invalid_name() {
 
 #[test]
 fn workspace_update_with_invalid_name() {
+    let _ = UserTestBuilder::new().sign_up();
     for name in invalid_workspace_name_test_case() {
-        let builder = AnnieTestBuilder::new();
         let request = CreateWorkspaceRequest { name, desc: "".to_owned() };
-
         assert_eq!(
-            builder.event(CreateWorkspace).request(request).sync_send().error().code,
+            WorkspaceTestBuilder::new()
+                .event(CreateWorkspace)
+                .request(request)
+                .sync_send()
+                .error()
+                .code,
             ErrorCode::WorkspaceNameInvalid
         )
     }