feat: save user metadata (#3754)

* feat: save user metadata

* chore: update client api

* refactor: separate test methods

* chore: save updated at

* chore: clippy

* chore: fix test
This commit is contained in:
Nathan.fooo
2023-10-24 20:11:06 +08:00
committed by GitHub
parent 16a4babdca
commit 71f80be8f7
62 changed files with 1525 additions and 1476 deletions

View File

@ -1,23 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ProtoBuf_Gen" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run --manifest-path ${flowy_tool} -- pb-gen --rust_sources=${rust_lib},${shared_lib} --derive_meta=${derive_meta} --flutter_package_lib=${flutter_package_lib}" />
<option name="workingDirectory" value="file://$PROJECT_DIR$/frontend" />
<option name="channel" value="DEFAULT" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="backtrace" value="SHORT" />
<envs>
<env name="flowy_tool" value="${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/scripts/flowy-tool/Cargo.toml" />
<env name="rust_lib" value="${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/" />
<env name="shared_lib" value="${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/../shared_lib" />
<env name="flutter_lib" value="${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/appflowy_flutter/packages" />
<env name="derive_meta" value="${shared_lib}/flowy-derive/src/derive_cache/derive_cache.rs" />
<env name="flutter_package_lib" value="${flutter_lib}/flowy_sdk/lib" />
</envs>
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
</component>

View File

@ -1,18 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run backend" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run --package backend --bin backend" />
<option name="workingDirectory" value="file://$PROJECT_DIR$/backend" />
<option name="channel" value="DEFAULT" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="backtrace" value="SHORT" />
<envs>
<env name="APP_ENVIRONMENT" value="production" />
</envs>
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
</component>

View File

@ -1,16 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="dart-event" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run --manifest-path $PROJECT_DIR$/scripts/flowy-tool/Cargo.toml -- dart-event --rust_source=$PROJECT_DIR$/rust-lib/ --output=$PROJECT_DIR$/appflowy_flutter/packages/flowy_sdk/lib/dispatch/dart_event.dart" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="backtrace" value="SHORT" />
<envs />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
</component>

View File

@ -42,10 +42,7 @@ class PluginSandbox {
PluginConfig? config, PluginConfig? config,
}) { }) {
if (_pluginBuilders.containsKey(pluginType)) { if (_pluginBuilders.containsKey(pluginType)) {
throw PlatformException( return;
code: '-1',
message: "$pluginType was registered before",
);
} }
_pluginBuilders[pluginType] = builder; _pluginBuilders[pluginType] = builder;

View File

@ -2,6 +2,7 @@ import 'package:appflowy/env/env.dart';
import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/auth_service.dart'; import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/user/application/user_auth_listener.dart'; import 'package:appflowy/user/application/user_auth_listener.dart';
import 'package:appflowy_backend/log.dart';
class InitAppFlowyCloudTask extends LaunchTask { class InitAppFlowyCloudTask extends LaunchTask {
final _authStateListener = UserAuthStateListener(); final _authStateListener = UserAuthStateListener();
@ -18,6 +19,7 @@ class InitAppFlowyCloudTask extends LaunchTask {
isLoggingOut = false; isLoggingOut = false;
}, },
onInvalidAuth: (message) async { onInvalidAuth: (message) async {
Log.error(message);
await getIt<AuthService>().signOut(); await getIt<AuthService>().signOut();
// TODO(nathan): Show a dialog to notify the user that the session is expired. // TODO(nathan): Show a dialog to notify the user that the session is expired.
if (!isLoggingOut) { if (!isLoggingOut) {

View File

@ -53,7 +53,7 @@ class UserAuthStateListener {
_didSignIn?.call(); _didSignIn?.call();
break; break;
case AuthStatePB.InvalidAuth: case AuthStatePB.InvalidAuth:
_onInvalidAuth?.call(""); _onInvalidAuth?.call(pb.message);
break; break;
default: default:
break; break;

View File

@ -14,16 +14,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
// [[diagram: splash screen]]
// ┌────────────────┐1.get user ┌──────────┐ ┌────────────┐ 2.send UserEventCheckUser
// │ SplashScreen │──────────▶│SplashBloc│────▶│ISplashUser │─────┐
// └────────────────┘ └──────────┘ └────────────┘ │
// │
// ▼
// ┌───────────┐ ┌─────────────┐ ┌────────┐
// │HomeScreen │◀───────────│BlocListener │◀────────────────│RustSDK │
// └───────────┘ └─────────────┘ └────────┘
// 4. Show HomeScreen or SignIn 3.return AuthState
class SplashScreen extends StatelessWidget { class SplashScreen extends StatelessWidget {
/// Root Page of the app. /// Root Page of the app.
const SplashScreen({ const SplashScreen({
@ -82,7 +72,7 @@ class SplashScreen extends StatelessWidget {
(check) async { (check) async {
/// If encryption is needed, the user is navigated to the encryption screen. /// If encryption is needed, the user is navigated to the encryption screen.
/// Otherwise, it fetches the current workspace for the user and navigates them /// Otherwise, it fetches the current workspace for the user and navigates them
if (check.isNeedSecret) { if (check.requireSecret) {
getIt<AuthRouter>().pushEncryptionScreen(context, userProfile); getIt<AuthRouter>().pushEncryptionScreen(context, userProfile);
} else { } else {
final result = await FolderEventGetCurrentWorkspace().send(); final result = await FolderEventGetCurrentWorkspace().send();

View File

@ -762,7 +762,7 @@ dependencies = [
[[package]] [[package]]
name = "client-api" name = "client-api"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7a309c6f69d8b34709292052e9ef0561e16c82a1#7a309c6f69d8b34709292052e9ef0561e16c82a1" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=141354bfa9d08c387cf9beb9697b89b052790e89#141354bfa9d08c387cf9beb9697b89b052790e89"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -1438,7 +1438,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]] [[package]]
name = "database-entity" name = "database-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7a309c6f69d8b34709292052e9ef0561e16c82a1#7a309c6f69d8b34709292052e9ef0561e16c82a1" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=141354bfa9d08c387cf9beb9697b89b052790e89#141354bfa9d08c387cf9beb9697b89b052790e89"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@ -2781,7 +2781,7 @@ dependencies = [
[[package]] [[package]]
name = "gotrue" name = "gotrue"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7a309c6f69d8b34709292052e9ef0561e16c82a1#7a309c6f69d8b34709292052e9ef0561e16c82a1" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=141354bfa9d08c387cf9beb9697b89b052790e89#141354bfa9d08c387cf9beb9697b89b052790e89"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"futures-util", "futures-util",
@ -2797,7 +2797,7 @@ dependencies = [
[[package]] [[package]]
name = "gotrue-entity" name = "gotrue-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7a309c6f69d8b34709292052e9ef0561e16c82a1#7a309c6f69d8b34709292052e9ef0561e16c82a1" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=141354bfa9d08c387cf9beb9697b89b052790e89#141354bfa9d08c387cf9beb9697b89b052790e89"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"jsonwebtoken", "jsonwebtoken",
@ -3232,7 +3232,7 @@ dependencies = [
[[package]] [[package]]
name = "infra" name = "infra"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7a309c6f69d8b34709292052e9ef0561e16c82a1#7a309c6f69d8b34709292052e9ef0561e16c82a1" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=141354bfa9d08c387cf9beb9697b89b052790e89#141354bfa9d08c387cf9beb9697b89b052790e89"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"reqwest", "reqwest",
@ -4915,7 +4915,7 @@ dependencies = [
[[package]] [[package]]
name = "realtime-entity" name = "realtime-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7a309c6f69d8b34709292052e9ef0561e16c82a1#7a309c6f69d8b34709292052e9ef0561e16c82a1" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=141354bfa9d08c387cf9beb9697b89b052790e89#141354bfa9d08c387cf9beb9697b89b052790e89"
dependencies = [ dependencies = [
"bytes", "bytes",
"collab", "collab",
@ -5637,9 +5637,10 @@ dependencies = [
[[package]] [[package]]
name = "shared_entity" name = "shared_entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7a309c6f69d8b34709292052e9ef0561e16c82a1#7a309c6f69d8b34709292052e9ef0561e16c82a1" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=141354bfa9d08c387cf9beb9697b89b052790e89#141354bfa9d08c387cf9beb9697b89b052790e89"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"collab-entity",
"database-entity", "database-entity",
"gotrue-entity", "gotrue-entity",
"opener", "opener",

View File

@ -38,7 +38,7 @@ custom-protocol = ["tauri/custom-protocol"]
# Run the script: # Run the script:
# scripts/tool/update_client_api_rev.sh new_rev_id # scripts/tool/update_client_api_rev.sh new_rev_id
# ⚠️⚠️⚠️️ # ⚠️⚠️⚠️️
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "7a309c6f69d8b34709292052e9ef0561e16c82a1" } client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "141354bfa9d08c387cf9beb9697b89b052790e89" }
# Please use the following script to update collab. # Please use the following script to update collab.
# Working directory: frontend # Working directory: frontend
# #

View File

@ -660,7 +660,7 @@ dependencies = [
[[package]] [[package]]
name = "client-api" name = "client-api"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7a309c6f69d8b34709292052e9ef0561e16c82a1#7a309c6f69d8b34709292052e9ef0561e16c82a1" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=141354bfa9d08c387cf9beb9697b89b052790e89#141354bfa9d08c387cf9beb9697b89b052790e89"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -1138,7 +1138,7 @@ dependencies = [
"cssparser-macros", "cssparser-macros",
"dtoa-short", "dtoa-short",
"itoa", "itoa",
"phf 0.8.0", "phf 0.11.2",
"smallvec", "smallvec",
] ]
@ -1265,7 +1265,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]] [[package]]
name = "database-entity" name = "database-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7a309c6f69d8b34709292052e9ef0561e16c82a1#7a309c6f69d8b34709292052e9ef0561e16c82a1" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=141354bfa9d08c387cf9beb9697b89b052790e89#141354bfa9d08c387cf9beb9697b89b052790e89"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@ -2440,7 +2440,7 @@ dependencies = [
[[package]] [[package]]
name = "gotrue" name = "gotrue"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7a309c6f69d8b34709292052e9ef0561e16c82a1#7a309c6f69d8b34709292052e9ef0561e16c82a1" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=141354bfa9d08c387cf9beb9697b89b052790e89#141354bfa9d08c387cf9beb9697b89b052790e89"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"futures-util", "futures-util",
@ -2456,7 +2456,7 @@ dependencies = [
[[package]] [[package]]
name = "gotrue-entity" name = "gotrue-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7a309c6f69d8b34709292052e9ef0561e16c82a1#7a309c6f69d8b34709292052e9ef0561e16c82a1" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=141354bfa9d08c387cf9beb9697b89b052790e89#141354bfa9d08c387cf9beb9697b89b052790e89"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"jsonwebtoken", "jsonwebtoken",
@ -2816,7 +2816,7 @@ dependencies = [
[[package]] [[package]]
name = "infra" name = "infra"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7a309c6f69d8b34709292052e9ef0561e16c82a1#7a309c6f69d8b34709292052e9ef0561e16c82a1" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=141354bfa9d08c387cf9beb9697b89b052790e89#141354bfa9d08c387cf9beb9697b89b052790e89"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"reqwest", "reqwest",
@ -3623,7 +3623,7 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
dependencies = [ dependencies = [
"phf_macros", "phf_macros 0.8.0",
"phf_shared 0.8.0", "phf_shared 0.8.0",
"proc-macro-hack", "proc-macro-hack",
] ]
@ -3643,6 +3643,7 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [ dependencies = [
"phf_macros 0.11.2",
"phf_shared 0.11.2", "phf_shared 0.11.2",
] ]
@ -3710,6 +3711,19 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "phf_macros"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b"
dependencies = [
"phf_generator 0.11.2",
"phf_shared 0.11.2",
"proc-macro2",
"quote",
"syn 2.0.31",
]
[[package]] [[package]]
name = "phf_shared" name = "phf_shared"
version = "0.8.0" version = "0.8.0"
@ -4251,7 +4265,7 @@ dependencies = [
[[package]] [[package]]
name = "realtime-entity" name = "realtime-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7a309c6f69d8b34709292052e9ef0561e16c82a1#7a309c6f69d8b34709292052e9ef0561e16c82a1" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=141354bfa9d08c387cf9beb9697b89b052790e89#141354bfa9d08c387cf9beb9697b89b052790e89"
dependencies = [ dependencies = [
"bytes", "bytes",
"collab", "collab",
@ -4872,9 +4886,10 @@ dependencies = [
[[package]] [[package]]
name = "shared_entity" name = "shared_entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7a309c6f69d8b34709292052e9ef0561e16c82a1#7a309c6f69d8b34709292052e9ef0561e16c82a1" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=141354bfa9d08c387cf9beb9697b89b052790e89#141354bfa9d08c387cf9beb9697b89b052790e89"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"collab-entity",
"database-entity", "database-entity",
"gotrue-entity", "gotrue-entity",
"opener", "opener",

View File

@ -82,7 +82,7 @@ incremental = false
# Run the script: # Run the script:
# scripts/tool/update_client_api_rev.sh new_rev_id # scripts/tool/update_client_api_rev.sh new_rev_id
# ⚠️⚠️⚠️️ # ⚠️⚠️⚠️️
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "7a309c6f69d8b34709292052e9ef0561e16c82a1" } client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "141354bfa9d08c387cf9beb9697b89b052790e89" }
# Please use the following script to update collab. # Please use the following script to update collab.
# Working directory: frontend # Working directory: frontend
# #

View File

@ -1,18 +1,20 @@
use std::collections::HashMap;
use serde_json::Value;
use flowy_document2::entities::*; use flowy_document2::entities::*;
use flowy_document2::event_map::DocumentEvent; use flowy_document2::event_map::DocumentEvent;
use flowy_folder2::entities::{CreateViewPayloadPB, ViewLayoutPB, ViewPB}; use flowy_folder2::entities::{CreateViewPayloadPB, ViewLayoutPB, ViewPB};
use flowy_folder2::event_map::FolderEvent; use flowy_folder2::event_map::FolderEvent;
use serde_json::Value;
use std::collections::HashMap;
use crate::document::utils::{gen_delta_str, gen_id, gen_text_block_data}; use crate::document::utils::{gen_delta_str, gen_id, gen_text_block_data};
use crate::event_builder::EventBuilder; use crate::event_builder::EventBuilder;
use crate::FlowyCoreTest; use crate::EventIntegrationTest;
const TEXT_BLOCK_TY: &str = "paragraph"; const TEXT_BLOCK_TY: &str = "paragraph";
pub struct DocumentEventTest { pub struct DocumentEventTest {
inner: FlowyCoreTest, inner: EventIntegrationTest,
} }
pub struct OpenDocumentData { pub struct OpenDocumentData {
@ -22,11 +24,11 @@ pub struct OpenDocumentData {
impl DocumentEventTest { impl DocumentEventTest {
pub async fn new() -> Self { pub async fn new() -> Self {
let sdk = FlowyCoreTest::new_with_guest_user().await; let sdk = EventIntegrationTest::new_with_guest_user().await;
Self { inner: sdk } Self { inner: sdk }
} }
pub fn new_with_core(core: FlowyCoreTest) -> Self { pub fn new_with_core(core: EventIntegrationTest) -> Self {
Self { inner: core } Self { inner: core }
} }

View File

@ -10,7 +10,7 @@ use lib_dispatch::prelude::{
AFPluginDispatcher, AFPluginEventResponse, AFPluginFromBytes, AFPluginRequest, ToBytes, *, AFPluginDispatcher, AFPluginEventResponse, AFPluginFromBytes, AFPluginRequest, ToBytes, *,
}; };
use crate::FlowyCoreTest; use crate::EventIntegrationTest;
#[derive(Clone)] #[derive(Clone)]
pub struct EventBuilder { pub struct EventBuilder {
@ -18,7 +18,7 @@ pub struct EventBuilder {
} }
impl EventBuilder { impl EventBuilder {
pub fn new(sdk: FlowyCoreTest) -> Self { pub fn new(sdk: EventIntegrationTest) -> Self {
Self { Self {
context: TestContext::new(sdk), context: TestContext::new(sdk),
} }
@ -121,13 +121,13 @@ impl EventBuilder {
#[derive(Clone)] #[derive(Clone)]
pub struct TestContext { pub struct TestContext {
pub sdk: FlowyCoreTest, pub sdk: EventIntegrationTest,
request: Option<AFPluginRequest>, request: Option<AFPluginRequest>,
response: Option<AFPluginEventResponse>, response: Option<AFPluginEventResponse>,
} }
impl TestContext { impl TestContext {
pub fn new(sdk: FlowyCoreTest) -> Self { pub fn new(sdk: EventIntegrationTest) -> Self {
Self { Self {
sdk, sdk,
request: None, request: None,

View File

@ -1,115 +0,0 @@
use crate::event_builder::EventBuilder;
use crate::FlowyCoreTest;
use flowy_folder2::entities::*;
use flowy_folder2::event_map::FolderEvent::*;
pub struct ViewTest {
pub sdk: FlowyCoreTest,
pub workspace: WorkspacePB,
pub parent_view: ViewPB,
pub child_view: ViewPB,
}
impl ViewTest {
#[allow(dead_code)]
pub async fn new(sdk: &FlowyCoreTest, layout: ViewLayoutPB, data: Vec<u8>) -> Self {
let workspace = create_workspace(sdk, "Workspace", "").await;
open_workspace(sdk, &workspace.id).await;
let app = create_app(sdk, "App", "AppFlowy GitHub Project", &workspace.id).await;
let view = create_view(sdk, &app.id, layout, data).await;
Self {
sdk: sdk.clone(),
workspace,
parent_view: app,
child_view: view,
}
}
pub async fn new_grid_view(sdk: &FlowyCoreTest, data: Vec<u8>) -> Self {
Self::new(sdk, ViewLayoutPB::Grid, data).await
}
pub async fn new_board_view(sdk: &FlowyCoreTest, data: Vec<u8>) -> Self {
Self::new(sdk, ViewLayoutPB::Board, data).await
}
pub async fn new_calendar_view(sdk: &FlowyCoreTest, data: Vec<u8>) -> Self {
Self::new(sdk, ViewLayoutPB::Calendar, data).await
}
pub async fn new_document_view(sdk: &FlowyCoreTest) -> Self {
Self::new(sdk, ViewLayoutPB::Document, vec![]).await
}
}
async fn create_workspace(sdk: &FlowyCoreTest, name: &str, desc: &str) -> WorkspacePB {
let request = CreateWorkspacePayloadPB {
name: name.to_owned(),
desc: desc.to_owned(),
};
EventBuilder::new(sdk.clone())
.event(CreateWorkspace)
.payload(request)
.async_send()
.await
.parse::<WorkspacePB>()
}
async fn open_workspace(sdk: &FlowyCoreTest, workspace_id: &str) {
let payload = WorkspaceIdPB {
value: Some(workspace_id.to_owned()),
};
let _ = EventBuilder::new(sdk.clone())
.event(OpenWorkspace)
.payload(payload)
.async_send()
.await;
}
async fn create_app(sdk: &FlowyCoreTest, name: &str, desc: &str, workspace_id: &str) -> ViewPB {
let create_app_request = CreateViewPayloadPB {
parent_view_id: workspace_id.to_owned(),
name: name.to_string(),
desc: desc.to_string(),
thumbnail: None,
layout: ViewLayoutPB::Document,
initial_data: vec![],
meta: Default::default(),
set_as_current: true,
index: None,
};
EventBuilder::new(sdk.clone())
.event(CreateView)
.payload(create_app_request)
.async_send()
.await
.parse::<ViewPB>()
}
async fn create_view(
sdk: &FlowyCoreTest,
app_id: &str,
layout: ViewLayoutPB,
data: Vec<u8>,
) -> ViewPB {
let payload = CreateViewPayloadPB {
parent_view_id: app_id.to_string(),
name: "View A".to_string(),
desc: "".to_string(),
thumbnail: Some("http://1.png".to_string()),
layout,
initial_data: data,
meta: Default::default(),
set_as_current: true,
index: None,
};
EventBuilder::new(sdk.clone())
.event(CreateView)
.payload(payload)
.async_send()
.await
.parse::<ViewPB>()
}

View File

@ -1,59 +1,33 @@
use std::collections::HashMap;
use std::convert::TryFrom;
use std::env::temp_dir; use std::env::temp_dir;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use bytes::Bytes;
use collab::core::collab::MutexCollab;
use collab::core::origin::CollabOrigin;
use collab::preclude::updates::decoder::Decode;
use collab::preclude::{merge_updates_v1, Update};
use collab_document::blocks::DocumentData;
use collab_document::document::Document;
use nanoid::nanoid; use nanoid::nanoid;
use parking_lot::RwLock; use parking_lot::RwLock;
use protobuf::ProtobufError;
use tokio::sync::broadcast::{channel, Sender};
use uuid::Uuid;
use flowy_core::{AppFlowyCore, AppFlowyCoreConfig}; use flowy_core::{AppFlowyCore, AppFlowyCoreConfig};
use flowy_database2::entities::*; use flowy_notification::register_notification_sender;
use flowy_database2::event_map::DatabaseEvent; use flowy_user::entities::AuthTypePB;
use flowy_document2::entities::{DocumentDataPB, OpenDocumentPayloadPB};
use flowy_document2::event_map::DocumentEvent;
use flowy_folder2::entities::icon::UpdateViewIconPayloadPB;
use flowy_folder2::entities::*;
use flowy_folder2::event_map::FolderEvent;
use flowy_notification::entities::SubscribeObject;
use flowy_notification::{register_notification_sender, NotificationSender};
use flowy_server::supabase::define::{USER_DEVICE_ID, USER_EMAIL, USER_SIGN_IN_URL, USER_UUID};
use flowy_user::entities::{
AuthTypePB, OauthSignInPB, SignInUrlPB, SignInUrlPayloadPB, UpdateCloudConfigPB,
UserCloudConfigPB, UserProfilePB,
};
use flowy_user::errors::{FlowyError, FlowyResult};
use flowy_user::event_map::UserEvent::*;
use crate::document::document_event::{DocumentEventTest, OpenDocumentData}; use crate::test_user::TestNotificationSender;
use crate::event_builder::EventBuilder;
use crate::user_event::{async_sign_up, SignUpContext};
pub mod document; pub mod document;
pub mod event_builder; pub mod event_builder;
pub mod folder_event; pub mod test_database;
pub mod user_event; pub mod test_document;
pub mod test_folder;
pub mod test_user;
#[derive(Clone)] #[derive(Clone)]
pub struct FlowyCoreTest { pub struct EventIntegrationTest {
auth_type: Arc<RwLock<AuthTypePB>>, pub auth_type: Arc<RwLock<AuthTypePB>>,
inner: AppFlowyCore, pub inner: AppFlowyCore,
#[allow(dead_code)] #[allow(dead_code)]
cleaner: Arc<Cleaner>, cleaner: Arc<Cleaner>,
pub notification_sender: TestNotificationSender, pub notification_sender: TestNotificationSender,
} }
impl Default for FlowyCoreTest { impl Default for EventIntegrationTest {
fn default() -> Self { fn default() -> Self {
let temp_dir = temp_dir().join(nanoid!(6)); let temp_dir = temp_dir().join(nanoid!(6));
std::fs::create_dir_all(&temp_dir).unwrap(); std::fs::create_dir_all(&temp_dir).unwrap();
@ -61,51 +35,10 @@ impl Default for FlowyCoreTest {
} }
} }
impl FlowyCoreTest { impl EventIntegrationTest {
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
} }
pub async fn insert_document_text(&self, document_id: &str, text: &str, index: usize) {
let document_event = DocumentEventTest::new_with_core(self.clone());
document_event
.insert_index(document_id, text, index, None)
.await;
}
pub async fn get_document_data(&self, view_id: &str) -> DocumentData {
let pb = EventBuilder::new(self.clone())
.event(DocumentEvent::GetDocumentData)
.payload(OpenDocumentPayloadPB {
document_id: view_id.to_string(),
})
.async_send()
.await
.parse::<DocumentDataPB>();
DocumentData::from(pb)
}
pub async fn get_document_update(&self, document_id: &str) -> Vec<u8> {
let workspace_id = self.user_manager.workspace_id().unwrap();
let cloud_service = self.document_manager.get_cloud_service().clone();
let remote_updates = cloud_service
.get_document_updates(document_id, &workspace_id)
.await
.unwrap();
if remote_updates.is_empty() {
return vec![];
}
let updates = remote_updates
.iter()
.map(|update| update.as_ref())
.collect::<Vec<&[u8]>>();
merge_updates_v1(&updates).unwrap()
}
pub fn new_with_user_data_path(path: PathBuf, name: String) -> Self { pub fn new_with_user_data_path(path: PathBuf, name: String) -> Self {
let config = AppFlowyCoreConfig::new(path.to_str().unwrap(), name).log_filter( let config = AppFlowyCoreConfig::new(path.to_str().unwrap(), name).log_filter(
"trace", "trace",
@ -129,711 +62,9 @@ impl FlowyCoreTest {
cleaner: Arc::new(Cleaner(path)), cleaner: Arc::new(Cleaner(path)),
} }
} }
pub async fn enable_encryption(&self) -> String {
let config = EventBuilder::new(self.clone())
.event(GetCloudConfig)
.async_send()
.await
.parse::<UserCloudConfigPB>();
let update = UpdateCloudConfigPB {
enable_sync: None,
enable_encrypt: Some(true),
};
let error = EventBuilder::new(self.clone())
.event(SetCloudConfig)
.payload(update)
.async_send()
.await
.error();
assert!(error.is_none());
config.encrypt_secret
}
pub async fn get_user_profile(&self) -> Result<UserProfilePB, FlowyError> {
EventBuilder::new(self.clone())
.event(GetUserProfile)
.async_send()
.await
.try_parse::<UserProfilePB>()
}
pub async fn new_with_guest_user() -> Self {
let test = Self::default();
test.sign_up_as_guest().await;
test
}
pub async fn sign_up_as_guest(&self) -> SignUpContext {
async_sign_up(self.inner.dispatcher(), AuthTypePB::Local).await
}
pub async fn supabase_party_sign_up(&self) -> UserProfilePB {
let map = third_party_sign_up_param(Uuid::new_v4().to_string());
let payload = OauthSignInPB {
map,
auth_type: AuthTypePB::Supabase,
};
EventBuilder::new(self.clone())
.event(OauthSignIn)
.payload(payload)
.async_send()
.await
.parse::<UserProfilePB>()
}
pub async fn sign_out(&self) {
EventBuilder::new(self.clone())
.event(SignOut)
.async_send()
.await;
}
pub fn set_auth_type(&self, auth_type: AuthTypePB) {
*self.auth_type.write() = auth_type;
}
pub async fn init_user(&self) -> UserProfilePB {
self.sign_up_as_guest().await.user_profile
}
pub async fn af_cloud_sign_in_with_email(&self, email: &str) -> FlowyResult<UserProfilePB> {
let payload = SignInUrlPayloadPB {
email: email.to_string(),
auth_type: AuthTypePB::AFCloud,
};
let sign_in_url = EventBuilder::new(self.clone())
.event(GetSignInURL)
.payload(payload)
.async_send()
.await
.try_parse::<SignInUrlPB>()?
.sign_in_url;
let mut map = HashMap::new();
map.insert(USER_SIGN_IN_URL.to_string(), sign_in_url);
map.insert(USER_DEVICE_ID.to_string(), Uuid::new_v4().to_string());
let payload = OauthSignInPB {
map,
auth_type: AuthTypePB::AFCloud,
};
let user_profile = EventBuilder::new(self.clone())
.event(OauthSignIn)
.payload(payload)
.async_send()
.await
.try_parse::<UserProfilePB>()?;
Ok(user_profile)
}
pub async fn supabase_sign_up_with_uuid(
&self,
uuid: &str,
email: Option<String>,
) -> FlowyResult<UserProfilePB> {
let mut map = HashMap::new();
map.insert(USER_UUID.to_string(), uuid.to_string());
map.insert(USER_DEVICE_ID.to_string(), uuid.to_string());
map.insert(
USER_EMAIL.to_string(),
email.unwrap_or_else(|| format!("{}@appflowy.io", nanoid!(10))),
);
let payload = OauthSignInPB {
map,
auth_type: AuthTypePB::Supabase,
};
let user_profile = EventBuilder::new(self.clone())
.event(OauthSignIn)
.payload(payload)
.async_send()
.await
.try_parse::<UserProfilePB>()?;
Ok(user_profile)
}
// Must sign up/ sign in first
pub async fn get_current_workspace(&self) -> WorkspaceSettingPB {
EventBuilder::new(self.clone())
.event(FolderEvent::GetCurrentWorkspace)
.async_send()
.await
.parse::<WorkspaceSettingPB>()
}
pub async fn get_all_workspace_views(&self) -> Vec<ViewPB> {
EventBuilder::new(self.clone())
.event(FolderEvent::ReadWorkspaceViews)
.async_send()
.await
.parse::<RepeatedViewPB>()
.items
}
pub async fn get_views(&self, parent_view_id: &str) -> ViewPB {
EventBuilder::new(self.clone())
.event(FolderEvent::ReadView)
.payload(ViewIdPB {
value: parent_view_id.to_string(),
})
.async_send()
.await
.parse::<ViewPB>()
}
pub async fn delete_view(&self, view_id: &str) {
let payload = RepeatedViewIdPB {
items: vec![view_id.to_string()],
};
// delete the view. the view will be moved to trash
EventBuilder::new(self.clone())
.event(FolderEvent::DeleteView)
.payload(payload)
.async_send()
.await;
}
pub async fn update_view(&self, changeset: UpdateViewPayloadPB) -> Option<FlowyError> {
// delete the view. the view will be moved to trash
EventBuilder::new(self.clone())
.event(FolderEvent::UpdateView)
.payload(changeset)
.async_send()
.await
.error()
}
pub async fn update_view_icon(&self, payload: UpdateViewIconPayloadPB) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(FolderEvent::UpdateViewIcon)
.payload(payload)
.async_send()
.await
.error()
}
pub async fn create_view(&self, parent_id: &str, name: String) -> ViewPB {
let payload = CreateViewPayloadPB {
parent_view_id: parent_id.to_string(),
name,
desc: "".to_string(),
thumbnail: None,
layout: Default::default(),
initial_data: vec![],
meta: Default::default(),
set_as_current: false,
index: None,
};
EventBuilder::new(self.clone())
.event(FolderEvent::CreateView)
.payload(payload)
.async_send()
.await
.parse::<ViewPB>()
}
pub async fn create_document(
&self,
parent_id: &str,
name: String,
initial_data: Vec<u8>,
) -> ViewPB {
let payload = CreateViewPayloadPB {
parent_view_id: parent_id.to_string(),
name,
desc: "".to_string(),
thumbnail: None,
layout: ViewLayoutPB::Document,
initial_data,
meta: Default::default(),
set_as_current: true,
index: None,
};
let view = EventBuilder::new(self.clone())
.event(FolderEvent::CreateView)
.payload(payload)
.async_send()
.await
.parse::<ViewPB>();
let payload = OpenDocumentPayloadPB {
document_id: view.id.clone(),
};
let _ = EventBuilder::new(self.clone())
.event(DocumentEvent::OpenDocument)
.payload(payload)
.async_send()
.await
.parse::<DocumentDataPB>();
view
}
pub async fn create_grid(&self, parent_id: &str, name: String, initial_data: Vec<u8>) -> ViewPB {
let payload = CreateViewPayloadPB {
parent_view_id: parent_id.to_string(),
name,
desc: "".to_string(),
thumbnail: None,
layout: ViewLayoutPB::Grid,
initial_data,
meta: Default::default(),
set_as_current: true,
index: None,
};
EventBuilder::new(self.clone())
.event(FolderEvent::CreateView)
.payload(payload)
.async_send()
.await
.parse::<ViewPB>()
}
pub async fn open_database(&self, view_id: &str) {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetDatabase)
.payload(DatabaseViewIdPB {
value: view_id.to_string(),
})
.async_send()
.await;
}
pub async fn open_document(&self, doc_id: String) -> OpenDocumentData {
let payload = OpenDocumentPayloadPB {
document_id: doc_id.clone(),
};
let data = EventBuilder::new(self.clone())
.event(DocumentEvent::OpenDocument)
.payload(payload)
.async_send()
.await
.parse::<DocumentDataPB>();
OpenDocumentData { id: doc_id, data }
}
pub async fn create_board(&self, parent_id: &str, name: String, initial_data: Vec<u8>) -> ViewPB {
let payload = CreateViewPayloadPB {
parent_view_id: parent_id.to_string(),
name,
desc: "".to_string(),
thumbnail: None,
layout: ViewLayoutPB::Board,
initial_data,
meta: Default::default(),
set_as_current: true,
index: None,
};
EventBuilder::new(self.clone())
.event(FolderEvent::CreateView)
.payload(payload)
.async_send()
.await
.parse::<ViewPB>()
}
pub async fn create_calendar(
&self,
parent_id: &str,
name: String,
initial_data: Vec<u8>,
) -> ViewPB {
let payload = CreateViewPayloadPB {
parent_view_id: parent_id.to_string(),
name,
desc: "".to_string(),
thumbnail: None,
layout: ViewLayoutPB::Calendar,
initial_data,
meta: Default::default(),
set_as_current: true,
index: None,
};
EventBuilder::new(self.clone())
.event(FolderEvent::CreateView)
.payload(payload)
.async_send()
.await
.parse::<ViewPB>()
}
pub async fn get_database(&self, view_id: &str) -> DatabasePB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetDatabase)
.payload(DatabaseViewIdPB {
value: view_id.to_string(),
})
.async_send()
.await
.parse::<DatabasePB>()
}
pub async fn get_all_database_fields(&self, view_id: &str) -> RepeatedFieldPB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetFields)
.payload(GetFieldPayloadPB {
view_id: view_id.to_string(),
field_ids: None,
})
.async_send()
.await
.parse::<RepeatedFieldPB>()
}
pub async fn create_field(&self, view_id: &str, field_type: FieldType) -> FieldPB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::CreateTypeOption)
.payload(CreateFieldPayloadPB {
view_id: view_id.to_string(),
field_type,
type_option_data: None,
})
.async_send()
.await
.parse::<TypeOptionPB>()
.field
}
pub async fn update_field(&self, changeset: FieldChangesetPB) {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateField)
.payload(changeset)
.async_send()
.await;
}
pub async fn delete_field(&self, view_id: &str, field_id: &str) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::DeleteField)
.payload(DeleteFieldPayloadPB {
view_id: view_id.to_string(),
field_id: field_id.to_string(),
})
.async_send()
.await
.error()
}
pub async fn update_field_type(
&self,
view_id: &str,
field_id: &str,
field_type: FieldType,
) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateFieldType)
.payload(UpdateFieldTypePayloadPB {
view_id: view_id.to_string(),
field_id: field_id.to_string(),
field_type,
})
.async_send()
.await
.error()
}
pub async fn duplicate_field(&self, view_id: &str, field_id: &str) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::DuplicateField)
.payload(DuplicateFieldPayloadPB {
view_id: view_id.to_string(),
field_id: field_id.to_string(),
})
.async_send()
.await
.error()
}
pub async fn get_primary_field(&self, database_view_id: &str) -> FieldPB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetPrimaryField)
.payload(DatabaseViewIdPB {
value: database_view_id.to_string(),
})
.async_send()
.await
.parse::<FieldPB>()
}
pub async fn create_row(
&self,
view_id: &str,
start_row_id: Option<String>,
data: Option<RowDataPB>,
) -> RowMetaPB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::CreateRow)
.payload(CreateRowPayloadPB {
view_id: view_id.to_string(),
start_row_id,
group_id: None,
data,
})
.async_send()
.await
.parse::<RowMetaPB>()
}
pub async fn delete_row(&self, view_id: &str, row_id: &str) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::DeleteRow)
.payload(RowIdPB {
view_id: view_id.to_string(),
row_id: row_id.to_string(),
group_id: None,
})
.async_send()
.await
.error()
}
pub async fn get_row(&self, view_id: &str, row_id: &str) -> OptionalRowPB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetRow)
.payload(RowIdPB {
view_id: view_id.to_string(),
row_id: row_id.to_string(),
group_id: None,
})
.async_send()
.await
.parse::<OptionalRowPB>()
}
pub async fn get_row_meta(&self, view_id: &str, row_id: &str) -> RowMetaPB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetRowMeta)
.payload(RowIdPB {
view_id: view_id.to_string(),
row_id: row_id.to_string(),
group_id: None,
})
.async_send()
.await
.parse::<RowMetaPB>()
}
pub async fn update_row_meta(&self, changeset: UpdateRowMetaChangesetPB) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateRowMeta)
.payload(changeset)
.async_send()
.await
.error()
}
pub async fn duplicate_row(&self, view_id: &str, row_id: &str) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::DuplicateRow)
.payload(RowIdPB {
view_id: view_id.to_string(),
row_id: row_id.to_string(),
group_id: None,
})
.async_send()
.await
.error()
}
pub async fn move_row(&self, view_id: &str, row_id: &str, to_row_id: &str) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::MoveRow)
.payload(MoveRowPayloadPB {
view_id: view_id.to_string(),
from_row_id: row_id.to_string(),
to_row_id: to_row_id.to_string(),
})
.async_send()
.await
.error()
}
pub async fn update_cell(&self, changeset: CellChangesetPB) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateCell)
.payload(changeset)
.async_send()
.await
.error()
}
pub async fn update_date_cell(&self, changeset: DateChangesetPB) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateDateCell)
.payload(changeset)
.async_send()
.await
.error()
}
pub async fn get_cell(&self, view_id: &str, row_id: &str, field_id: &str) -> CellPB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetCell)
.payload(CellIdPB {
view_id: view_id.to_string(),
row_id: row_id.to_string(),
field_id: field_id.to_string(),
})
.async_send()
.await
.parse::<CellPB>()
}
pub async fn get_date_cell(&self, view_id: &str, row_id: &str, field_id: &str) -> DateCellDataPB {
let cell = self.get_cell(view_id, row_id, field_id).await;
DateCellDataPB::try_from(Bytes::from(cell.data)).unwrap()
}
pub async fn get_checklist_cell(
&self,
view_id: &str,
field_id: &str,
row_id: &str,
) -> ChecklistCellDataPB {
let cell = self.get_cell(view_id, row_id, field_id).await;
ChecklistCellDataPB::try_from(Bytes::from(cell.data)).unwrap()
}
pub async fn update_checklist_cell(
&self,
changeset: ChecklistCellDataChangesetPB,
) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateChecklistCell)
.payload(changeset)
.async_send()
.await
.error()
}
pub async fn insert_option(
&self,
view_id: &str,
field_id: &str,
row_id: &str,
name: &str,
) -> Option<FlowyError> {
let option = EventBuilder::new(self.clone())
.event(DatabaseEvent::CreateSelectOption)
.payload(CreateSelectOptionPayloadPB {
field_id: field_id.to_string(),
view_id: view_id.to_string(),
option_name: name.to_string(),
})
.async_send()
.await
.parse::<SelectOptionPB>();
EventBuilder::new(self.clone())
.event(DatabaseEvent::InsertOrUpdateSelectOption)
.payload(RepeatedSelectOptionPayload {
view_id: view_id.to_string(),
field_id: field_id.to_string(),
row_id: row_id.to_string(),
items: vec![option],
})
.async_send()
.await
.error()
}
pub async fn get_groups(&self, view_id: &str) -> Vec<GroupPB> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetGroups)
.payload(DatabaseViewIdPB {
value: view_id.to_string(),
})
.async_send()
.await
.parse::<RepeatedGroupPB>()
.items
}
pub async fn move_group(&self, view_id: &str, from_id: &str, to_id: &str) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::MoveGroup)
.payload(MoveGroupPayloadPB {
view_id: view_id.to_string(),
from_group_id: from_id.to_string(),
to_group_id: to_id.to_string(),
})
.async_send()
.await
.error()
}
pub async fn set_group_by_field(&self, view_id: &str, field_id: &str) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::SetGroupByField)
.payload(GroupByFieldPayloadPB {
field_id: field_id.to_string(),
view_id: view_id.to_string(),
})
.async_send()
.await
.error()
}
pub async fn update_group(
&self,
view_id: &str,
group_id: &str,
name: Option<String>,
visible: Option<bool>,
) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateGroup)
.payload(UpdateGroupPB {
view_id: view_id.to_string(),
group_id: group_id.to_string(),
name,
visible,
})
.async_send()
.await
.error()
}
pub async fn update_setting(&self, changeset: DatabaseSettingChangesetPB) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateDatabaseSetting)
.payload(changeset)
.async_send()
.await
.error()
}
pub async fn get_all_calendar_events(&self, view_id: &str) -> Vec<CalendarEventPB> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetAllCalendarEvents)
.payload(CalendarEventRequestPB {
view_id: view_id.to_string(),
})
.async_send()
.await
.parse::<RepeatedCalendarEventPB>()
.items
}
pub async fn get_view(&self, view_id: &str) -> ViewPB {
EventBuilder::new(self.clone())
.event(FolderEvent::ReadView)
.payload(ViewIdPB {
value: view_id.to_string(),
})
.async_send()
.await
.parse::<ViewPB>()
}
} }
impl std::ops::Deref for FlowyCoreTest { impl std::ops::Deref for EventIntegrationTest {
type Target = AppFlowyCore; type Target = AppFlowyCore;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
@ -841,89 +72,6 @@ impl std::ops::Deref for FlowyCoreTest {
} }
} }
#[derive(Clone)]
pub struct TestNotificationSender {
sender: Arc<Sender<SubscribeObject>>,
}
impl Default for TestNotificationSender {
fn default() -> Self {
let (sender, _) = channel(1000);
Self {
sender: Arc::new(sender),
}
}
}
impl TestNotificationSender {
pub fn new() -> Self {
Self::default()
}
pub fn subscribe<T>(&self, id: &str, ty: impl Into<i32> + Send) -> tokio::sync::mpsc::Receiver<T>
where
T: TryFrom<Bytes, Error = ProtobufError> + Send + 'static,
{
let id = id.to_string();
let (tx, rx) = tokio::sync::mpsc::channel::<T>(10);
let mut receiver = self.sender.subscribe();
let ty = ty.into();
tokio::spawn(async move {
// DatabaseNotification::DidUpdateDatabaseSnapshotState
while let Ok(value) = receiver.recv().await {
if value.id == id && value.ty == ty {
if let Some(payload) = value.payload {
match T::try_from(Bytes::from(payload)) {
Ok(object) => {
let _ = tx.send(object).await;
},
Err(e) => {
panic!(
"Failed to parse notification payload to type: {:?} with error: {}",
std::any::type_name::<T>(),
e
);
},
}
}
}
}
});
rx
}
pub fn subscribe_with_condition<T, F>(&self, id: &str, when: F) -> tokio::sync::mpsc::Receiver<T>
where
T: TryFrom<Bytes, Error = ProtobufError> + Send + 'static,
F: Fn(&T) -> bool + Send + 'static,
{
let id = id.to_string();
let (tx, rx) = tokio::sync::mpsc::channel::<T>(10);
let mut receiver = self.sender.subscribe();
tokio::spawn(async move {
while let Ok(value) = receiver.recv().await {
if value.id == id {
if let Some(payload) = value.payload {
if let Ok(object) = T::try_from(Bytes::from(payload)) {
if when(&object) {
let _ = tx.send(object).await;
}
}
}
}
}
});
rx
}
}
impl NotificationSender for TestNotificationSender {
fn send_subject(&self, subject: SubscribeObject) -> Result<(), String> {
let _ = self.sender.send(subject);
Ok(())
}
}
pub struct Cleaner(PathBuf); pub struct Cleaner(PathBuf);
impl Cleaner { impl Cleaner {
@ -941,25 +89,3 @@ impl Drop for Cleaner {
Self::cleanup(&self.0) Self::cleanup(&self.0)
} }
} }
pub fn third_party_sign_up_param(uuid: String) -> HashMap<String, String> {
let mut params = HashMap::new();
params.insert(USER_UUID.to_string(), uuid);
params.insert(
USER_EMAIL.to_string(),
format!("{}@test.com", Uuid::new_v4()),
);
params.insert(USER_DEVICE_ID.to_string(), Uuid::new_v4().to_string());
params
}
pub fn assert_document_data_equal(collab_update: &[u8], doc_id: &str, expected: DocumentData) {
let collab = MutexCollab::new(CollabOrigin::Server, doc_id, vec![]);
collab.lock().with_origin_transact_mut(|txn| {
let update = Update::decode_v1(collab_update).unwrap();
txn.apply_update(update);
});
let document = Document::open(Arc::new(collab)).unwrap();
let actual = document.get_document_data().unwrap();
assert_eq!(actual, expected);
}

