fix multithread test issue of database init

This commit is contained in:
appflowy 2021-09-03 16:43:03 +08:00
parent b66b3b3dc2
commit d27e2b9475
25 changed files with 301 additions and 344 deletions

View File

@ -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,

View File

@ -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',

View File

@ -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}

View File

@ -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)]

View File

@ -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);

View File

@ -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();
let _ = DocTestBuilder::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(),
};
// #[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 doc = AnnieTestBuilder::new()
.event(ReadDocInfo)
.request(request)
.sync_send()
.parse::<DocInfo>();
doc
}
pub fn read_doc_data(doc_id: &str, path: &str) -> DocData {
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()

View File

@ -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"}

View File

@ -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 WorkspaceTestBuilder = Builder<RandomUserTester<WorkspaceError>>;
impl WorkspaceTestBuilder {
pub fn new() -> Self { Builder::test(Box::new(RandomUserTester::<WorkspaceError>::new())) }
}
pub type TestBuilder = Builder<RandomUserTester<UserError>>;
impl TestBuilder {
pub type DocTestBuilder = Builder<RandomUserTester<DocError>>;
impl DocTestBuilder {
pub fn new() -> Self { Builder::test(Box::new(RandomUserTester::<DocError>::new())) }
}
pub type UserTestBuilder = Builder<RandomUserTester<UserError>>;
impl UserTestBuilder {
pub fn new() -> Self { Builder::test(Box::new(RandomUserTester::<UserError>::new())) }
}
pub struct Builder<T: TesterTrait> {
pub tester: Box<T>,
pub user_detail: Option<UserDetail>,
}
impl<T> Builder<T>
where
T: TesterTrait,
{
fn test(tester: Box<T>) -> Self { Self { tester, user_detail: None } }
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,

View File

@ -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 { "annie@appflowy.io".to_string() }
pub fn login_email() -> String { "annie@appflowy.io".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();

View File

@ -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::*;
}

View File

@ -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)); }
}

View File

@ -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")]

View File

@ -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)
}

View File

@ -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\

View File

@ -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;

View File

@ -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,
})
})
}

View File

@ -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(),
}

View File

@ -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(),
}
}
}

View File

@ -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();
}
}

View File

@ -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

View File

@ -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();
}

View File

@ -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);

View File

@ -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>()
}

View File

@ -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,

View File

@ -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
)
}