add folder migration & add folder unit test

This commit is contained in:
appflowy 2022-01-19 16:00:11 +08:00
parent 44ff74a37c
commit 324dc53e5f
22 changed files with 479 additions and 341 deletions

View File

@ -44,8 +44,8 @@ crossbeam-utils = "0.8"
chrono = "0.4"
[dev-dependencies]
flowy-test = { path = "../flowy-test" }
serial_test = "0.5.1"
flowy-test = { path = "../flowy-test" }
[features]
default = []

View File

@ -1,13 +1,12 @@
use bytes::Bytes;
use chrono::Utc;
use flowy_collaboration::client_document::default::{initial_delta, initial_read_me};
use flowy_core_data_model::{entities::view::CreateViewParams, user_default};
use flowy_core_data_model::user_default;
use flowy_document::context::DocumentContext;
use flowy_sync::RevisionWebSocket;
use lazy_static::lazy_static;
use futures_core::future::BoxFuture;
use flowy_collaboration::folder::FolderPad;
use parking_lot::RwLock;
use std::{collections::HashMap, sync::Arc};
@ -16,11 +15,18 @@ use crate::{
entities::workspace::RepeatedWorkspace,
errors::FlowyResult,
module::{FolderCouldServiceV1, WorkspaceUser},
services::{persistence::FolderPersistence, AppController, TrashController, ViewController, WorkspaceController},
services::{
persistence::FolderPersistence,
set_current_workspace,
AppController,
TrashController,
ViewController,
WorkspaceController,
},
};
lazy_static! {
static ref INIT_WORKSPACE: RwLock<HashMap<String, bool>> = RwLock::new(HashMap::new());
static ref INIT_FOLDER_FLAG: RwLock<HashMap<String, bool>> = RwLock::new(HashMap::new());
}
pub struct FolderManager {
@ -43,7 +49,7 @@ impl FolderManager {
ws_sender: Arc<dyn RevisionWebSocket>,
) -> Self {
if let Ok(token) = user.token() {
INIT_WORKSPACE.write().insert(token, false);
INIT_FOLDER_FLAG.write().insert(token, false);
}
let trash_controller = Arc::new(TrashController::new(
@ -97,74 +103,56 @@ impl FolderManager {
pub async fn did_receive_ws_data(&self, _data: Bytes) {}
pub async fn initialize(&self, token: &str) -> FlowyResult<()> {
self.initialize_with_fn(token, || Box::pin(async { Ok(()) })).await?;
Ok(())
}
pub async fn clear(&self) { self.persistence.user_did_logout() }
pub async fn initialize_with_new_user(&self, token: &str) -> FlowyResult<()> {
self.initialize_with_fn(token, || Box::pin(self.initial_default_workspace()))
.await
}
async fn initialize_with_fn<'a, F>(&'a self, token: &str, f: F) -> FlowyResult<()>
where
F: FnOnce() -> BoxFuture<'a, FlowyResult<()>>,
{
if let Some(is_init) = INIT_WORKSPACE.read().get(token) {
pub async fn initialize(&self, user_id: &str) -> FlowyResult<()> {
if let Some(is_init) = INIT_FOLDER_FLAG.read().get(user_id) {
if *is_init {
return Ok(());
}
}
INIT_WORKSPACE.write().insert(token.to_owned(), true);
self.persistence.initialize().await?;
f().await?;
let _ = self.persistence.initialize(user_id).await?;
let _ = self.app_controller.initialize()?;
let _ = self.view_controller.initialize()?;
INIT_FOLDER_FLAG.write().insert(user_id.to_owned(), true);
Ok(())
}
async fn initial_default_workspace(&self) -> FlowyResult<()> {
pub async fn initialize_with_new_user(&self, user_id: &str, token: &str) -> FlowyResult<()> {
DefaultFolderBuilder::build(token, user_id, self.persistence.clone(), self.view_controller.clone()).await?;
self.initialize(user_id).await
}
pub async fn clear(&self) { self.persistence.user_did_logout() }
}
struct DefaultFolderBuilder();
impl DefaultFolderBuilder {
async fn build(
token: &str,
user_id: &str,
persistence: Arc<FolderPersistence>,
view_controller: Arc<ViewController>,
) -> FlowyResult<()> {
log::debug!("Create user default workspace");
let time = Utc::now();
let workspace = user_default::create_default_workspace(time);
let apps = workspace.apps.clone().into_inner();
let cloned_workspace = workspace.clone();
let _ = self.workspace_controller.create_workspace_on_local(workspace).await?;
for app in apps {
let app_id = app.id.clone();
let views = app.belongings.clone().into_inner();
let _ = self.app_controller.create_app_on_local(app).await?;
for (index, view) in views.into_iter().enumerate() {
set_current_workspace(&workspace.id);
for app in workspace.apps.iter() {
for (index, view) in app.belongings.iter().enumerate() {
let view_data = if index == 0 {
initial_read_me().to_json()
} else {
initial_delta().to_json()
};
self.view_controller.set_latest_view(&view);
let params = CreateViewParams {
belong_to_id: app_id.clone(),
name: view.name,
desc: view.desc,
thumbnail: "".to_string(),
view_type: view.view_type,
view_data,
view_id: view.id.clone(),
};
let _ = self.view_controller.create_view_from_params(params).await?;
view_controller.set_latest_view(&view);
let _ = view_controller
.create_view_document_content(&view.id, view_data)
.await?;
}
}
let token = self.user.token()?;
let repeated_workspace = RepeatedWorkspace {
items: vec![cloned_workspace],
};
send_dart_notification(&token, WorkspaceNotification::UserCreateWorkspace)
let folder = FolderPad::new(vec![workspace.clone()], vec![])?;
let _ = persistence.save_folder(user_id, folder).await?;
let repeated_workspace = RepeatedWorkspace { items: vec![workspace] };
send_dart_notification(token, WorkspaceNotification::UserCreateWorkspace)
.payload(repeated_workspace)
.send();
Ok(())

View File

@ -0,0 +1,81 @@
use crate::{
module::WorkspaceDatabase,
services::persistence::{AppTableSql, TrashTableSql, ViewTableSql, WorkspaceTableSql, FOLDER_ID},
};
use flowy_collaboration::{
entities::revision::{md5, Revision},
folder::FolderPad,
};
use flowy_core_data_model::entities::{
app::{App, RepeatedApp},
view::{RepeatedView, View},
workspace::Workspace,
};
use flowy_database::kv::KV;
use flowy_error::{FlowyError, FlowyResult};
use flowy_sync::{RevisionCache, RevisionManager};
use std::sync::Arc;
pub(crate) const V1_MIGRATION: &str = "FOLDER_V1_MIGRATION";
pub(crate) struct FolderMigration {
user_id: String,
database: Arc<dyn WorkspaceDatabase>,
}
impl FolderMigration {
pub fn new(user_id: &str, database: Arc<dyn WorkspaceDatabase>) -> Self {
Self {
user_id: user_id.to_owned(),
database,
}
}
pub fn run_v1_migration(&self) -> FlowyResult<Option<FolderPad>> {
let key = md5(format!("{}{}", self.user_id, V1_MIGRATION));
if KV::get_bool(&key).unwrap_or(false) {
return Ok(None);
}
tracing::trace!("Run folder version 1 migrations");
let pool = self.database.db_pool()?;
let conn = &*pool.get()?;
let workspaces = conn.immediate_transaction::<_, FlowyError, _>(|| {
let mut workspaces = WorkspaceTableSql::read_workspaces(&self.user_id, None, conn)?
.into_iter()
.map(Workspace::from)
.collect::<Vec<_>>();
for workspace in workspaces.iter_mut() {
let mut apps = AppTableSql::read_workspace_apps(&workspace.id, conn)?
.into_iter()
.map(App::from)
.collect::<Vec<_>>();
for app in apps.iter_mut() {
let views = ViewTableSql::read_views(&app.id, conn)?
.into_iter()
.map(View::from)
.collect::<Vec<_>>();
app.belongings = RepeatedView { items: views };
}
workspace.apps = RepeatedApp { items: apps };
}
Ok(workspaces)
})?;
if workspaces.is_empty() {
return Ok(None);
}
let trash = conn.immediate_transaction::<_, FlowyError, _>(|| {
let trash = TrashTableSql::read_all(conn)?.take_items();
Ok(trash)
})?;
let folder = FolderPad::new(workspaces, trash)?;
KV::set_bool(&key, true);
Ok(Some(folder))
}
}

View File

@ -1,13 +1,18 @@
mod migration;
pub mod version_1;
mod version_2;
use flowy_collaboration::{
entities::revision::{Revision, RevisionState},
folder::FolderPad,
};
use parking_lot::RwLock;
use std::sync::Arc;
pub use version_1::{app_sql::*, trash_sql::*, v1_impl::V1Transaction, view_sql::*, workspace_sql::*};
use crate::{
module::{WorkspaceDatabase, WorkspaceUser},
services::persistence::version_2::v2_impl::FolderEditor,
services::persistence::{migration::FolderMigration, version_2::v2_impl::FolderEditor},
};
use flowy_core_data_model::entities::{
app::App,
@ -17,6 +22,9 @@ use flowy_core_data_model::entities::{
workspace::Workspace,
};
use flowy_error::{FlowyError, FlowyResult};
use flowy_sync::{mk_revision_disk_cache, RevisionCache, RevisionManager, RevisionRecord};
pub const FOLDER_ID: &str = "flowy_folder";
pub trait FolderPersistenceTransaction {
fn create_workspace(&self, user_id: &str, workspace: Workspace) -> FlowyResult<()>;
@ -57,8 +65,12 @@ impl FolderPersistence {
}
}
#[deprecated(
since = "0.0.3",
note = "please use `begin_transaction` instead, this interface will be removed in the future"
)]
#[allow(dead_code)]
pub fn begin_transaction2<F, O>(&self, f: F) -> FlowyResult<O>
pub fn begin_transaction_v_1<F, O>(&self, f: F) -> FlowyResult<O>
where
F: for<'a> FnOnce(Box<dyn FolderPersistenceTransaction + 'a>) -> FlowyResult<O>,
{
@ -93,7 +105,13 @@ impl FolderPersistence {
pub fn user_did_logout(&self) { *self.folder_editor.write() = None; }
pub async fn initialize(&self) -> FlowyResult<()> {
pub async fn initialize(&self, user_id: &str) -> FlowyResult<()> {
let migrations = FolderMigration::new(user_id, self.database.clone());
if let Some(migrated_folder) = migrations.run_v1_migration()? {
tracing::trace!("Save migration folder");
self.save_folder(user_id, migrated_folder).await?;
}
let _ = self.init_folder_editor().await?;
Ok(())
}
@ -107,4 +125,20 @@ impl FolderPersistence {
*self.folder_editor.write() = Some(editor.clone());
Ok(editor)
}
pub async fn save_folder(&self, user_id: &str, folder: FolderPad) -> FlowyResult<()> {
let pool = self.database.db_pool()?;
let delta_data = folder.delta().to_bytes();
let md5 = folder.md5();
let revision = Revision::new(FOLDER_ID, 0, 0, delta_data, user_id, md5);
let record = RevisionRecord {
revision,
state: RevisionState::Sync,
write_to_disk: true,
};
let conn = pool.get()?;
let disk_cache = mk_revision_disk_cache(user_id, pool);
disk_cache.write_revision_records(vec![record], &conn)
}
}

View File

@ -41,12 +41,10 @@ impl AppTableSql {
pub(crate) fn read_workspace_apps(
workspace_id: &str,
is_trash: bool,
conn: &SqliteConnection,
) -> Result<Vec<AppTable>, FlowyError> {
let app_table = dsl::app_table
.filter(app_table::workspace_id.eq(workspace_id))
.filter(app_table::is_trash.eq(is_trash))
.order(app_table::create_time.asc())
.load::<AppTable>(conn)?;

View File

@ -23,7 +23,7 @@ impl<'a> FolderPersistenceTransaction for V1Transaction<'a> {
}
fn read_workspaces(&self, user_id: &str, workspace_id: Option<String>) -> FlowyResult<Vec<Workspace>> {
let tables = WorkspaceTableSql::read_workspaces(workspace_id, user_id, &*self.0)?;
let tables = WorkspaceTableSql::read_workspaces(user_id, workspace_id, &*self.0)?;
let workspaces = tables.into_iter().map(Workspace::from).collect::<Vec<_>>();
Ok(workspaces)
}
@ -52,7 +52,7 @@ impl<'a> FolderPersistenceTransaction for V1Transaction<'a> {
}
fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult<Vec<App>> {
let tables = AppTableSql::read_workspace_apps(workspace_id, false, &*self.0)?;
let tables = AppTableSql::read_workspace_apps(workspace_id, &*self.0)?;
let apps = tables.into_iter().map(App::from).collect::<Vec<_>>();
Ok(apps)
}

View File

@ -29,8 +29,8 @@ impl WorkspaceTableSql {
}
pub(crate) fn read_workspaces(
workspace_id: Option<String>,
user_id: &str,
workspace_id: Option<String>,
conn: &SqliteConnection,
) -> Result<Vec<WorkspaceTable>, FlowyError> {
let mut filter = dsl::workspace_table

View File

@ -1,4 +1,10 @@
use crate::services::persistence::{AppChangeset, FolderPersistenceTransaction, ViewChangeset, WorkspaceChangeset};
use crate::services::persistence::{
AppChangeset,
FolderPersistenceTransaction,
ViewChangeset,
WorkspaceChangeset,
FOLDER_ID,
};
use flowy_collaboration::{
entities::revision::Revision,
folder::{FolderChange, FolderPad},
@ -14,8 +20,6 @@ use lib_sqlite::ConnectionPool;
use parking_lot::RwLock;
use std::sync::Arc;
const FOLDER_ID: &str = "flowy_folder";
pub struct FolderEditor {
user_id: String,
folder_pad: Arc<RwLock<FolderPad>>,

View File

@ -83,6 +83,27 @@ impl ViewController {
Ok(view)
}
#[tracing::instrument(level = "debug", skip(self, view_id, view_data), err)]
pub(crate) async fn create_view_document_content(
&self,
view_id: &str,
view_data: String,
) -> Result<(), FlowyError> {
if view_data.is_empty() {
return Err(FlowyError::internal().context("The content of the view should not be empty"));
}
let delta_data = Bytes::from(view_data);
let user_id = self.user.user_id()?;
let repeated_revision: RepeatedRevision = Revision::initial_revision(&user_id, view_id, delta_data).into();
let _ = self
.document_ctx
.controller
.save_document(view_id, repeated_revision)
.await?;
Ok(())
}
pub(crate) async fn create_view_on_local(&self, view: View) -> Result<(), FlowyError> {
let trash_controller = self.trash_controller.clone();
self.persistence.begin_transaction(|transaction| {

View File

@ -39,10 +39,6 @@ impl WorkspaceController {
params: CreateWorkspaceParams,
) -> Result<Workspace, FlowyError> {
let workspace = self.create_workspace_on_server(params.clone()).await?;
self.create_workspace_on_local(workspace).await
}
pub(crate) async fn create_workspace_on_local(&self, workspace: Workspace) -> Result<Workspace, FlowyError> {
let user_id = self.user.user_id()?;
let token = self.user.token()?;
let workspaces = self.persistence.begin_transaction(|transaction| {
@ -184,7 +180,7 @@ impl WorkspaceController {
const CURRENT_WORKSPACE_ID: &str = "current_workspace_id";
fn set_current_workspace(workspace_id: &str) { KV::set_str(CURRENT_WORKSPACE_ID, workspace_id.to_owned()); }
pub fn set_current_workspace(workspace_id: &str) { KV::set_str(CURRENT_WORKSPACE_ID, workspace_id.to_owned()); }
pub fn get_current_workspace() -> Result<String, FlowyError> {
match KV::get_str(CURRENT_WORKSPACE_ID) {

View File

@ -1,78 +0,0 @@
use flowy_core::entities::{
app::QueryAppRequest,
trash::{TrashId, TrashType},
view::*,
};
use flowy_test::helper::*;
#[tokio::test]
#[should_panic]
async fn app_delete() {
let test = AppTest::new().await;
delete_app(&test.sdk, &test.app.id).await;
let query = QueryAppRequest {
app_ids: vec![test.app.id.clone()],
};
let _ = read_app(&test.sdk, query).await;
}
#[tokio::test]
async fn app_delete_then_putback() {
let test = AppTest::new().await;
delete_app(&test.sdk, &test.app.id).await;
putback_trash(
&test.sdk,
TrashId {
id: test.app.id.clone(),
ty: TrashType::App,
},
)
.await;
let query = QueryAppRequest {
app_ids: vec![test.app.id.clone()],
};
let app = read_app(&test.sdk, query).await;
assert_eq!(&app, &test.app);
}
#[tokio::test]
async fn app_read() {
let test = AppTest::new().await;
let query = QueryAppRequest {
app_ids: vec![test.app.id.clone()],
};
let app_from_db = read_app(&test.sdk, query).await;
assert_eq!(app_from_db, test.app);
}
#[tokio::test]
async fn app_create_with_view() {
let test = AppTest::new().await;
let request_a = CreateViewRequest {
belong_to_id: test.app.id.clone(),
name: "View A".to_string(),
desc: "".to_string(),
thumbnail: Some("http://1.png".to_string()),
view_type: ViewType::Doc,
};
let request_b = CreateViewRequest {
belong_to_id: test.app.id.clone(),
name: "View B".to_string(),
desc: "".to_string(),
thumbnail: Some("http://1.png".to_string()),
view_type: ViewType::Doc,
};
let view_a = create_view_with_request(&test.sdk, request_a).await;
let view_b = create_view_with_request(&test.sdk, request_b).await;
let query = QueryAppRequest {
app_ids: vec![test.app.id.clone()],
};
let view_from_db = read_app(&test.sdk, query).await;
assert_eq!(view_from_db.belongings[0], view_a);
assert_eq!(view_from_db.belongings[1], view_b);
}

View File

@ -0,0 +1,244 @@
use flowy_core::{
entities::workspace::{CreateWorkspaceRequest, QueryWorkspaceRequest},
event::WorkspaceEvent::*,
prelude::*,
};
use flowy_test::{event_builder::*, helper::*, FlowySDKTest};
#[tokio::test]
async fn workspace_read_all() {
let test = WorkspaceTest::new().await;
let workspace = read_workspace(&test.sdk, QueryWorkspaceRequest::new(None)).await;
assert_eq!(workspace.len(), 2);
}
#[tokio::test]
async fn workspace_read() {
let test = WorkspaceTest::new().await;
let request = QueryWorkspaceRequest::new(Some(test.workspace.id.clone()));
let workspace_from_db = read_workspace(&test.sdk, request)
.await
.drain(..1)
.collect::<Vec<Workspace>>()
.pop()
.unwrap();
assert_eq!(test.workspace, workspace_from_db);
}
#[tokio::test]
async fn workspace_create_with_apps() {
let test = WorkspaceTest::new().await;
let app = create_app(&test.sdk, "App A", "AppFlowy GitHub Project", &test.workspace.id).await;
let request = QueryWorkspaceRequest::new(Some(test.workspace.id.clone()));
let workspace_from_db = read_workspace(&test.sdk, request)
.await
.drain(..1)
.collect::<Vec<Workspace>>()
.pop()
.unwrap();
assert_eq!(&app, workspace_from_db.apps.first_or_crash());
}
#[tokio::test]
async fn workspace_create_with_invalid_name() {
for (name, code) in invalid_workspace_name_test_case() {
let sdk = FlowySDKTest::default();
let request = CreateWorkspaceRequest {
name,
desc: "".to_owned(),
};
assert_eq!(
CoreModuleEventBuilder::new(sdk)
.event(CreateWorkspace)
.request(request)
.async_send()
.await
.error()
.code,
code.value()
)
}
}
#[tokio::test]
async fn workspace_update_with_invalid_name() {
let sdk = FlowySDKTest::default();
for (name, code) in invalid_workspace_name_test_case() {
let request = CreateWorkspaceRequest {
name,
desc: "".to_owned(),
};
assert_eq!(
CoreModuleEventBuilder::new(sdk.clone())
.event(CreateWorkspace)
.request(request)
.async_send()
.await
.error()
.code,
code.value()
)
}
}
#[tokio::test]
#[should_panic]
async fn app_delete() {
let test = AppTest::new().await;
delete_app(&test.sdk, &test.app.id).await;
let query = QueryAppRequest {
app_ids: vec![test.app.id.clone()],
};
let _ = read_app(&test.sdk, query).await;
}
#[tokio::test]
async fn app_delete_then_putback() {
let test = AppTest::new().await;
delete_app(&test.sdk, &test.app.id).await;
putback_trash(
&test.sdk,
TrashId {
id: test.app.id.clone(),
ty: TrashType::App,
},
)
.await;
let query = QueryAppRequest {
app_ids: vec![test.app.id.clone()],
};
let app = read_app(&test.sdk, query).await;
assert_eq!(&app, &test.app);
}
#[tokio::test]
async fn app_read() {
let test = AppTest::new().await;
let query = QueryAppRequest {
app_ids: vec![test.app.id.clone()],
};
let app_from_db = read_app(&test.sdk, query).await;
assert_eq!(app_from_db, test.app);
}
#[tokio::test]
async fn app_create_with_view() {
let test = AppTest::new().await;
let request_a = CreateViewRequest {
belong_to_id: test.app.id.clone(),
name: "View A".to_string(),
desc: "".to_string(),
thumbnail: Some("http://1.png".to_string()),
view_type: ViewType::Doc,
};
let request_b = CreateViewRequest {
belong_to_id: test.app.id.clone(),
name: "View B".to_string(),
desc: "".to_string(),
thumbnail: Some("http://1.png".to_string()),
view_type: ViewType::Doc,
};
let view_a = create_view_with_request(&test.sdk, request_a).await;
let view_b = create_view_with_request(&test.sdk, request_b).await;
let query = QueryAppRequest {
app_ids: vec![test.app.id.clone()],
};
let view_from_db = read_app(&test.sdk, query).await;
assert_eq!(view_from_db.belongings[0], view_a);
assert_eq!(view_from_db.belongings[1], view_b);
}
#[tokio::test]
#[should_panic]
async fn view_delete() {
let test = FlowySDKTest::default();
let _ = test.init_user().await;
let test = ViewTest::new(&test).await;
test.delete_views(vec![test.view.id.clone()]).await;
let query = QueryViewRequest {
view_ids: vec![test.view.id.clone()],
};
let _ = read_view(&test.sdk, query).await;
}
#[tokio::test]
async fn view_delete_then_putback() {
let test = FlowySDKTest::default();
let _ = test.init_user().await;
let test = ViewTest::new(&test).await;
test.delete_views(vec![test.view.id.clone()]).await;
putback_trash(
&test.sdk,
TrashId {
id: test.view.id.clone(),
ty: TrashType::View,
},
)
.await;
let query = QueryViewRequest {
view_ids: vec![test.view.id.clone()],
};
let view = read_view(&test.sdk, query).await;
assert_eq!(&view, &test.view);
}
#[tokio::test]
async fn view_delete_all() {
let test = FlowySDKTest::default();
let _ = test.init_user().await;
let test = ViewTest::new(&test).await;
let view1 = test.view.clone();
let view2 = create_view(&test.sdk, &test.app.id).await;
let view3 = create_view(&test.sdk, &test.app.id).await;
let view_ids = vec![view1.id.clone(), view2.id.clone(), view3.id.clone()];
let query = QueryAppRequest {
app_ids: vec![test.app.id.clone()],
};
let app = read_app(&test.sdk, query.clone()).await;
assert_eq!(app.belongings.len(), view_ids.len());
test.delete_views(view_ids.clone()).await;
assert_eq!(read_app(&test.sdk, query).await.belongings.len(), 0);
assert_eq!(read_trash(&test.sdk).await.len(), view_ids.len());
}
#[tokio::test]
async fn view_delete_all_permanent() {
let test = FlowySDKTest::default();
let _ = test.init_user().await;
let test = ViewTest::new(&test).await;
let view1 = test.view.clone();
let view2 = create_view(&test.sdk, &test.app.id).await;
let view_ids = vec![view1.id.clone(), view2.id.clone()];
test.delete_views_permanent(view_ids).await;
let query = QueryAppRequest {
app_ids: vec![test.app.id.clone()],
};
assert_eq!(read_app(&test.sdk, query).await.belongings.len(), 0);
assert_eq!(read_trash(&test.sdk).await.len(), 0);
}
#[tokio::test]
async fn view_open_doc() {
let test = FlowySDKTest::default();
let _ = test.init_user().await;
let test = ViewTest::new(&test).await;
let request = QueryViewRequest {
view_ids: vec![test.view.id.clone()],
};
let _ = open_view(&test.sdk, request).await;
}

View File

@ -1,4 +1 @@
mod app_test;
// mod helper;
mod view_test;
mod workspace_test;
mod folder_test;

View File

@ -1,96 +0,0 @@
use flowy_core::entities::{
app::QueryAppRequest,
trash::{TrashId, TrashType},
view::*,
};
use flowy_test::{helper::*, FlowySDKTest};
#[tokio::test]
#[should_panic]
async fn view_delete() {
let test = FlowySDKTest::default();
let _ = test.init_user().await;
let test = ViewTest::new(&test).await;
test.delete_views(vec![test.view.id.clone()]).await;
let query = QueryViewRequest {
view_ids: vec![test.view.id.clone()],
};
let _ = read_view(&test.sdk, query).await;
}
#[tokio::test]
async fn view_delete_then_putback() {
let test = FlowySDKTest::default();
let _ = test.init_user().await;
let test = ViewTest::new(&test).await;
test.delete_views(vec![test.view.id.clone()]).await;
putback_trash(
&test.sdk,
TrashId {
id: test.view.id.clone(),
ty: TrashType::View,
},
)
.await;
let query = QueryViewRequest {
view_ids: vec![test.view.id.clone()],
};
let view = read_view(&test.sdk, query).await;
assert_eq!(&view, &test.view);
}
#[tokio::test]
async fn view_delete_all() {
let test = FlowySDKTest::default();
let _ = test.init_user().await;
let test = ViewTest::new(&test).await;
let view1 = test.view.clone();
let view2 = create_view(&test.sdk, &test.app.id).await;
let view3 = create_view(&test.sdk, &test.app.id).await;
let view_ids = vec![view1.id.clone(), view2.id.clone(), view3.id.clone()];
let query = QueryAppRequest {
app_ids: vec![test.app.id.clone()],
};
let app = read_app(&test.sdk, query.clone()).await;
assert_eq!(app.belongings.len(), view_ids.len());
test.delete_views(view_ids.clone()).await;
assert_eq!(read_app(&test.sdk, query).await.belongings.len(), 0);
assert_eq!(read_trash(&test.sdk).await.len(), view_ids.len());
}
#[tokio::test]
async fn view_delete_all_permanent() {
let test = FlowySDKTest::default();
let _ = test.init_user().await;
let test = ViewTest::new(&test).await;
let view1 = test.view.clone();
let view2 = create_view(&test.sdk, &test.app.id).await;
let view_ids = vec![view1.id.clone(), view2.id.clone()];
test.delete_views_permanent(view_ids).await;
let query = QueryAppRequest {
app_ids: vec![test.app.id.clone()],
};
assert_eq!(read_app(&test.sdk, query).await.belongings.len(), 0);
assert_eq!(read_trash(&test.sdk).await.len(), 0);
}
#[tokio::test]
async fn view_open_doc() {
let test = FlowySDKTest::default();
let _ = test.init_user().await;
let test = ViewTest::new(&test).await;
let request = QueryViewRequest {
view_ids: vec![test.view.id.clone()],
};
let _ = open_view(&test.sdk, request).await;
}

View File

@ -1,84 +0,0 @@
use flowy_core::{
entities::workspace::{CreateWorkspaceRequest, QueryWorkspaceRequest},
event::WorkspaceEvent::*,
prelude::*,
};
use flowy_test::{event_builder::*, helper::*, FlowySDKTest};
#[tokio::test]
async fn workspace_read_all() {
let test = WorkspaceTest::new().await;
let workspace = read_workspace(&test.sdk, QueryWorkspaceRequest::new(None)).await;
assert_eq!(workspace.len(), 2);
}
#[tokio::test]
async fn workspace_read() {
let test = WorkspaceTest::new().await;
let request = QueryWorkspaceRequest::new(Some(test.workspace.id.clone()));
let workspace_from_db = read_workspace(&test.sdk, request)
.await
.drain(..1)
.collect::<Vec<Workspace>>()
.pop()
.unwrap();
assert_eq!(test.workspace, workspace_from_db);
}
#[tokio::test]
async fn workspace_create_with_apps() {
let test = WorkspaceTest::new().await;
let app = create_app(&test.sdk, "App A", "AppFlowy GitHub Project", &test.workspace.id).await;
let request = QueryWorkspaceRequest::new(Some(test.workspace.id.clone()));
let workspace_from_db = read_workspace(&test.sdk, request)
.await
.drain(..1)
.collect::<Vec<Workspace>>()
.pop()
.unwrap();
assert_eq!(&app, workspace_from_db.apps.first_or_crash());
}
#[tokio::test]
async fn workspace_create_with_invalid_name() {
for (name, code) in invalid_workspace_name_test_case() {
let sdk = FlowySDKTest::default();
let request = CreateWorkspaceRequest {
name,
desc: "".to_owned(),
};
assert_eq!(
CoreModuleEventBuilder::new(sdk)
.event(CreateWorkspace)
.request(request)
.async_send()
.await
.error()
.code,
code.value()
)
}
}
#[tokio::test]
async fn workspace_update_with_invalid_name() {
let sdk = FlowySDKTest::default();
for (name, code) in invalid_workspace_name_test_case() {
let request = CreateWorkspaceRequest {
name,
desc: "".to_owned(),
};
assert_eq!(
CoreModuleEventBuilder::new(sdk.clone())
.event(CreateWorkspace)
.request(request)
.async_send()
.await
.error()
.code,
code.value()
)
}
}
// TODO 1) delete workspace, but can't delete the last workspace

View File

@ -177,7 +177,7 @@ async fn _listen_user_status(
match status {
UserStatus::Login { token, user_id } => {
tracing::trace!("User did login");
let _ = folder_manager.initialize(&token).await?;
let _ = folder_manager.initialize(&user_id).await?;
let _ = ws_conn.start(token, user_id).await?;
},
UserStatus::Logout { .. } => {
@ -192,7 +192,9 @@ async fn _listen_user_status(
},
UserStatus::SignUp { profile, ret } => {
tracing::trace!("User did sign up");
let _ = folder_manager.initialize_with_new_user(&profile.token).await?;
let _ = folder_manager
.initialize_with_new_user(&profile.id, &profile.token)
.await?;
let _ = ws_conn.start(profile.token.clone(), profile.id.clone()).await?;
let _ = ret.send(());
},

View File

@ -24,6 +24,13 @@ pub struct RevisionCache {
latest_rev_id: AtomicI64,
}
pub fn mk_revision_disk_cache(
user_id: &str,
pool: Arc<ConnectionPool>,
) -> Arc<dyn RevisionDiskCache<Error = FlowyError>> {
Arc::new(SQLitePersistence::new(user_id, pool))
}
impl RevisionCache {
pub fn new(user_id: &str, object_id: &str, pool: Arc<ConnectionPool>) -> RevisionCache {
let disk_cache = Arc::new(SQLitePersistence::new(user_id, pool));

View File

@ -4,8 +4,9 @@ pub mod helper;
use crate::helper::*;
use backend_service::configuration::{get_client_server_configuration, ClientServerConfiguration};
use flowy_sdk::{FlowySDK, FlowySDKConfig};
use flowy_user::entities::UserProfile;
use flowy_user::{entities::UserProfile, services::database::UserDB};
use lib_infra::uuid_string;
use std::sync::Arc;
pub mod prelude {
pub use crate::{event_builder::*, helper::*, *};
@ -51,3 +52,15 @@ impl FlowySDKTest {
context.user_profile
}
}
pub struct MigrationTest {
pub db: UserDB,
}
impl MigrationTest {
pub fn new() -> Self {
let dir = root_dir();
let db = UserDB::new(&dir);
Self { db }
}
}

View File

@ -11,12 +11,12 @@ lazy_static! {
static ref DB: RwLock<Option<Database>> = RwLock::new(None);
}
pub(crate) struct UserDB {
pub struct UserDB {
db_dir: String,
}
impl UserDB {
pub(crate) fn new(db_dir: &str) -> Self {
pub fn new(db_dir: &str) -> Self {
Self {
db_dir: db_dir.to_owned(),
}

View File

@ -1,4 +1,4 @@
mod database;
pub mod database;
pub mod notifier;
mod user_session;
pub use user_session::*;

View File

@ -39,6 +39,15 @@ pub struct FolderChange {
}
impl FolderPad {
pub fn new(workspaces: Vec<Workspace>, trash: Vec<Trash>) -> CollaborateResult<Self> {
let mut pad = FolderPad::default();
pad.workspaces = workspaces.into_iter().map(Arc::new).collect::<Vec<_>>();
pad.trash = trash.into_iter().map(Arc::new).collect::<Vec<_>>();
let json = pad.to_json()?;
pad.root = PlainDeltaBuilder::new().insert(&json).build();
Ok(pad)
}
pub fn from_revisions(revisions: Vec<Revision>) -> CollaborateResult<Self> {
let mut folder_delta = PlainDelta::new();
for revision in revisions {
@ -65,6 +74,8 @@ impl FolderPad {
Ok(folder)
}
pub fn delta(&self) -> &PlainDelta { &self.root }
pub fn create_workspace(&mut self, workspace: Workspace) -> CollaborateResult<Option<FolderChange>> {
let workspace = Arc::new(workspace);
if self.workspaces.contains(&workspace) {

View File

@ -23,7 +23,7 @@ impl RevisionSyncObject<RichTextAttributes> for ServerDocument {
fn id(&self) -> &str { &self.doc_id }
fn compose(&mut self, other: &RichTextDelta) -> Result<(), CollaborateError> {
tracing::trace!("{} compose {}", &self.delta.to_json(), other.to_json());
// tracing::trace!("{} compose {}", &self.delta.to_json(), other.to_json());
let new_delta = self.delta.compose(other)?;
self.delta = new_delta;
Ok(())