View File

@ -0,0 +1,447 @@
use std::convert::TryFrom;
use bytes::Bytes;
use flowy_database2::entities::*;
use flowy_database2::event_map::DatabaseEvent;
use flowy_folder2::entities::*;
use flowy_folder2::event_map::FolderEvent;
use flowy_user::errors::FlowyError;
use crate::event_builder::EventBuilder;
use crate::EventIntegrationTest;
impl EventIntegrationTest {
pub async fn create_grid(&self, parent_id: &str, name: String, initial_data: Vec<u8>) -> ViewPB {
let payload = CreateViewPayloadPB {
parent_view_id: parent_id.to_string(),
name,
desc: "".to_string(),
thumbnail: None,
layout: ViewLayoutPB::Grid,
initial_data,
meta: Default::default(),
set_as_current: true,
index: None,
};
EventBuilder::new(self.clone())
.event(FolderEvent::CreateView)
.payload(payload)
.async_send()
.await
.parse::<ViewPB>()
}
pub async fn open_database(&self, view_id: &str) {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetDatabase)
.payload(DatabaseViewIdPB {
value: view_id.to_string(),
})
.async_send()
.await;
}
pub async fn create_board(&self, parent_id: &str, name: String, initial_data: Vec<u8>) -> ViewPB {
let payload = CreateViewPayloadPB {
parent_view_id: parent_id.to_string(),
name,
desc: "".to_string(),
thumbnail: None,
layout: ViewLayoutPB::Board,
initial_data,
meta: Default::default(),
set_as_current: true,
index: None,
};
EventBuilder::new(self.clone())
.event(FolderEvent::CreateView)
.payload(payload)
.async_send()
.await
.parse::<ViewPB>()
}
pub async fn create_calendar(
&self,
parent_id: &str,
name: String,
initial_data: Vec<u8>,
) -> ViewPB {
let payload = CreateViewPayloadPB {
parent_view_id: parent_id.to_string(),
name,
desc: "".to_string(),
thumbnail: None,
layout: ViewLayoutPB::Calendar,
initial_data,
meta: Default::default(),
set_as_current: true,
index: None,
};
EventBuilder::new(self.clone())
.event(FolderEvent::CreateView)
.payload(payload)
.async_send()
.await
.parse::<ViewPB>()
}
pub async fn get_database(&self, view_id: &str) -> DatabasePB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetDatabase)
.payload(DatabaseViewIdPB {
value: view_id.to_string(),
})
.async_send()
.await
.parse::<DatabasePB>()
}
pub async fn get_all_database_fields(&self, view_id: &str) -> RepeatedFieldPB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetFields)
.payload(GetFieldPayloadPB {
view_id: view_id.to_string(),
field_ids: None,
})
.async_send()
.await
.parse::<RepeatedFieldPB>()
}
pub async fn create_field(&self, view_id: &str, field_type: FieldType) -> FieldPB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::CreateTypeOption)
.payload(CreateFieldPayloadPB {
view_id: view_id.to_string(),
field_type,
type_option_data: None,
})
.async_send()
.await
.parse::<TypeOptionPB>()
.field
}
pub async fn update_field(&self, changeset: FieldChangesetPB) {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateField)
.payload(changeset)
.async_send()
.await;
}
pub async fn delete_field(&self, view_id: &str, field_id: &str) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::DeleteField)
.payload(DeleteFieldPayloadPB {
view_id: view_id.to_string(),
field_id: field_id.to_string(),
})
.async_send()
.await
.error()
}
pub async fn update_field_type(
&self,
view_id: &str,
field_id: &str,
field_type: FieldType,
) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateFieldType)
.payload(UpdateFieldTypePayloadPB {
view_id: view_id.to_string(),
field_id: field_id.to_string(),
field_type,
})
.async_send()
.await
.error()
}
pub async fn duplicate_field(&self, view_id: &str, field_id: &str) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::DuplicateField)
.payload(DuplicateFieldPayloadPB {
view_id: view_id.to_string(),
field_id: field_id.to_string(),
})
.async_send()
.await
.error()
}
pub async fn get_primary_field(&self, database_view_id: &str) -> FieldPB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetPrimaryField)
.payload(DatabaseViewIdPB {
value: database_view_id.to_string(),
})
.async_send()
.await
.parse::<FieldPB>()
}
pub async fn create_row(
&self,
view_id: &str,
start_row_id: Option<String>,
data: Option<RowDataPB>,
) -> RowMetaPB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::CreateRow)
.payload(CreateRowPayloadPB {
view_id: view_id.to_string(),
start_row_id,
group_id: None,
data,
})
.async_send()
.await
.parse::<RowMetaPB>()
}
pub async fn delete_row(&self, view_id: &str, row_id: &str) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::DeleteRow)
.payload(RowIdPB {
view_id: view_id.to_string(),
row_id: row_id.to_string(),
group_id: None,
})
.async_send()
.await
.error()
}
pub async fn get_row(&self, view_id: &str, row_id: &str) -> OptionalRowPB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetRow)
.payload(RowIdPB {
view_id: view_id.to_string(),
row_id: row_id.to_string(),
group_id: None,
})
.async_send()
.await
.parse::<OptionalRowPB>()
}
pub async fn get_row_meta(&self, view_id: &str, row_id: &str) -> RowMetaPB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetRowMeta)
.payload(RowIdPB {
view_id: view_id.to_string(),
row_id: row_id.to_string(),
group_id: None,
})
.async_send()
.await
.parse::<RowMetaPB>()
}
pub async fn update_row_meta(&self, changeset: UpdateRowMetaChangesetPB) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateRowMeta)
.payload(changeset)
.async_send()
.await
.error()
}
pub async fn duplicate_row(&self, view_id: &str, row_id: &str) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::DuplicateRow)
.payload(RowIdPB {
view_id: view_id.to_string(),
row_id: row_id.to_string(),
group_id: None,
})
.async_send()
.await
.error()
}
pub async fn move_row(&self, view_id: &str, row_id: &str, to_row_id: &str) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::MoveRow)
.payload(MoveRowPayloadPB {
view_id: view_id.to_string(),
from_row_id: row_id.to_string(),
to_row_id: to_row_id.to_string(),
})
.async_send()
.await
.error()
}
pub async fn update_cell(&self, changeset: CellChangesetPB) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateCell)
.payload(changeset)
.async_send()
.await
.error()
}
pub async fn update_date_cell(&self, changeset: DateChangesetPB) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateDateCell)
.payload(changeset)
.async_send()
.await
.error()
}
pub async fn get_cell(&self, view_id: &str, row_id: &str, field_id: &str) -> CellPB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetCell)
.payload(CellIdPB {
view_id: view_id.to_string(),
row_id: row_id.to_string(),
field_id: field_id.to_string(),
})
.async_send()
.await
.parse::<CellPB>()
}
pub async fn get_date_cell(&self, view_id: &str, row_id: &str, field_id: &str) -> DateCellDataPB {
let cell = self.get_cell(view_id, row_id, field_id).await;
DateCellDataPB::try_from(Bytes::from(cell.data)).unwrap()
}
pub async fn get_checklist_cell(
&self,
view_id: &str,
field_id: &str,
row_id: &str,
) -> ChecklistCellDataPB {
let cell = self.get_cell(view_id, row_id, field_id).await;
ChecklistCellDataPB::try_from(Bytes::from(cell.data)).unwrap()
}
pub async fn update_checklist_cell(
&self,
changeset: ChecklistCellDataChangesetPB,
) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateChecklistCell)
.payload(changeset)
.async_send()
.await
.error()
}
pub async fn insert_option(
&self,
view_id: &str,
field_id: &str,
row_id: &str,
name: &str,
) -> Option<FlowyError> {
let option = EventBuilder::new(self.clone())
.event(DatabaseEvent::CreateSelectOption)
.payload(CreateSelectOptionPayloadPB {
field_id: field_id.to_string(),
view_id: view_id.to_string(),
option_name: name.to_string(),
})
.async_send()
.await
.parse::<SelectOptionPB>();
EventBuilder::new(self.clone())
.event(DatabaseEvent::InsertOrUpdateSelectOption)
.payload(RepeatedSelectOptionPayload {
view_id: view_id.to_string(),
field_id: field_id.to_string(),
row_id: row_id.to_string(),
items: vec![option],
})
.async_send()
.await
.error()
}
pub async fn get_groups(&self, view_id: &str) -> Vec<GroupPB> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetGroups)
.payload(DatabaseViewIdPB {
value: view_id.to_string(),
})
.async_send()
.await
.parse::<RepeatedGroupPB>()
.items
}
pub async fn move_group(&self, view_id: &str, from_id: &str, to_id: &str) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::MoveGroup)
.payload(MoveGroupPayloadPB {
view_id: view_id.to_string(),
from_group_id: from_id.to_string(),
to_group_id: to_id.to_string(),
})
.async_send()
.await
.error()
}
pub async fn set_group_by_field(&self, view_id: &str, field_id: &str) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::SetGroupByField)
.payload(GroupByFieldPayloadPB {
field_id: field_id.to_string(),
view_id: view_id.to_string(),
})
.async_send()
.await
.error()
}
pub async fn update_group(
&self,
view_id: &str,
group_id: &str,
name: Option<String>,
visible: Option<bool>,
) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateGroup)
.payload(UpdateGroupPB {
view_id: view_id.to_string(),
group_id: group_id.to_string(),
name,
visible,
})
.async_send()
.await
.error()
}
pub async fn update_setting(&self, changeset: DatabaseSettingChangesetPB) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateDatabaseSetting)
.payload(changeset)
.async_send()
.await
.error()
}
pub async fn get_all_calendar_events(&self, view_id: &str) -> Vec<CalendarEventPB> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetAllCalendarEvents)
.payload(CalendarEventRequestPB {
view_id: view_id.to_string(),
})
.async_send()
.await
.parse::<RepeatedCalendarEventPB>()
.items
}
}

