Browse Source

extract tester as trait

appflowy 3 years ago
parent
commit
bab373ba35
32 changed files with 461 additions and 369 deletions
  1. 2 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/errors.pbenum.dart
  2. 2 1
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/errors.pbjson.dart
  3. 2 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbenum.dart
  4. 2 1
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbjson.dart
  5. 1 1
      rust-lib/dart-ffi/src/lib.rs
  6. 1 1
      rust-lib/flowy-derive/tests/progress.rs
  7. 12 2
      rust-lib/flowy-dispatch/src/dispatch.rs
  8. 1 10
      rust-lib/flowy-dispatch/src/request/payload.rs
  9. 1 1
      rust-lib/flowy-dispatch/tests/api/module.rs
  10. 7 6
      rust-lib/flowy-sdk/src/module.rs
  11. 82 0
      rust-lib/flowy-test/src/builder.rs
  12. 43 0
      rust-lib/flowy-test/src/helper.rs
  13. 8 240
      rust-lib/flowy-test/src/lib.rs
  14. 176 0
      rust-lib/flowy-test/src/tester.rs
  15. 2 2
      rust-lib/flowy-user/src/handlers/auth_handler.rs
  16. 3 3
      rust-lib/flowy-user/src/handlers/user_handler.rs
  17. 1 2
      rust-lib/flowy-user/src/services/user_session/user_server.rs
  18. 49 61
      rust-lib/flowy-user/src/services/user_session/user_session.rs
  19. 1 1
      rust-lib/flowy-user/tests/event/helper.rs
  20. 1 1
      rust-lib/flowy-user/tests/event/main.rs
  21. 1 1
      rust-lib/flowy-user/tests/event/user_status_test.rs
  22. 0 0
      rust-lib/flowy-user/tests/event/user_update_test.rs
  23. 2 0
      rust-lib/flowy-workspace/src/entities/workspace/mod.rs
  24. 6 0
      rust-lib/flowy-workspace/src/entities/workspace/workspace_user_detail.rs
  25. 3 0
      rust-lib/flowy-workspace/src/errors.rs
  26. 4 0
      rust-lib/flowy-workspace/src/event.rs
  27. 1 1
      rust-lib/flowy-workspace/src/module.rs
  28. 33 27
      rust-lib/flowy-workspace/src/protobuf/model/errors.rs
  29. 11 6
      rust-lib/flowy-workspace/src/protobuf/model/event.rs
  30. 1 0
      rust-lib/flowy-workspace/src/protobuf/proto/errors.proto
  31. 1 0
      rust-lib/flowy-workspace/src/protobuf/proto/event.proto
  32. 1 1
      rust-lib/flowy-workspace/tests/event/helper.rs

+ 2 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/errors.pbenum.dart

