Merge branch 'develop'
@ -1,26 +0,0 @@
|
||||
// This is a basic Flutter widget test.
|
||||
//
|
||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||
// utility that Flutter provides. For example, you can send tap and scroll
|
||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:flowy_infra_ui_example/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Verify Platform version', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(const ExampleApp());
|
||||
|
||||
// Verify that platform version is retrieved.
|
||||
expect(
|
||||
find.byWidgetPredicate(
|
||||
(Widget widget) => widget is Text && widget.data!.startsWith('Running on:'),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
// This file is provided as a convenience for running integration tests via the
|
||||
// flutter drive command.
|
||||
//
|
||||
// flutter drive --driver integration_test/driver.dart --target integration_test/app_test.dart
|
||||
|
||||
import 'package:integration_test/integration_test_driver.dart';
|
||||
|
||||
Future<void> main() => integrationDriver();
|
@ -1,29 +0,0 @@
|
||||
// This is a basic Flutter widget test.
|
||||
//
|
||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||
// utility that Flutter provides. For example, you can send tap and scroll
|
||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../lib/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Verify Platform version', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(MyApp());
|
||||
|
||||
// Verify that platform version is retrieved.
|
||||
expect(
|
||||
find.byWidgetPredicate((Widget widget) {
|
||||
if (widget is Text && widget.data != null) {
|
||||
return widget.data.startsWith('Running on:');
|
||||
}
|
||||
return false;
|
||||
}),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
// Auto-generated, do not edit
|
||||
export './revision.pb.dart';
|
||||
export './doc.pb.dart';
|
@ -1,76 +0,0 @@
|
||||
///
|
||||
// Generated code. Do not modify.
|
||||
// source: errors.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
|
||||
|
||||
import 'dart:core' as $core;
|
||||
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
import 'errors.pbenum.dart';
|
||||
|
||||
export 'errors.pbenum.dart';
|
||||
|
||||
class DocError extends $pb.GeneratedMessage {
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'DocError', createEmptyInstance: create)
|
||||
..e<ErrorCode>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'code', $pb.PbFieldType.OE, defaultOrMaker: ErrorCode.DocIdInvalid, valueOf: ErrorCode.valueOf, enumValues: ErrorCode.values)
|
||||
..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'msg')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
DocError._() : super();
|
||||
factory DocError({
|
||||
ErrorCode? code,
|
||||
$core.String? msg,
|
||||
}) {
|
||||
final _result = create();
|
||||
if (code != null) {
|
||||
_result.code = code;
|
||||
}
|
||||
if (msg != null) {
|
||||
_result.msg = msg;
|
||||
}
|
||||
return _result;
|
||||
}
|
||||
factory DocError.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory DocError.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
DocError clone() => DocError()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
DocError copyWith(void Function(DocError) updates) => super.copyWith((message) => updates(message as DocError)) as DocError; // ignore: deprecated_member_use
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static DocError create() => DocError._();
|
||||
DocError createEmptyInstance() => create();
|
||||
static $pb.PbList<DocError> createRepeated() => $pb.PbList<DocError>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static DocError getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DocError>(create);
|
||||
static DocError? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
ErrorCode get code => $_getN(0);
|
||||
@$pb.TagNumber(1)
|
||||
set code(ErrorCode v) { setField(1, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasCode() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearCode() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.String get msg => $_getSZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set msg($core.String v) { $_setString(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasMsg() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearMsg() => clearField(2);
|
||||
}
|
||||
|
@ -1,40 +0,0 @@
|
||||
///
|
||||
// Generated code. Do not modify.
|
||||
// source: errors.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
|
||||
|
||||
// ignore_for_file: UNDEFINED_SHOWN_NAME
|
||||
import 'dart:core' as $core;
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
class ErrorCode extends $pb.ProtobufEnum {
|
||||
static const ErrorCode DocIdInvalid = ErrorCode._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DocIdInvalid');
|
||||
static const ErrorCode DocNotfound = ErrorCode._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DocNotfound');
|
||||
static const ErrorCode WsConnectError = ErrorCode._(10, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WsConnectError');
|
||||
static const ErrorCode UndoFail = ErrorCode._(200, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UndoFail');
|
||||
static const ErrorCode RedoFail = ErrorCode._(201, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'RedoFail');
|
||||
static const ErrorCode OutOfBound = ErrorCode._(202, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'OutOfBound');
|
||||
static const ErrorCode DuplicateRevision = ErrorCode._(400, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DuplicateRevision');
|
||||
static const ErrorCode UserUnauthorized = ErrorCode._(999, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserUnauthorized');
|
||||
static const ErrorCode InternalError = ErrorCode._(1000, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'InternalError');
|
||||
|
||||
static const $core.List<ErrorCode> values = <ErrorCode> [
|
||||
DocIdInvalid,
|
||||
DocNotfound,
|
||||
WsConnectError,
|
||||
UndoFail,
|
||||
RedoFail,
|
||||
OutOfBound,
|
||||
DuplicateRevision,
|
||||
UserUnauthorized,
|
||||
InternalError,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, ErrorCode> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static ErrorCode? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const ErrorCode._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
@ -1,39 +0,0 @@
|
||||
///
|
||||
// Generated code. Do not modify.
|
||||
// source: errors.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
|
||||
|
||||
import 'dart:core' as $core;
|
||||
import 'dart:convert' as $convert;
|
||||
import 'dart:typed_data' as $typed_data;
|
||||
@$core.Deprecated('Use errorCodeDescriptor instead')
|
||||
const ErrorCode$json = const {
|
||||
'1': 'ErrorCode',
|
||||
'2': const [
|
||||
const {'1': 'DocIdInvalid', '2': 0},
|
||||
const {'1': 'DocNotfound', '2': 1},
|
||||
const {'1': 'WsConnectError', '2': 10},
|
||||
const {'1': 'UndoFail', '2': 200},
|
||||
const {'1': 'RedoFail', '2': 201},
|
||||
const {'1': 'OutOfBound', '2': 202},
|
||||
const {'1': 'DuplicateRevision', '2': 400},
|
||||
const {'1': 'UserUnauthorized', '2': 999},
|
||||
const {'1': 'InternalError', '2': 1000},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ErrorCode`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||
final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode('CglFcnJvckNvZGUSEAoMRG9jSWRJbnZhbGlkEAASDwoLRG9jTm90Zm91bmQQARISCg5Xc0Nvbm5lY3RFcnJvchAKEg0KCFVuZG9GYWlsEMgBEg0KCFJlZG9GYWlsEMkBEg8KCk91dE9mQm91bmQQygESFgoRRHVwbGljYXRlUmV2aXNpb24QkAMSFQoQVXNlclVuYXV0aG9yaXplZBDnBxISCg1JbnRlcm5hbEVycm9yEOgH');
|
||||
@$core.Deprecated('Use docErrorDescriptor instead')
|
||||
const DocError$json = const {
|
||||
'1': 'DocError',
|
||||
'2': const [
|
||||
const {'1': 'code', '3': 1, '4': 1, '5': 14, '6': '.ErrorCode', '10': 'code'},
|
||||
const {'1': 'msg', '3': 2, '4': 1, '5': 9, '10': 'msg'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `DocError`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List docErrorDescriptor = $convert.base64Decode('CghEb2NFcnJvchIeCgRjb2RlGAEgASgOMgouRXJyb3JDb2RlUgRjb2RlEhAKA21zZxgCIAEoCVIDbXNn');
|
@ -1,4 +0,0 @@
|
||||
// Auto-generated, do not edit
|
||||
export './ws.pb.dart';
|
||||
export './observable.pb.dart';
|
||||
export './errors.pb.dart';
|
@ -60,13 +60,12 @@ pin-project = "1.0.0"
|
||||
byteorder = {version = "1.3.4"}
|
||||
async-stream = "0.3.2"
|
||||
|
||||
flowy-user-infra = { path = "../rust-lib/flowy-user-infra" }
|
||||
flowy-workspace-infra = { path = "../rust-lib/flowy-workspace-infra" }
|
||||
flowy-document-infra = { path = "../rust-lib/flowy-document-infra" }
|
||||
flowy-document = { path = "../rust-lib/flowy-document" }
|
||||
flowy-ws = { path = "../rust-lib/flowy-ws" }
|
||||
flowy-ot = { path = "../rust-lib/flowy-ot" }
|
||||
flowy-net = { path = "../rust-lib/flowy-net", features = ["http_server"] }
|
||||
flowy-user-infra = { path = "../frontend/rust-lib/flowy-user-infra" }
|
||||
flowy-workspace-infra = { path = "../frontend/rust-lib/flowy-workspace-infra" }
|
||||
flowy-document-infra = { path = "../frontend/rust-lib/flowy-document-infra" }
|
||||
lib-ws = { path = "../frontend/rust-lib/lib-ws" }
|
||||
lib-ot = { path = "../frontend/rust-lib/lib-ot" }
|
||||
backend-service = { path = "../frontend/rust-lib/backend-service", features = ["http_server"] }
|
||||
|
||||
ormx = { version = "0.7", features = ["postgres"]}
|
||||
[dependencies.sqlx]
|
||||
@ -99,15 +98,14 @@ ignore_auth = []
|
||||
parking_lot = "0.11"
|
||||
once_cell = "1.7.2"
|
||||
linkify = "0.5.0"
|
||||
backend = { path = ".", features = ["flowy_test"]}
|
||||
flowy-backend-api = { path = "../rust-lib/flowy-backend-api"}
|
||||
flowy-sdk = { path = "../rust-lib/flowy-sdk", features = ["http_server"] }
|
||||
flowy-user = { path = "../rust-lib/flowy-user", features = ["http_server"] }
|
||||
flowy-document = { path = "../rust-lib/flowy-document", features = ["flowy_test", "http_server"] }
|
||||
futures-util = "0.3.15"
|
||||
|
||||
flowy-ws = { path = "../rust-lib/flowy-ws" }
|
||||
flowy-test = { path = "../rust-lib/flowy-test" }
|
||||
flowy-infra = { path = "../rust-lib/flowy-infra" }
|
||||
flowy-ot = { path = "../rust-lib/flowy-ot" }
|
||||
flowy-sqlite = { path = "../rust-lib/flowy-sqlite" }
|
||||
futures-util = "0.3.15"
|
||||
backend = { path = ".", features = ["flowy_test"]}
|
||||
flowy-sdk = { path = "../frontend/rust-lib/flowy-sdk", features = ["http_server"] }
|
||||
flowy-user = { path = "../frontend/rust-lib/flowy-user", features = ["http_server"] }
|
||||
flowy-document = { path = "../frontend/rust-lib/flowy-document", features = ["flowy_test", "http_server"] }
|
||||
lib-ws = { path = "../frontend/rust-lib/lib-ws" }
|
||||
flowy-test = { path = "../frontend/rust-lib/flowy-test" }
|
||||
lib-infra = { path = "../frontend/rust-lib/lib-infra" }
|
||||
lib-ot = { path = "../frontend/rust-lib/lib-ot" }
|
||||
lib-sqlite = { path = "../frontend/rust-lib/lib-sqlite" }
|
||||
|
@ -4,7 +4,7 @@ use crate::service::{
|
||||
};
|
||||
use actix::Addr;
|
||||
use actix_web::web::Data;
|
||||
use flowy_ws::WsModule;
|
||||
use lib_ws::WsModule;
|
||||
use sqlx::PgPool;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::config::env::{domain, jwt_secret};
|
||||
use backend_service::errors::ServerError;
|
||||
use chrono::{Duration, Local};
|
||||
use derive_more::{From, Into};
|
||||
use flowy_net::errors::ServerError;
|
||||
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@ -76,7 +76,7 @@ impl Token {
|
||||
|
||||
use crate::service::user::EXPIRED_DURATION_DAYS;
|
||||
use actix_web::{dev::Payload, FromRequest, HttpRequest};
|
||||
use flowy_net::config::HEADER_TOKEN;
|
||||
use backend_service::config::HEADER_TOKEN;
|
||||
use futures::future::{ready, Ready};
|
||||
|
||||
impl FromRequest for Token {
|
||||
|
@ -9,7 +9,7 @@ use actix_web::{
|
||||
|
||||
use crate::config::IGNORE_ROUTES;
|
||||
use actix_web::{body::AnyBody, dev::MessageBody};
|
||||
use flowy_net::{config::HEADER_TOKEN, errors::ServerError};
|
||||
use backend_service::{config::HEADER_TOKEN, errors::ServerError};
|
||||
use futures::future::{ok, LocalBoxFuture, Ready};
|
||||
use std::{
|
||||
convert::TryInto,
|
||||
|
@ -5,8 +5,8 @@ use crate::{
|
||||
};
|
||||
|
||||
use crate::service::trash::read_trash_ids;
|
||||
use backend_service::errors::{invalid_params, ServerError};
|
||||
use chrono::Utc;
|
||||
use flowy_net::errors::{invalid_params, ServerError};
|
||||
use flowy_workspace_infra::{
|
||||
parser::{
|
||||
app::{AppDesc, AppName},
|
||||
|
@ -2,7 +2,7 @@ use actix_web::{
|
||||
web::{Data, Payload},
|
||||
HttpResponse,
|
||||
};
|
||||
use flowy_net::errors::{invalid_params, ServerError};
|
||||
use backend_service::errors::{invalid_params, ServerError};
|
||||
use flowy_workspace_infra::protobuf::{AppIdentifier, CreateAppParams, UpdateAppParams};
|
||||
use protobuf::Message;
|
||||
use sqlx::PgPool;
|
||||
@ -16,7 +16,7 @@ use crate::service::{
|
||||
util::parse_from_payload,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use flowy_net::response::FlowyResponse;
|
||||
use backend_service::response::FlowyResponse;
|
||||
use flowy_workspace_infra::parser::app::{AppDesc, AppName};
|
||||
|
||||
pub async fn create_handler(
|
||||
|
@ -2,8 +2,8 @@ use crate::{
|
||||
entities::workspace::{AppTable, APP_TABLE},
|
||||
sqlx_ext::SqlBuilder,
|
||||
};
|
||||
use backend_service::errors::{invalid_params, ServerError};
|
||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||
use flowy_net::errors::{invalid_params, ServerError};
|
||||
use flowy_workspace_infra::{
|
||||
parser::app::AppId,
|
||||
protobuf::{App, ColorStyle},
|
||||
|
@ -3,8 +3,8 @@ use crate::{
|
||||
sqlx_ext::{map_sqlx_error, DBTransaction, SqlBuilder},
|
||||
};
|
||||
use anyhow::Context;
|
||||
use backend_service::errors::ServerError;
|
||||
use flowy_document_infra::protobuf::{CreateDocParams, Doc, DocIdentifier, UpdateDocParams};
|
||||
use flowy_net::errors::ServerError;
|
||||
use sqlx::{postgres::PgArguments, PgPool, Postgres};
|
||||
use uuid::Uuid;
|
||||
|
||||
|
@ -7,9 +7,9 @@ use crate::service::{
|
||||
ws::{WsBizHandler, WsClientData},
|
||||
};
|
||||
use actix_web::web::Data;
|
||||
use backend_service::errors::{internal_error, ServerError};
|
||||
use dashmap::DashMap;
|
||||
use flowy_document_infra::protobuf::DocIdentifier;
|
||||
use flowy_net::errors::{internal_error, ServerError};
|
||||
use sqlx::PgPool;
|
||||
use std::sync::Arc;
|
||||
use tokio::{
|
||||
|
@ -4,8 +4,8 @@ use crate::service::{
|
||||
};
|
||||
use actix_web::web::Data;
|
||||
use async_stream::stream;
|
||||
use backend_service::errors::{internal_error, Result as DocResult, ServerError};
|
||||
use flowy_document_infra::protobuf::{Doc, Revision};
|
||||
use flowy_net::errors::{internal_error, Result as DocResult, ServerError};
|
||||
use futures::stream::StreamExt;
|
||||
use sqlx::PgPool;
|
||||
use std::sync::{atomic::Ordering::SeqCst, Arc};
|
||||
|
@ -1,27 +1,22 @@
|
||||
use crate::service::{
|
||||
doc::{edit::edit_actor::EditUser, update_doc},
|
||||
util::md5,
|
||||
ws::WsMessageAdaptor,
|
||||
ws::{entities::Socket, WsMessageAdaptor},
|
||||
};
|
||||
use actix_web::web::Data;
|
||||
|
||||
use crate::service::ws::entities::Socket;
|
||||
use bytes::Bytes;
|
||||
use backend_service::errors::{internal_error, ServerError};
|
||||
use dashmap::DashMap;
|
||||
use flowy_document::{
|
||||
use flowy_document_infra::{
|
||||
core::Document,
|
||||
entities::ws::{WsDataType, WsDocumentData},
|
||||
services::doc::Document,
|
||||
protobuf::{Doc, RevId, RevType, Revision, RevisionRange, UpdateDocParams},
|
||||
};
|
||||
use flowy_document_infra::protobuf::{Doc, RevId, RevType, Revision, RevisionRange, UpdateDocParams};
|
||||
use flowy_net::errors::{internal_error, ServerError};
|
||||
use flowy_ot::core::{Delta, OperationTransformable};
|
||||
use flowy_ws::WsMessage;
|
||||
use lib_ot::core::{Delta, OperationTransformable};
|
||||
use parking_lot::RwLock;
|
||||
use protobuf::Message;
|
||||
use sqlx::PgPool;
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
convert::TryInto,
|
||||
sync::{
|
||||
atomic::{AtomicI64, Ordering::SeqCst},
|
||||
Arc,
|
||||
@ -213,7 +208,7 @@ fn mk_push_message(doc_id: &str, revision: Revision) -> WsMessageAdaptor {
|
||||
ty: WsDataType::PushRev,
|
||||
data: bytes,
|
||||
};
|
||||
mk_ws_message(data)
|
||||
data.into()
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(socket, doc_id), err)]
|
||||
@ -236,7 +231,7 @@ fn mk_pull_message(doc_id: &str, from_rev_id: i64, to_rev_id: i64) -> WsMessageA
|
||||
ty: WsDataType::PullRev,
|
||||
data: bytes,
|
||||
};
|
||||
mk_ws_message(data)
|
||||
data.into()
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(socket, revision), err)]
|
||||
@ -248,7 +243,6 @@ fn send_acked_msg(socket: &Socket, revision: &Revision) -> Result<(), ServerErro
|
||||
fn mk_acked_message(revision: &Revision) -> WsMessageAdaptor {
|
||||
// let mut wtr = vec![];
|
||||
// let _ = wtr.write_i64::<BigEndian>(revision.rev_id);
|
||||
|
||||
let mut rev_id = RevId::new();
|
||||
rev_id.set_value(revision.rev_id);
|
||||
let data = rev_id.write_to_bytes().unwrap();
|
||||
@ -259,13 +253,7 @@ fn mk_acked_message(revision: &Revision) -> WsMessageAdaptor {
|
||||
data,
|
||||
};
|
||||
|
||||
mk_ws_message(data)
|
||||
}
|
||||
|
||||
fn mk_ws_message<T: Into<WsMessage>>(data: T) -> WsMessageAdaptor {
|
||||
let msg: WsMessage = data.into();
|
||||
let bytes: Bytes = msg.try_into().unwrap();
|
||||
WsMessageAdaptor(bytes)
|
||||
data.into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -3,8 +3,8 @@ use crate::service::{
|
||||
ws::{entities::Socket, WsUser},
|
||||
};
|
||||
use actix_web::web::Data;
|
||||
use backend_service::errors::{internal_error, Result as DocResult, ServerError};
|
||||
use flowy_document_infra::protobuf::{Doc, Revision};
|
||||
use flowy_net::errors::{internal_error, Result as DocResult, ServerError};
|
||||
use sqlx::PgPool;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
|
@ -7,8 +7,8 @@ use actix_web::{
|
||||
HttpResponse,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use backend_service::{errors::ServerError, response::FlowyResponse};
|
||||
use flowy_document_infra::protobuf::{CreateDocParams, DocIdentifier, UpdateDocParams};
|
||||
use flowy_net::{errors::ServerError, response::FlowyResponse};
|
||||
use sqlx::PgPool;
|
||||
|
||||
pub async fn create_handler(payload: Payload, pool: Data<PgPool>) -> Result<HttpResponse, ServerError> {
|
||||
|
@ -6,9 +6,8 @@ use crate::service::{
|
||||
use actix_rt::task::spawn_blocking;
|
||||
use actix_web::web::Data;
|
||||
use async_stream::stream;
|
||||
use flowy_document::protobuf::{WsDataType, WsDocumentData};
|
||||
use flowy_document_infra::protobuf::{NewDocUser, Revision};
|
||||
use flowy_net::errors::{internal_error, Result as DocResult, ServerError};
|
||||
use backend_service::errors::{internal_error, Result as DocResult, ServerError};
|
||||
use flowy_document_infra::protobuf::{NewDocUser, Revision, WsDataType, WsDocumentData};
|
||||
use futures::stream::StreamExt;
|
||||
use sqlx::PgPool;
|
||||
use std::sync::Arc;
|
||||
|
@ -9,7 +9,7 @@ use actix_web::{
|
||||
HttpResponse,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use flowy_net::{
|
||||
use backend_service::{
|
||||
errors::{invalid_params, ServerError},
|
||||
response::FlowyResponse,
|
||||
};
|
||||
|
@ -8,7 +8,7 @@ use crate::{
|
||||
sqlx_ext::{map_sqlx_error, DBTransaction, SqlBuilder},
|
||||
};
|
||||
use ::protobuf::ProtobufEnum;
|
||||
use flowy_net::errors::ServerError;
|
||||
use backend_service::errors::ServerError;
|
||||
use flowy_workspace_infra::protobuf::{RepeatedTrash, Trash, TrashType};
|
||||
use sqlx::{postgres::PgArguments, Postgres, Row};
|
||||
use uuid::Uuid;
|
||||
|
@ -4,11 +4,11 @@ use crate::{
|
||||
sqlx_ext::{map_sqlx_error, DBTransaction, SqlBuilder},
|
||||
};
|
||||
use anyhow::Context;
|
||||
use chrono::Utc;
|
||||
use flowy_net::{
|
||||
use backend_service::{
|
||||
errors::{invalid_params, ErrorCode, ServerError},
|
||||
response::FlowyResponse,
|
||||
};
|
||||
use chrono::Utc;
|
||||
use flowy_user_infra::{
|
||||
parser::{UserEmail, UserName, UserPassword},
|
||||
protobuf::{SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserParams, UserProfile},
|
||||
|
@ -1,8 +1,8 @@
|
||||
use crate::entities::token::{Claim, Token};
|
||||
use actix_web::http::HeaderValue;
|
||||
use backend_service::errors::ServerError;
|
||||
use chrono::{DateTime, Utc};
|
||||
use dashmap::DashMap;
|
||||
use flowy_net::errors::ServerError;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
lazy_static! {
|
||||
|
@ -6,7 +6,7 @@ use actix_web::{
|
||||
};
|
||||
use sqlx::PgPool;
|
||||
|
||||
use flowy_net::{errors::ServerError, response::FlowyResponse};
|
||||
use backend_service::{errors::ServerError, response::FlowyResponse};
|
||||
use flowy_user_infra::protobuf::{SignInParams, SignUpParams, UpdateUserParams};
|
||||
|
||||
use crate::{
|
||||
|
@ -7,9 +7,9 @@ use crate::{
|
||||
};
|
||||
|
||||
use crate::service::view::{create_view_with_args, sql_builder::NewViewSqlBuilder};
|
||||
use backend_service::errors::ServerError;
|
||||
use chrono::Utc;
|
||||
use flowy_document_infra::user_default::doc_initial_string;
|
||||
use flowy_net::errors::ServerError;
|
||||
use flowy_workspace_infra::protobuf::Workspace;
|
||||
use std::convert::TryInto;
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
use backend_service::errors::{ErrorCode, ServerError};
|
||||
use bcrypt::{hash, verify, DEFAULT_COST};
|
||||
use flowy_net::errors::{ErrorCode, ServerError};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn uuid() -> String { uuid::Uuid::new_v4().to_string() }
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::config::MAX_PAYLOAD_SIZE;
|
||||
use actix_web::web;
|
||||
use flowy_net::errors::{ErrorCode, ServerError};
|
||||
use backend_service::errors::{ErrorCode, ServerError};
|
||||
use futures::StreamExt;
|
||||
use protobuf::{Message, ProtobufResult};
|
||||
|
||||
@ -10,9 +10,7 @@ pub async fn parse_from_payload<T: Message>(payload: web::Payload) -> Result<T,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn parse_from_dev_payload<T: Message>(
|
||||
payload: &mut actix_web::dev::Payload,
|
||||
) -> Result<T, ServerError> {
|
||||
pub async fn parse_from_dev_payload<T: Message>(payload: &mut actix_web::dev::Payload) -> Result<T, ServerError> {
|
||||
let bytes = poll_payload(payload).await?;
|
||||
parse_from_bytes(&bytes)
|
||||
}
|
||||
@ -31,9 +29,7 @@ pub fn parse_from_bytes<T: Message>(bytes: &[u8]) -> Result<T, ServerError> {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn poll_payload(
|
||||
payload: &mut actix_web::dev::Payload,
|
||||
) -> Result<web::BytesMut, ServerError> {
|
||||
pub async fn poll_payload(payload: &mut actix_web::dev::Payload) -> Result<web::BytesMut, ServerError> {
|
||||
let mut body = web::BytesMut::new();
|
||||
while let Some(chunk) = payload.next().await {
|
||||
let chunk = chunk.map_err(|err| ServerError::internal().context(err))?;
|
||||
|
@ -9,7 +9,7 @@ use actix_web::{
|
||||
HttpResponse,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use flowy_net::{
|
||||
use backend_service::{
|
||||
errors::{invalid_params, ServerError},
|
||||
response::FlowyResponse,
|
||||
};
|
||||
|
@ -2,8 +2,8 @@ use crate::{
|
||||
entities::workspace::{ViewTable, VIEW_TABLE},
|
||||
sqlx_ext::SqlBuilder,
|
||||
};
|
||||
use backend_service::errors::{invalid_params, ServerError};
|
||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||
use flowy_net::errors::{invalid_params, ServerError};
|
||||
use flowy_workspace_infra::{
|
||||
parser::view::ViewId,
|
||||
protobuf::{View, ViewType},
|
||||
|
@ -8,9 +8,9 @@ use crate::{
|
||||
},
|
||||
sqlx_ext::{map_sqlx_error, DBTransaction, SqlBuilder},
|
||||
};
|
||||
use backend_service::errors::{invalid_params, ServerError};
|
||||
use chrono::Utc;
|
||||
use flowy_document_infra::protobuf::CreateDocParams;
|
||||
use flowy_net::errors::{invalid_params, ServerError};
|
||||
use flowy_workspace_infra::{
|
||||
parser::{
|
||||
app::AppId,
|
||||
|
@ -14,7 +14,7 @@ use actix_web::{
|
||||
HttpResponse,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use flowy_net::{
|
||||
use backend_service::{
|
||||
errors::{invalid_params, ServerError},
|
||||
response::FlowyResponse,
|
||||
};
|
||||
|
@ -2,8 +2,8 @@ use crate::{
|
||||
entities::workspace::{WorkspaceTable, WORKSPACE_TABLE},
|
||||
sqlx_ext::SqlBuilder,
|
||||
};
|
||||
use backend_service::errors::{invalid_params, ServerError};
|
||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||
use flowy_net::errors::{invalid_params, ServerError};
|
||||
use flowy_workspace_infra::{parser::workspace::WorkspaceId, protobuf::Workspace};
|
||||
use sqlx::postgres::PgArguments;
|
||||
use uuid::Uuid;
|
||||
|
@ -5,7 +5,7 @@ use crate::{
|
||||
sqlx_ext::*,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use flowy_net::errors::{invalid_params, ServerError};
|
||||
use backend_service::errors::{invalid_params, ServerError};
|
||||
use flowy_workspace_infra::{
|
||||
parser::workspace::WorkspaceId,
|
||||
protobuf::{RepeatedApp, RepeatedWorkspace, Workspace},
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::service::ws::WsClientData;
|
||||
|
||||
use flowy_ws::WsModule;
|
||||
use lib_ws::WsModule;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
pub trait WsBizHandler: Send + Sync {
|
||||
@ -13,15 +13,9 @@ pub struct WsBizHandlers {
|
||||
}
|
||||
|
||||
impl WsBizHandlers {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: HashMap::new(),
|
||||
}
|
||||
}
|
||||
pub fn new() -> Self { Self { inner: HashMap::new() } }
|
||||
|
||||
pub fn register(&mut self, source: WsModule, handler: BizHandler) {
|
||||
self.inner.insert(source, handler);
|
||||
}
|
||||
pub fn register(&mut self, source: WsModule, handler: BizHandler) { self.inner.insert(source, handler); }
|
||||
|
||||
pub fn get(&self, source: &WsModule) -> Option<BizHandler> {
|
||||
match self.inner.get(source) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::service::ws::WsMessageAdaptor;
|
||||
use actix::{Message, Recipient};
|
||||
use flowy_net::errors::ServerError;
|
||||
use backend_service::errors::ServerError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Formatter;
|
||||
|
||||
|
@ -1,5 +1,8 @@
|
||||
use actix::Message;
|
||||
use bytes::Bytes;
|
||||
use flowy_document_infra::entities::ws::WsDocumentData;
|
||||
use lib_ws::{WsMessage, WsModule};
|
||||
use std::convert::TryInto;
|
||||
|
||||
#[derive(Debug, Message, Clone)]
|
||||
#[rtype(result = "()")]
|
||||
@ -10,3 +13,16 @@ impl std::ops::Deref for WsMessageAdaptor {
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl std::convert::From<WsDocumentData> for WsMessageAdaptor {
|
||||
fn from(data: WsDocumentData) -> Self {
|
||||
let bytes: Bytes = data.try_into().unwrap();
|
||||
let msg = WsMessage {
|
||||
module: WsModule::Doc,
|
||||
data: bytes.to_vec(),
|
||||
};
|
||||
|
||||
let bytes: Bytes = msg.try_into().unwrap();
|
||||
WsMessageAdaptor(bytes)
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ use actix::*;
|
||||
use actix_web::web::Data;
|
||||
use actix_web_actors::{ws, ws::Message::Text};
|
||||
use bytes::Bytes;
|
||||
use flowy_ws::WsMessage;
|
||||
use lib_ws::WsMessage;
|
||||
use std::{convert::TryFrom, sync::Arc, time::Instant};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -3,8 +3,8 @@ use crate::service::ws::{
|
||||
WsMessageAdaptor,
|
||||
};
|
||||
use actix::{Actor, Context, Handler};
|
||||
use backend_service::errors::ServerError;
|
||||
use dashmap::DashMap;
|
||||
use flowy_net::errors::ServerError;
|
||||
|
||||
pub struct WsServer {
|
||||
sessions: DashMap<SessionId, Session>,
|
||||
|
@ -1,4 +1,4 @@
|
||||
use flowy_net::errors::ServerError;
|
||||
use backend_service::errors::ServerError;
|
||||
use sql_builder::SqlBuilder as InnerBuilder;
|
||||
use sqlx::{postgres::PgArguments, Arguments, Encode, Postgres, Type};
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
use flowy_net::errors::{ErrorCode, ServerError};
|
||||
use backend_service::errors::{ErrorCode, ServerError};
|
||||
use sqlx::{Error, Postgres, Transaction};
|
||||
|
||||
pub type DBTransaction<'a> = Transaction<'a, Postgres>;
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::helper::{spawn_user_server, TestUserServer};
|
||||
use flowy_net::errors::ErrorCode;
|
||||
use crate::util::helper::{spawn_user_server, TestUserServer};
|
||||
use backend_service::errors::ErrorCode;
|
||||
use flowy_user_infra::entities::{SignInParams, SignUpParams, SignUpResponse, UpdateUserParams};
|
||||
|
||||
#[actix_rt::test]
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::helper::ViewTest;
|
||||
use crate::util::helper::ViewTest;
|
||||
use flowy_document_infra::entities::doc::DocIdentifier;
|
||||
use flowy_workspace_infra::entities::view::ViewIdentifiers;
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::helper::*;
|
||||
use crate::util::helper::*;
|
||||
use flowy_workspace_infra::entities::{
|
||||
app::{AppIdentifier, UpdateAppParams},
|
||||
trash::{TrashIdentifier, TrashIdentifiers, TrashType},
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::document::helper::{DocScript, DocumentTest};
|
||||
use flowy_document::services::doc::{Document, FlowyDoc};
|
||||
use flowy_ot::core::{Attribute, Interval};
|
||||
use flowy_document_infra::core::{Document, FlowyDoc};
|
||||
use lib_ot::core::{Attribute, Interval};
|
||||
|
||||
#[rustfmt::skip]
|
||||
// ┌─────────┐ ┌─────────┐
|
||||
@ -20,10 +20,10 @@ use flowy_ot::core::{Attribute, Interval};
|
||||
async fn delta_sync_while_editing() {
|
||||
let test = DocumentTest::new().await;
|
||||
test.run_scripts(vec![
|
||||
DocScript::ConnectWs,
|
||||
DocScript::OpenDoc,
|
||||
DocScript::InsertText(0, "abc"),
|
||||
DocScript::InsertText(3, "123"),
|
||||
DocScript::ClientConnectWs,
|
||||
DocScript::ClientOpenDoc,
|
||||
DocScript::ClientInsertText(0, "abc"),
|
||||
DocScript::ClientInsertText(3, "123"),
|
||||
DocScript::AssertClient(r#"[{"insert":"abc123\n"}]"#),
|
||||
DocScript::AssertServer(r#"[{"insert":"abc123\n"}]"#, 2),
|
||||
])
|
||||
@ -34,12 +34,12 @@ async fn delta_sync_while_editing() {
|
||||
async fn delta_sync_multi_revs() {
|
||||
let test = DocumentTest::new().await;
|
||||
test.run_scripts(vec![
|
||||
DocScript::ConnectWs,
|
||||
DocScript::OpenDoc,
|
||||
DocScript::InsertText(0, "abc"),
|
||||
DocScript::InsertText(3, "123"),
|
||||
DocScript::InsertText(6, "efg"),
|
||||
DocScript::InsertText(9, "456"),
|
||||
DocScript::ClientConnectWs,
|
||||
DocScript::ClientOpenDoc,
|
||||
DocScript::ClientInsertText(0, "abc"),
|
||||
DocScript::ClientInsertText(3, "123"),
|
||||
DocScript::ClientInsertText(6, "efg"),
|
||||
DocScript::ClientInsertText(9, "456"),
|
||||
])
|
||||
.await;
|
||||
}
|
||||
@ -48,14 +48,14 @@ async fn delta_sync_multi_revs() {
|
||||
async fn delta_sync_while_editing_with_attribute() {
|
||||
let test = DocumentTest::new().await;
|
||||
test.run_scripts(vec![
|
||||
DocScript::ConnectWs,
|
||||
DocScript::OpenDoc,
|
||||
DocScript::InsertText(0, "abc"),
|
||||
DocScript::FormatText(Interval::new(0, 3), Attribute::Bold(true)),
|
||||
DocScript::ClientConnectWs,
|
||||
DocScript::ClientOpenDoc,
|
||||
DocScript::ClientInsertText(0, "abc"),
|
||||
DocScript::ClientFormatText(Interval::new(0, 3), Attribute::Bold(true)),
|
||||
DocScript::AssertClient(r#"[{"insert":"abc","attributes":{"bold":true}},{"insert":"\n"}]"#),
|
||||
DocScript::AssertServer(r#"[{"insert":"abc","attributes":{"bold":true}},{"insert":"\n"}]"#, 2),
|
||||
DocScript::InsertText(3, "efg"),
|
||||
DocScript::FormatText(Interval::new(3, 5), Attribute::Italic(true)),
|
||||
DocScript::ClientInsertText(3, "efg"),
|
||||
DocScript::ClientFormatText(Interval::new(3, 5), Attribute::Italic(true)),
|
||||
DocScript::AssertClient(r#"[{"insert":"abc","attributes":{"bold":true}},{"insert":"ef","attributes":{"bold":true,"italic":true}},{"insert":"g","attributes":{"bold":true}},{"insert":"\n"}]"#),
|
||||
DocScript::AssertServer(r#"[{"insert":"abc","attributes":{"bold":true}},{"insert":"ef","attributes":{"bold":true,"italic":true}},{"insert":"g","attributes":{"bold":true}},{"insert":"\n"}]"#, 4),
|
||||
])
|
||||
@ -84,8 +84,8 @@ async fn delta_sync_with_http_request() {
|
||||
let json = document.to_json();
|
||||
|
||||
test.run_scripts(vec![
|
||||
DocScript::SetServerDocument(json, 3),
|
||||
DocScript::OpenDoc,
|
||||
DocScript::ServerSaveDocument(json, 3),
|
||||
DocScript::ClientOpenDoc,
|
||||
DocScript::AssertClient(r#"[{"insert":"123456\n"}]"#),
|
||||
DocScript::AssertServer(r#"[{"insert":"123456\n"}]"#, 3),
|
||||
])
|
||||
@ -100,9 +100,9 @@ async fn delta_sync_with_server_push_delta() {
|
||||
let json = document.to_json();
|
||||
|
||||
test.run_scripts(vec![
|
||||
DocScript::OpenDoc,
|
||||
DocScript::SetServerDocument(json, 3),
|
||||
DocScript::ConnectWs,
|
||||
DocScript::ClientOpenDoc,
|
||||
DocScript::ServerSaveDocument(json, 3),
|
||||
DocScript::ClientConnectWs,
|
||||
DocScript::AssertClient(r#"[{"insert":"\n123\n"}]"#),
|
||||
])
|
||||
.await;
|
||||
@ -147,10 +147,10 @@ async fn delta_sync_while_local_rev_less_than_server_rev() {
|
||||
let json = document.to_json();
|
||||
|
||||
test.run_scripts(vec![
|
||||
DocScript::OpenDoc,
|
||||
DocScript::SetServerDocument(json, 3),
|
||||
DocScript::InsertText(0, "abc"),
|
||||
DocScript::ConnectWs,
|
||||
DocScript::ClientOpenDoc,
|
||||
DocScript::ServerSaveDocument(json, 3),
|
||||
DocScript::ClientInsertText(0, "abc"),
|
||||
DocScript::ClientConnectWs,
|
||||
DocScript::AssertClient(r#"[{"insert":"abc\n123\n"}]"#),
|
||||
DocScript::AssertServer(r#"[{"insert":"abc\n123\n"}]"#, 4),
|
||||
])
|
||||
@ -190,14 +190,14 @@ async fn delta_sync_while_local_rev_greater_than_server_rev() {
|
||||
let json = document.to_json();
|
||||
|
||||
test.run_scripts(vec![
|
||||
DocScript::SetServerDocument(json, 1),
|
||||
DocScript::OpenDoc,
|
||||
DocScript::ServerSaveDocument(json, 1),
|
||||
DocScript::ClientOpenDoc,
|
||||
DocScript::AssertClient(r#"[{"insert":"123\n"}]"#),
|
||||
DocScript::InsertText(3, "abc"),
|
||||
DocScript::InsertText(6, "efg"),
|
||||
DocScript::ConnectWs,
|
||||
DocScript::ClientInsertText(3, "abc"),
|
||||
DocScript::ClientInsertText(6, "efg"),
|
||||
DocScript::ClientConnectWs,
|
||||
DocScript::AssertClient(r#"[{"insert":"123abcefg\n"}]"#),
|
||||
DocScript::AssertServer(r#"[{"insert":"123abcefg\n"}]"#, 3),
|
||||
// DocScript::AssertServer(r#"[{"insert":"123abcefg\n"}]"#, 3),
|
||||
])
|
||||
.await;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use actix_web::web::Data;
|
||||
use backend::service::doc::{crud::update_doc, doc::DocManager};
|
||||
use backend_service::config::ServerConfig;
|
||||
use flowy_document::services::doc::ClientEditDoc as ClientEditDocContext;
|
||||
use flowy_net::config::ServerConfig;
|
||||
use flowy_test::{workspace::ViewTest, FlowyTest};
|
||||
use flowy_user::services::user::UserSession;
|
||||
use futures_util::{stream, stream::StreamExt};
|
||||
@ -9,9 +9,9 @@ use sqlx::PgPool;
|
||||
use std::sync::Arc;
|
||||
use tokio::time::{sleep, Duration};
|
||||
// use crate::helper::*;
|
||||
use crate::helper::{spawn_server, TestServer};
|
||||
use crate::util::helper::{spawn_server, TestServer};
|
||||
use flowy_document_infra::{entities::doc::DocIdentifier, protobuf::UpdateDocParams};
|
||||
use flowy_ot::core::{Attribute, Delta, Interval};
|
||||
use lib_ot::core::{Attribute, Delta, Interval};
|
||||
use parking_lot::RwLock;
|
||||
|
||||
pub struct DocumentTest {
|
||||
@ -20,13 +20,13 @@ pub struct DocumentTest {
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub enum DocScript {
|
||||
ConnectWs,
|
||||
InsertText(usize, &'static str),
|
||||
FormatText(Interval, Attribute),
|
||||
ClientConnectWs,
|
||||
ClientInsertText(usize, &'static str),
|
||||
ClientFormatText(Interval, Attribute),
|
||||
ClientOpenDoc,
|
||||
AssertClient(&'static str),
|
||||
AssertServer(&'static str, i64),
|
||||
SetServerDocument(String, i64), // delta_json, rev_id
|
||||
OpenDoc,
|
||||
ServerSaveDocument(String, i64), // delta_json, rev_id
|
||||
}
|
||||
|
||||
impl DocumentTest {
|
||||
@ -95,19 +95,19 @@ async fn run_scripts(context: Arc<RwLock<ScriptContext>>, scripts: Vec<DocScript
|
||||
let fut = async move {
|
||||
let doc_id = context.read().doc_id.clone();
|
||||
match script {
|
||||
DocScript::ConnectWs => {
|
||||
DocScript::ClientConnectWs => {
|
||||
// sleep(Duration::from_millis(300)).await;
|
||||
let user_session = context.read().client_user_session.clone();
|
||||
let token = user_session.token().unwrap();
|
||||
let _ = user_session.start_ws_connection(&token).await.unwrap();
|
||||
},
|
||||
DocScript::OpenDoc => {
|
||||
DocScript::ClientOpenDoc => {
|
||||
context.write().open_doc().await;
|
||||
},
|
||||
DocScript::InsertText(index, s) => {
|
||||
DocScript::ClientInsertText(index, s) => {
|
||||
context.read().client_edit_context().insert(index, s).await.unwrap();
|
||||
},
|
||||
DocScript::FormatText(interval, attribute) => {
|
||||
DocScript::ClientFormatText(interval, attribute) => {
|
||||
context
|
||||
.read()
|
||||
.client_edit_context()
|
||||
@ -129,7 +129,7 @@ async fn run_scripts(context: Arc<RwLock<ScriptContext>>, scripts: Vec<DocScript
|
||||
let json = edit_doc.document_json().await.unwrap();
|
||||
assert_eq(s, &json);
|
||||
},
|
||||
DocScript::SetServerDocument(json, rev_id) => {
|
||||
DocScript::ServerSaveDocument(json, rev_id) => {
|
||||
let pg_pool = context.read().server_pg_pool.clone();
|
||||
save_doc(&doc_id, json, rev_id, pg_pool).await;
|
||||
},
|
||||
|
@ -1,357 +0,0 @@
|
||||
use backend::{
|
||||
application::{get_connection_pool, init_app_context, Application},
|
||||
config::{get_configuration, DatabaseSettings},
|
||||
context::AppContext,
|
||||
};
|
||||
use flowy_backend_api::{user_request::*, workspace_request::*};
|
||||
use flowy_document::services::server::read_doc_request;
|
||||
use flowy_document_infra::entities::doc::{Doc, DocIdentifier};
|
||||
use flowy_net::errors::ServerError;
|
||||
use flowy_user_infra::entities::*;
|
||||
use flowy_workspace_infra::entities::prelude::*;
|
||||
use sqlx::{Connection, Executor, PgConnection, PgPool};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct TestUserServer {
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub pg_pool: PgPool,
|
||||
pub user_token: Option<String>,
|
||||
pub user_id: Option<String>,
|
||||
}
|
||||
|
||||
impl TestUserServer {
|
||||
pub async fn new() -> Self {
|
||||
let mut server: TestUserServer = spawn_server().await.into();
|
||||
let response = server.register_user().await;
|
||||
server.user_token = Some(response.token);
|
||||
server.user_id = Some(response.user_id);
|
||||
server
|
||||
}
|
||||
|
||||
pub async fn sign_in(&self, params: SignInParams) -> Result<SignInResponse, ServerError> {
|
||||
let url = format!("{}/api/auth", self.http_addr());
|
||||
let resp = user_sign_in_request(params, &url).await?;
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
pub async fn sign_out(&self) {
|
||||
let url = format!("{}/api/auth", self.http_addr());
|
||||
let _ = user_sign_out_request(self.user_token(), &url).await.unwrap();
|
||||
}
|
||||
|
||||
pub fn user_token(&self) -> &str { self.user_token.as_ref().expect("must call register_user first ") }
|
||||
|
||||
pub fn user_id(&self) -> &str { self.user_id.as_ref().expect("must call register_user first ") }
|
||||
|
||||
pub async fn get_user_profile(&self) -> UserProfile {
|
||||
let url = format!("{}/api/user", self.http_addr());
|
||||
let user_profile = get_user_profile_request(self.user_token(), &url).await.unwrap();
|
||||
user_profile
|
||||
}
|
||||
|
||||
pub async fn update_user_profile(&self, params: UpdateUserParams) -> Result<(), ServerError> {
|
||||
let url = format!("{}/api/user", self.http_addr());
|
||||
let _ = update_user_profile_request(self.user_token(), params, &url).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn create_workspace(&self, params: CreateWorkspaceParams) -> Workspace {
|
||||
let url = format!("{}/api/workspace", self.http_addr());
|
||||
let workspace = create_workspace_request(self.user_token(), params, &url).await.unwrap();
|
||||
workspace
|
||||
}
|
||||
|
||||
pub async fn read_workspaces(&self, params: WorkspaceIdentifier) -> RepeatedWorkspace {
|
||||
let url = format!("{}/api/workspace", self.http_addr());
|
||||
let workspaces = read_workspaces_request(self.user_token(), params, &url).await.unwrap();
|
||||
workspaces
|
||||
}
|
||||
|
||||
pub async fn update_workspace(&self, params: UpdateWorkspaceParams) {
|
||||
let url = format!("{}/api/workspace", self.http_addr());
|
||||
update_workspace_request(self.user_token(), params, &url).await.unwrap();
|
||||
}
|
||||
|
||||
pub async fn delete_workspace(&self, params: WorkspaceIdentifier) {
|
||||
let url = format!("{}/api/workspace", self.http_addr());
|
||||
delete_workspace_request(self.user_token(), params, &url).await.unwrap();
|
||||
}
|
||||
|
||||
pub async fn create_app(&self, params: CreateAppParams) -> App {
|
||||
let url = format!("{}/api/app", self.http_addr());
|
||||
let app = create_app_request(self.user_token(), params, &url).await.unwrap();
|
||||
app
|
||||
}
|
||||
|
||||
pub async fn read_app(&self, params: AppIdentifier) -> Option<App> {
|
||||
let url = format!("{}/api/app", self.http_addr());
|
||||
let app = read_app_request(self.user_token(), params, &url).await.unwrap();
|
||||
app
|
||||
}
|
||||
|
||||
pub async fn update_app(&self, params: UpdateAppParams) {
|
||||
let url = format!("{}/api/app", self.http_addr());
|
||||
update_app_request(self.user_token(), params, &url).await.unwrap();
|
||||
}
|
||||
|
||||
pub async fn delete_app(&self, params: AppIdentifier) {
|
||||
let url = format!("{}/api/app", self.http_addr());
|
||||
delete_app_request(self.user_token(), params, &url).await.unwrap();
|
||||
}
|
||||
|
||||
pub async fn create_view(&self, params: CreateViewParams) -> View {
|
||||
let url = format!("{}/api/view", self.http_addr());
|
||||
let view = create_view_request(self.user_token(), params, &url).await.unwrap();
|
||||
view
|
||||
}
|
||||
|
||||
pub async fn read_view(&self, params: ViewIdentifier) -> Option<View> {
|
||||
let url = format!("{}/api/view", self.http_addr());
|
||||
let view = read_view_request(self.user_token(), params, &url).await.unwrap();
|
||||
view
|
||||
}
|
||||
|
||||
pub async fn update_view(&self, params: UpdateViewParams) {
|
||||
let url = format!("{}/api/view", self.http_addr());
|
||||
update_view_request(self.user_token(), params, &url).await.unwrap();
|
||||
}
|
||||
|
||||
pub async fn delete_view(&self, params: ViewIdentifiers) {
|
||||
let url = format!("{}/api/view", self.http_addr());
|
||||
delete_view_request(self.user_token(), params, &url).await.unwrap();
|
||||
}
|
||||
|
||||
pub async fn create_view_trash(&self, view_id: &str) {
|
||||
let identifier = TrashIdentifier {
|
||||
id: view_id.to_string(),
|
||||
ty: TrashType::View,
|
||||
};
|
||||
let url = format!("{}/api/trash", self.http_addr());
|
||||
create_trash_request(self.user_token(), vec![identifier].into(), &url)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub async fn delete_view_trash(&self, trash_identifiers: TrashIdentifiers) {
|
||||
let url = format!("{}/api/trash", self.http_addr());
|
||||
|
||||
delete_trash_request(self.user_token(), trash_identifiers, &url)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub async fn read_trash(&self) -> RepeatedTrash {
|
||||
let url = format!("{}/api/trash", self.http_addr());
|
||||
read_trash_request(self.user_token(), &url).await.unwrap()
|
||||
}
|
||||
|
||||
pub async fn read_doc(&self, params: DocIdentifier) -> Option<Doc> {
|
||||
let url = format!("{}/api/doc", self.http_addr());
|
||||
let doc = read_doc_request(self.user_token(), params, &url).await.unwrap();
|
||||
doc
|
||||
}
|
||||
|
||||
pub async fn register_user(&self) -> SignUpResponse {
|
||||
let params = SignUpParams {
|
||||
email: "annie@appflowy.io".to_string(),
|
||||
name: "annie".to_string(),
|
||||
password: "HelloAppFlowy123!".to_string(),
|
||||
};
|
||||
|
||||
self.register(params).await
|
||||
}
|
||||
|
||||
pub async fn register(&self, params: SignUpParams) -> SignUpResponse {
|
||||
let url = format!("{}/api/register", self.http_addr());
|
||||
let response = user_sign_up_request(params, &url).await.unwrap();
|
||||
response
|
||||
}
|
||||
|
||||
pub fn http_addr(&self) -> String { format!("http://{}", self.host) }
|
||||
|
||||
pub fn ws_addr(&self) -> String { format!("ws://{}/ws/{}", self.host, self.user_token.as_ref().unwrap()) }
|
||||
}
|
||||
|
||||
impl std::convert::From<TestServer> for TestUserServer {
|
||||
fn from(server: TestServer) -> Self {
|
||||
TestUserServer {
|
||||
host: server.host,
|
||||
port: server.port,
|
||||
pg_pool: server.pg_pool,
|
||||
user_token: None,
|
||||
user_id: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn spawn_user_server() -> TestUserServer {
|
||||
let server: TestUserServer = spawn_server().await.into();
|
||||
server
|
||||
}
|
||||
|
||||
pub struct TestServer {
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub pg_pool: PgPool,
|
||||
pub app_ctx: AppContext,
|
||||
}
|
||||
|
||||
pub async fn spawn_server() -> TestServer {
|
||||
let database_name = format!("{}", Uuid::new_v4().to_string());
|
||||
let configuration = {
|
||||
let mut c = get_configuration().expect("Failed to read configuration.");
|
||||
c.database.database_name = database_name.clone();
|
||||
// Use a random OS port
|
||||
c.application.port = 0;
|
||||
c
|
||||
};
|
||||
|
||||
let _ = configure_database(&configuration.database).await;
|
||||
let app_ctx = init_app_context(&configuration).await;
|
||||
let application = Application::build(configuration.clone(), app_ctx.clone())
|
||||
.await
|
||||
.expect("Failed to build application.");
|
||||
let application_port = application.port();
|
||||
|
||||
let _ = tokio::spawn(async {
|
||||
let _ = application.run_until_stopped();
|
||||
// drop_test_database(database_name).await;
|
||||
});
|
||||
|
||||
TestServer {
|
||||
host: format!("localhost:{}", application_port),
|
||||
port: application_port,
|
||||
pg_pool: get_connection_pool(&configuration.database)
|
||||
.await
|
||||
.expect("Failed to connect to the database"),
|
||||
app_ctx,
|
||||
}
|
||||
}
|
||||
|
||||
async fn configure_database(config: &DatabaseSettings) -> PgPool {
|
||||
// Create database
|
||||
let mut connection = PgConnection::connect_with(&config.without_db())
|
||||
.await
|
||||
.expect("Failed to connect to Postgres");
|
||||
connection
|
||||
.execute(&*format!(r#"CREATE DATABASE "{}";"#, config.database_name))
|
||||
.await
|
||||
.expect("Failed to create database.");
|
||||
|
||||
// Migrate database
|
||||
let connection_pool = PgPool::connect_with(config.with_db())
|
||||
.await
|
||||
.expect("Failed to connect to Postgres.");
|
||||
|
||||
sqlx::migrate!("./migrations")
|
||||
.run(&connection_pool)
|
||||
.await
|
||||
.expect("Failed to migrate the database");
|
||||
|
||||
connection_pool
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
async fn drop_test_database(database_name: String) {
|
||||
// https://stackoverflow.com/questions/36502401/postgres-drop-database-error-pq-cannot-drop-the-currently-open-database?rq=1
|
||||
let configuration = {
|
||||
let mut c = get_configuration().expect("Failed to read configuration.");
|
||||
c.database.database_name = "flowy".to_owned();
|
||||
c.application.port = 0;
|
||||
c
|
||||
};
|
||||
|
||||
let mut connection = PgConnection::connect_with(&configuration.database.without_db())
|
||||
.await
|
||||
.expect("Failed to connect to Postgres");
|
||||
|
||||
connection
|
||||
.execute(&*format!(r#"Drop DATABASE "{}";"#, database_name))
|
||||
.await
|
||||
.expect("Failed to drop database.");
|
||||
}
|
||||
|
||||
pub async fn create_test_workspace(server: &TestUserServer) -> Workspace {
|
||||
let params = CreateWorkspaceParams {
|
||||
name: "My first workspace".to_string(),
|
||||
desc: "This is my first workspace".to_string(),
|
||||
};
|
||||
|
||||
let workspace = server.create_workspace(params).await;
|
||||
workspace
|
||||
}
|
||||
|
||||
pub async fn create_test_app(server: &TestUserServer, workspace_id: &str) -> App {
|
||||
let params = CreateAppParams {
|
||||
workspace_id: workspace_id.to_owned(),
|
||||
name: "My first app".to_string(),
|
||||
desc: "This is my first app".to_string(),
|
||||
color_style: ColorStyle::default(),
|
||||
};
|
||||
|
||||
let app = server.create_app(params).await;
|
||||
app
|
||||
}
|
||||
|
||||
pub async fn create_test_view(application: &TestUserServer, app_id: &str) -> View {
|
||||
let name = "My first view".to_string();
|
||||
let desc = "This is my first view".to_string();
|
||||
let thumbnail = "http://1.png".to_string();
|
||||
|
||||
let params = CreateViewParams::new(app_id.to_owned(), name, desc, ViewType::Doc, thumbnail);
|
||||
let app = application.create_view(params).await;
|
||||
app
|
||||
}
|
||||
|
||||
pub struct WorkspaceTest {
|
||||
pub server: TestUserServer,
|
||||
pub workspace: Workspace,
|
||||
}
|
||||
|
||||
impl WorkspaceTest {
|
||||
pub async fn new() -> Self {
|
||||
let server = TestUserServer::new().await;
|
||||
let workspace = create_test_workspace(&server).await;
|
||||
Self { server, workspace }
|
||||
}
|
||||
|
||||
pub async fn create_app(&self) -> App { create_test_app(&self.server, &self.workspace.id).await }
|
||||
}
|
||||
|
||||
pub struct AppTest {
|
||||
pub server: TestUserServer,
|
||||
pub workspace: Workspace,
|
||||
pub app: App,
|
||||
}
|
||||
|
||||
impl AppTest {
|
||||
pub async fn new() -> Self {
|
||||
let server = TestUserServer::new().await;
|
||||
let workspace = create_test_workspace(&server).await;
|
||||
let app = create_test_app(&server, &workspace.id).await;
|
||||
Self { server, workspace, app }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ViewTest {
|
||||
pub server: TestUserServer,
|
||||
pub workspace: Workspace,
|
||||
pub app: App,
|
||||
pub view: View,
|
||||
}
|
||||
|
||||
impl ViewTest {
|
||||
pub async fn new() -> Self {
|
||||
let server = TestUserServer::new().await;
|
||||
let workspace = create_test_workspace(&server).await;
|
||||
let app = create_test_app(&server, &workspace.id).await;
|
||||
let view = create_test_view(&server, &app.id).await;
|
||||
Self {
|
||||
server,
|
||||
workspace,
|
||||
app,
|
||||
view,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,3 @@
|
||||
mod api;
|
||||
mod document;
|
||||
pub mod helper;
|
||||
pub mod util;
|
||||
|
356
backend/tests/util/helper.rs
Normal file
@ -0,0 +1,356 @@
|
||||
use backend::{
|
||||
application::{get_connection_pool, init_app_context, Application},
|
||||
config::{get_configuration, DatabaseSettings},
|
||||
context::AppContext,
|
||||
};
|
||||
use backend_service::{errors::ServerError, user_request::*, workspace_request::*};
|
||||
use flowy_document::services::server::read_doc_request;
|
||||
use flowy_document_infra::entities::doc::{Doc, DocIdentifier};
|
||||
use flowy_user_infra::entities::*;
|
||||
use flowy_workspace_infra::entities::prelude::*;
|
||||
use sqlx::{Connection, Executor, PgConnection, PgPool};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct TestUserServer {
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub pg_pool: PgPool,
|
||||
pub user_token: Option<String>,
|
||||
pub user_id: Option<String>,
|
||||
}
|
||||
|
||||
impl TestUserServer {
|
||||
pub async fn new() -> Self {
|
||||
let mut server: TestUserServer = spawn_server().await.into();
|
||||
let response = server.register_user().await;
|
||||
server.user_token = Some(response.token);
|
||||
server.user_id = Some(response.user_id);
|
||||
server
|
||||
}
|
||||
|
||||
pub async fn sign_in(&self, params: SignInParams) -> Result<SignInResponse, ServerError> {
|
||||
let url = format!("{}/api/auth", self.http_addr());
|
||||
let resp = user_sign_in_request(params, &url).await?;
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
pub async fn sign_out(&self) {
|
||||
let url = format!("{}/api/auth", self.http_addr());
|
||||
let _ = user_sign_out_request(self.user_token(), &url).await.unwrap();
|
||||
}
|
||||
|
||||
pub fn user_token(&self) -> &str { self.user_token.as_ref().expect("must call register_user first ") }
|
||||
|
||||
pub fn user_id(&self) -> &str { self.user_id.as_ref().expect("must call register_user first ") }
|
||||
|
||||
pub async fn get_user_profile(&self) -> UserProfile {
|
||||
let url = format!("{}/api/user", self.http_addr());
|
||||
let user_profile = get_user_profile_request(self.user_token(), &url).await.unwrap();
|
||||
user_profile
|
||||
}
|
||||
|
||||
pub async fn update_user_profile(&self, params: UpdateUserParams) -> Result<(), ServerError> {
|
||||
let url = format!("{}/api/user", self.http_addr());
|
||||
let _ = update_user_profile_request(self.user_token(), params, &url).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn create_workspace(&self, params: CreateWorkspaceParams) -> Workspace {
|
||||
let url = format!("{}/api/workspace", self.http_addr());
|
||||
let workspace = create_workspace_request(self.user_token(), params, &url).await.unwrap();
|
||||
workspace
|
||||
}
|
||||
|
||||
pub async fn read_workspaces(&self, params: WorkspaceIdentifier) -> RepeatedWorkspace {
|
||||
let url = format!("{}/api/workspace", self.http_addr());
|
||||
let workspaces = read_workspaces_request(self.user_token(), params, &url).await.unwrap();
|
||||
workspaces
|
||||
}
|
||||
|
||||
pub async fn update_workspace(&self, params: UpdateWorkspaceParams) {
|
||||
let url = format!("{}/api/workspace", self.http_addr());
|
||||
update_workspace_request(self.user_token(), params, &url).await.unwrap();
|
||||
}
|
||||
|
||||
pub async fn delete_workspace(&self, params: WorkspaceIdentifier) {
|
||||
let url = format!("{}/api/workspace", self.http_addr());
|
||||
delete_workspace_request(self.user_token(), params, &url).await.unwrap();
|
||||
}
|
||||
|
||||
pub async fn create_app(&self, params: CreateAppParams) -> App {
|
||||
let url = format!("{}/api/app", self.http_addr());
|
||||
let app = create_app_request(self.user_token(), params, &url).await.unwrap();
|
||||
app
|
||||
}
|
||||
|
||||
pub async fn read_app(&self, params: AppIdentifier) -> Option<App> {
|
||||
let url = format!("{}/api/app", self.http_addr());
|
||||
let app = read_app_request(self.user_token(), params, &url).await.unwrap();
|
||||
app
|
||||
}
|
||||
|
||||
pub async fn update_app(&self, params: UpdateAppParams) {
|
||||
let url = format!("{}/api/app", self.http_addr());
|
||||
update_app_request(self.user_token(), params, &url).await.unwrap();
|
||||
}
|
||||
|
||||
pub async fn delete_app(&self, params: AppIdentifier) {
|
||||
let url = format!("{}/api/app", self.http_addr());
|
||||
delete_app_request(self.user_token(), params, &url).await.unwrap();
|
||||
}
|
||||
|
||||
pub async fn create_view(&self, params: CreateViewParams) -> View {
|
||||
let url = format!("{}/api/view", self.http_addr());
|
||||
let view = create_view_request(self.user_token(), params, &url).await.unwrap();
|
||||
view
|
||||
}
|
||||
|
||||
pub async fn read_view(&self, params: ViewIdentifier) -> Option<View> {
|
||||
let url = format!("{}/api/view", self.http_addr());
|
||||
let view = read_view_request(self.user_token(), params, &url).await.unwrap();
|
||||
view
|
||||
}
|
||||
|
||||
pub async fn update_view(&self, params: UpdateViewParams) {
|
||||
let url = format!("{}/api/view", self.http_addr());
|
||||
update_view_request(self.user_token(), params, &url).await.unwrap();
|
||||
}
|
||||
|
||||
pub async fn delete_view(&self, params: ViewIdentifiers) {
|
||||
let url = format!("{}/api/view", self.http_addr());
|
||||
delete_view_request(self.user_token(), params, &url).await.unwrap();
|
||||
}
|
||||
|
||||
pub async fn create_view_trash(&self, view_id: &str) {
|
||||
let identifier = TrashIdentifier {
|
||||
id: view_id.to_string(),
|
||||
ty: TrashType::View,
|
||||
};
|
||||
let url = format!("{}/api/trash", self.http_addr());
|
||||
create_trash_request(self.user_token(), vec![identifier].into(), &url)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub async fn delete_view_trash(&self, trash_identifiers: TrashIdentifiers) {
|
||||
let url = format!("{}/api/trash", self.http_addr());
|
||||
|
||||
delete_trash_request(self.user_token(), trash_identifiers, &url)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub async fn read_trash(&self) -> RepeatedTrash {
|
||||
let url = format!("{}/api/trash", self.http_addr());
|
||||
read_trash_request(self.user_token(), &url).await.unwrap()
|
||||
}
|
||||
|
||||
pub async fn read_doc(&self, params: DocIdentifier) -> Option<Doc> {
|
||||
let url = format!("{}/api/doc", self.http_addr());
|
||||
let doc = read_doc_request(self.user_token(), params, &url).await.unwrap();
|
||||
doc
|
||||
}
|
||||
|
||||
pub async fn register_user(&self) -> SignUpResponse {
|
||||
let params = SignUpParams {
|
||||
email: "annie@appflowy.io".to_string(),
|
||||
name: "annie".to_string(),
|
||||
password: "HelloAppFlowy123!".to_string(),
|
||||
};
|
||||
|
||||
self.register(params).await
|
||||
}
|
||||
|
||||
pub async fn register(&self, params: SignUpParams) -> SignUpResponse {
|
||||
let url = format!("{}/api/register", self.http_addr());
|
||||
let response = user_sign_up_request(params, &url).await.unwrap();
|
||||
response
|
||||
}
|
||||
|
||||
pub fn http_addr(&self) -> String { format!("http://{}", self.host) }
|
||||
|
||||
pub fn ws_addr(&self) -> String { format!("ws://{}/ws/{}", self.host, self.user_token.as_ref().unwrap()) }
|
||||
}
|
||||
|
||||
impl std::convert::From<TestServer> for TestUserServer {
|
||||
fn from(server: TestServer) -> Self {
|
||||
TestUserServer {
|
||||
host: server.host,
|
||||
port: server.port,
|
||||
pg_pool: server.pg_pool,
|
||||
user_token: None,
|
||||
user_id: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn spawn_user_server() -> TestUserServer {
|
||||
let server: TestUserServer = spawn_server().await.into();
|
||||
server
|
||||
}
|
||||
|
||||
pub struct TestServer {
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub pg_pool: PgPool,
|
||||
pub app_ctx: AppContext,
|
||||
}
|
||||
|
||||
pub async fn spawn_server() -> TestServer {
|
||||
let database_name = format!("{}", Uuid::new_v4().to_string());
|
||||
let configuration = {
|
||||
let mut c = get_configuration().expect("Failed to read configuration.");
|
||||
c.database.database_name = database_name.clone();
|
||||
// Use a random OS port
|
||||
c.application.port = 0;
|
||||
c
|
||||
};
|
||||
|
||||
let _ = configure_database(&configuration.database).await;
|
||||
let app_ctx = init_app_context(&configuration).await;
|
||||
let application = Application::build(configuration.clone(), app_ctx.clone())
|
||||
.await
|
||||
.expect("Failed to build application.");
|
||||
let application_port = application.port();
|
||||
|
||||
let _ = tokio::spawn(async {
|
||||
let _ = application.run_until_stopped();
|
||||
// drop_test_database(database_name).await;
|
||||
});
|
||||
|
||||
TestServer {
|
||||
host: format!("localhost:{}", application_port),
|
||||
port: application_port,
|
||||
pg_pool: get_connection_pool(&configuration.database)
|
||||
.await
|
||||
.expect("Failed to connect to the database"),
|
||||
app_ctx,
|
||||
}
|
||||
}
|
||||
|
||||
async fn configure_database(config: &DatabaseSettings) -> PgPool {
|
||||
// Create database
|
||||
let mut connection = PgConnection::connect_with(&config.without_db())
|
||||
.await
|
||||
.expect("Failed to connect to Postgres");
|
||||
connection
|
||||
.execute(&*format!(r#"CREATE DATABASE "{}";"#, config.database_name))
|
||||
.await
|
||||
.expect("Failed to create database.");
|
||||
|
||||
// Migrate database
|
||||
let connection_pool = PgPool::connect_with(config.with_db())
|
||||
.await
|
||||
.expect("Failed to connect to Postgres.");
|
||||
|
||||
sqlx::migrate!("./migrations")
|
||||
.run(&connection_pool)
|
||||
.await
|
||||
.expect("Failed to migrate the database");
|
||||
|
||||
connection_pool
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
async fn drop_test_database(database_name: String) {
|
||||
// https://stackoverflow.com/questions/36502401/postgres-drop-database-error-pq-cannot-drop-the-currently-open-database?rq=1
|
||||
let configuration = {
|
||||
let mut c = get_configuration().expect("Failed to read configuration.");
|
||||
c.database.database_name = "flowy".to_owned();
|
||||
c.application.port = 0;
|
||||
c
|
||||
};
|
||||
|
||||
let mut connection = PgConnection::connect_with(&configuration.database.without_db())
|
||||
.await
|
||||
.expect("Failed to connect to Postgres");
|
||||
|
||||
connection
|
||||
.execute(&*format!(r#"Drop DATABASE "{}";"#, database_name))
|
||||
.await
|
||||
.expect("Failed to drop database.");
|
||||
}
|
||||
|
||||
pub async fn create_test_workspace(server: &TestUserServer) -> Workspace {
|
||||
let params = CreateWorkspaceParams {
|
||||
name: "My first workspace".to_string(),
|
||||
desc: "This is my first workspace".to_string(),
|
||||
};
|
||||
|
||||
let workspace = server.create_workspace(params).await;
|
||||
workspace
|
||||
}
|
||||
|
||||
pub async fn create_test_app(server: &TestUserServer, workspace_id: &str) -> App {
|
||||
let params = CreateAppParams {
|
||||
workspace_id: workspace_id.to_owned(),
|
||||
name: "My first app".to_string(),
|
||||
desc: "This is my first app".to_string(),
|
||||
color_style: ColorStyle::default(),
|
||||
};
|
||||
|
||||
let app = server.create_app(params).await;
|
||||
app
|
||||
}
|
||||
|
||||
pub async fn create_test_view(application: &TestUserServer, app_id: &str) -> View {
|
||||
let name = "My first view".to_string();
|
||||
let desc = "This is my first view".to_string();
|
||||
let thumbnail = "http://1.png".to_string();
|
||||
|
||||
let params = CreateViewParams::new(app_id.to_owned(), name, desc, ViewType::Doc, thumbnail);
|
||||
let app = application.create_view(params).await;
|
||||
app
|
||||
}
|
||||
|
||||
pub struct WorkspaceTest {
|
||||
pub server: TestUserServer,
|
||||
pub workspace: Workspace,
|
||||
}
|
||||
|
||||
impl WorkspaceTest {
|
||||
pub async fn new() -> Self {
|
||||
let server = TestUserServer::new().await;
|
||||
let workspace = create_test_workspace(&server).await;
|
||||
Self { server, workspace }
|
||||
}
|
||||
|
||||
pub async fn create_app(&self) -> App { create_test_app(&self.server, &self.workspace.id).await }
|
||||
}
|
||||
|
||||
pub struct AppTest {
|
||||
pub server: TestUserServer,
|
||||
pub workspace: Workspace,
|
||||
pub app: App,
|
||||
}
|
||||
|
||||
impl AppTest {
|
||||
pub async fn new() -> Self {
|
||||
let server = TestUserServer::new().await;
|
||||
let workspace = create_test_workspace(&server).await;
|
||||
let app = create_test_app(&server, &workspace.id).await;
|
||||
Self { server, workspace, app }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ViewTest {
|
||||
pub server: TestUserServer,
|
||||
pub workspace: Workspace,
|
||||
pub app: App,
|
||||
pub view: View,
|
||||
}
|
||||
|
||||
impl ViewTest {
|
||||
pub async fn new() -> Self {
|
||||
let server = TestUserServer::new().await;
|
||||
let workspace = create_test_workspace(&server).await;
|
||||
let app = create_test_app(&server, &workspace.id).await;
|
||||
let view = create_test_view(&server, &app.id).await;
|
||||
Self {
|
||||
server,
|
||||
workspace,
|
||||
app,
|
||||
view,
|
||||
}
|
||||
}
|
||||
}
|
1
backend/tests/util/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod helper;
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 544 B |
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 442 B |
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 721 B |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 268 B After Width: | Height: | Size: 268 B |
Before Width: | Height: | Size: 361 B After Width: | Height: | Size: 361 B |
Before Width: | Height: | Size: 361 B After Width: | Height: | Size: 361 B |
Before Width: | Height: | Size: 361 B After Width: | Height: | Size: 361 B |
Before Width: | Height: | Size: 195 B After Width: | Height: | Size: 195 B |
Before Width: | Height: | Size: 194 B After Width: | Height: | Size: 194 B |
Before Width: | Height: | Size: 797 B After Width: | Height: | Size: 797 B |
Before Width: | Height: | Size: 641 B After Width: | Height: | Size: 641 B |
Before Width: | Height: | Size: 277 B After Width: | Height: | Size: 277 B |
Before Width: | Height: | Size: 202 B After Width: | Height: | Size: 202 B |
Before Width: | Height: | Size: 561 B After Width: | Height: | Size: 561 B |
Before Width: | Height: | Size: 421 B After Width: | Height: | Size: 421 B |
Before Width: | Height: | Size: 274 B After Width: | Height: | Size: 274 B |
Before Width: | Height: | Size: 336 B After Width: | Height: | Size: 336 B |
Before Width: | Height: | Size: 148 B After Width: | Height: | Size: 148 B |