View File

@ -0,0 +1,119 @@
use std::sync::Arc;
use collab::core::collab::MutexCollab;
use collab::core::origin::CollabOrigin;
use collab::preclude::updates::decoder::Decode;
use collab::preclude::{merge_updates_v1, Update};
use collab_document::blocks::DocumentData;
use collab_document::document::Document;
use flowy_document2::entities::{DocumentDataPB, OpenDocumentPayloadPB};
use flowy_document2::event_map::DocumentEvent;
use flowy_folder2::entities::{CreateViewPayloadPB, ViewLayoutPB, ViewPB};
use flowy_folder2::event_map::FolderEvent;
use crate::document::document_event::{DocumentEventTest, OpenDocumentData};
use crate::event_builder::EventBuilder;
use crate::EventIntegrationTest;
impl EventIntegrationTest {
pub async fn create_document(
&self,
parent_id: &str,
name: String,
initial_data: Vec<u8>,
) -> ViewPB {
let payload = CreateViewPayloadPB {
parent_view_id: parent_id.to_string(),
name,
desc: "".to_string(),
thumbnail: None,
layout: ViewLayoutPB::Document,
initial_data,
meta: Default::default(),
set_as_current: true,
index: None,
};
let view = EventBuilder::new(self.clone())
.event(FolderEvent::CreateView)
.payload(payload)
.async_send()
.await
.parse::<ViewPB>();
let payload = OpenDocumentPayloadPB {
document_id: view.id.clone(),
};
let _ = EventBuilder::new(self.clone())
.event(DocumentEvent::OpenDocument)
.payload(payload)
.async_send()
.await
.parse::<DocumentDataPB>();
view
}
pub async fn open_document(&self, doc_id: String) -> OpenDocumentData {
let payload = OpenDocumentPayloadPB {
document_id: doc_id.clone(),
};
let data = EventBuilder::new(self.clone())
.event(DocumentEvent::OpenDocument)
.payload(payload)
.async_send()
.await
.parse::<DocumentDataPB>();
OpenDocumentData { id: doc_id, data }
}
pub async fn insert_document_text(&self, document_id: &str, text: &str, index: usize) {
let document_event = DocumentEventTest::new_with_core(self.clone());
document_event
.insert_index(document_id, text, index, None)
.await;
}
pub async fn get_document_data(&self, view_id: &str) -> DocumentData {
let pb = EventBuilder::new(self.clone())
.event(DocumentEvent::GetDocumentData)
.payload(OpenDocumentPayloadPB {
document_id: view_id.to_string(),
})
.async_send()
.await
.parse::<DocumentDataPB>();
DocumentData::from(pb)
}
pub async fn get_document_update(&self, document_id: &str) -> Vec<u8> {
let workspace_id = self.user_manager.workspace_id().unwrap();
let cloud_service = self.document_manager.get_cloud_service().clone();
let remote_updates = cloud_service
.get_document_updates(document_id, &workspace_id)
.await
.unwrap();
if remote_updates.is_empty() {
return vec![];
}
let updates = remote_updates
.iter()
.map(|update| update.as_ref())
.collect::<Vec<&[u8]>>();
merge_updates_v1(&updates).unwrap()
}
}
pub fn assert_document_data_equal(collab_update: &[u8], doc_id: &str, expected: DocumentData) {
let collab = MutexCollab::new(CollabOrigin::Server, doc_id, vec![]);
collab.lock().with_origin_transact_mut(|txn| {
let update = Update::decode_v1(collab_update).unwrap();
txn.apply_update(update);
});
let document = Document::open(Arc::new(collab)).unwrap();
let actual = document.get_document_data().unwrap();
assert_eq!(actual, expected);
}

View File

@ -0,0 +1,173 @@
use flowy_folder2::entities::icon::UpdateViewIconPayloadPB;
use flowy_folder2::entities::*;
use flowy_folder2::event_map::FolderEvent;
use flowy_folder2::event_map::FolderEvent::*;
use flowy_user::errors::FlowyError;
use crate::event_builder::EventBuilder;
use crate::EventIntegrationTest;
impl EventIntegrationTest {
// Must sign up/ sign in first
pub async fn get_current_workspace(&self) -> WorkspaceSettingPB {
EventBuilder::new(self.clone())
.event(FolderEvent::GetCurrentWorkspace)
.async_send()
.await
.parse::<WorkspaceSettingPB>()
}
pub async fn get_all_workspace_views(&self) -> Vec<ViewPB> {
EventBuilder::new(self.clone())
.event(FolderEvent::ReadWorkspaceViews)
.async_send()
.await
.parse::<RepeatedViewPB>()
.items
}
pub async fn get_views(&self, parent_view_id: &str) -> ViewPB {
EventBuilder::new(self.clone())
.event(FolderEvent::ReadView)
.payload(ViewIdPB {
value: parent_view_id.to_string(),
})
.async_send()
.await
.parse::<ViewPB>()
}
pub async fn delete_view(&self, view_id: &str) {
let payload = RepeatedViewIdPB {
items: vec![view_id.to_string()],
};
// delete the view. the view will be moved to trash
EventBuilder::new(self.clone())
.event(FolderEvent::DeleteView)
.payload(payload)
.async_send()
.await;
}
pub async fn update_view(&self, changeset: UpdateViewPayloadPB) -> Option<FlowyError> {
// delete the view. the view will be moved to trash
EventBuilder::new(self.clone())
.event(FolderEvent::UpdateView)
.payload(changeset)
.async_send()
.await
.error()
}
pub async fn update_view_icon(&self, payload: UpdateViewIconPayloadPB) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(FolderEvent::UpdateViewIcon)
.payload(payload)
.async_send()
.await
.error()
}
pub async fn create_view(&self, parent_id: &str, name: String) -> ViewPB {
let payload = CreateViewPayloadPB {
parent_view_id: parent_id.to_string(),
name,
desc: "".to_string(),
thumbnail: None,
layout: Default::default(),
initial_data: vec![],
meta: Default::default(),
set_as_current: false,
index: None,
};
EventBuilder::new(self.clone())
.event(FolderEvent::CreateView)
.payload(payload)
.async_send()
.await
.parse::<ViewPB>()
}
pub async fn get_view(&self, view_id: &str) -> ViewPB {
EventBuilder::new(self.clone())
.event(FolderEvent::ReadView)
.payload(ViewIdPB {
value: view_id.to_string(),
})
.async_send()
.await
.parse::<ViewPB>()
}
}
pub struct ViewTest {
pub sdk: EventIntegrationTest,
pub workspace: WorkspacePB,
pub child_view: ViewPB,
}
impl ViewTest {
#[allow(dead_code)]
pub async fn new(sdk: &EventIntegrationTest, layout: ViewLayoutPB, data: Vec<u8>) -> Self {
let workspace = create_workspace(sdk, "Workspace", "").await;
let payload = WorkspaceIdPB {
value: Some(workspace.id.clone()),
};
let _ = EventBuilder::new(sdk.clone())
.event(OpenWorkspace)
.payload(payload)
.async_send()
.await;
let payload = CreateViewPayloadPB {
parent_view_id: workspace.id.clone(),
name: "View A".to_string(),
desc: "".to_string(),
thumbnail: Some("http://1.png".to_string()),
layout,
initial_data: data,
meta: Default::default(),
set_as_current: true,
index: None,
};
let view = EventBuilder::new(sdk.clone())
.event(CreateView)
.payload(payload)
.async_send()
.await
.parse::<ViewPB>();
Self {
sdk: sdk.clone(),
workspace,
child_view: view,
}
}
pub async fn new_grid_view(sdk: &EventIntegrationTest, data: Vec<u8>) -> Self {
Self::new(sdk, ViewLayoutPB::Grid, data).await
}
pub async fn new_board_view(sdk: &EventIntegrationTest, data: Vec<u8>) -> Self {
Self::new(sdk, ViewLayoutPB::Board, data).await
}
pub async fn new_calendar_view(sdk: &EventIntegrationTest, data: Vec<u8>) -> Self {
Self::new(sdk, ViewLayoutPB::Calendar, data).await
}
}
async fn create_workspace(sdk: &EventIntegrationTest, name: &str, desc: &str) -> WorkspacePB {
let request = CreateWorkspacePayloadPB {
name: name.to_owned(),
desc: desc.to_owned(),
};
EventBuilder::new(sdk.clone())
.event(CreateWorkspace)
.payload(request)
.async_send()
.await
.parse::<WorkspacePB>()
}