@@ -18,6 +18,7 @@ class WorkspaceErrorCode extends $pb.ProtobufEnum {
   static const WorkspaceErrorCode DatabaseConnectionFail = WorkspaceErrorCode._(5, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DatabaseConnectionFail');
   static const WorkspaceErrorCode DatabaseInternalError = WorkspaceErrorCode._(6, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DatabaseInternalError');
   static const WorkspaceErrorCode UserInternalError = WorkspaceErrorCode._(10, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserInternalError');
+  static const WorkspaceErrorCode UserNotLoginYet = WorkspaceErrorCode._(11, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserNotLoginYet');
 
   static const $core.List<WorkspaceErrorCode> values = <WorkspaceErrorCode> [
     Unknown,
@@ -28,6 +29,7 @@ class WorkspaceErrorCode extends $pb.ProtobufEnum {
     DatabaseConnectionFail,
     DatabaseInternalError,
     UserInternalError,
+    UserNotLoginYet,
   ];
 
   static final $core.Map<$core.int, WorkspaceErrorCode> _byValue = $pb.ProtobufEnum.initByValue(values);

+ 2 - 1
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/errors.pbjson.dart

@@ -20,11 +20,12 @@ const WorkspaceErrorCode$json = const {
     const {'1': 'DatabaseConnectionFail', '2': 5},
     const {'1': 'DatabaseInternalError', '2': 6},
     const {'1': 'UserInternalError', '2': 10},
+    const {'1': 'UserNotLoginYet', '2': 11},
   ],
 };
 
 /// Descriptor for `WorkspaceErrorCode`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List workspaceErrorCodeDescriptor = $convert.base64Decode('ChJXb3Jrc3BhY2VFcnJvckNvZGUSCwoHVW5rbm93bhAAEhgKFFdvcmtzcGFjZU5hbWVJbnZhbGlkEAESFgoSV29ya3NwYWNlSWRJbnZhbGlkEAISGAoUQXBwQ29sb3JTdHlsZUludmFsaWQQAxIQCgxBcHBJZEludmFsaWQQBBIaChZEYXRhYmFzZUNvbm5lY3Rpb25GYWlsEAUSGQoVRGF0YWJhc2VJbnRlcm5hbEVycm9yEAYSFQoRVXNlckludGVybmFsRXJyb3IQCg==');
+final $typed_data.Uint8List workspaceErrorCodeDescriptor = $convert.base64Decode('ChJXb3Jrc3BhY2VFcnJvckNvZGUSCwoHVW5rbm93bhAAEhgKFFdvcmtzcGFjZU5hbWVJbnZhbGlkEAESFgoSV29ya3NwYWNlSWRJbnZhbGlkEAISGAoUQXBwQ29sb3JTdHlsZUludmFsaWQQAxIQCgxBcHBJZEludmFsaWQQBBIaChZEYXRhYmFzZUNvbm5lY3Rpb25GYWlsEAUSGQoVRGF0YWJhc2VJbnRlcm5hbEVycm9yEAYSFQoRVXNlckludGVybmFsRXJyb3IQChITCg9Vc2VyTm90TG9naW5ZZXQQCw==');
 @$core.Deprecated('Use workspaceErrorDescriptor instead')
 const WorkspaceError$json = const {
   '1': 'WorkspaceError',

+ 2 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbenum.dart

@@ -11,9 +11,11 @@ import 'package:protobuf/protobuf.dart' as $pb;
 
 class WorkspaceEvent extends $pb.ProtobufEnum {
   static const WorkspaceEvent CreateWorkspace = WorkspaceEvent._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateWorkspace');
+  static const WorkspaceEvent GetWorkspaceUserDetail = WorkspaceEvent._(100, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetWorkspaceUserDetail');
 
   static const $core.List<WorkspaceEvent> values = <WorkspaceEvent> [
     CreateWorkspace,
+    GetWorkspaceUserDetail,
   ];
 
   static final $core.Map<$core.int, WorkspaceEvent> _byValue = $pb.ProtobufEnum.initByValue(values);

+ 2 - 1
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbjson.dart

@@ -13,8 +13,9 @@ const WorkspaceEvent$json = const {
   '1': 'WorkspaceEvent',
   '2': const [
     const {'1': 'CreateWorkspace', '2': 0},
+    const {'1': 'GetWorkspaceUserDetail', '2': 100},
   ],
 };
 
 /// Descriptor for `WorkspaceEvent`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List workspaceEventDescriptor = $convert.base64Decode('Cg5Xb3Jrc3BhY2VFdmVudBITCg9DcmVhdGVXb3Jrc3BhY2UQAA==');
+final $typed_data.Uint8List workspaceEventDescriptor = $convert.base64Decode('Cg5Xb3Jrc3BhY2VFdmVudBITCg9DcmVhdGVXb3Jrc3BhY2UQABIaChZHZXRXb3Jrc3BhY2VVc2VyRGV0YWlsEGQ=');

+ 1 - 1
rust-lib/dart-ffi/src/lib.rs

@@ -40,7 +40,7 @@ pub extern "C" fn async_command(port: i64, input: *const u8, len: usize) {
         port
     );
 
-    let _ = EventDispatch::async_send(request, move |resp: EventResponse| {
+    let _ = EventDispatch::async_send_with_callback(request, move |resp: EventResponse| {
         log::trace!("[FFI]: Post data to dart through {} port", port);
         Box::pin(post_to_flutter(resp, port))
     });

+ 1 - 1
rust-lib/flowy-derive/tests/progress.rs

@@ -1,5 +1,5 @@
 #[tokio::test]
 async fn tests() {
-    let t = trybuild::TestCases::new();
+    let _t = trybuild::TestCases::new();
     // t.pass("tests/protobuf_enum.rs");
 }

+ 12 - 2
rust-lib/flowy-dispatch/src/dispatch.rs

@@ -38,7 +38,17 @@ impl EventDispatch {
         *(EVENT_DISPATCH.write().unwrap()) = Some(dispatch);
     }
 
-    pub fn async_send<Req, Callback>(request: Req, callback: Callback) -> DispatchFuture
+    pub fn async_send<Req>(request: Req) -> DispatchFuture
+    where
+        Req: std::convert::Into<ModuleRequest>,
+    {
+        EventDispatch::async_send_with_callback(request, |_| Box::pin(async {}))
+    }
+
+    pub fn async_send_with_callback<Req, Callback>(
+        request: Req,
+        callback: Callback,
+    ) -> DispatchFuture
     where
         Req: std::convert::Into<ModuleRequest>,
         Callback: FnOnce(EventResponse) -> BoxFuture<'static, ()> + 'static + Send + Sync,
@@ -83,7 +93,7 @@ impl EventDispatch {
 
     pub fn sync_send(request: ModuleRequest) -> EventResponse {
         futures::executor::block_on(async {
-            EventDispatch::async_send(request, |_| Box::pin(async {})).await
+            EventDispatch::async_send_with_callback(request, |_| Box::pin(async {})).await
         })
     }
 }

+ 1 - 10
rust-lib/flowy-dispatch/src/request/payload.rs

@@ -1,3 +1,4 @@
+use crate::byte_trait::ToBytes;
 use bytes::Bytes;
 use std::{fmt, fmt::Formatter};
 
@@ -48,16 +49,6 @@ impl std::convert::Into<Payload> for Vec<u8> {
     fn into(self) -> Payload { Payload::Bytes(self) }
 }
 
-// = note: conflicting implementation in crate `core`:
-// - impl<T, U> TryInto<U> for T where U: TryFrom<T>;
-//
-// impl std::convert::TryInto<Payload> for Vec<u8> {
-//     type Error = String;
-//     fn try_into(self) -> Result<Payload, Self::Error> {
-//         Ok(Payload::Bytes(self))
-//     }
-// }
-
 impl std::convert::Into<Payload> for &str {
     fn into(self) -> Payload { self.to_string().into() }
 }

+ 1 - 1
rust-lib/flowy-dispatch/tests/api/module.rs

@@ -10,7 +10,7 @@ async fn test_init() {
     init_dispatch(|| vec![Module::new().event(event, hello)]);
 
     let request = ModuleRequest::new(event);
-    let _ = EventDispatch::async_send(request, |resp| {
+    let _ = EventDispatch::async_send_with_callback(request, |resp| {
         Box::pin(async move {
             dbg!(&resp);
         })

+ 7 - 6
rust-lib/flowy-sdk/src/module.rs

@@ -3,7 +3,7 @@ use flowy_user::prelude::*;
 
 use crate::flowy_server::{ArcFlowyServer, MockFlowyServer};
 use flowy_database::DBConnection;
-use flowy_user::errors::UserError;
+
 use flowy_workspace::prelude::*;
 use std::sync::Arc;
 
@@ -11,7 +11,7 @@ pub struct ModuleConfig {
     pub root: String,
 }
 
-pub fn build_modules(config: ModuleConfig, server: ArcFlowyServer) -> Vec<Module> {
+pub fn build_modules(config: ModuleConfig, _server: ArcFlowyServer) -> Vec<Module> {
     let user_session = Arc::new(
         UserSessionBuilder::new()
             .root_dir(&config.root)
@@ -33,14 +33,15 @@ pub struct WorkspaceUserImpl {
 }
 
 impl WorkspaceUser for WorkspaceUserImpl {
-    fn set_current_workspace(&self, id: &str) { unimplemented!() }
+    fn set_current_workspace(&self, id: &str) { UserSession::set_current_workspace(id); }
 
     fn get_current_workspace(&self) -> Result<String, WorkspaceError> {
-        self.user_session.get_current_workspace().map_err(|e| {
-            ErrorBuilder::new(WorkspaceErrorCode::UserInternalError)
+        let user_detail = self.user_session.user_detail().map_err(|e| {
+            ErrorBuilder::new(WorkspaceErrorCode::UserNotLoginYet)
                 .error(e)
                 .build()
-        })
+        })?;
+        Ok(user_detail.id)
     }
 
     fn db_connection(&self) -> Result<DBConnection, WorkspaceError> {

+ 82 - 0
rust-lib/flowy-test/src/builder.rs

@@ -0,0 +1,82 @@
+use crate::{helper::new_user_after_login, init_sdk, tester::Tester};
+use flowy_dispatch::prelude::{EventDispatch, FromBytes, ModuleRequest, ToBytes};
+use flowy_user::{entities::UserDetail, event::UserEvent::SignOut};
+use std::{
+    fmt::{Debug, Display},
+    hash::Hash,
+};
+
+pub struct TestBuilder<Error> {
+    login: Option<bool>,
+    inner: Option<Tester<Error>>,
+    pub user_detail: Option<UserDetail>,
+}
+
+impl<Error> TestBuilder<Error>
+where
+    Error: FromBytes + Debug,
+{
+    pub fn new() -> Self {
+        TestBuilder::<Error> {
+            login: None,
+            inner: None,
+            user_detail: None,
+        }
+    }
+
+    pub fn login(mut self) -> Self {
+        let user_detail = new_user_after_login();
+        self.user_detail = Some(user_detail);
+        self
+    }
+
+    pub fn logout(self) -> Self {
+        init_sdk();
+        let _ = EventDispatch::sync_send(ModuleRequest::new(SignOut));
+        self
+    }
+
+    pub fn event<E>(mut self, event: E) -> Self
+    where
+        E: Eq + Hash + Debug + Clone + Display,
+    {
+        self.inner = Some(Tester::<Error>::new(event));
+        self
+    }
+
+    pub fn request<P>(mut self, request: P) -> Self
+    where
+        P: ToBytes,
+    {
+        self.inner.as_mut().unwrap().set_request(request);
+        self
+    }
+
+    pub fn sync_send(mut self) -> Self {
+        self.inner.as_mut().unwrap().sync_send();
+        self
+    }
+
+    pub fn parse<R>(mut self) -> R
+    where
+        R: FromBytes,
+    {
+        let inner = self.inner.take().unwrap();
+        inner.parse::<R>()
+    }
+
+    pub fn error(mut self) -> Error {
+        let inner = self.inner.take().unwrap();
+        inner.error()
+    }
+
+    pub fn assert_error(mut self) -> Self {
+        self.inner.as_mut().unwrap().assert_error();
+        self
+    }
+
+    pub fn assert_success(mut self) -> Self {
+        self.inner.as_mut().unwrap().assert_success();
+        self
+    }
+}

+ 43 - 0
rust-lib/flowy-test/src/helper.rs

@@ -0,0 +1,43 @@
+use crate::{init_sdk, tester::Tester};
+use flowy_dispatch::prelude::*;
+use flowy_user::{
+    entities::{SignInRequest, UserDetail},
+    errors::UserError,
+    event::UserEvent::{SignIn, SignOut},
+};
+use std::{fs, path::PathBuf};
+
+pub fn root_dir() -> String {
+    // https://doc.rust-lang.org/cargo/reference/environment-variables.html
+    let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or("./".to_owned());
+    let mut path_buf = fs::canonicalize(&PathBuf::from(&manifest_dir)).unwrap();
+    path_buf.pop(); // rust-lib
+    path_buf.push("flowy-test");
+    path_buf.push("temp");
+    path_buf.push("flowy");
+
+    let root_dir = path_buf.to_str().unwrap().to_string();
+    if !std::path::Path::new(&root_dir).exists() {
+        std::fs::create_dir_all(&root_dir).unwrap();
+    }
+    root_dir
+}
+
+pub fn new_user_after_login() -> UserDetail {
+    init_sdk();
+    let _ = EventDispatch::sync_send(ModuleRequest::new(SignOut));
+    let request = SignInRequest {
+        email: valid_email(),
+        password: valid_password(),
+    };
+
+    let mut tester = Tester::<UserError>::new(SignIn);
+    tester.set_request(request);
+    tester.sync_send();
+
+    tester.parse::<UserDetail>()
+}
+
+pub(crate) fn valid_email() -> String { "[email protected]".to_string() }
+
+pub(crate) fn valid_password() -> String { "HelloWorld!123".to_string() }

+ 8 - 240
rust-lib/flowy-test/src/lib.rs

@@ -1,25 +1,14 @@
-use flowy_dispatch::prelude::*;
-pub use flowy_sdk::*;
-use flowy_user::{
-    errors::UserError,
-    event::UserEvent::{SignIn, SignOut},
-    prelude::*,
-};
-use std::{
-    convert::TryFrom,
-    fmt::{Debug, Display},
-    fs,
-    hash::Hash,
-    marker::PhantomData,
-    path::PathBuf,
-    sync::Once,
-    thread,
-};
+mod builder;
+mod helper;
+mod tester;
+
+use crate::helper::root_dir;
+use flowy_sdk::FlowySDK;
+use std::{sync::Once, thread};
 
 pub mod prelude {
-    pub use crate::Tester;
+    pub use crate::{builder::TestBuilder, tester::Tester};
     pub use flowy_dispatch::prelude::*;
-    pub use std::convert::TryFrom;
 }
 
 static INIT: Once = Once::new();
@@ -30,224 +19,3 @@ pub fn init_sdk() {
         FlowySDK::new(&root_dir).construct();
     });
 }
-
-fn root_dir() -> String {
-    // https://doc.rust-lang.org/cargo/reference/environment-variables.html
-    let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or("./".to_owned());
-    let mut path_buf = fs::canonicalize(&PathBuf::from(&manifest_dir)).unwrap();
-    path_buf.pop(); // rust-lib
-    path_buf.push("flowy-test");
-    path_buf.push("temp");
-    path_buf.push("flowy");
-
-    let root_dir = path_buf.to_str().unwrap().to_string();
-    if !std::path::Path::new(&root_dir).exists() {
-        std::fs::create_dir_all(&root_dir).unwrap();
-    }
-    root_dir
-}
-
-pub struct TestBuilder<Error> {
-    login: Option<bool>,
-    inner: Option<Tester<Error>>,
-    pub user_detail: Option<UserDetail>,
-}
-
-impl<Error> TestBuilder<Error>
-where
-    Error: FromBytes + Debug,
-{
-    pub fn new() -> Self {
-        TestBuilder::<Error> {
-            login: None,
-            inner: None,
-            user_detail: None,
-        }
-    }
-
-    pub fn login(mut self) -> Self {
-        let user_detail = new_user_after_login();
-        self.user_detail = Some(user_detail);
-        self
-    }
-
-    pub fn logout(self) -> Self {
-        init_sdk();
-        let _ = EventDispatch::sync_send(ModuleRequest::new(SignOut));
-        self
-    }
-
-    pub fn event<E>(mut self, event: E) -> Self
-    where
-        E: Eq + Hash + Debug + Clone + Display,
-    {
-        self.inner = Some(Tester::<Error>::new(event));
-        self
-    }
-
-    pub fn request<P>(mut self, request: P) -> Self
-    where
-        P: ToBytes,
-    {
-        let mut inner = self.inner.unwrap();
-        self.inner = Some(inner.request(request));
-        self
-    }
-
-    pub fn sync_send(mut self) -> Self {
-        let inner = self.inner.take().unwrap();
-        self.inner = Some(inner.sync_send());
-        self
-    }
-
-    pub fn parse<R>(mut self) -> R
-    where
-        R: FromBytes,
-    {
-        let inner = self.inner.take().unwrap();
-        inner.parse::<R>()
-    }
-
-    pub fn error(mut self) -> Error {
-        let inner = self.inner.take().unwrap();
-        inner.error()
-    }
-
-    pub fn assert_error(mut self) -> Self {
-        let inner = self.inner.take().unwrap();
-        self.inner = Some(inner.assert_error());
-        self
-    }
-
-    pub fn assert_success(mut self) -> Self {
-        let inner = self.inner.take().unwrap();
-        self.inner = Some(inner.assert_success());
-        self
-    }
-}
-
-pub struct Tester<Error> {
-    inner_request: Option<ModuleRequest>,
-    assert_status_code: Option<StatusCode>,
-    response: Option<EventResponse>,
-    err_phantom: PhantomData<Error>,
-    user_detail: Option<UserDetail>,
-}
-
-impl<Error> Tester<Error>
-where
-    Error: FromBytes + Debug,
-{
-    pub fn new<E>(event: E) -> Self
-    where
-        E: Eq + Hash + Debug + Clone + Display,
-    {
-        init_sdk();
-        log::trace!(
-            "{:?} thread started: thread_id= {}",
-            thread::current(),
-            thread_id::get()
-        );
-
-        Self {
-            inner_request: Some(ModuleRequest::new(event)),
-            assert_status_code: None,
-            response: None,
-            err_phantom: PhantomData,
-            user_detail: None,
-        }
-    }
-
-    pub fn request<P>(mut self, request: P) -> Self
-    where
-        P: ToBytes,
-    {
-        let mut inner_request = self.inner_request.take().unwrap();
-        let bytes = request.into_bytes().unwrap();
-        inner_request = inner_request.payload(bytes);
-        self.inner_request = Some(inner_request);
-        self
-    }
-
-    pub fn assert_status_code(mut self, status_code: StatusCode) -> Self {
-        self.assert_status_code = Some(status_code);
-        self
-    }
-
-    pub fn assert_error(mut self) -> Self {
-        self.assert_status_code = Some(StatusCode::Err);
-        self
-    }
-
-    pub fn assert_success(mut self) -> Self {
-        self.assert_status_code = Some(StatusCode::Ok);
-        self
-    }
-
-    pub async fn async_send(mut self) -> Self {
-        assert_eq!(self.inner_request.is_some(), true, "must set event");
-
-        let resp =
-            EventDispatch::async_send(self.inner_request.take().unwrap(), |_| Box::pin(async {}))
-                .await;
-        check(&resp, &self.assert_status_code);
-        self.response = Some(resp);
-        self
-    }
-
-    pub fn sync_send(mut self) -> Self {
-        let resp = EventDispatch::sync_send(self.inner_request.take().unwrap());
-        check(&resp, &self.assert_status_code);
-        self.response = Some(resp);
-        self
-    }
-
-    pub fn parse<R>(self) -> R
-    where
-        R: FromBytes,
-    {
-        let response = self.response.unwrap();
-        match response.parse::<R, Error>() {
-            Ok(Ok(data)) => data,
-            Ok(Err(e)) => panic!("parse failed: {:?}", e),
-            Err(e) => panic!("Internal error: {:?}", e),
-        }
-    }
-
-    pub fn error(self) -> Error {
-        let response = self.response.unwrap();
-        assert_eq!(response.status_code, StatusCode::Err);
-        <Data<Error>>::try_from(response.payload)
-            .unwrap()
-            .into_inner()
-    }
-}
-
-fn check(response: &EventResponse, status_code: &Option<StatusCode>) {
-    if let Some(ref status_code) = status_code {
-        if &response.status_code != status_code {
-            eprintln!("{:#?}", response);
-        }
-        assert_eq!(&response.status_code, status_code)
-    }
-}
-
-fn new_user_after_login() -> UserDetail {
-    init_sdk();
-    let _ = EventDispatch::sync_send(ModuleRequest::new(SignOut));
-    let request = SignInRequest {
-        email: valid_email(),
-        password: valid_password(),
-    };
-
-    let user_detail = Tester::<UserError>::new(SignIn)
-        .request(request)
-        .sync_send()
-        .parse::<UserDetail>();
-
-    user_detail
-}
-
-pub(crate) fn valid_email() -> String { "[email protected]".to_string() }
-
-pub(crate) fn valid_password() -> String { "HelloWorld!123".to_string() }

+ 176 - 0
rust-lib/flowy-test/src/tester.rs

@@ -0,0 +1,176 @@
+use crate::init_sdk;
+use flowy_dispatch::prelude::*;
+pub use flowy_sdk::*;
+use flowy_user::prelude::*;
+use std::{
+    convert::TryFrom,
+    fmt::{Debug, Display},
+    hash::Hash,
+    marker::PhantomData,
+    thread,
+};
+
+pub struct Tester<Error> {
+    inner_request: Option<ModuleRequest>,
+    assert_status_code: Option<StatusCode>,
+    response: Option<EventResponse>,
+    err_phantom: PhantomData<Error>,
+    user_detail: Option<UserDetail>,
+}
+
+impl<Error> Tester<Error>
+where
+    Error: FromBytes + Debug,
+{
+    pub fn new<E>(event: E) -> Self
+    where
+        E: Eq + Hash + Debug + Clone + Display,
+    {
+        init_sdk();
+        log::trace!(
+            "{:?} thread started: thread_id= {}",
+            thread::current(),
+            thread_id::get()
+        );
+
+        Self {
+            inner_request: Some(ModuleRequest::new(event)),
+            assert_status_code: None,
+            response: None,
+            err_phantom: PhantomData,
+            user_detail: None,
+        }
+    }
+
+    pub fn set_request<P>(&mut self, request: P)
+    where
+        P: ToBytes,
+    {
+        let mut inner_request = self.inner_request.take().unwrap();
+        let bytes = request.into_bytes().unwrap();
+        inner_request = inner_request.payload(bytes);
+        self.inner_request = Some(inner_request);
+    }
+
+    pub fn assert_error(&mut self) { self.assert_status_code = Some(StatusCode::Err); }
+
+    pub fn assert_success(&mut self) { self.assert_status_code = Some(StatusCode::Ok); }
+
+    pub async fn async_send(&mut self) {
+        assert_eq!(self.inner_request.is_some(), true, "must set event");
+
+        let resp = EventDispatch::async_send(self.inner_request.take().unwrap()).await;
+        self.response = Some(resp);
+    }
+
+    pub fn sync_send(&mut self) {
+        let resp = EventDispatch::sync_send(self.inner_request.take().unwrap());
+        self.response = Some(resp);
+    }
+
+    pub fn parse<R>(self) -> R
+    where
+        R: FromBytes,
+    {
+        let response = self.response.unwrap();
+        match response.parse::<R, Error>() {
+            Ok(Ok(data)) => data,
+            Ok(Err(e)) => panic!("parse failed: {:?}", e),
+            Err(e) => panic!("Internal error: {:?}", e),
+        }
+    }
+
+    pub fn error(self) -> Error {
+        let response = self.response.unwrap();
+        assert_eq!(response.status_code, StatusCode::Err);
+        <Data<Error>>::try_from(response.payload)
+            .unwrap()
+            .into_inner()
+    }
+}
+
+pub struct RandomUserTester<Error> {
+    context: TesterContext,
+    err_phantom: PhantomData<Error>,
+}
+
+impl<Error> RandomUserTester<Error>
+where
+    Error: FromBytes + Debug,
+{
+    pub fn new() -> Self {
+        Self {
+            context: TesterContext::default(),
+            err_phantom: PhantomData,
+        }
+    }
+}
+
+impl<Error> TesterTrait for RandomUserTester<Error>
+where
+    Error: FromBytes + Debug,
+{
+    type Error = Error;
+
+    fn context(&mut self) -> &mut TesterContext { &mut self.context }
+}
+
+pub struct TesterContext {
+    request: Option<ModuleRequest>,
+    status_code: StatusCode,
+    response: Option<EventResponse>,
+}
+
+impl std::default::Default for TesterContext {
+    fn default() -> Self {
+        Self {
+            request: None,
+            status_code: StatusCode::Ok,
+            response: None,
+        }
+    }
+}
+
+pub trait TesterTrait {
+    type Error: FromBytes + Debug;
+
+    fn context(&mut self) -> &mut TesterContext;
+
+    fn assert_error(&mut self) { self.context().status_code = StatusCode::Err; }
+
+    fn assert_success(&mut self) { self.context().status_code = StatusCode::Ok; }
+
+    fn set_payload<P>(&mut self, payload: P)
+    where
+        P: ToBytes,
+    {
+        let bytes = payload.into_bytes().unwrap();
+        let mut module_request = self.context().request.take().unwrap();
+        self.context().request = Some(module_request.payload(bytes));
+    }
+
+    fn sync_send(&mut self) {
+        let resp = EventDispatch::sync_send(self.context().request.take().unwrap());
+        self.context().response = Some(resp);
+    }
+
+    fn parse<R>(&mut self) -> R
+    where
+        R: FromBytes,
+    {
+        let response = self.context().response.clone().unwrap();
+        match response.parse::<R, Self::Error>() {
+            Ok(Ok(data)) => data,
+            Ok(Err(e)) => panic!("parse failed: {:?}", e),
+            Err(e) => panic!("Internal error: {:?}", e),
+        }
+    }
+
+    fn error(&mut self) -> Self::Error {
+        let response = self.context().response.clone().unwrap();
+        assert_eq!(response.status_code, StatusCode::Err);
+        <Data<Self::Error>>::try_from(response.payload)
+            .unwrap()
+            .into_inner()
+    }
+}

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

@@ -15,7 +15,7 @@ pub async fn user_sign_in_handler(
     session: ModuleData<Arc<UserSession>>,
 ) -> ResponseResult<UserDetail, UserError> {
     let params: SignInParams = data.into_inner().try_into()?;
-    let user = session.sign_in(params).await?;
+    let user = session.sign_in(params)?;
     let user_detail = UserDetail::from(user);
     response_ok(user_detail)
 }
@@ -33,7 +33,7 @@ pub async fn user_sign_up_handler(
     session: ModuleData<Arc<UserSession>>,
 ) -> ResponseResult<UserDetail, UserError> {
     let params: SignUpParams = data.into_inner().try_into()?;
-    let user = session.sign_up(params).await?;
+    let user = session.sign_up(params)?;
     let user_detail = UserDetail::from(user);
     response_ok(user_detail)
 }

+ 3 - 3
rust-lib/flowy-user/src/handlers/user_handler.rs

@@ -5,12 +5,12 @@ use std::{convert::TryInto, sync::Arc};
 pub async fn user_get_status_handler(
     session: ModuleData<Arc<UserSession>>,
 ) -> ResponseResult<UserDetail, UserError> {
-    let user_detail = session.current_user_detail()?;
+    let user_detail = session.user_detail()?;
     response_ok(user_detail)
 }
 
 pub async fn sign_out_handler(session: ModuleData<Arc<UserSession>>) -> Result<(), UserError> {
-    let _ = session.sign_out().await?;
+    let _ = session.sign_out()?;
     Ok(())
 }
 
@@ -19,6 +19,6 @@ pub async fn update_user_handler(
     session: ModuleData<Arc<UserSession>>,
 ) -> ResponseResult<UserDetail, UserError> {
     let params: UpdateUserParams = data.into_inner().try_into()?;
-    let user_detail = session.update_user(params).await?;
+    let user_detail = session.update_user(params)?;
     response_ok(user_detail)
 }

+ 1 - 2
rust-lib/flowy-user/src/services/user_session/user_server.rs

@@ -1,9 +1,8 @@
 use crate::{
     entities::{SignInParams, SignUpParams, UserDetail},
-    errors::{ErrorBuilder, UserError, UserErrorCode},
+    errors::UserError,
     sql_tables::User,
 };
-use flowy_infra::uuid;
 
 pub trait UserServer {
     fn sign_up(&self, params: SignUpParams) -> Result<User, UserError>;

+ 49 - 61
rust-lib/flowy-user/src/services/user_session/user_session.rs

@@ -10,14 +10,13 @@ use lazy_static::lazy_static;
 use std::sync::{Arc, RwLock};
 
 use crate::{
-    entities::{SignInParams, SignUpParams, UpdateUserParams, UserDetail},
+    entities::{SignInParams, SignUpParams, UpdateUserParams, UpdateUserRequest, UserDetail},
     errors::{ErrorBuilder, UserError, UserErrorCode},
-    event::UserEvent::GetStatus,
+    event::UserEvent::*,
     services::user_session::{database::UserDB, user_server::UserServer},
     sql_tables::{User, UserChangeset},
 };
-use flowy_dispatch::prelude::{Data, EventDispatch, ModuleRequest};
-use std::convert::TryFrom;
+use flowy_dispatch::prelude::{EventDispatch, ModuleRequest, ToBytes};
 
 pub struct UserSessionConfig {
     root_dir: String,
@@ -50,20 +49,25 @@ impl UserSession {
         }
     }
 
-    pub async fn sign_in(&self, params: SignInParams) -> Result<User, UserError> {
+    pub fn get_db_connection(&self) -> Result<DBConnection, UserError> {
+        let user_id = get_current_user_id()?;
+        self.database.get_connection(&user_id)
+    }
+
+    pub fn sign_in(&self, params: SignInParams) -> Result<User, UserError> {
         let user = self.server.sign_in(params)?;
         let _ = set_current_user_id(Some(user.id.clone()))?;
         self.save_user(user)
     }
 
-    pub async fn sign_up(&self, params: SignUpParams) -> Result<User, UserError> {
+    pub fn sign_up(&self, params: SignUpParams) -> Result<User, UserError> {
         let user = self.server.sign_up(params)?;
         let _ = set_current_user_id(Some(user.id.clone()))?;
         self.save_user(user)
     }
 
-    pub async fn sign_out(&self) -> Result<(), UserError> {
-        let user_id = self.current_user_id()?;
+    pub fn sign_out(&self) -> Result<(), UserError> {
+        let user_id = current_user_id()?;
         let conn = self.get_db_connection()?;
         let _ = diesel::delete(dsl::user_table.filter(dsl::id.eq(&user_id))).execute(&*conn)?;
 
@@ -77,17 +81,26 @@ impl UserSession {
         Ok(())
     }
 
-    pub async fn update_user(&self, params: UpdateUserParams) -> Result<UserDetail, UserError> {
+    fn save_user(&self, user: User) -> Result<User, UserError> {
+        let conn = self.get_db_connection()?;
+        let _ = diesel::insert_into(user_table::table)
+            .values(user.clone())
+            .execute(&*conn)?;
+
+        Ok(user)
+    }
+
+    pub fn update_user(&self, params: UpdateUserParams) -> Result<UserDetail, UserError> {
         let changeset = UserChangeset::new(params);
         let conn = self.get_db_connection()?;
         diesel_update_table!(user_table, changeset, conn);
 
-        let user_detail = self.current_user_detail()?;
+        let user_detail = self.user_detail()?;
         Ok(user_detail)
     }
 
-    pub fn current_user_detail(&self) -> Result<UserDetail, UserError> {
-        let user_id = self.current_user_id()?;
+    pub fn user_detail(&self) -> Result<UserDetail, UserError> {
+        let user_id = current_user_id()?;
         let conn = self.get_db_connection()?;
 
         let user = dsl::user_table
@@ -105,60 +118,35 @@ impl UserSession {
 
         Ok(UserDetail::from(user))
     }
-
-    pub fn get_db_connection(&self) -> Result<DBConnection, UserError> {
-        let user_id = get_current_user_id()?;
-        self.database.get_connection(&user_id)
-    }
-
-    pub fn set_current_workspace() {
-        unimplemented!()
-
-        // let request = SignInRequest {
-        //     email: valid_email(),
-        //     password: valid_password(),
-        // };
-        //
-        // let user_detail = Tester::<UserError>::new(SignIn)
-        //     .request(request)
-        //     .sync_send()
-        //     .parse::<UserDetail>();
-        //
-        // user_detail
-    }
-
-    #[allow(dead_code)]
-    pub fn get_current_workspace(&self) -> Result<String, UserError> {
-        // let response = EventDispatch::sync_send(ModuleRequest::new(GetStatus));
-        // let user_detail =
-        // <Data<UserDetail>>::try_from(response.payload).unwrap().into_inner();
-        let user_id = get_current_user_id()?;
-        let conn = self.get_db_connection()?;
-
-        let workspace = dsl::user_table
-            .filter(user_table::id.eq(&user_id))
-            .select(user_table::workspace)
-            .first::<String>(&*conn)?;
-
-        Ok(workspace)
-    }
 }
 
 impl UserSession {
-    fn save_user(&self, user: User) -> Result<User, UserError> {
-        let conn = self.get_db_connection()?;
-        let _ = diesel::insert_into(user_table::table)
-            .values(user.clone())
-            .execute(&*conn)?;
-
-        Ok(user)
+    pub async fn set_current_workspace(workspace: &str) -> Result<(), UserError> {
+        let user_id = current_user_id()?;
+        let payload: Vec<u8> = UpdateUserRequest {
+            id: user_id,
+            name: None,
+            email: None,
+            workspace: Some(workspace.to_owned()),
+            password: None,
+        }
+        .into_bytes()
+        .unwrap();
+
+        let request = ModuleRequest::new(UpdateUser).payload(payload);
+        let _user_detail = EventDispatch::async_send(request)
+            .await
+            .parse::<UserDetail, UserError>()
+            .unwrap()
+            .unwrap();
+        Ok(())
     }
+}
 
-    fn current_user_id(&self) -> Result<String, UserError> {
-        match KVStore::get_str(USER_ID_DISK_CACHE_KEY) {
-            None => Err(ErrorBuilder::new(UserErrorCode::UserNotLoginYet).build()),
-            Some(user_id) => Ok(user_id),
-        }
+fn current_user_id() -> Result<String, UserError> {
+    match KVStore::get_str(USER_ID_DISK_CACHE_KEY) {
+        None => Err(ErrorBuilder::new(UserErrorCode::UserNotLoginYet).build()),
+        Some(user_id) => Ok(user_id),
     }
 }
 

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

@@ -1,4 +1,4 @@
-use flowy_test::{TestBuilder, Tester};
+use flowy_test::prelude::TestBuilder;
 use flowy_user::errors::UserError;
 
 pub type UserTestBuilder = TestBuilder<UserError>;

+ 1 - 1
rust-lib/flowy-user/tests/event/main.rs

@@ -1,5 +1,5 @@
 mod helper;
 mod sign_in_test;
 mod sign_up_test;
-mod user_detail_test;
 mod user_status_test;
+mod user_update_test;

+ 1 - 1
rust-lib/flowy-user/tests/event/user_status_test.rs

@@ -1,5 +1,5 @@
 use crate::helper::*;
-use flowy_user::{errors::UserErrorCode, event::UserEvent::*, prelude::*};
+use flowy_user::{event::UserEvent::*, prelude::*};
 use serial_test::*;
 
 #[test]

+ 0 - 0
rust-lib/flowy-user/tests/event/user_detail_test.rs → rust-lib/flowy-user/tests/event/user_update_test.rs


+ 2 - 0
rust-lib/flowy-workspace/src/entities/workspace/mod.rs

@@ -1,8 +1,10 @@
 pub use workspace_create::*;
 pub use workspace_query::*;
 pub use workspace_update::*;
+pub use workspace_user_detail::*;
 
 pub(crate) mod parser;
 mod workspace_create;
 mod workspace_query;
 mod workspace_update;
+mod workspace_user_detail;

+ 6 - 0
rust-lib/flowy-workspace/src/entities/workspace/workspace_user_detail.rs

@@ -0,0 +1,6 @@
+pub struct WorkspaceUserQueryRequest {
+    fetch_owner: bool,
+    fetch_all: bool,
+}
+
+pub struct WorkspaceUserDetail {}

+ 3 - 0
rust-lib/flowy-workspace/src/errors.rs

@@ -46,6 +46,9 @@ pub enum WorkspaceErrorCode {
 
     #[display(fmt = "User internal error")]
     UserInternalError    = 10,
+
+    #[display(fmt = "User not login yet")]
+    UserNotLoginYet      = 11,
 }
 
 impl std::default::Default for WorkspaceErrorCode {

+ 4 - 0
rust-lib/flowy-workspace/src/event.rs

@@ -7,4 +7,8 @@ pub enum WorkspaceEvent {
     #[display(fmt = "Create workspace")]
     #[event(input = "CreateSpaceRequest", output = "WorkspaceDetail")]
     CreateWorkspace = 0,
+
+    #[display(fmt = "Get workspace user")]
+    #[event(output = "UserDetail")]
+    GetWorkspaceUserDetail = 100,
 }

+ 1 - 1
rust-lib/flowy-workspace/src/module.rs

@@ -6,7 +6,7 @@ use crate::{
     handlers::create_workspace,
     services::{AppController, WorkspaceController},
 };
-use flowy_database::{DBConnection, UserDatabaseConnection};
+use flowy_database::DBConnection;
 use std::sync::Arc;
 
 pub trait WorkspaceUser: Send + Sync {

+ 33 - 27
rust-lib/flowy-workspace/src/protobuf/model/errors.rs

@@ -223,6 +223,7 @@ pub enum WorkspaceErrorCode {
     DatabaseConnectionFail = 5,
     DatabaseInternalError = 6,
     UserInternalError = 10,
+    UserNotLoginYet = 11,
 }
 
 impl ::protobuf::ProtobufEnum for WorkspaceErrorCode {
@@ -240,6 +241,7 @@ impl ::protobuf::ProtobufEnum for WorkspaceErrorCode {
             5 => ::std::option::Option::Some(WorkspaceErrorCode::DatabaseConnectionFail),
             6 => ::std::option::Option::Some(WorkspaceErrorCode::DatabaseInternalError),
             10 => ::std::option::Option::Some(WorkspaceErrorCode::UserInternalError),
+            11 => ::std::option::Option::Some(WorkspaceErrorCode::UserNotLoginYet),
             _ => ::std::option::Option::None
         }
     }
@@ -254,6 +256,7 @@ impl ::protobuf::ProtobufEnum for WorkspaceErrorCode {
             WorkspaceErrorCode::DatabaseConnectionFail,
             WorkspaceErrorCode::DatabaseInternalError,
             WorkspaceErrorCode::UserInternalError,
+            WorkspaceErrorCode::UserNotLoginYet,
         ];
         values
     }
@@ -284,36 +287,39 @@ impl ::protobuf::reflect::ProtobufValue for WorkspaceErrorCode {
 static file_descriptor_proto_data: &'static [u8] = b"\
     \n\x0cerrors.proto\"K\n\x0eWorkspaceError\x12'\n\x04code\x18\x01\x20\x01\
     (\x0e2\x13.WorkspaceErrorCodeR\x04code\x12\x10\n\x03msg\x18\x02\x20\x01(\
-    \tR\x03msg*\xcd\x01\n\x12WorkspaceErrorCode\x12\x0b\n\x07Unknown\x10\0\
+    \tR\x03msg*\xe2\x01\n\x12WorkspaceErrorCode\x12\x0b\n\x07Unknown\x10\0\
     \x12\x18\n\x14WorkspaceNameInvalid\x10\x01\x12\x16\n\x12WorkspaceIdInval\
     id\x10\x02\x12\x18\n\x14AppColorStyleInvalid\x10\x03\x12\x10\n\x0cAppIdI\
     nvalid\x10\x04\x12\x1a\n\x16DatabaseConnectionFail\x10\x05\x12\x19\n\x15\
-    DatabaseInternalError\x10\x06\x12\x15\n\x11UserInternalError\x10\nJ\xf8\
-    \x03\n\x06\x12\x04\0\0\x0f\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\
-    \x04\0\x12\x04\x02\0\x05\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x16\n\
-    \x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x20\n\x0c\n\x05\x04\0\x02\0\x06\
-    \x12\x03\x03\x04\x16\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\x17\x1b\n\
-    \x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x1e\x1f\n\x0b\n\x04\x04\0\x02\x01\
-    \x12\x03\x04\x04\x13\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x04\x04\n\n\
-    \x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x04\x0b\x0e\n\x0c\n\x05\x04\0\x02\
-    \x01\x03\x12\x03\x04\x11\x12\n\n\n\x02\x05\0\x12\x04\x06\0\x0f\x01\n\n\n\
-    \x03\x05\0\x01\x12\x03\x06\x05\x17\n\x0b\n\x04\x05\0\x02\0\x12\x03\x07\
-    \x04\x10\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x07\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\x1d\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x08\x04\x18\n\x0c\n\x05\
-    \x05\0\x02\x01\x02\x12\x03\x08\x1b\x1c\n\x0b\n\x04\x05\0\x02\x02\x12\x03\
-    \t\x04\x1b\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\t\x04\x16\n\x0c\n\x05\
-    \x05\0\x02\x02\x02\x12\x03\t\x19\x1a\n\x0b\n\x04\x05\0\x02\x03\x12\x03\n\
-    \x04\x1d\n\x0c\n\x05\x05\0\x02\x03\x01\x12\x03\n\x04\x18\n\x0c\n\x05\x05\
-    \0\x02\x03\x02\x12\x03\n\x1b\x1c\n\x0b\n\x04\x05\0\x02\x04\x12\x03\x0b\
-    \x04\x15\n\x0c\n\x05\x05\0\x02\x04\x01\x12\x03\x0b\x04\x10\n\x0c\n\x05\
-    \x05\0\x02\x04\x02\x12\x03\x0b\x13\x14\n\x0b\n\x04\x05\0\x02\x05\x12\x03\
-    \x0c\x04\x1f\n\x0c\n\x05\x05\0\x02\x05\x01\x12\x03\x0c\x04\x1a\n\x0c\n\
-    \x05\x05\0\x02\x05\x02\x12\x03\x0c\x1d\x1e\n\x0b\n\x04\x05\0\x02\x06\x12\
-    \x03\r\x04\x1e\n\x0c\n\x05\x05\0\x02\x06\x01\x12\x03\r\x04\x19\n\x0c\n\
-    \x05\x05\0\x02\x06\x02\x12\x03\r\x1c\x1d\n\x0b\n\x04\x05\0\x02\x07\x12\
-    \x03\x0e\x04\x1b\n\x0c\n\x05\x05\0\x02\x07\x01\x12\x03\x0e\x04\x15\n\x0c\
-    \n\x05\x05\0\x02\x07\x02\x12\x03\x0e\x18\x1ab\x06proto3\
+    DatabaseInternalError\x10\x06\x12\x15\n\x11UserInternalError\x10\n\x12\
+    \x13\n\x0fUserNotLoginYet\x10\x0bJ\xa1\x04\n\x06\x12\x04\0\0\x10\x01\n\
+    \x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x05\x01\n\n\
+    \n\x03\x04\0\x01\x12\x03\x02\x08\x16\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\
+    \x04\x20\n\x0c\n\x05\x04\0\x02\0\x06\x12\x03\x03\x04\x16\n\x0c\n\x05\x04\
+    \0\x02\0\x01\x12\x03\x03\x17\x1b\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\
+    \x1e\x1f\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x04\x04\x13\n\x0c\n\x05\x04\0\
+    \x02\x01\x05\x12\x03\x04\x04\n\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x04\
+    \x0b\x0e\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x04\x11\x12\n\n\n\x02\x05\
+    \0\x12\x04\x06\0\x10\x01\n\n\n\x03\x05\0\x01\x12\x03\x06\x05\x17\n\x0b\n\
+    \x04\x05\0\x02\0\x12\x03\x07\x04\x10\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\
+    \x07\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\x1d\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\
+    \x08\x04\x18\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x08\x1b\x1c\n\x0b\n\
+    \x04\x05\0\x02\x02\x12\x03\t\x04\x1b\n\x0c\n\x05\x05\0\x02\x02\x01\x12\
+    \x03\t\x04\x16\n\x0c\n\x05\x05\0\x02\x02\x02\x12\x03\t\x19\x1a\n\x0b\n\
+    \x04\x05\0\x02\x03\x12\x03\n\x04\x1d\n\x0c\n\x05\x05\0\x02\x03\x01\x12\
+    \x03\n\x04\x18\n\x0c\n\x05\x05\0\x02\x03\x02\x12\x03\n\x1b\x1c\n\x0b\n\
+    \x04\x05\0\x02\x04\x12\x03\x0b\x04\x15\n\x0c\n\x05\x05\0\x02\x04\x01\x12\
+    \x03\x0b\x04\x10\n\x0c\n\x05\x05\0\x02\x04\x02\x12\x03\x0b\x13\x14\n\x0b\
+    \n\x04\x05\0\x02\x05\x12\x03\x0c\x04\x1f\n\x0c\n\x05\x05\0\x02\x05\x01\
+    \x12\x03\x0c\x04\x1a\n\x0c\n\x05\x05\0\x02\x05\x02\x12\x03\x0c\x1d\x1e\n\
+    \x0b\n\x04\x05\0\x02\x06\x12\x03\r\x04\x1e\n\x0c\n\x05\x05\0\x02\x06\x01\
+    \x12\x03\r\x04\x19\n\x0c\n\x05\x05\0\x02\x06\x02\x12\x03\r\x1c\x1d\n\x0b\
+    \n\x04\x05\0\x02\x07\x12\x03\x0e\x04\x1b\n\x0c\n\x05\x05\0\x02\x07\x01\
+    \x12\x03\x0e\x04\x15\n\x0c\n\x05\x05\0\x02\x07\x02\x12\x03\x0e\x18\x1a\n\
+    \x0b\n\x04\x05\0\x02\x08\x12\x03\x0f\x04\x19\n\x0c\n\x05\x05\0\x02\x08\
+    \x01\x12\x03\x0f\x04\x13\n\x0c\n\x05\x05\0\x02\x08\x02\x12\x03\x0f\x16\
+    \x18b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 11 - 6
rust-lib/flowy-workspace/src/protobuf/model/event.rs

@@ -26,6 +26,7 @@
 #[derive(Clone,PartialEq,Eq,Debug,Hash)]
 pub enum WorkspaceEvent {
     CreateWorkspace = 0,
+    GetWorkspaceUserDetail = 100,
 }
 
 impl ::protobuf::ProtobufEnum for WorkspaceEvent {
@@ -36,6 +37,7 @@ impl ::protobuf::ProtobufEnum for WorkspaceEvent {
     fn from_i32(value: i32) -> ::std::option::Option<WorkspaceEvent> {
         match value {
             0 => ::std::option::Option::Some(WorkspaceEvent::CreateWorkspace),
+            100 => ::std::option::Option::Some(WorkspaceEvent::GetWorkspaceUserDetail),
             _ => ::std::option::Option::None
         }
     }
@@ -43,6 +45,7 @@ impl ::protobuf::ProtobufEnum for WorkspaceEvent {
     fn values() -> &'static [Self] {
         static values: &'static [WorkspaceEvent] = &[
             WorkspaceEvent::CreateWorkspace,
+            WorkspaceEvent::GetWorkspaceUserDetail,
         ];
         values
     }
@@ -71,12 +74,14 @@ impl ::protobuf::reflect::ProtobufValue for WorkspaceEvent {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x0bevent.proto*%\n\x0eWorkspaceEvent\x12\x13\n\x0fCreateWorkspace\x10\
-    \0JS\n\x06\x12\x04\0\0\x04\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\
-    \x05\0\x12\x04\x02\0\x04\x01\n\n\n\x03\x05\0\x01\x12\x03\x02\x05\x13\n\
-    \x0b\n\x04\x05\0\x02\0\x12\x03\x03\x04\x18\n\x0c\n\x05\x05\0\x02\0\x01\
-    \x12\x03\x03\x04\x13\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\x03\x16\x17b\
-    \x06proto3\
+    \n\x0bevent.proto*A\n\x0eWorkspaceEvent\x12\x13\n\x0fCreateWorkspace\x10\
+    \0\x12\x1a\n\x16GetWorkspaceUserDetail\x10dJ|\n\x06\x12\x04\0\0\x05\x01\
+    \n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x05\0\x12\x04\x02\0\x05\x01\n\
+    \n\n\x03\x05\0\x01\x12\x03\x02\x05\x13\n\x0b\n\x04\x05\0\x02\0\x12\x03\
+    \x03\x04\x18\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x03\x04\x13\n\x0c\n\x05\
+    \x05\0\x02\0\x02\x12\x03\x03\x16\x17\n\x0b\n\x04\x05\0\x02\x01\x12\x03\
+    \x04\x04!\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x04\x04\x1a\n\x0c\n\x05\
+    \x05\0\x02\x01\x02\x12\x03\x04\x1d\x20b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 1 - 0
rust-lib/flowy-workspace/src/protobuf/proto/errors.proto

@@ -13,4 +13,5 @@ enum WorkspaceErrorCode {
     DatabaseConnectionFail = 5;
     DatabaseInternalError = 6;
     UserInternalError = 10;
+    UserNotLoginYet = 11;
 }

+ 1 - 0
rust-lib/flowy-workspace/src/protobuf/proto/event.proto

@@ -2,4 +2,5 @@ syntax = "proto3";
 
 enum WorkspaceEvent {
     CreateWorkspace = 0;
+    GetWorkspaceUserDetail = 100;
 }

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

@@ -1,4 +1,4 @@
-use flowy_test::TestBuilder;
+use flowy_test::prelude::TestBuilder;
 use flowy_workspace::errors::WorkspaceError;
 
 pub type WorkspaceTestBuilder = TestBuilder<WorkspaceError>;