View File

@ -0,0 +1,296 @@
use std::collections::HashMap;
use std::convert::TryFrom;
use std::sync::Arc;
use bytes::Bytes;
use nanoid::nanoid;
use protobuf::ProtobufError;
use tokio::sync::broadcast::{channel, Sender};
use uuid::Uuid;
use flowy_notification::entities::SubscribeObject;
use flowy_notification::NotificationSender;
use flowy_server::supabase::define::{USER_DEVICE_ID, USER_EMAIL, USER_SIGN_IN_URL, USER_UUID};
use flowy_user::entities::{
AuthTypePB, OauthSignInPB, SignInUrlPB, SignInUrlPayloadPB, SignUpPayloadPB, UpdateCloudConfigPB,
UpdateUserProfilePayloadPB, UserCloudConfigPB, UserProfilePB,
};
use flowy_user::errors::{FlowyError, FlowyResult};
use flowy_user::event_map::UserEvent::*;
use lib_dispatch::prelude::{AFPluginDispatcher, AFPluginRequest, ToBytes};
use crate::event_builder::EventBuilder;
use crate::EventIntegrationTest;
impl EventIntegrationTest {
pub async fn enable_encryption(&self) -> String {
let config = EventBuilder::new(self.clone())
.event(GetCloudConfig)
.async_send()
.await
.parse::<UserCloudConfigPB>();
let update = UpdateCloudConfigPB {
enable_sync: None,
enable_encrypt: Some(true),
};
let error = EventBuilder::new(self.clone())
.event(SetCloudConfig)
.payload(update)
.async_send()
.await
.error();
assert!(error.is_none());
config.encrypt_secret
}
pub async fn new_with_guest_user() -> Self {
let test = Self::default();
test.sign_up_as_guest().await;
test
}
pub async fn sign_up_as_guest(&self) -> SignUpContext {
let password = login_password();
let email = unique_email();
let payload = SignUpPayloadPB {
email,
name: "appflowy".to_string(),
password: password.clone(),
auth_type: AuthTypePB::Local,
device_id: uuid::Uuid::new_v4().to_string(),
}
.into_bytes()
.unwrap();
let request = AFPluginRequest::new(SignUp).payload(payload);
let user_profile = AFPluginDispatcher::async_send(self.inner.dispatcher(), request)
.await
.parse::<UserProfilePB, FlowyError>()
.unwrap()
.unwrap();
// let _ = create_default_workspace_if_need(dispatch.clone(), &user_profile.id);
SignUpContext {
user_profile,
password,
}
}
pub async fn af_cloud_sign_up(&self) -> UserProfilePB {
let email = unique_email();
self.af_cloud_sign_in_with_email(&email).await.unwrap()
}
pub async fn supabase_party_sign_up(&self) -> UserProfilePB {
let map = third_party_sign_up_param(Uuid::new_v4().to_string());
let payload = OauthSignInPB {
map,
auth_type: AuthTypePB::Supabase,
};
EventBuilder::new(self.clone())
.event(OauthSignIn)
.payload(payload)
.async_send()
.await
.parse::<UserProfilePB>()
}
pub async fn sign_out(&self) {
EventBuilder::new(self.clone())
.event(SignOut)
.async_send()
.await;
}
pub fn set_auth_type(&self, auth_type: AuthTypePB) {
*self.auth_type.write() = auth_type;
}
pub async fn init_user(&self) -> UserProfilePB {
self.sign_up_as_guest().await.user_profile
}
pub async fn get_user_profile(&self) -> Result<UserProfilePB, FlowyError> {
EventBuilder::new(self.clone())
.event(GetUserProfile)
.async_send()
.await
.try_parse::<UserProfilePB>()
}
pub async fn update_user_profile(&self, params: UpdateUserProfilePayloadPB) {
EventBuilder::new(self.clone())
.event(UpdateUserProfile)
.payload(params)
.async_send()
.await;
}
pub async fn af_cloud_sign_in_with_email(&self, email: &str) -> FlowyResult<UserProfilePB> {
let payload = SignInUrlPayloadPB {
email: email.to_string(),
auth_type: AuthTypePB::AFCloud,
};
let sign_in_url = EventBuilder::new(self.clone())
.event(GetSignInURL)
.payload(payload)
.async_send()
.await
.try_parse::<SignInUrlPB>()?
.sign_in_url;
let mut map = HashMap::new();
map.insert(USER_SIGN_IN_URL.to_string(), sign_in_url);
map.insert(USER_DEVICE_ID.to_string(), Uuid::new_v4().to_string());
let payload = OauthSignInPB {
map,
auth_type: AuthTypePB::AFCloud,
};
let user_profile = EventBuilder::new(self.clone())
.event(OauthSignIn)
.payload(payload)
.async_send()
.await
.try_parse::<UserProfilePB>()?;
Ok(user_profile)
}
pub async fn supabase_sign_up_with_uuid(
&self,
uuid: &str,
email: Option<String>,
) -> FlowyResult<UserProfilePB> {
let mut map = HashMap::new();
map.insert(USER_UUID.to_string(), uuid.to_string());
map.insert(USER_DEVICE_ID.to_string(), uuid.to_string());
map.insert(
USER_EMAIL.to_string(),
email.unwrap_or_else(|| format!("{}@appflowy.io", nanoid!(10))),
);
let payload = OauthSignInPB {
map,
auth_type: AuthTypePB::Supabase,
};
let user_profile = EventBuilder::new(self.clone())
.event(OauthSignIn)
.payload(payload)
.async_send()
.await
.try_parse::<UserProfilePB>()?;
Ok(user_profile)
}
}
#[derive(Clone)]
pub struct TestNotificationSender {
sender: Arc<Sender<SubscribeObject>>,
}
impl Default for TestNotificationSender {
fn default() -> Self {
let (sender, _) = channel(1000);
Self {
sender: Arc::new(sender),
}
}
}
impl TestNotificationSender {
pub fn new() -> Self {
Self::default()
}
pub fn subscribe<T>(&self, id: &str, ty: impl Into<i32> + Send) -> tokio::sync::mpsc::Receiver<T>
where
T: TryFrom<Bytes, Error = ProtobufError> + Send + 'static,
{
let id = id.to_string();
let (tx, rx) = tokio::sync::mpsc::channel::<T>(10);
let mut receiver = self.sender.subscribe();
let ty = ty.into();
tokio::spawn(async move {
// DatabaseNotification::DidUpdateDatabaseSnapshotState
while let Ok(value) = receiver.recv().await {
if value.id == id && value.ty == ty {
if let Some(payload) = value.payload {
match T::try_from(Bytes::from(payload)) {
Ok(object) => {
let _ = tx.send(object).await;
},
Err(e) => {
panic!(
"Failed to parse notification payload to type: {:?} with error: {}",
std::any::type_name::<T>(),
e
);
},
}
}
}
}
});
rx
}
pub fn subscribe_with_condition<T, F>(&self, id: &str, when: F) -> tokio::sync::mpsc::Receiver<T>
where
T: TryFrom<Bytes, Error = ProtobufError> + Send + 'static,
F: Fn(&T) -> bool + Send + 'static,
{
let id = id.to_string();
let (tx, rx) = tokio::sync::mpsc::channel::<T>(10);
let mut receiver = self.sender.subscribe();
tokio::spawn(async move {
while let Ok(value) = receiver.recv().await {
if value.id == id {
if let Some(payload) = value.payload {
if let Ok(object) = T::try_from(Bytes::from(payload)) {
if when(&object) {
let _ = tx.send(object).await;
}
}
}
}
}
});
rx
}
}
impl NotificationSender for TestNotificationSender {
fn send_subject(&self, subject: SubscribeObject) -> Result<(), String> {
let _ = self.sender.send(subject);
Ok(())
}
}
pub fn third_party_sign_up_param(uuid: String) -> HashMap<String, String> {
let mut params = HashMap::new();
params.insert(USER_UUID.to_string(), uuid);
params.insert(
USER_EMAIL.to_string(),
format!("{}@test.com", Uuid::new_v4()),
);
params.insert(USER_DEVICE_ID.to_string(), Uuid::new_v4().to_string());
params
}
pub fn unique_email() -> String {
format!("{}@appflowy.io", Uuid::new_v4())
}
pub fn login_email() -> String {
"annie2@appflowy.io".to_string()
}
pub fn login_password() -> String {
"HelloWorld!123".to_string()
}
pub struct SignUpContext {
pub user_profile: UserProfilePB,
pub password: String,
}

View File

@ -1,84 +0,0 @@
use std::sync::Arc;
use nanoid::nanoid;
use flowy_user::entities::{AuthTypePB, SignUpPayloadPB, UserProfilePB};
use flowy_user::errors::FlowyError;
use flowy_user::event_map::UserEvent::*;
use lib_dispatch::prelude::{AFPluginDispatcher, AFPluginRequest, ToBytes};
pub fn random_email() -> String {
format!("{}@appflowy.io", nanoid!(20))
}
pub fn login_email() -> String {
"annie2@appflowy.io".to_string()
}
pub fn login_password() -> String {
"HelloWorld!123".to_string()
}
pub struct SignUpContext {
pub user_profile: UserProfilePB,
pub password: String,
}
pub fn sign_up(dispatch: Arc<AFPluginDispatcher>) -> SignUpContext {
let password = login_password();
let payload = SignUpPayloadPB {
email: random_email(),
name: "app flowy".to_string(),
password: password.clone(),
auth_type: AuthTypePB::Local,
device_id: uuid::Uuid::new_v4().to_string(),
}
.into_bytes()
.unwrap();
let request = AFPluginRequest::new(SignUp).payload(payload);
let user_profile = AFPluginDispatcher::sync_send(dispatch, request)
.parse::<UserProfilePB, FlowyError>()
.unwrap()
.unwrap();
SignUpContext {
user_profile,
password,
}
}
pub async fn async_sign_up(
dispatch: Arc<AFPluginDispatcher>,
auth_type: AuthTypePB,
) -> SignUpContext {
let password = login_password();
let email = random_email();
let payload = SignUpPayloadPB {
email,
name: "appflowy".to_string(),
password: password.clone(),
auth_type,
device_id: uuid::Uuid::new_v4().to_string(),
}
.into_bytes()
.unwrap();
let request = AFPluginRequest::new(SignUp).payload(payload);
let user_profile = AFPluginDispatcher::async_send(dispatch.clone(), request)
.await
.parse::<UserProfilePB, FlowyError>()
.unwrap()
.unwrap();
// let _ = create_default_workspace_if_need(dispatch.clone(), &user_profile.id);
SignUpContext {
user_profile,
password,
}
}
pub async fn init_user_setting(dispatch: Arc<AFPluginDispatcher>) {
let request = AFPluginRequest::new(InitUser);
let _ = AFPluginDispatcher::async_send(dispatch.clone(), request).await;
}

View File

@ -3,7 +3,7 @@ use std::convert::TryFrom;
use bytes::Bytes; use bytes::Bytes;
use event_integration::event_builder::EventBuilder; use event_integration::event_builder::EventBuilder;
use event_integration::FlowyCoreTest; use event_integration::EventIntegrationTest;
use flowy_database2::entities::{ use flowy_database2::entities::{
CellChangesetPB, CellIdPB, ChecklistCellDataChangesetPB, DatabaseLayoutPB, CellChangesetPB, CellIdPB, ChecklistCellDataChangesetPB, DatabaseLayoutPB,
DatabaseSettingChangesetPB, DatabaseViewIdPB, DateChangesetPB, FieldType, SelectOptionCellDataPB, DatabaseSettingChangesetPB, DatabaseViewIdPB, DateChangesetPB, FieldType, SelectOptionCellDataPB,
@ -13,7 +13,7 @@ use lib_infra::util::timestamp;
#[tokio::test] #[tokio::test]
async fn get_database_id_event_test() { async fn get_database_id_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -35,7 +35,7 @@ async fn get_database_id_event_test() {
#[tokio::test] #[tokio::test]
async fn get_database_event_test() { async fn get_database_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -49,7 +49,7 @@ async fn get_database_event_test() {
#[tokio::test] #[tokio::test]
async fn get_field_event_test() { async fn get_field_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -64,7 +64,7 @@ async fn get_field_event_test() {
#[tokio::test] #[tokio::test]
async fn create_field_event_test() { async fn create_field_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -78,7 +78,7 @@ async fn create_field_event_test() {
#[tokio::test] #[tokio::test]
async fn delete_field_event_test() { async fn delete_field_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -99,7 +99,7 @@ async fn delete_field_event_test() {
// The primary field is not allowed to be deleted. // The primary field is not allowed to be deleted.
#[tokio::test] #[tokio::test]
async fn delete_primary_field_event_test() { async fn delete_primary_field_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -114,7 +114,7 @@ async fn delete_primary_field_event_test() {
#[tokio::test] #[tokio::test]
async fn update_field_type_event_test() { async fn update_field_type_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -132,7 +132,7 @@ async fn update_field_type_event_test() {
#[tokio::test] #[tokio::test]
async fn update_primary_field_type_event_test() { async fn update_primary_field_type_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -151,7 +151,7 @@ async fn update_primary_field_type_event_test() {
#[tokio::test] #[tokio::test]
async fn duplicate_field_event_test() { async fn duplicate_field_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -169,7 +169,7 @@ async fn duplicate_field_event_test() {
// The primary field is not allowed to be duplicated. So this test should return an error. // The primary field is not allowed to be duplicated. So this test should return an error.
#[tokio::test] #[tokio::test]
async fn duplicate_primary_field_test() { async fn duplicate_primary_field_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -183,7 +183,7 @@ async fn duplicate_primary_field_test() {
#[tokio::test] #[tokio::test]
async fn get_primary_field_event_test() { async fn get_primary_field_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -196,7 +196,7 @@ async fn get_primary_field_event_test() {
#[tokio::test] #[tokio::test]
async fn create_row_event_test() { async fn create_row_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -209,7 +209,7 @@ async fn create_row_event_test() {
#[tokio::test] #[tokio::test]
async fn delete_row_event_test() { async fn delete_row_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -232,7 +232,7 @@ async fn delete_row_event_test() {
#[tokio::test] #[tokio::test]
async fn get_row_event_test() { async fn get_row_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -248,7 +248,7 @@ async fn get_row_event_test() {
#[tokio::test] #[tokio::test]
async fn update_row_meta_event_with_url_test() { async fn update_row_meta_event_with_url_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -276,7 +276,7 @@ async fn update_row_meta_event_with_url_test() {
#[tokio::test] #[tokio::test]
async fn update_row_meta_event_with_cover_test() { async fn update_row_meta_event_with_cover_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -304,7 +304,7 @@ async fn update_row_meta_event_with_cover_test() {
#[tokio::test] #[tokio::test]
async fn delete_row_event_with_invalid_row_id_test() { async fn delete_row_event_with_invalid_row_id_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -317,7 +317,7 @@ async fn delete_row_event_with_invalid_row_id_test() {
#[tokio::test] #[tokio::test]
async fn duplicate_row_event_test() { async fn duplicate_row_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -334,7 +334,7 @@ async fn duplicate_row_event_test() {
#[tokio::test] #[tokio::test]
async fn duplicate_row_event_with_invalid_row_id_test() { async fn duplicate_row_event_with_invalid_row_id_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -351,7 +351,7 @@ async fn duplicate_row_event_with_invalid_row_id_test() {
#[tokio::test] #[tokio::test]
async fn move_row_event_test() { async fn move_row_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -371,7 +371,7 @@ async fn move_row_event_test() {
#[tokio::test] #[tokio::test]
async fn move_row_event_test2() { async fn move_row_event_test2() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -391,7 +391,7 @@ async fn move_row_event_test2() {
#[tokio::test] #[tokio::test]
async fn move_row_event_with_invalid_row_id_test() { async fn move_row_event_with_invalid_row_id_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -418,7 +418,7 @@ async fn move_row_event_with_invalid_row_id_test() {
#[tokio::test] #[tokio::test]
async fn update_text_cell_event_test() { async fn update_text_cell_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -448,7 +448,7 @@ async fn update_text_cell_event_test() {
#[tokio::test] #[tokio::test]
async fn update_checkbox_cell_event_test() { async fn update_checkbox_cell_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -479,7 +479,7 @@ async fn update_checkbox_cell_event_test() {
#[tokio::test] #[tokio::test]
async fn update_single_select_cell_event_test() { async fn update_single_select_cell_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -506,7 +506,7 @@ async fn update_single_select_cell_event_test() {
#[tokio::test] #[tokio::test]
async fn update_date_cell_event_test() { async fn update_date_cell_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -543,7 +543,7 @@ async fn update_date_cell_event_test() {
#[tokio::test] #[tokio::test]
async fn update_date_cell_event_with_empty_time_str_test() { async fn update_date_cell_event_with_empty_time_str_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -579,7 +579,7 @@ async fn update_date_cell_event_with_empty_time_str_test() {
#[tokio::test] #[tokio::test]
async fn create_checklist_field_test() { async fn create_checklist_field_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -600,7 +600,7 @@ async fn create_checklist_field_test() {
#[tokio::test] #[tokio::test]
async fn update_checklist_cell_test() { async fn update_checklist_cell_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -657,7 +657,7 @@ async fn update_checklist_cell_test() {
// The number of groups should be 0 if there is no group by field in grid // The number of groups should be 0 if there is no group by field in grid
#[tokio::test] #[tokio::test]
async fn get_groups_event_with_grid_test() { async fn get_groups_event_with_grid_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my board view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my board view".to_owned(), vec![])
@ -669,7 +669,7 @@ async fn get_groups_event_with_grid_test() {
#[tokio::test] #[tokio::test]
async fn get_groups_event_test() { async fn get_groups_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let board_view = test let board_view = test
.create_board(&current_workspace.id, "my board view".to_owned(), vec![]) .create_board(&current_workspace.id, "my board view".to_owned(), vec![])
@ -681,7 +681,7 @@ async fn get_groups_event_test() {
#[tokio::test] #[tokio::test]
async fn move_group_event_test() { async fn move_group_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let board_view = test let board_view = test
.create_board(&current_workspace.id, "my board view".to_owned(), vec![]) .create_board(&current_workspace.id, "my board view".to_owned(), vec![])
@ -715,7 +715,7 @@ async fn move_group_event_test() {
#[tokio::test] #[tokio::test]
async fn move_group_event_with_invalid_id_test() { async fn move_group_event_with_invalid_id_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let board_view = test let board_view = test
.create_board(&current_workspace.id, "my board view".to_owned(), vec![]) .create_board(&current_workspace.id, "my board view".to_owned(), vec![])
@ -737,7 +737,7 @@ async fn move_group_event_with_invalid_id_test() {
#[tokio::test] #[tokio::test]
async fn rename_group_event_test() { async fn rename_group_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let board_view = test let board_view = test
.create_board(&current_workspace.id, "my board view".to_owned(), vec![]) .create_board(&current_workspace.id, "my board view".to_owned(), vec![])
@ -761,7 +761,7 @@ async fn rename_group_event_test() {
#[tokio::test] #[tokio::test]
async fn hide_group_event_test2() { async fn hide_group_event_test2() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let board_view = test let board_view = test
.create_board(&current_workspace.id, "my board view".to_owned(), vec![]) .create_board(&current_workspace.id, "my board view".to_owned(), vec![])
@ -783,7 +783,7 @@ async fn hide_group_event_test2() {
// Update the database layout type from grid to board // Update the database layout type from grid to board
#[tokio::test] #[tokio::test]
async fn update_database_layout_event_test() { async fn update_database_layout_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -805,7 +805,7 @@ async fn update_database_layout_event_test() {
// Update the database layout type from grid to board. Set the checkbox field as the grouping field // Update the database layout type from grid to board. Set the checkbox field as the grouping field
#[tokio::test] #[tokio::test]
async fn update_database_layout_event_test2() { async fn update_database_layout_event_test2() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![]) .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -837,7 +837,7 @@ async fn update_database_layout_event_test2() {
// Create a checkbox field in the default board and then set it as the grouping field. // Create a checkbox field in the default board and then set it as the grouping field.
#[tokio::test] #[tokio::test]
async fn set_group_by_checkbox_field_test() { async fn set_group_by_checkbox_field_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let board_view = test let board_view = test
.create_board(&current_workspace.id, "my board view".to_owned(), vec![]) .create_board(&current_workspace.id, "my board view".to_owned(), vec![])
@ -854,7 +854,7 @@ async fn set_group_by_checkbox_field_test() {
#[tokio::test] #[tokio::test]
async fn get_all_calendar_event_test() { async fn get_all_calendar_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let calendar_view = test let calendar_view = test
.create_calendar(&current_workspace.id, "my calendar view".to_owned(), vec![]) .create_calendar(&current_workspace.id, "my calendar view".to_owned(), vec![])
@ -867,7 +867,7 @@ async fn get_all_calendar_event_test() {
#[tokio::test] #[tokio::test]
async fn create_calendar_event_test() { async fn create_calendar_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let calendar_view = test let calendar_view = test
.create_calendar(&current_workspace.id, "my calendar view".to_owned(), vec![]) .create_calendar(&current_workspace.id, "my calendar view".to_owned(), vec![])

View File

@ -1,6 +1,6 @@
use std::time::Duration; use std::time::Duration;
use event_integration::assert_document_data_equal; use event_integration::test_document::assert_document_data_equal;
use flowy_document2::entities::DocumentSyncStatePB; use flowy_document2::entities::DocumentSyncStatePB;
use crate::document::af_cloud_test::util::AFCloudDocumentTest; use crate::document::af_cloud_test::util::AFCloudDocumentTest;

View File

@ -1,6 +1,6 @@
use std::time::Duration; use std::time::Duration;
use event_integration::assert_document_data_equal; use event_integration::test_document::assert_document_data_equal;
use flowy_document2::entities::DocumentSyncStatePB; use flowy_document2::entities::DocumentSyncStatePB;
use crate::document::supabase_test::helper::FlowySupabaseDocumentTest; use crate::document::supabase_test::helper::FlowySupabaseDocumentTest;

View File

@ -1,7 +1,7 @@
use collab_folder::core::ViewLayout; use collab_folder::core::ViewLayout;
use event_integration::event_builder::EventBuilder; use event_integration::event_builder::EventBuilder;
use event_integration::FlowyCoreTest; use event_integration::EventIntegrationTest;
use flowy_folder2::entities::icon::{UpdateViewIconPayloadPB, ViewIconPB}; use flowy_folder2::entities::icon::{UpdateViewIconPayloadPB, ViewIconPB};
use flowy_folder2::entities::*; use flowy_folder2::entities::*;
use flowy_folder2::event_map::FolderEvent::*; use flowy_folder2::event_map::FolderEvent::*;
@ -64,7 +64,7 @@ pub enum FolderScript {
} }
pub struct FolderTest { pub struct FolderTest {
pub sdk: FlowyCoreTest, pub sdk: EventIntegrationTest,
pub all_workspace: Vec<WorkspacePB>, pub all_workspace: Vec<WorkspacePB>,
pub workspace: WorkspacePB, pub workspace: WorkspacePB,
pub parent_view: ViewPB, pub parent_view: ViewPB,
@ -75,7 +75,7 @@ pub struct FolderTest {
impl FolderTest { impl FolderTest {
pub async fn new() -> Self { pub async fn new() -> Self {
let sdk = FlowyCoreTest::new(); let sdk = EventIntegrationTest::new();
let _ = sdk.init_user().await; let _ = sdk.init_user().await;
let workspace = create_workspace(&sdk, "FolderWorkspace", "Folder test workspace").await; let workspace = create_workspace(&sdk, "FolderWorkspace", "Folder test workspace").await;
let parent_view = create_app(&sdk, &workspace.id, "Folder App", "Folder test app").await; let parent_view = create_app(&sdk, &workspace.id, "Folder App", "Folder test app").await;
@ -201,7 +201,7 @@ impl FolderTest {
} }
} }
} }
pub async fn create_workspace(sdk: &FlowyCoreTest, name: &str, desc: &str) -> WorkspacePB { pub async fn create_workspace(sdk: &EventIntegrationTest, name: &str, desc: &str) -> WorkspacePB {
let request = CreateWorkspacePayloadPB { let request = CreateWorkspacePayloadPB {
name: name.to_owned(), name: name.to_owned(),
desc: desc.to_owned(), desc: desc.to_owned(),
@ -215,7 +215,10 @@ pub async fn create_workspace(sdk: &FlowyCoreTest, name: &str, desc: &str) -> Wo
.parse::<WorkspacePB>() .parse::<WorkspacePB>()
} }
pub async fn read_workspace(sdk: &FlowyCoreTest, workspace_id: Option<String>) -> Vec<WorkspacePB> { pub async fn read_workspace(
sdk: &EventIntegrationTest,
workspace_id: Option<String>,
) -> Vec<WorkspacePB> {
let request = WorkspaceIdPB { let request = WorkspaceIdPB {
value: workspace_id, value: workspace_id,
}; };
@ -241,7 +244,12 @@ pub async fn read_workspace(sdk: &FlowyCoreTest, workspace_id: Option<String>) -
workspaces workspaces
} }
pub async fn create_app(sdk: &FlowyCoreTest, workspace_id: &str, name: &str, desc: &str) -> ViewPB { pub async fn create_app(
sdk: &EventIntegrationTest,
workspace_id: &str,
name: &str,
desc: &str,
) -> ViewPB {
let create_view_request = CreateViewPayloadPB { let create_view_request = CreateViewPayloadPB {
parent_view_id: workspace_id.to_owned(), parent_view_id: workspace_id.to_owned(),
name: name.to_string(), name: name.to_string(),
@ -263,7 +271,7 @@ pub async fn create_app(sdk: &FlowyCoreTest, workspace_id: &str, name: &str, des
} }
pub async fn create_view( pub async fn create_view(
sdk: &FlowyCoreTest, sdk: &EventIntegrationTest,
app_id: &str, app_id: &str,
name: &str, name: &str,
desc: &str, desc: &str,
@ -288,7 +296,7 @@ pub async fn create_view(
.parse::<ViewPB>() .parse::<ViewPB>()
} }
pub async fn read_view(sdk: &FlowyCoreTest, view_id: &str) -> ViewPB { pub async fn read_view(sdk: &EventIntegrationTest, view_id: &str) -> ViewPB {
let view_id = ViewIdPB::from(view_id); let view_id = ViewIdPB::from(view_id);
EventBuilder::new(sdk.clone()) EventBuilder::new(sdk.clone())
.event(ReadView) .event(ReadView)
@ -299,7 +307,7 @@ pub async fn read_view(sdk: &FlowyCoreTest, view_id: &str) -> ViewPB {
} }
pub async fn move_view( pub async fn move_view(
sdk: &FlowyCoreTest, sdk: &EventIntegrationTest,
view_id: String, view_id: String,
parent_id: String, parent_id: String,
prev_view_id: Option<String>, prev_view_id: Option<String>,
@ -319,7 +327,7 @@ pub async fn move_view(
assert!(error.is_none()); assert!(error.is_none());
} }
pub async fn update_view( pub async fn update_view(
sdk: &FlowyCoreTest, sdk: &EventIntegrationTest,
view_id: &str, view_id: &str,
name: Option<String>, name: Option<String>,
desc: Option<String>, desc: Option<String>,
@ -340,7 +348,7 @@ pub async fn update_view(
.await; .await;
} }
pub async fn update_view_icon(sdk: &FlowyCoreTest, view_id: &str, icon: Option<ViewIconPB>) { pub async fn update_view_icon(sdk: &EventIntegrationTest, view_id: &str, icon: Option<ViewIconPB>) {
let request = UpdateViewIconPayloadPB { let request = UpdateViewIconPayloadPB {
view_id: view_id.to_string(), view_id: view_id.to_string(),
icon, icon,
@ -352,7 +360,7 @@ pub async fn update_view_icon(sdk: &FlowyCoreTest, view_id: &str, icon: Option<V
.await; .await;
} }
pub async fn delete_view(sdk: &FlowyCoreTest, view_ids: Vec<String>) { pub async fn delete_view(sdk: &EventIntegrationTest, view_ids: Vec<String>) {
let request = RepeatedViewIdPB { items: view_ids }; let request = RepeatedViewIdPB { items: view_ids };
EventBuilder::new(sdk.clone()) EventBuilder::new(sdk.clone())
.event(DeleteView) .event(DeleteView)
@ -361,7 +369,7 @@ pub async fn delete_view(sdk: &FlowyCoreTest, view_ids: Vec<String>) {
.await; .await;
} }
pub async fn read_trash(sdk: &FlowyCoreTest) -> RepeatedTrashPB { pub async fn read_trash(sdk: &EventIntegrationTest) -> RepeatedTrashPB {
EventBuilder::new(sdk.clone()) EventBuilder::new(sdk.clone())
.event(ReadTrash) .event(ReadTrash)
.async_send() .async_send()
@ -369,7 +377,7 @@ pub async fn read_trash(sdk: &FlowyCoreTest) -> RepeatedTrashPB {
.parse::<RepeatedTrashPB>() .parse::<RepeatedTrashPB>()
} }
pub async fn restore_app_from_trash(sdk: &FlowyCoreTest, app_id: &str) { pub async fn restore_app_from_trash(sdk: &EventIntegrationTest, app_id: &str) {
let id = TrashIdPB { let id = TrashIdPB {
id: app_id.to_owned(), id: app_id.to_owned(),
}; };
@ -380,7 +388,7 @@ pub async fn restore_app_from_trash(sdk: &FlowyCoreTest, app_id: &str) {
.await; .await;
} }
pub async fn restore_view_from_trash(sdk: &FlowyCoreTest, view_id: &str) { pub async fn restore_view_from_trash(sdk: &EventIntegrationTest, view_id: &str) {
let id = TrashIdPB { let id = TrashIdPB {
id: view_id.to_owned(), id: view_id.to_owned(),
}; };
@ -391,14 +399,14 @@ pub async fn restore_view_from_trash(sdk: &FlowyCoreTest, view_id: &str) {
.await; .await;
} }
pub async fn delete_all_trash(sdk: &FlowyCoreTest) { pub async fn delete_all_trash(sdk: &EventIntegrationTest) {
EventBuilder::new(sdk.clone()) EventBuilder::new(sdk.clone())
.event(DeleteAllTrash) .event(DeleteAllTrash)
.async_send() .async_send()
.await; .await;
} }
pub async fn toggle_favorites(sdk: &FlowyCoreTest, view_id: Vec<String>) { pub async fn toggle_favorites(sdk: &EventIntegrationTest, view_id: Vec<String>) {
let request = RepeatedViewIdPB { items: view_id }; let request = RepeatedViewIdPB { items: view_id };
EventBuilder::new(sdk.clone()) EventBuilder::new(sdk.clone())
.event(ToggleFavorite) .event(ToggleFavorite)
@ -407,7 +415,7 @@ pub async fn toggle_favorites(sdk: &FlowyCoreTest, view_id: Vec<String>) {
.await; .await;
} }
pub async fn read_favorites(sdk: &FlowyCoreTest) -> RepeatedViewPB { pub async fn read_favorites(sdk: &EventIntegrationTest) -> RepeatedViewPB {
EventBuilder::new(sdk.clone()) EventBuilder::new(sdk.clone())
.event(ReadFavorites) .event(ReadFavorites)
.async_send() .async_send()

View File

@ -1,6 +1,6 @@
use std::time::Duration; use std::time::Duration;
use event_integration::FlowyCoreTest; use event_integration::EventIntegrationTest;
use flowy_folder2::entities::{ChildViewUpdatePB, RepeatedViewPB, UpdateViewPayloadPB}; use flowy_folder2::entities::{ChildViewUpdatePB, RepeatedViewPB, UpdateViewPayloadPB};
use flowy_folder2::notification::FolderNotification; use flowy_folder2::notification::FolderNotification;
@ -16,7 +16,7 @@ use crate::util::receive_with_timeout;
/// 5. Await the notification for workspace view updates with a timeout of 30 seconds. /// 5. Await the notification for workspace view updates with a timeout of 30 seconds.
/// 6. Ensure that the received views contain the newly created "test_view". /// 6. Ensure that the received views contain the newly created "test_view".
async fn create_child_view_in_workspace_subscription_test() { async fn create_child_view_in_workspace_subscription_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let workspace = test.get_current_workspace().await.workspace; let workspace = test.get_current_workspace().await.workspace;
let mut rx = test let mut rx = test
.notification_sender .notification_sender
@ -40,7 +40,7 @@ async fn create_child_view_in_workspace_subscription_test() {
#[tokio::test] #[tokio::test]
async fn create_child_view_in_view_subscription_test() { async fn create_child_view_in_view_subscription_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let mut workspace = test.get_current_workspace().await.workspace; let mut workspace = test.get_current_workspace().await.workspace;
let workspace_child_view = workspace.views.pop().unwrap(); let workspace_child_view = workspace.views.pop().unwrap();
let mut rx = test.notification_sender.subscribe::<ChildViewUpdatePB>( let mut rx = test.notification_sender.subscribe::<ChildViewUpdatePB>(
@ -72,7 +72,7 @@ async fn create_child_view_in_view_subscription_test() {
#[tokio::test] #[tokio::test]
async fn delete_view_subscription_test() { async fn delete_view_subscription_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let workspace = test.get_current_workspace().await.workspace; let workspace = test.get_current_workspace().await.workspace;
let mut rx = test let mut rx = test
.notification_sender .notification_sender
@ -94,7 +94,7 @@ async fn delete_view_subscription_test() {
#[tokio::test] #[tokio::test]
async fn update_view_subscription_test() { async fn update_view_subscription_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let mut workspace = test.get_current_workspace().await.workspace; let mut workspace = test.get_current_workspace().await.workspace;
let mut rx = test let mut rx = test
.notification_sender .notification_sender

View File

@ -1,12 +1,12 @@
use event_integration::event_builder::EventBuilder; use event_integration::event_builder::EventBuilder;
use event_integration::FlowyCoreTest; use event_integration::EventIntegrationTest;
use flowy_folder2::entities::icon::{UpdateViewIconPayloadPB, ViewIconPB, ViewIconTypePB}; use flowy_folder2::entities::icon::{UpdateViewIconPayloadPB, ViewIconPB, ViewIconTypePB};
use flowy_folder2::entities::*; use flowy_folder2::entities::*;
use flowy_user::errors::ErrorCode; use flowy_user::errors::ErrorCode;
#[tokio::test] #[tokio::test]
async fn create_workspace_event_test() { async fn create_workspace_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let request = CreateWorkspacePayloadPB { let request = CreateWorkspacePayloadPB {
name: "my second workspace".to_owned(), name: "my second workspace".to_owned(),
desc: "".to_owned(), desc: "".to_owned(),
@ -22,7 +22,7 @@ async fn create_workspace_event_test() {
#[tokio::test] #[tokio::test]
async fn open_workspace_event_test() { async fn open_workspace_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let payload = CreateWorkspacePayloadPB { let payload = CreateWorkspacePayloadPB {
name: "my second workspace".to_owned(), name: "my second workspace".to_owned(),
desc: "".to_owned(), desc: "".to_owned(),
@ -52,7 +52,7 @@ async fn open_workspace_event_test() {
#[tokio::test] #[tokio::test]
async fn create_view_event_test() { async fn create_view_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let view = test let view = test
.create_view(&current_workspace.id, "My first view".to_string()) .create_view(&current_workspace.id, "My first view".to_string())
@ -64,7 +64,7 @@ async fn create_view_event_test() {
#[tokio::test] #[tokio::test]
async fn update_view_event_with_name_test() { async fn update_view_event_with_name_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let view = test let view = test
.create_view(&current_workspace.id, "My first view".to_string()) .create_view(&current_workspace.id, "My first view".to_string())
@ -85,7 +85,7 @@ async fn update_view_event_with_name_test() {
#[tokio::test] #[tokio::test]
async fn update_view_icon_event_test() { async fn update_view_icon_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let view = test let view = test
.create_view(&current_workspace.id, "My first view".to_string()) .create_view(&current_workspace.id, "My first view".to_string())
@ -109,7 +109,7 @@ async fn update_view_icon_event_test() {
#[tokio::test] #[tokio::test]
async fn delete_view_event_test() { async fn delete_view_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let view = test let view = test
.create_view(&current_workspace.id, "My first view".to_string()) .create_view(&current_workspace.id, "My first view".to_string())
@ -132,7 +132,7 @@ async fn delete_view_event_test() {
#[tokio::test] #[tokio::test]
async fn put_back_trash_event_test() { async fn put_back_trash_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let view = test let view = test
.create_view(&current_workspace.id, "My first view".to_string()) .create_view(&current_workspace.id, "My first view".to_string())
@ -175,7 +175,7 @@ async fn put_back_trash_event_test() {
#[tokio::test] #[tokio::test]
async fn delete_view_permanently_event_test() { async fn delete_view_permanently_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let view = test let view = test
.create_view(&current_workspace.id, "My first view".to_string()) .create_view(&current_workspace.id, "My first view".to_string())
@ -224,7 +224,7 @@ async fn delete_view_permanently_event_test() {
#[tokio::test] #[tokio::test]
async fn delete_all_trash_test() { async fn delete_all_trash_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
for i in 0..3 { for i in 0..3 {
@ -268,7 +268,7 @@ async fn delete_all_trash_test() {
#[tokio::test] #[tokio::test]
async fn multiple_hierarchy_view_test() { async fn multiple_hierarchy_view_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
for i in 1..4 { for i in 1..4 {
let parent = test let parent = test
@ -344,7 +344,7 @@ async fn multiple_hierarchy_view_test() {
#[tokio::test] #[tokio::test]
async fn move_view_event_test() { async fn move_view_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
for i in 1..4 { for i in 1..4 {
let parent = test let parent = test
@ -382,7 +382,7 @@ async fn move_view_event_test() {
#[tokio::test] #[tokio::test]
async fn move_view_event_after_delete_view_test() { async fn move_view_event_after_delete_view_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
for i in 1..6 { for i in 1..6 {
let _ = test let _ = test
@ -424,7 +424,7 @@ async fn move_view_event_after_delete_view_test() {
#[tokio::test] #[tokio::test]
async fn move_view_event_after_delete_view_test2() { async fn move_view_event_after_delete_view_test2() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let parent = test let parent = test
.create_view(&current_workspace.id, "My view".to_string()) .create_view(&current_workspace.id, "My view".to_string())
@ -466,7 +466,7 @@ async fn move_view_event_after_delete_view_test2() {
#[tokio::test] #[tokio::test]
async fn create_parent_view_with_invalid_name() { async fn create_parent_view_with_invalid_name() {
for (name, code) in invalid_workspace_name_test_case() { for (name, code) in invalid_workspace_name_test_case() {
let sdk = FlowyCoreTest::new(); let sdk = EventIntegrationTest::new();
let request = CreateWorkspacePayloadPB { let request = CreateWorkspacePayloadPB {
name, name,
desc: "".to_owned(), desc: "".to_owned(),
@ -494,7 +494,7 @@ fn invalid_workspace_name_test_case() -> Vec<(String, ErrorCode)> {
#[tokio::test] #[tokio::test]
async fn move_view_across_parent_test() { async fn move_view_across_parent_test() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let parent_1 = test let parent_1 = test
.create_view(&current_workspace.id, "My view 1".to_string()) .create_view(&current_workspace.id, "My view 1".to_string())
@ -539,7 +539,7 @@ async fn move_view_across_parent_test() {
} }
async fn move_folder_nested_view( async fn move_folder_nested_view(
sdk: FlowyCoreTest, sdk: EventIntegrationTest,
view_id: String, view_id: String,
new_parent_id: String, new_parent_id: String,
prev_view_id: Option<String>, prev_view_id: Option<String>,

View File

@ -1,13 +1,36 @@
use event_integration::FlowyCoreTest; use event_integration::EventIntegrationTest;
use flowy_user::entities::UpdateUserProfilePayloadPB;
use crate::util::{generate_test_email, get_af_cloud_config}; use crate::util::{generate_test_email, get_af_cloud_config};
#[tokio::test] #[tokio::test]
async fn af_cloud_sign_up_test() { async fn af_cloud_sign_up_test() {
if get_af_cloud_config().is_some() { if get_af_cloud_config().is_some() {
let test = FlowyCoreTest::new(); let test = EventIntegrationTest::new();
let email = generate_test_email(); let email = generate_test_email();
let user = test.af_cloud_sign_in_with_email(&email).await.unwrap(); let user = test.af_cloud_sign_in_with_email(&email).await.unwrap();
assert_eq!(user.email, email); assert_eq!(user.email, email);
} }
} }
#[tokio::test]
async fn af_cloud_update_user_metadata_of_open_ai_key() {
if get_af_cloud_config().is_some() {
let test = EventIntegrationTest::new();
let user = test.af_cloud_sign_up().await;
let old_profile = test.get_user_profile().await.unwrap();
assert_eq!(old_profile.openai_key, "".to_string());
test
.update_user_profile(UpdateUserProfilePayloadPB {
id: user.id,
openai_key: Some("new openai_key".to_string()),
..Default::default()
})
.await;
let new_profile = test.get_user_profile().await.unwrap();
assert_eq!(new_profile.openai_key, "new openai_key".to_string());
}
}

View File

@ -1,5 +1,5 @@
use event_integration::user_event::*; use event_integration::test_user::{login_password, unique_email};
use event_integration::{event_builder::EventBuilder, FlowyCoreTest}; use event_integration::{event_builder::EventBuilder, EventIntegrationTest};
use flowy_user::entities::{AuthTypePB, SignInPayloadPB, SignUpPayloadPB}; use flowy_user::entities::{AuthTypePB, SignInPayloadPB, SignUpPayloadPB};
use flowy_user::errors::ErrorCode; use flowy_user::errors::ErrorCode;
use flowy_user::event_map::UserEvent::*; use flowy_user::event_map::UserEvent::*;
@ -9,7 +9,7 @@ use crate::user::local_test::helper::*;
#[tokio::test] #[tokio::test]
async fn sign_up_with_invalid_email() { async fn sign_up_with_invalid_email() {
for email in invalid_email_test_case() { for email in invalid_email_test_case() {
let sdk = FlowyCoreTest::new(); let sdk = EventIntegrationTest::new();
let request = SignUpPayloadPB { let request = SignUpPayloadPB {
email: email.to_string(), email: email.to_string(),
name: valid_name(), name: valid_name(),
@ -33,9 +33,9 @@ async fn sign_up_with_invalid_email() {
} }
#[tokio::test] #[tokio::test]
async fn sign_up_with_long_password() { async fn sign_up_with_long_password() {
let sdk = FlowyCoreTest::new(); let sdk = EventIntegrationTest::new();
let request = SignUpPayloadPB { let request = SignUpPayloadPB {
email: random_email(), email: unique_email(),
name: valid_name(), name: valid_name(),
password: "1234".repeat(100).as_str().to_string(), password: "1234".repeat(100).as_str().to_string(),
auth_type: AuthTypePB::Local, auth_type: AuthTypePB::Local,
@ -58,7 +58,7 @@ async fn sign_up_with_long_password() {
#[tokio::test] #[tokio::test]
async fn sign_in_with_invalid_email() { async fn sign_in_with_invalid_email() {
for email in invalid_email_test_case() { for email in invalid_email_test_case() {
let sdk = FlowyCoreTest::new(); let sdk = EventIntegrationTest::new();
let request = SignInPayloadPB { let request = SignInPayloadPB {
email: email.to_string(), email: email.to_string(),
password: login_password(), password: login_password(),
@ -84,10 +84,10 @@ async fn sign_in_with_invalid_email() {
#[tokio::test] #[tokio::test]
async fn sign_in_with_invalid_password() { async fn sign_in_with_invalid_password() {
for password in invalid_password_test_case() { for password in invalid_password_test_case() {
let sdk = FlowyCoreTest::new(); let sdk = EventIntegrationTest::new();
let request = SignInPayloadPB { let request = SignInPayloadPB {
email: random_email(), email: unique_email(),
password, password,
name: "".to_string(), name: "".to_string(),
auth_type: AuthTypePB::Local, auth_type: AuthTypePB::Local,

View File

@ -1,13 +1,13 @@
use std::collections::HashMap; use std::collections::HashMap;
use event_integration::event_builder::EventBuilder; use event_integration::event_builder::EventBuilder;
use event_integration::FlowyCoreTest; use event_integration::EventIntegrationTest;
use flowy_user::entities::{ReminderPB, RepeatedReminderPB}; use flowy_user::entities::{ReminderPB, RepeatedReminderPB};
use flowy_user::event_map::UserEvent::*; use flowy_user::event_map::UserEvent::*;
#[tokio::test] #[tokio::test]
async fn user_update_with_name() { async fn user_update_with_name() {
let sdk = FlowyCoreTest::new(); let sdk = EventIntegrationTest::new();
let _ = sdk.sign_up_as_guest().await; let _ = sdk.sign_up_as_guest().await;
let mut meta = HashMap::new(); let mut meta = HashMap::new();
meta.insert("object_id".to_string(), "".to_string()); meta.insert("object_id".to_string(), "".to_string());

View File

@ -1,14 +1,16 @@
use crate::user::local_test::helper::*; use nanoid::nanoid;
use event_integration::{event_builder::EventBuilder, FlowyCoreTest};
use event_integration::{event_builder::EventBuilder, EventIntegrationTest};
use flowy_user::entities::{UpdateUserProfilePayloadPB, UserProfilePB}; use flowy_user::entities::{UpdateUserProfilePayloadPB, UserProfilePB};
use flowy_user::{errors::ErrorCode, event_map::UserEvent::*}; use flowy_user::{errors::ErrorCode, event_map::UserEvent::*};
use nanoid::nanoid;
use crate::user::local_test::helper::*;
// use serial_test::*; // use serial_test::*;
#[tokio::test] #[tokio::test]
async fn user_profile_get_failed() { async fn user_profile_get_failed() {
let sdk = FlowyCoreTest::new(); let sdk = EventIntegrationTest::new();
let result = EventBuilder::new(sdk) let result = EventBuilder::new(sdk)
.event(GetUserProfile) .event(GetUserProfile)
.async_send() .async_send()
@ -19,7 +21,7 @@ async fn user_profile_get_failed() {
#[tokio::test] #[tokio::test]
async fn user_profile_get() { async fn user_profile_get() {
let test = FlowyCoreTest::new(); let test = EventIntegrationTest::new();
let user_profile = test.init_user().await; let user_profile = test.init_user().await;
let user = EventBuilder::new(test.clone()) let user = EventBuilder::new(test.clone())
.event(GetUserProfile) .event(GetUserProfile)
@ -30,7 +32,7 @@ async fn user_profile_get() {
#[tokio::test] #[tokio::test]
async fn user_update_with_name() { async fn user_update_with_name() {
let sdk = FlowyCoreTest::new(); let sdk = EventIntegrationTest::new();
let user = sdk.init_user().await; let user = sdk.init_user().await;
let new_name = "hello_world".to_owned(); let new_name = "hello_world".to_owned();
let request = UpdateUserProfilePayloadPB::new(user.id).name(&new_name); let request = UpdateUserProfilePayloadPB::new(user.id).name(&new_name);
@ -49,7 +51,7 @@ async fn user_update_with_name() {
#[tokio::test] #[tokio::test]
async fn user_update_with_ai_key() { async fn user_update_with_ai_key() {
let sdk = FlowyCoreTest::new(); let sdk = EventIntegrationTest::new();
let user = sdk.init_user().await; let user = sdk.init_user().await;
let openai_key = "openai_key".to_owned(); let openai_key = "openai_key".to_owned();
let stability_ai_key = "stability_ai_key".to_owned(); let stability_ai_key = "stability_ai_key".to_owned();
@ -72,7 +74,7 @@ async fn user_update_with_ai_key() {
#[tokio::test] #[tokio::test]
async fn user_update_with_email() { async fn user_update_with_email() {
let sdk = FlowyCoreTest::new(); let sdk = EventIntegrationTest::new();
let user = sdk.init_user().await; let user = sdk.init_user().await;
let new_email = format!("{}@gmail.com", nanoid!(6)); let new_email = format!("{}@gmail.com", nanoid!(6));
let request = UpdateUserProfilePayloadPB::new(user.id).email(&new_email); let request = UpdateUserProfilePayloadPB::new(user.id).email(&new_email);
@ -90,7 +92,7 @@ async fn user_update_with_email() {
#[tokio::test] #[tokio::test]
async fn user_update_with_invalid_email() { async fn user_update_with_invalid_email() {
let test = FlowyCoreTest::new(); let test = EventIntegrationTest::new();
let user = test.init_user().await; let user = test.init_user().await;
for email in invalid_email_test_case() { for email in invalid_email_test_case() {
let request = UpdateUserProfilePayloadPB::new(user.id).email(&email); let request = UpdateUserProfilePayloadPB::new(user.id).email(&email);
@ -109,7 +111,7 @@ async fn user_update_with_invalid_email() {
#[tokio::test] #[tokio::test]
async fn user_update_with_invalid_password() { async fn user_update_with_invalid_password() {
let test = FlowyCoreTest::new(); let test = EventIntegrationTest::new();
let user = test.init_user().await; let user = test.init_user().await;
for password in invalid_password_test_case() { for password in invalid_password_test_case() {
let request = UpdateUserProfilePayloadPB::new(user.id).password(&password); let request = UpdateUserProfilePayloadPB::new(user.id).password(&password);
@ -126,7 +128,7 @@ async fn user_update_with_invalid_password() {
#[tokio::test] #[tokio::test]
async fn user_update_with_invalid_name() { async fn user_update_with_invalid_name() {
let test = FlowyCoreTest::new(); let test = EventIntegrationTest::new();
let user = test.init_user().await; let user = test.init_user().await;
let request = UpdateUserProfilePayloadPB::new(user.id).name(""); let request = UpdateUserProfilePayloadPB::new(user.id).name("");
assert!(EventBuilder::new(test.clone()) assert!(EventBuilder::new(test.clone())

View File

@ -1,4 +1,4 @@
use event_integration::FlowyCoreTest; use event_integration::EventIntegrationTest;
use flowy_core::DEFAULT_NAME; use flowy_core::DEFAULT_NAME;
use flowy_folder2::entities::ViewLayoutPB; use flowy_folder2::entities::ViewLayoutPB;
@ -11,7 +11,7 @@ async fn migrate_historical_empty_document_test() {
"historical_empty_document", "historical_empty_document",
) )
.unwrap(); .unwrap();
let test = FlowyCoreTest::new_with_user_data_path(user_db_path, DEFAULT_NAME.to_string()); let test = EventIntegrationTest::new_with_user_data_path(user_db_path, DEFAULT_NAME.to_string());
let views = test.get_all_workspace_views().await; let views = test.get_all_workspace_views().await;
assert_eq!(views.len(), 3); assert_eq!(views.len(), 3);

View File

@ -1,4 +1,4 @@
use event_integration::FlowyCoreTest; use event_integration::EventIntegrationTest;
use flowy_core::DEFAULT_NAME; use flowy_core::DEFAULT_NAME;
use flowy_folder2::entities::ViewLayoutPB; use flowy_folder2::entities::ViewLayoutPB;
@ -11,7 +11,7 @@ async fn migrate_020_historical_empty_document_test() {
"020_historical_user_data", "020_historical_user_data",
) )
.unwrap(); .unwrap();
let test = FlowyCoreTest::new_with_user_data_path(user_db_path, DEFAULT_NAME.to_string()); let test = EventIntegrationTest::new_with_user_data_path(user_db_path, DEFAULT_NAME.to_string());
let mut views = test.get_all_workspace_views().await; let mut views = test.get_all_workspace_views().await;
assert_eq!(views.len(), 1); assert_eq!(views.len(), 1);

View File

@ -10,7 +10,7 @@ use serde_json::json;
use event_integration::document::document_event::DocumentEventTest; use event_integration::document::document_event::DocumentEventTest;
use event_integration::event_builder::EventBuilder; use event_integration::event_builder::EventBuilder;
use event_integration::FlowyCoreTest; use event_integration::EventIntegrationTest;
use flowy_core::DEFAULT_NAME; use flowy_core::DEFAULT_NAME;
use flowy_encrypt::decrypt_text; use flowy_encrypt::decrypt_text;
use flowy_server::supabase::define::{USER_EMAIL, USER_UUID}; use flowy_server::supabase::define::{USER_EMAIL, USER_UUID};
@ -23,7 +23,7 @@ use crate::util::*;
#[tokio::test] #[tokio::test]
async fn third_party_sign_up_test() { async fn third_party_sign_up_test() {
if get_supabase_config().is_some() { if get_supabase_config().is_some() {
let test = FlowyCoreTest::new(); let test = EventIntegrationTest::new();
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert(USER_UUID.to_string(), uuid::Uuid::new_v4().to_string()); map.insert(USER_UUID.to_string(), uuid::Uuid::new_v4().to_string());
map.insert( map.insert(
@ -48,7 +48,7 @@ async fn third_party_sign_up_test() {
#[tokio::test] #[tokio::test]
async fn third_party_sign_up_with_encrypt_test() { async fn third_party_sign_up_with_encrypt_test() {
if get_supabase_config().is_some() { if get_supabase_config().is_some() {
let test = FlowyCoreTest::new(); let test = EventIntegrationTest::new();
test.supabase_party_sign_up().await; test.supabase_party_sign_up().await;
let user_profile = test.get_user_profile().await.unwrap(); let user_profile = test.get_user_profile().await.unwrap();
assert!(user_profile.encryption_sign.is_empty()); assert!(user_profile.encryption_sign.is_empty());
@ -65,7 +65,7 @@ async fn third_party_sign_up_with_encrypt_test() {
#[tokio::test] #[tokio::test]
async fn third_party_sign_up_with_duplicated_uuid() { async fn third_party_sign_up_with_duplicated_uuid() {
if get_supabase_config().is_some() { if get_supabase_config().is_some() {
let test = FlowyCoreTest::new(); let test = EventIntegrationTest::new();
let email = format!("{}@appflowy.io", nanoid!(6)); let email = format!("{}@appflowy.io", nanoid!(6));
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert(USER_UUID.to_string(), uuid::Uuid::new_v4().to_string()); map.insert(USER_UUID.to_string(), uuid::Uuid::new_v4().to_string());
@ -98,7 +98,7 @@ async fn third_party_sign_up_with_duplicated_uuid() {
#[tokio::test] #[tokio::test]
async fn third_party_sign_up_with_duplicated_email() { async fn third_party_sign_up_with_duplicated_email() {
if get_supabase_config().is_some() { if get_supabase_config().is_some() {
let test = FlowyCoreTest::new(); let test = EventIntegrationTest::new();
let email = format!("{}@appflowy.io", nanoid!(6)); let email = format!("{}@appflowy.io", nanoid!(6));
test test
.supabase_sign_up_with_uuid(&uuid::Uuid::new_v4().to_string(), Some(email.clone())) .supabase_sign_up_with_uuid(&uuid::Uuid::new_v4().to_string(), Some(email.clone()))
@ -116,7 +116,7 @@ async fn third_party_sign_up_with_duplicated_email() {
#[tokio::test] #[tokio::test]
async fn sign_up_as_guest_and_then_update_to_new_cloud_user_test() { async fn sign_up_as_guest_and_then_update_to_new_cloud_user_test() {
if get_supabase_config().is_some() { if get_supabase_config().is_some() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let old_views = test let old_views = test
.folder_manager .folder_manager
.get_current_workspace_views() .get_current_workspace_views()
@ -148,7 +148,7 @@ async fn sign_up_as_guest_and_then_update_to_new_cloud_user_test() {
#[tokio::test] #[tokio::test]
async fn sign_up_as_guest_and_then_update_to_existing_cloud_user_test() { async fn sign_up_as_guest_and_then_update_to_existing_cloud_user_test() {
if get_supabase_config().is_some() { if get_supabase_config().is_some() {
let test = FlowyCoreTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let uuid = uuid::Uuid::new_v4().to_string(); let uuid = uuid::Uuid::new_v4().to_string();
let email = format!("{}@appflowy.io", nanoid!(6)); let email = format!("{}@appflowy.io", nanoid!(6));
@ -260,7 +260,7 @@ async fn update_user_profile_with_existing_email_test() {
#[tokio::test] #[tokio::test]
async fn migrate_anon_document_on_cloud_signup() { async fn migrate_anon_document_on_cloud_signup() {
if get_supabase_config().is_some() { if get_supabase_config().is_some() {
let test = FlowyCoreTest::new(); let test = EventIntegrationTest::new();
let user_profile = test.sign_up_as_guest().await.user_profile; let user_profile = test.sign_up_as_guest().await.user_profile;
let view = test let view = test
@ -305,7 +305,8 @@ async fn migrate_anon_data_on_cloud_signup() {
"workspace_sync", "workspace_sync",
) )
.unwrap(); .unwrap();
let test = FlowyCoreTest::new_with_user_data_path(user_db_path, DEFAULT_NAME.to_string()); let test =
EventIntegrationTest::new_with_user_data_path(user_db_path, DEFAULT_NAME.to_string());
let user_profile = test.supabase_party_sign_up().await; let user_profile = test.supabase_party_sign_up().await;
// Get the folder data from remote // Get the folder data from remote

View File

@ -1,6 +1,6 @@
use std::collections::HashMap; use std::collections::HashMap;
use event_integration::{event_builder::EventBuilder, FlowyCoreTest}; use event_integration::{event_builder::EventBuilder, EventIntegrationTest};
use flowy_folder2::entities::WorkspaceSettingPB; use flowy_folder2::entities::WorkspaceSettingPB;
use flowy_folder2::event_map::FolderEvent::GetCurrentWorkspace; use flowy_folder2::event_map::FolderEvent::GetCurrentWorkspace;
use flowy_server::supabase::define::{USER_EMAIL, USER_UUID}; use flowy_server::supabase::define::{USER_EMAIL, USER_UUID};
@ -12,7 +12,7 @@ use crate::util::*;
#[tokio::test] #[tokio::test]
async fn initial_workspace_test() { async fn initial_workspace_test() {
if get_supabase_config().is_some() { if get_supabase_config().is_some() {
let test = FlowyCoreTest::new(); let test = EventIntegrationTest::new();
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert(USER_UUID.to_string(), uuid::Uuid::new_v4().to_string()); map.insert(USER_UUID.to_string(), uuid::Uuid::new_v4().to_string());
map.insert( map.insert(

View File

@ -16,7 +16,7 @@ use zip::ZipArchive;
use event_integration::event_builder::EventBuilder; use event_integration::event_builder::EventBuilder;
use event_integration::Cleaner; use event_integration::Cleaner;
use event_integration::FlowyCoreTest; use event_integration::EventIntegrationTest;
use flowy_database_deps::cloud::DatabaseCloudService; use flowy_database_deps::cloud::DatabaseCloudService;
use flowy_folder_deps::cloud::{FolderCloudService, FolderSnapshot}; use flowy_folder_deps::cloud::{FolderCloudService, FolderSnapshot};
use flowy_server::supabase::api::*; use flowy_server::supabase::api::*;
@ -36,13 +36,13 @@ pub fn get_supabase_config() -> Option<SupabaseConfiguration> {
} }
pub struct FlowySupabaseTest { pub struct FlowySupabaseTest {
inner: FlowyCoreTest, inner: EventIntegrationTest,
} }
impl FlowySupabaseTest { impl FlowySupabaseTest {
pub fn new() -> Option<Self> { pub fn new() -> Option<Self> {
let _ = get_supabase_config()?; let _ = get_supabase_config()?;
let test = FlowyCoreTest::new(); let test = EventIntegrationTest::new();
test.set_auth_type(AuthTypePB::Supabase); test.set_auth_type(AuthTypePB::Supabase);
test.server_provider.set_auth_type(AuthType::Supabase); test.server_provider.set_auth_type(AuthType::Supabase);
@ -76,7 +76,7 @@ impl FlowySupabaseTest {
} }
impl Deref for FlowySupabaseTest { impl Deref for FlowySupabaseTest {
type Target = FlowyCoreTest; type Target = EventIntegrationTest;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.inner &self.inner
@ -215,13 +215,13 @@ pub fn unzip_history_user_db(root: &str, folder_name: &str) -> std::io::Result<(
} }
pub struct AFCloudTest { pub struct AFCloudTest {
inner: FlowyCoreTest, inner: EventIntegrationTest,
} }
impl AFCloudTest { impl AFCloudTest {
pub fn new() -> Option<Self> { pub fn new() -> Option<Self> {
let _ = get_af_cloud_config()?; let _ = get_af_cloud_config()?;
let test = FlowyCoreTest::new(); let test = EventIntegrationTest::new();
test.set_auth_type(AuthTypePB::AFCloud); test.set_auth_type(AuthTypePB::AFCloud);
test.server_provider.set_auth_type(AuthType::AFCloud); test.server_provider.set_auth_type(AuthType::AFCloud);
@ -230,7 +230,7 @@ impl AFCloudTest {
} }
impl Deref for AFCloudTest { impl Deref for AFCloudTest {
type Target = FlowyCoreTest; type Target = EventIntegrationTest;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.inner &self.inner

View File

@ -132,7 +132,12 @@ impl DatabaseManager {
Ok(()) Ok(())
} }
#[instrument(level = "debug", skip_all, err)] #[instrument(
name = "database_initialize_with_new_user",
level = "debug",
skip_all,
err
)]
pub async fn initialize_with_new_user( pub async fn initialize_with_new_user(
&self, &self,
user_id: i64, user_id: i64,

View File

@ -6,8 +6,8 @@ use collab_database::fields::Field;
use collab_database::rows::{CreateRowParams, RowDetail, RowId}; use collab_database::rows::{CreateRowParams, RowDetail, RowId};
use strum::EnumCount; use strum::EnumCount;
use event_integration::folder_event::ViewTest; use event_integration::test_folder::ViewTest;
use event_integration::FlowyCoreTest; use event_integration::EventIntegrationTest;
use flowy_database2::entities::{FieldType, FilterPB, RowMetaPB}; use flowy_database2::entities::{FieldType, FilterPB, RowMetaPB};
use flowy_database2::services::cell::{CellBuilder, ToCellChangeset}; use flowy_database2::services::cell::{CellBuilder, ToCellChangeset};
use flowy_database2::services::database::DatabaseEditor; use flowy_database2::services::database::DatabaseEditor;
@ -26,8 +26,7 @@ use crate::database::mock_data::{
}; };
pub struct DatabaseEditorTest { pub struct DatabaseEditorTest {
pub sdk: FlowyCoreTest, pub sdk: EventIntegrationTest,
pub app_id: String,
pub view_id: String, pub view_id: String,
pub editor: Arc<DatabaseEditor>, pub editor: Arc<DatabaseEditor>,
pub fields: Vec<Arc<Field>>, pub fields: Vec<Arc<Field>>,
@ -38,7 +37,7 @@ pub struct DatabaseEditorTest {
impl DatabaseEditorTest { impl DatabaseEditorTest {
pub async fn new_grid() -> Self { pub async fn new_grid() -> Self {
let sdk = FlowyCoreTest::new(); let sdk = EventIntegrationTest::new();
let _ = sdk.init_user().await; let _ = sdk.init_user().await;
let params = make_test_grid(); let params = make_test_grid();
@ -47,7 +46,7 @@ impl DatabaseEditorTest {
} }
pub async fn new_no_date_grid() -> Self { pub async fn new_no_date_grid() -> Self {
let sdk = FlowyCoreTest::new(); let sdk = EventIntegrationTest::new();
let _ = sdk.init_user().await; let _ = sdk.init_user().await;
let params = make_no_date_test_grid(); let params = make_no_date_test_grid();
@ -56,7 +55,7 @@ impl DatabaseEditorTest {
} }
pub async fn new_board() -> Self { pub async fn new_board() -> Self {
let sdk = FlowyCoreTest::new(); let sdk = EventIntegrationTest::new();
let _ = sdk.init_user().await; let _ = sdk.init_user().await;
let params = make_test_board(); let params = make_test_board();
@ -65,7 +64,7 @@ impl DatabaseEditorTest {
} }
pub async fn new_calendar() -> Self { pub async fn new_calendar() -> Self {
let sdk = FlowyCoreTest::new(); let sdk = EventIntegrationTest::new();
let _ = sdk.init_user().await; let _ = sdk.init_user().await;
let params = make_test_calendar(); let params = make_test_calendar();
@ -73,7 +72,7 @@ impl DatabaseEditorTest {
Self::new(sdk, view_test).await Self::new(sdk, view_test).await
} }
pub async fn new(sdk: FlowyCoreTest, test: ViewTest) -> Self { pub async fn new(sdk: EventIntegrationTest, test: ViewTest) -> Self {
let editor = sdk let editor = sdk
.database_manager .database_manager
.get_database_with_view_id(&test.child_view.id) .get_database_with_view_id(&test.child_view.id)
@ -92,10 +91,8 @@ impl DatabaseEditorTest {
.collect(); .collect();
let view_id = test.child_view.id; let view_id = test.child_view.id;
let app_id = test.parent_view.id;
Self { Self {
sdk, sdk,
app_id,
view_id, view_id,
editor, editor,
fields, fields,

View File

@ -59,7 +59,12 @@ impl DocumentManager {
Ok(()) Ok(())
} }
#[instrument(level = "debug", skip_all, err)] #[instrument(
name = "document_initialize_with_new_user",
level = "debug",
skip_all,
err
)]
pub async fn initialize_with_new_user(&self, uid: i64, workspace_id: String) -> FlowyResult<()> { pub async fn initialize_with_new_user(&self, uid: i64, workspace_id: String) -> FlowyResult<()> {
self.initialize(uid, workspace_id).await?; self.initialize(uid, workspace_id).await?;
Ok(()) Ok(())

View File

@ -29,6 +29,7 @@ impl std::convert::From<i32> for DocumentNotification {
} }
} }
#[tracing::instrument(level = "trace")]
pub(crate) fn send_notification(id: &str, ty: DocumentNotification) -> NotificationBuilder { pub(crate) fn send_notification(id: &str, ty: DocumentNotification) -> NotificationBuilder {
NotificationBuilder::new(id, ty, DOCUMENT_OBSERVABLE_SOURCE) NotificationBuilder::new(id, ty, DOCUMENT_OBSERVABLE_SOURCE)
} }

View File

@ -265,7 +265,12 @@ impl FolderManager {
/// Initialize the folder for the new user. /// Initialize the folder for the new user.
/// Using the [DefaultFolderBuilder] to create the default workspace for the new user. /// Using the [DefaultFolderBuilder] to create the default workspace for the new user.
#[instrument(level = "debug", skip_all, err)] #[instrument(
name = "folder_initialize_with_new_user",
level = "debug",
skip_all,
err
)]
pub async fn initialize_with_new_user( pub async fn initialize_with_new_user(
&self, &self,
user_id: i64, user_id: i64,

View File

@ -23,7 +23,7 @@ bytes = { version = "1.5", features = ["serde"] }
tokio-retry = "0.3" tokio-retry = "0.3"
anyhow = "1.0" anyhow = "1.0"
uuid = { version = "1.3.3", features = ["v4"] } uuid = { version = "1.3.3", features = ["v4"] }
chrono = { version = "0.4.31", default-features = false, features = ["clock"] } chrono = { version = "0.4.31", default-features = false, features = ["clock", "serde"] }
collab = { version = "0.1.0" } collab = { version = "0.1.0" }
collab-plugins = { version = "0.1.0"} collab-plugins = { version = "0.1.0"}
collab-document = { version = "0.1.0" } collab-document = { version = "0.1.0" }

View File

@ -2,11 +2,8 @@ use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use anyhow::{anyhow, Error}; use anyhow::{anyhow, Error};
use client_api::entity::dto::auth_dto::UpdateUsernameParams; use client_api::entity::workspace_dto::CreateWorkspaceMember;
use client_api::entity::dto::workspace_dto::CreateWorkspaceMember; use client_api::entity::{AFRole, AFWorkspace, InsertCollabParams, OAuthProvider};
use client_api::entity::{
AFRole, AFUserProfileView, AFWorkspace, AFWorkspaces, InsertCollabParams, OAuthProvider,
};
use collab_entity::CollabObject; use collab_entity::CollabObject;
use flowy_error::{ErrorCode, FlowyError}; use flowy_error::{ErrorCode, FlowyError};
@ -15,6 +12,10 @@ use flowy_user_deps::entities::*;
use lib_infra::box_any::BoxAny; use lib_infra::box_any::BoxAny;
use lib_infra::future::FutureResult; use lib_infra::future::FutureResult;
use crate::af_cloud::impls::user::dto::{
af_update_from_update_params, user_profile_from_af_profile,
};
use crate::af_cloud::impls::user::util::encryption_type_from_profile;
use crate::af_cloud::{AFCloudClient, AFServer}; use crate::af_cloud::{AFCloudClient, AFServer};
use crate::supabase::define::{USER_DEVICE_ID, USER_SIGN_IN_URL}; use crate::supabase::define::{USER_DEVICE_ID, USER_SIGN_IN_URL};
@ -92,12 +93,7 @@ where
FutureResult::new(async move { FutureResult::new(async move {
let client = try_get_client?; let client = try_get_client?;
client client
.update_user(UpdateUsernameParams { .update_user(af_update_from_update_params(params))
name: params.name,
email: params.email,
password: params.password,
metadata: None,
})
.await?; .await?;
Ok(()) Ok(())
}) })
@ -111,30 +107,18 @@ where
FutureResult::new(async move { FutureResult::new(async move {
let client = try_get_client?; let client = try_get_client?;
let profile = client.get_profile().await?; let profile = client.get_profile().await?;
let encryption_type = encryption_type_from_profile(&profile); let token = client.get_token()?;
Ok(Some(UserProfile { let profile = user_profile_from_af_profile(token, profile)?;
email: profile.email.unwrap_or("".to_string()),
name: profile.name.unwrap_or("".to_string()), Ok(Some(profile))
token: client.get_token()?,
icon_url: "".to_owned(),
openai_key: "".to_owned(),
stability_ai_key: "".to_owned(),
workspace_id: match profile.latest_workspace_id {
Some(w) => w.to_string(),
None => "".to_string(),
},
auth_type: AuthType::AFCloud,
encryption_type,
uid: profile.uid.ok_or(anyhow!("no uid found"))?,
}))
}) })
} }
fn get_user_workspaces(&self, _uid: i64) -> FutureResult<Vec<UserWorkspace>, Error> { fn get_all_user_workspaces(&self, _uid: i64) -> FutureResult<Vec<UserWorkspace>, Error> {
let try_get_client = self.server.try_get_client(); let try_get_client = self.server.try_get_client();
FutureResult::new(async move { FutureResult::new(async move {
let workspaces = try_get_client?.get_workspaces().await?; let workspaces = try_get_client?.get_workspaces().await?;
Ok(to_user_workspaces(workspaces)?) Ok(to_user_workspaces(workspaces.0)?)
}) })
} }
@ -151,7 +135,7 @@ where
let client_token = client.access_token()?; let client_token = client.access_token()?;
// compare and check // compare and check
if uid != profile.uid.ok_or(anyhow!("expecting uid"))? { if uid != profile.uid {
return Err(anyhow!("uid mismatch")); return Err(anyhow!("uid mismatch"));
} }
if token != client_token { if token != client_token {
@ -238,59 +222,42 @@ pub async fn user_sign_in_with_url(
params: AFCloudOAuthParams, params: AFCloudOAuthParams,
) -> Result<AuthResponse, FlowyError> { ) -> Result<AuthResponse, FlowyError> {
let is_new_user = client.sign_in_with_url(&params.sign_in_url).await?; let is_new_user = client.sign_in_with_url(&params.sign_in_url).await?;
let (profile, af_workspaces) = tokio::try_join!(client.get_profile(), client.get_workspaces())?;
let latest_workspace = to_user_workspace( let workspace_profile = client.get_user_workspace_info().await?;
af_workspaces let user_profile = workspace_profile.user_profile;
.get_latest(&profile)
.or(af_workspaces.first().cloned())
.ok_or(anyhow!("no workspace found"))?,
)?;
let user_workspaces = to_user_workspaces(af_workspaces)?; let latest_workspace = to_user_workspace(workspace_profile.visiting_workspace);
let encryption_type = encryption_type_from_profile(&profile); let user_workspaces = to_user_workspaces(workspace_profile.workspaces)?;
let encryption_type = encryption_type_from_profile(&user_profile);
Ok(AuthResponse { Ok(AuthResponse {
user_id: profile.uid.ok_or(anyhow!("no uid found"))?, user_id: user_profile.uid,
name: profile.name.ok_or(anyhow!("no name found"))?, name: user_profile.name.unwrap_or_default(),
latest_workspace, latest_workspace,
user_workspaces, user_workspaces,
email: profile.email, email: user_profile.email,
token: Some(client.get_token()?), token: Some(client.get_token()?),
device_id: params.device_id, device_id: params.device_id,
encryption_type, encryption_type,
is_new_user, is_new_user,
updated_at: user_profile.updated_at,
metadata: user_profile.metadata,
}) })
} }
fn encryption_type_from_profile(profile: &AFUserProfileView) -> EncryptionType { fn to_user_workspace(af_workspace: AFWorkspace) -> UserWorkspace {
match &profile.encryption_sign { UserWorkspace {
Some(e) => EncryptionType::SelfEncryption(e.to_string()), id: af_workspace.workspace_id.to_string(),
None => EncryptionType::NoEncryption, name: af_workspace.workspace_name,
created_at: af_workspace.created_at,
database_views_aggregate_id: af_workspace.database_storage_id.to_string(),
} }
} }
fn to_user_workspace(af_workspace: AFWorkspace) -> Result<UserWorkspace, FlowyError> { fn to_user_workspaces(workspaces: Vec<AFWorkspace>) -> Result<Vec<UserWorkspace>, FlowyError> {
Ok(UserWorkspace { let mut result = Vec::with_capacity(workspaces.len());
id: af_workspace.workspace_id.to_string(), for item in workspaces.into_iter() {
name: af_workspace result.push(to_user_workspace(item));
.workspace_name
.ok_or(anyhow!("no workspace_name found"))?,
created_at: af_workspace
.created_at
.ok_or(anyhow!("no created_at found"))?,
database_views_aggregate_id: af_workspace
.database_storage_id
.ok_or(anyhow!("no database_views_aggregate_id found"))?
.to_string(),
})
}
fn to_user_workspaces(af_workspaces: AFWorkspaces) -> Result<Vec<UserWorkspace>, FlowyError> {
let mut result = Vec::with_capacity(af_workspaces.len());
for item in af_workspaces.0.into_iter() {
let user_workspace = to_user_workspace(item)?;
result.push(user_workspace);
} }
Ok(result) Ok(result)
} }

View File

@ -0,0 +1,65 @@
use anyhow::Error;
use client_api::entity::auth_dto::{UpdateUserParams, UserMetaData};
use client_api::entity::AFUserProfile;
use flowy_user_deps::entities::{
AuthType, UpdateUserProfileParams, UserProfile, USER_METADATA_ICON_URL,
USER_METADATA_OPEN_AI_KEY, USER_METADATA_STABILITY_AI_KEY,
};
use crate::af_cloud::impls::user::util::encryption_type_from_profile;
pub fn af_update_from_update_params(update: UpdateUserProfileParams) -> UpdateUserParams {
let mut user_metadata = UserMetaData::new();
if let Some(openai_key) = update.openai_key {
user_metadata.insert(USER_METADATA_OPEN_AI_KEY, openai_key);
}
if let Some(stability_ai_key) = update.stability_ai_key {
user_metadata.insert(USER_METADATA_STABILITY_AI_KEY, stability_ai_key);
}
if let Some(icon_url) = update.icon_url {
user_metadata.insert(USER_METADATA_ICON_URL, icon_url);
}
UpdateUserParams {
name: update.name,
email: update.email,
password: update.password,
metadata: Some(user_metadata),
}
}
pub fn user_profile_from_af_profile(
token: String,
profile: AFUserProfile,
) -> Result<UserProfile, Error> {
let encryption_type = encryption_type_from_profile(&profile);
let (icon_url, openai_key, stability_ai_key) = {
profile
.metadata
.map(|m| {
(
m.get(USER_METADATA_ICON_URL).map(|v| v.to_string()),
m.get(USER_METADATA_OPEN_AI_KEY).map(|v| v.to_string()),
m.get(USER_METADATA_STABILITY_AI_KEY).map(|v| v.to_string()),
)
})
.unwrap_or_default()
};
Ok(UserProfile {
email: profile.email.unwrap_or("".to_string()),
name: profile.name.unwrap_or("".to_string()),
token,
icon_url: icon_url.unwrap_or_default(),
openai_key: openai_key.unwrap_or_default(),
stability_ai_key: stability_ai_key.unwrap_or_default(),
workspace_id: profile.latest_workspace_id.to_string(),
auth_type: AuthType::AFCloud,
encryption_type,
uid: profile.uid,
updated_at: profile.updated_at,
})
}

View File

@ -0,0 +1,5 @@
pub use cloud_service_impl::*;
mod cloud_service_impl;
mod dto;
mod util;

View File

@ -0,0 +1,10 @@
use client_api::entity::AFUserProfile;
use flowy_user_deps::entities::EncryptionType;
pub fn encryption_type_from_profile(profile: &AFUserProfile) -> EncryptionType {
match &profile.encryption_sign {
Some(e) => EncryptionType::SelfEncryption(e.to_string()),
None => EncryptionType::NoEncryption,
}
}

View File

@ -11,6 +11,7 @@ use flowy_user_deps::entities::*;
use flowy_user_deps::DEFAULT_USER_NAME; use flowy_user_deps::DEFAULT_USER_NAME;
use lib_infra::box_any::BoxAny; use lib_infra::box_any::BoxAny;
use lib_infra::future::FutureResult; use lib_infra::future::FutureResult;
use lib_infra::util::timestamp;
use crate::local_server::uid::UserIDGenerator; use crate::local_server::uid::UserIDGenerator;
use crate::local_server::LocalServerDB; use crate::local_server::LocalServerDB;
@ -46,6 +47,8 @@ impl UserCloudService for LocalServerUserAuthServiceImpl {
token: None, token: None,
device_id: params.device_id, device_id: params.device_id,
encryption_type: EncryptionType::NoEncryption, encryption_type: EncryptionType::NoEncryption,
updated_at: timestamp(),
metadata: None,
}) })
}) })
} }
@ -69,6 +72,8 @@ impl UserCloudService for LocalServerUserAuthServiceImpl {
token: None, token: None,
device_id: params.device_id, device_id: params.device_id,
encryption_type: EncryptionType::NoEncryption, encryption_type: EncryptionType::NoEncryption,
updated_at: timestamp(),
metadata: None,
}) })
}) })
} }
@ -104,7 +109,7 @@ impl UserCloudService for LocalServerUserAuthServiceImpl {
FutureResult::new(async { Ok(None) }) FutureResult::new(async { Ok(None) })
} }
fn get_user_workspaces(&self, _uid: i64) -> FutureResult<Vec<UserWorkspace>, Error> { fn get_all_user_workspaces(&self, _uid: i64) -> FutureResult<Vec<UserWorkspace>, Error> {
FutureResult::new(async { Ok(vec![]) }) FutureResult::new(async { Ok(vec![]) })
} }

View File

@ -129,6 +129,8 @@ where
token: None, token: None,
device_id: params.device_id, device_id: params.device_id,
encryption_type: EncryptionType::from_sign(&user_profile.encryption_sign), encryption_type: EncryptionType::from_sign(&user_profile.encryption_sign),
updated_at: user_profile.updated_at.timestamp(),
metadata: None,
}) })
}) })
} }
@ -158,6 +160,8 @@ where
token: None, token: None,
device_id: params.device_id, device_id: params.device_id,
encryption_type: EncryptionType::from_sign(&response.encryption_sign), encryption_type: EncryptionType::from_sign(&response.encryption_sign),
updated_at: response.updated_at.timestamp(),
metadata: None,
}) })
}) })
} }
@ -220,12 +224,13 @@ where
workspace_id: response.latest_workspace_id, workspace_id: response.latest_workspace_id,
auth_type: AuthType::Supabase, auth_type: AuthType::Supabase,
encryption_type: EncryptionType::from_sign(&response.encryption_sign), encryption_type: EncryptionType::from_sign(&response.encryption_sign),
updated_at: response.updated_at.timestamp(),
})), })),
} }
}) })
} }
fn get_user_workspaces(&self, uid: i64) -> FutureResult<Vec<UserWorkspace>, Error> { fn get_all_user_workspaces(&self, uid: i64) -> FutureResult<Vec<UserWorkspace>, Error> {
let try_get_postgrest = self.server.try_get_postgrest(); let try_get_postgrest = self.server.try_get_postgrest();
FutureResult::new(async move { FutureResult::new(async move {
let postgrest = try_get_postgrest?; let postgrest = try_get_postgrest?;
@ -419,7 +424,7 @@ async fn get_user_profile(
) -> Result<Option<UserProfileResponse>, Error> { ) -> Result<Option<UserProfileResponse>, Error> {
let mut builder = postgrest let mut builder = postgrest
.from(USER_PROFILE_VIEW) .from(USER_PROFILE_VIEW)
.select("uid, email, name, encryption_sign, latest_workspace_id"); .select("uid, email, name, encryption_sign, latest_workspace_id, updated_at");
match params { match params {
GetUserProfileParams::Uid(uid) => builder = builder.eq("uid", uid.to_string()), GetUserProfileParams::Uid(uid) => builder = builder.eq("uid", uid.to_string()),

View File

@ -1,6 +1,7 @@
use std::fmt; use std::fmt;
use std::fmt::Display; use std::fmt::Display;
use chrono::{DateTime, Utc};
use serde::Deserialize; use serde::Deserialize;
use serde_json::Value; use serde_json::Value;
use uuid::Uuid; use uuid::Uuid;
@ -27,6 +28,8 @@ pub(crate) struct UserProfileResponse {
#[serde(deserialize_with = "deserialize_null_or_default")] #[serde(deserialize_with = "deserialize_null_or_default")]
pub encryption_sign: String, pub encryption_sign: String,
pub updated_at: DateTime<Utc>,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]

View File

@ -0,0 +1,3 @@
-- This file should undo anything in `up.sql`
ALTER TABLE user_table
DROP COLUMN updated_at;

View File

@ -0,0 +1,3 @@
-- Your SQL goes here
ALTER TABLE user_table
ADD COLUMN updated_at BIGINT NOT NULL DEFAULT 0;

View File

@ -32,6 +32,7 @@ diesel::table! {
auth_type -> Integer, auth_type -> Integer,
encryption_type -> Text, encryption_type -> Text,
stability_ai_key -> Text, stability_ai_key -> Text,
updated_at -> BigInt,
} }
} }

View File

@ -95,7 +95,7 @@ pub trait UserCloudService: Send + Sync + 'static {
) -> FutureResult<Option<UserProfile>, FlowyError>; ) -> FutureResult<Option<UserProfile>, FlowyError>;
/// Return the all the workspaces of the user /// Return the all the workspaces of the user
fn get_user_workspaces(&self, uid: i64) -> FutureResult<Vec<UserWorkspace>, Error>; fn get_all_user_workspaces(&self, uid: i64) -> FutureResult<Vec<UserWorkspace>, Error>;
fn check_user(&self, credential: UserCredentials) -> FutureResult<(), Error>; fn check_user(&self, credential: UserCredentials) -> FutureResult<(), Error>;

View File

@ -2,9 +2,15 @@ use std::str::FromStr;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value;
use serde_repr::*; use serde_repr::*;
use uuid::Uuid; use uuid::Uuid;
pub const USER_METADATA_OPEN_AI_KEY: &str = "openai_key";
pub const USER_METADATA_STABILITY_AI_KEY: &str = "stability_ai_key";
pub const USER_METADATA_ICON_URL: &str = "icon_url";
pub const USER_METADATA_UPDATE_AT: &str = "updated_at";
pub trait UserAuthResponse { pub trait UserAuthResponse {
fn user_id(&self) -> i64; fn user_id(&self) -> i64;
fn user_name(&self) -> &str; fn user_name(&self) -> &str;
@ -14,52 +20,8 @@ pub trait UserAuthResponse {
fn user_token(&self) -> Option<String>; fn user_token(&self) -> Option<String>;
fn user_email(&self) -> Option<String>; fn user_email(&self) -> Option<String>;
fn encryption_type(&self) -> EncryptionType; fn encryption_type(&self) -> EncryptionType;
} fn metadata(&self) -> &Option<serde_json::Value>;
fn updated_at(&self) -> i64;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct SignInResponse {
pub user_id: i64,
pub name: String,
pub latest_workspace: UserWorkspace,
pub user_workspaces: Vec<UserWorkspace>,
pub email: Option<String>,
pub token: Option<String>,
pub device_id: String,
pub encryption_type: EncryptionType,
}
impl UserAuthResponse for SignInResponse {
fn user_id(&self) -> i64 {
self.user_id
}
fn user_name(&self) -> &str {
&self.name
}
fn latest_workspace(&self) -> &UserWorkspace {
&self.latest_workspace
}
fn user_workspaces(&self) -> &[UserWorkspace] {
&self.user_workspaces
}
fn device_id(&self) -> &str {
&self.device_id
}
fn user_token(&self) -> Option<String> {
self.token.clone()
}
fn user_email(&self) -> Option<String> {
self.email.clone()
}
fn encryption_type(&self) -> EncryptionType {
self.encryption_type.clone()
}
} }
#[derive(Default, Serialize, Deserialize, Debug)] #[derive(Default, Serialize, Deserialize, Debug)]
@ -91,6 +53,8 @@ pub struct AuthResponse {
pub token: Option<String>, pub token: Option<String>,
pub device_id: String, pub device_id: String,
pub encryption_type: EncryptionType, pub encryption_type: EncryptionType,
pub updated_at: i64,
pub metadata: Option<serde_json::Value>,
} }
impl UserAuthResponse for AuthResponse { impl UserAuthResponse for AuthResponse {
@ -125,6 +89,14 @@ impl UserAuthResponse for AuthResponse {
fn encryption_type(&self) -> EncryptionType { fn encryption_type(&self) -> EncryptionType {
self.encryption_type.clone() self.encryption_type.clone()
} }
fn metadata(&self) -> &Option<Value> {
&self.metadata
}
fn updated_at(&self) -> i64 {
self.updated_at
}
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -196,6 +168,7 @@ pub struct UserProfile {
pub auth_type: AuthType, pub auth_type: AuthType,
// If the encryption_sign is not empty, which means the user has enabled the encryption. // If the encryption_sign is not empty, which means the user has enabled the encryption.
pub encryption_type: EncryptionType, pub encryption_type: EncryptionType,
pub updated_at: i64,
} }
#[derive(Serialize, Deserialize, Debug, Clone, Default, Eq, PartialEq)] #[derive(Serialize, Deserialize, Debug, Clone, Default, Eq, PartialEq)]
@ -243,17 +216,37 @@ where
{ {
fn from(params: (&T, &AuthType)) -> Self { fn from(params: (&T, &AuthType)) -> Self {
let (value, auth_type) = params; let (value, auth_type) = params;
let (icon_url, openai_key, stability_ai_key) = {
value
.metadata()
.as_ref()
.map(|m| {
(
m.get(USER_METADATA_ICON_URL)
.map(|v| v.to_string())
.unwrap_or_default(),
m.get(USER_METADATA_OPEN_AI_KEY)
.map(|v| v.to_string())
.unwrap_or_default(),
m.get(USER_METADATA_STABILITY_AI_KEY)
.map(|v| v.to_string())
.unwrap_or_default(),
)
})
.unwrap_or_default()
};
Self { Self {
uid: value.user_id(), uid: value.user_id(),
email: value.user_email().unwrap_or_default(), email: value.user_email().unwrap_or_default(),
name: value.user_name().to_owned(), name: value.user_name().to_owned(),
token: value.user_token().unwrap_or_default(), token: value.user_token().unwrap_or_default(),
icon_url: "".to_owned(), icon_url,
openai_key: "".to_owned(), openai_key,
workspace_id: value.latest_workspace().id.to_owned(), workspace_id: value.latest_workspace().id.to_owned(),
auth_type: auth_type.clone(), auth_type: auth_type.clone(),
encryption_type: value.encryption_type(), encryption_type: value.encryption_type(),
stability_ai_key: "".to_owned(), stability_ai_key,
updated_at: value.updated_at(),
} }
} }
} }

View File

@ -240,6 +240,9 @@ pub struct UserStatePB {
pub struct AuthStateChangedPB { pub struct AuthStateChangedPB {
#[pb(index = 1)] #[pb(index = 1)]
pub state: AuthStatePB, pub state: AuthStatePB,
#[pb(index = 2)]
pub message: String,
} }
#[derive(ProtoBuf_Enum, Debug, Clone)] #[derive(ProtoBuf_Enum, Debug, Clone)]

View File

@ -173,9 +173,9 @@ pub struct UserSecretPB {
} }
#[derive(Default, ProtoBuf)] #[derive(Default, ProtoBuf)]
pub struct UserEncryptionSecretCheckPB { pub struct UserEncryptionConfigurationPB {
#[pb(index = 1)] #[pb(index = 1)]
pub is_need_secret: bool, pub require_secret: bool,
} }
impl From<UserCloudConfig> for UserCloudConfigPB { impl From<UserCloudConfig> for UserCloudConfigPB {

View File

@ -2,6 +2,7 @@ use std::sync::Weak;
use std::{convert::TryInto, sync::Arc}; use std::{convert::TryInto, sync::Arc};
use serde_json::Value; use serde_json::Value;
use tracing::event;
use flowy_error::{ErrorCode, FlowyError, FlowyResult}; use flowy_error::{ErrorCode, FlowyError, FlowyResult};
use flowy_sqlite::kv::StorePreferences; use flowy_sqlite::kv::StorePreferences;
@ -89,7 +90,7 @@ pub async fn check_user_handler(
Ok(()) Ok(())
} }
#[tracing::instrument(level = "debug", skip(manager))] #[tracing::instrument(level = "debug", skip(manager), err)]
pub async fn get_user_profile_handler( pub async fn get_user_profile_handler(
manager: AFPluginState<Weak<UserManager>>, manager: AFPluginState<Weak<UserManager>>,
) -> DataResult<UserProfilePB, FlowyError> { ) -> DataResult<UserProfilePB, FlowyError> {
@ -105,6 +106,12 @@ pub async fn get_user_profile_handler(
} }
}); });
event!(
tracing::Level::DEBUG,
"Get user profile: {:?}",
user_profile
);
data_result_ok(user_profile.into()) data_result_ok(user_profile.into())
} }
@ -330,7 +337,7 @@ pub async fn set_encrypt_secret_handler(
#[tracing::instrument(level = "debug", skip_all, err)] #[tracing::instrument(level = "debug", skip_all, err)]
pub async fn check_encrypt_secret_handler( pub async fn check_encrypt_secret_handler(
manager: AFPluginState<Weak<UserManager>>, manager: AFPluginState<Weak<UserManager>>,
) -> DataResult<UserEncryptionSecretCheckPB, FlowyError> { ) -> DataResult<UserEncryptionConfigurationPB, FlowyError> {
let manager = upgrade_manager(manager)?; let manager = upgrade_manager(manager)?;
let uid = manager.get_session()?.user_id; let uid = manager.get_session()?.user_id;
let profile = manager.get_user_profile(uid).await?; let profile = manager.get_user_profile(uid).await?;
@ -346,7 +353,9 @@ pub async fn check_encrypt_secret_handler(
}, },
}; };
data_result_ok(UserEncryptionSecretCheckPB { is_need_secret }) data_result_ok(UserEncryptionConfigurationPB {
require_secret: is_need_secret,
})
} }
#[tracing::instrument(level = "debug", skip_all, err)] #[tracing::instrument(level = "debug", skip_all, err)]

View File

@ -272,7 +272,7 @@ pub enum UserEvent {
#[event(input = "UserSecretPB")] #[event(input = "UserSecretPB")]
SetEncryptionSecret = 15, SetEncryptionSecret = 15,
#[event(output = "UserEncryptionSecretCheckPB")] #[event(output = "UserEncryptionConfigurationPB")]
CheckEncryptionSign = 16, CheckEncryptionSign = 16,
/// Return the all the workspaces of the user /// Return the all the workspaces of the user

View File

@ -149,6 +149,7 @@ impl UserManager {
UserTokenState::Invalid => { UserTokenState::Invalid => {
send_auth_state_notification(AuthStateChangedPB { send_auth_state_notification(AuthStateChangedPB {
state: AuthStatePB::InvalidAuth, state: AuthStatePB::InvalidAuth,
message: "Token is invalid".to_string(),
}) })
.send(); .send();
}, },
@ -256,6 +257,7 @@ impl UserManager {
} }
send_auth_state_notification(AuthStateChangedPB { send_auth_state_notification(AuthStateChangedPB {
state: AuthStatePB::AuthStateSignIn, state: AuthStatePB::AuthStateSignIn,
message: "Sign in success".to_string(),
}) })
.send(); .send();
Ok(user_profile) Ok(user_profile)
@ -387,6 +389,7 @@ impl UserManager {
send_auth_state_notification(AuthStateChangedPB { send_auth_state_notification(AuthStateChangedPB {
state: AuthStatePB::AuthStateSignIn, state: AuthStatePB::AuthStateSignIn,
message: "Sign up success".to_string(),
}) })
.send(); .send();
Ok(()) Ok(())
@ -402,7 +405,7 @@ impl UserManager {
tokio::spawn(async move { tokio::spawn(async move {
match server.sign_out(None).await { match server.sign_out(None).await {
Ok(_) => {}, Ok(_) => {},
Err(e) => tracing::error!("Sign out failed: {:?}", e), Err(e) => event!(tracing::Level::ERROR, "{:?}", e),
} }
}); });
Ok(()) Ok(())
@ -421,8 +424,12 @@ impl UserManager {
) -> Result<(), FlowyError> { ) -> Result<(), FlowyError> {
let changeset = UserTableChangeset::new(params.clone()); let changeset = UserTableChangeset::new(params.clone());
let session = self.get_session()?; let session = self.get_session()?;
save_user_profile_change(session.user_id, self.db_pool(session.user_id)?, changeset)?; upsert_user_profile_change(session.user_id, self.db_pool(session.user_id)?, changeset)?;
self.update_user(session.user_id, None, params).await?;
let profile = self.get_user_profile(session.user_id).await?;
self
.update_user(session.user_id, profile.token, params)
.await?;
Ok(()) Ok(())
} }
@ -462,15 +469,14 @@ impl UserManager {
.await? .await?
.ok_or_else(|| FlowyError::new(ErrorCode::RecordNotFound, "User not found"))?; .ok_or_else(|| FlowyError::new(ErrorCode::RecordNotFound, "User not found"))?;
if !is_user_encryption_sign_valid(old_user_profile, &new_user_profile.encryption_type.sign()) { if new_user_profile.updated_at > old_user_profile.updated_at {
return Err(FlowyError::new( check_encryption_sign(old_user_profile, &new_user_profile.encryption_type.sign());
ErrorCode::InvalidEncryptSecret,
"Invalid encryption sign", // Save the new user profile
)); let changeset = UserTableChangeset::from_user_profile(new_user_profile.clone());
let _ = upsert_user_profile_change(uid, self.database.get_pool(uid)?, changeset);
} }
let changeset = UserTableChangeset::from_user_profile(new_user_profile.clone());
let _ = save_user_profile_change(uid, self.database.get_pool(uid)?, changeset);
Ok(new_user_profile) Ok(new_user_profile)
} }
@ -501,13 +507,12 @@ impl UserManager {
async fn update_user( async fn update_user(
&self, &self,
uid: i64, uid: i64,
token: Option<String>, token: String,
params: UpdateUserProfileParams, params: UpdateUserProfileParams,
) -> Result<(), FlowyError> { ) -> Result<(), FlowyError> {
let server = self.cloud_services.get_user_service()?; let server = self.cloud_services.get_user_service()?;
let token = token.to_owned();
tokio::spawn(async move { tokio::spawn(async move {
let credentials = UserCredentials::new(token, Some(uid), None); let credentials = UserCredentials::new(Some(token), Some(uid), None);
server.update_user(credentials, params).await server.update_user(credentials, params).await
}) })
.await .await
@ -613,6 +618,7 @@ impl UserManager {
) -> Result<(), FlowyError> { ) -> Result<(), FlowyError> {
let user_profile = UserProfile::from((response, auth_type)); let user_profile = UserProfile::from((response, auth_type));
let uid = user_profile.uid; let uid = user_profile.uid;
event!(tracing::Level::DEBUG, "Save new history user: {:?}", uid);
self.add_historical_user( self.add_historical_user(
uid, uid,
response.device_id(), response.device_id(),
@ -620,7 +626,9 @@ impl UserManager {
auth_type, auth_type,
self.user_dir(uid), self.user_dir(uid),
); );
event!(tracing::Level::DEBUG, "Save new history user workspace");
save_user_workspaces(uid, self.db_pool(uid)?, response.user_workspaces())?; save_user_workspaces(uid, self.db_pool(uid)?, response.user_workspaces())?;
event!(tracing::Level::INFO, "Save new user profile to disk");
self self
.save_user(uid, (user_profile, auth_type.clone()).into()) .save_user(uid, (user_profile, auth_type.clone()).into())
.await?; .await?;
@ -640,12 +648,12 @@ impl UserManager {
if session.user_id == user_update.uid { if session.user_id == user_update.uid {
debug!("Receive user update: {:?}", user_update); debug!("Receive user update: {:?}", user_update);
let user_profile = self.get_user_profile(user_update.uid).await?; let user_profile = self.get_user_profile(user_update.uid).await?;
if !is_user_encryption_sign_valid(&user_profile, &user_update.encryption_sign) { if !check_encryption_sign(&user_profile, &user_update.encryption_sign) {
return Ok(()); return Ok(());
} }
// Save the user profile change // Save the user profile change
save_user_profile_change( upsert_user_profile_change(
user_update.uid, user_update.uid,
self.db_pool(user_update.uid)?, self.db_pool(user_update.uid)?,
UserTableChangeset::from(user_update), UserTableChangeset::from(user_update),
@ -685,24 +693,30 @@ impl UserManager {
} }
} }
fn is_user_encryption_sign_valid(user_profile: &UserProfile, encryption_sign: &str) -> bool { fn check_encryption_sign(user_profile: &UserProfile, encryption_sign: &str) -> bool {
// If the local user profile's encryption sign is not equal to the user update's encryption sign, // If the local user profile's encryption sign is not equal to the user update's encryption sign,
// which means the user enable encryption in another device, we should logout the current user. // which means the user enable encryption in another device, we should logout the current user.
let is_valid = user_profile.encryption_type.sign() == encryption_sign; let is_valid = user_profile.encryption_type.sign() == encryption_sign;
if !is_valid { if !is_valid {
send_auth_state_notification(AuthStateChangedPB { send_auth_state_notification(AuthStateChangedPB {
state: AuthStatePB::InvalidAuth, state: AuthStatePB::InvalidAuth,
message: "Encryption configuration was changed".to_string(),
}) })
.send(); .send();
} }
is_valid is_valid
} }
fn save_user_profile_change( fn upsert_user_profile_change(
uid: i64, uid: i64,
pool: Arc<ConnectionPool>, pool: Arc<ConnectionPool>,
changeset: UserTableChangeset, changeset: UserTableChangeset,
) -> FlowyResult<()> { ) -> FlowyResult<()> {
event!(
tracing::Level::DEBUG,
"Update user profile with changeset: {:?}",
changeset
);
let conn = pool.get()?; let conn = pool.get()?;
diesel_update_table!(user_table, changeset, &*conn); diesel_update_table!(user_table, changeset, &*conn);
let user: UserProfile = user_table::dsl::user_table let user: UserProfile = user_table::dsl::user_table
@ -719,5 +733,5 @@ fn save_user_profile_change(
fn save_user_token(uid: i64, pool: Arc<ConnectionPool>, token: String) -> FlowyResult<()> { fn save_user_token(uid: i64, pool: Arc<ConnectionPool>, token: String) -> FlowyResult<()> {
let params = UpdateUserProfileParams::new(uid).with_token(token); let params = UpdateUserProfileParams::new(uid).with_token(token);
let changeset = UserTableChangeset::new(params); let changeset = UserTableChangeset::new(params);
save_user_profile_change(uid, pool, changeset) upsert_user_profile_change(uid, pool, changeset)
} }

View File

@ -21,6 +21,7 @@ impl std::convert::From<UserNotification> for i32 {
} }
} }
#[tracing::instrument(level = "trace")]
pub(crate) fn send_notification(id: &str, ty: UserNotification) -> NotificationBuilder { pub(crate) fn send_notification(id: &str, ty: UserNotification) -> NotificationBuilder {
NotificationBuilder::new(id, ty, USER_OBSERVABLE_SOURCE) NotificationBuilder::new(id, ty, USER_OBSERVABLE_SOURCE)
} }

View File

@ -19,6 +19,7 @@ pub struct UserTable {
pub(crate) auth_type: i32, pub(crate) auth_type: i32,
pub(crate) encryption_type: String, pub(crate) encryption_type: String,
pub(crate) stability_ai_key: String, pub(crate) stability_ai_key: String,
pub(crate) updated_at: i64,
} }
impl UserTable { impl UserTable {
@ -43,6 +44,7 @@ impl From<(UserProfile, AuthType)> for UserTable {
auth_type: auth_type as i32, auth_type: auth_type as i32,
encryption_type, encryption_type,
stability_ai_key: user_profile.stability_ai_key, stability_ai_key: user_profile.stability_ai_key,
updated_at: user_profile.updated_at,
} }
} }
} }
@ -60,6 +62,7 @@ impl From<UserTable> for UserProfile {
auth_type: AuthType::from(table.auth_type), auth_type: AuthType::from(table.auth_type),
encryption_type: EncryptionType::from_str(&table.encryption_type).unwrap_or_default(), encryption_type: EncryptionType::from_str(&table.encryption_type).unwrap_or_default(),
stability_ai_key: table.stability_ai_key, stability_ai_key: table.stability_ai_key,
updated_at: table.updated_at,
} }
} }
} }

View File

@ -74,7 +74,7 @@ impl UserManager {
if let Ok(service) = self.cloud_services.get_user_service() { if let Ok(service) = self.cloud_services.get_user_service() {
if let Ok(pool) = self.db_pool(uid) { if let Ok(pool) = self.db_pool(uid) {
tokio::spawn(async move { tokio::spawn(async move {
if let Ok(new_user_workspaces) = service.get_user_workspaces(uid).await { if let Ok(new_user_workspaces) = service.get_all_user_workspaces(uid).await {
let _ = save_user_workspaces(uid, pool, &new_user_workspaces); let _ = save_user_workspaces(uid, pool, &new_user_workspaces);
let repeated_workspace_pbs = RepeatedUserWorkspacePB::from(new_user_workspaces); let repeated_workspace_pbs = RepeatedUserWorkspacePB::from(new_user_workspaces);
send_notification(&uid.to_string(), UserNotification::DidUpdateUserWorkspaces) send_notification(&uid.to_string(), UserNotification::DidUpdateUserWorkspaces)

View File

@ -62,7 +62,7 @@ flutter test --dart-define=RUST_LOG=${RUST_LOG} -j, --concurrency=1 --coverage
script_runner = "@shell" script_runner = "@shell"
[tasks.rust_unit_test] [tasks.rust_unit_test]
run_task = { name = ["rust_lib_unit_test", "shared_lib_unit_test"] } run_task = { name = ["rust_lib_unit_test"] }
[tasks.supabase_unit_test] [tasks.supabase_unit_test]
env = { RUST_LOG = "info" } env = { RUST_LOG = "info" }