mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
add flowy-test crate
This commit is contained in:
parent
2fca817136
commit
dfc2cbff4f
@ -14,6 +14,8 @@
|
|||||||
<sourceFolder url="file://$MODULE_DIR$/rust-lib/flowy-sdk/tests" isTestSource="true" />
|
<sourceFolder url="file://$MODULE_DIR$/rust-lib/flowy-sdk/tests" isTestSource="true" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/rust-lib/flowy-protobuf/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/rust-lib/flowy-protobuf/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/scripts/flowy-tool/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/scripts/flowy-tool/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/rust-lib/flowy-test/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/rust-lib/flowy-user/tests" isTestSource="true" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/app_flowy/packages/af_protobuf/.pub" />
|
<excludeFolder url="file://$MODULE_DIR$/app_flowy/packages/af_protobuf/.pub" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/app_flowy/packages/af_protobuf/.dart_tool" />
|
<excludeFolder url="file://$MODULE_DIR$/app_flowy/packages/af_protobuf/.dart_tool" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/app_flowy/packages/af_protobuf/build" />
|
<excludeFolder url="file://$MODULE_DIR$/app_flowy/packages/af_protobuf/build" />
|
||||||
|
1
rust-lib/.gitignore
vendored
1
rust-lib/.gitignore
vendored
@ -9,4 +9,5 @@ Cargo.lock
|
|||||||
# These are backup files generated by rustfmt
|
# These are backup files generated by rustfmt
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
**/**/*.log*
|
**/**/*.log*
|
||||||
|
**/**/temp
|
||||||
bin/
|
bin/
|
@ -7,6 +7,7 @@ members = [
|
|||||||
"flowy-user",
|
"flowy-user",
|
||||||
"flowy-ast",
|
"flowy-ast",
|
||||||
"flowy-derive",
|
"flowy-derive",
|
||||||
|
"flowy-test",
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
|
@ -93,7 +93,7 @@ impl std::convert::From<FFIRequest> for DispatchRequest {
|
|||||||
} else {
|
} else {
|
||||||
Payload::None
|
Payload::None
|
||||||
};
|
};
|
||||||
let request = DispatchRequest::new(ffi_request.event, payload);
|
let request = DispatchRequest::new(ffi_request.event).payload(payload);
|
||||||
request
|
request
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
// https://docs.rs/syn/1.0.48/syn/struct.DeriveInput.html
|
// https://docs.rs/syn/1.0.48/syn/struct.DeriveInput.html
|
||||||
#![feature(str_split_once)]
|
|
||||||
extern crate proc_macro;
|
extern crate proc_macro;
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
|
@ -1,79 +0,0 @@
|
|||||||
pub use flowy_sdk::*;
|
|
||||||
use flowy_sys::prelude::*;
|
|
||||||
use std::{
|
|
||||||
fmt::{Debug, Display},
|
|
||||||
fs,
|
|
||||||
hash::Hash,
|
|
||||||
sync::Once,
|
|
||||||
};
|
|
||||||
|
|
||||||
static INIT: Once = Once::new();
|
|
||||||
pub fn init_sdk() {
|
|
||||||
let root_dir = root_dir();
|
|
||||||
|
|
||||||
INIT.call_once(|| {
|
|
||||||
FlowySDK::init_log(&root_dir);
|
|
||||||
});
|
|
||||||
FlowySDK::init(&root_dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn root_dir() -> String {
|
|
||||||
let mut path = fs::canonicalize(".").unwrap();
|
|
||||||
path.push("tests/temp/flowy/");
|
|
||||||
let path_str = path.to_str().unwrap().to_string();
|
|
||||||
if !std::path::Path::new(&path).exists() {
|
|
||||||
std::fs::create_dir_all(path).unwrap();
|
|
||||||
}
|
|
||||||
path_str
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventTester {
|
|
||||||
request: DispatchRequest,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventTester {
|
|
||||||
pub fn new<E, P>(event: E, payload: P) -> Self
|
|
||||||
where
|
|
||||||
E: Eq + Hash + Debug + Clone + Display,
|
|
||||||
P: std::convert::Into<Payload>,
|
|
||||||
{
|
|
||||||
init_sdk();
|
|
||||||
Self {
|
|
||||||
request: DispatchRequest::new(event, payload.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[allow(dead_code)]
|
|
||||||
// pub fn bytes_payload<T>(mut self, payload: T) -> Self
|
|
||||||
// where
|
|
||||||
// T: serde::Serialize,
|
|
||||||
// {
|
|
||||||
// let bytes: Vec<u8> = bincode::serialize(&payload).unwrap();
|
|
||||||
// self.request = self.request.payload(Payload::Bytes(bytes));
|
|
||||||
// self
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// #[allow(dead_code)]
|
|
||||||
// pub fn protobuf_payload<T>(mut self, payload: T) -> Self
|
|
||||||
// where
|
|
||||||
// T: ::protobuf::Message,
|
|
||||||
// {
|
|
||||||
// let bytes: Vec<u8> = payload.write_to_bytes().unwrap();
|
|
||||||
// self.request = self.request.payload(Payload::Bytes(bytes));
|
|
||||||
// self
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub async fn async_send(self) -> EventResponse {
|
|
||||||
let resp = async_send(self.request).await;
|
|
||||||
dbg!(&resp);
|
|
||||||
resp
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn sync_send(self) -> EventResponse {
|
|
||||||
let resp = sync_send(self.request);
|
|
||||||
dbg!(&resp);
|
|
||||||
resp
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
mod helper;
|
|
||||||
mod user;
|
|
@ -1,23 +0,0 @@
|
|||||||
use crate::helper::*;
|
|
||||||
use flowy_sys::prelude::*;
|
|
||||||
use flowy_user::prelude::*;
|
|
||||||
use std::convert::{TryFrom, TryInto};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn sign_in_without_password() {
|
|
||||||
let params = UserSignInParams {
|
|
||||||
email: "annie@appflowy.io".to_string(),
|
|
||||||
password: "".to_string(),
|
|
||||||
};
|
|
||||||
let bytes: Vec<u8> = params.try_into().unwrap();
|
|
||||||
let resp = EventTester::new(SignIn, Payload::Bytes(bytes)).sync_send();
|
|
||||||
match resp.payload {
|
|
||||||
Payload::None => {},
|
|
||||||
Payload::Bytes(bytes) => {
|
|
||||||
let result = UserSignInResult::try_from(&bytes).unwrap();
|
|
||||||
dbg!(&result);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_eq!(resp.status_code, StatusCode::Ok);
|
|
||||||
}
|
|
121
rust-lib/flowy-sys/src/data.rs
Normal file
121
rust-lib/flowy-sys/src/data.rs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
use crate::{
|
||||||
|
error::{InternalError, SystemError},
|
||||||
|
request::{unexpected_none_payload, EventRequest, FromRequest, Payload},
|
||||||
|
response::{EventResponse, Responder, ResponseBuilder, ToBytes},
|
||||||
|
util::ready::{ready, Ready},
|
||||||
|
};
|
||||||
|
use std::ops;
|
||||||
|
|
||||||
|
pub struct Data<T>(pub T);
|
||||||
|
|
||||||
|
impl<T> Data<T> {
|
||||||
|
pub fn into_inner(self) -> T { self.0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ops::Deref for Data<T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &T { &self.0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ops::DerefMut for Data<T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut T { &mut self.0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait FromBytes: Sized {
|
||||||
|
fn parse_from_bytes(bytes: &Vec<u8>) -> Result<Self, String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "use_protobuf")]
|
||||||
|
impl<T> FromBytes for T
|
||||||
|
where
|
||||||
|
// https://stackoverflow.com/questions/62871045/tryfromu8-trait-bound-in-trait
|
||||||
|
T: for<'a> std::convert::TryFrom<&'a Vec<u8>, Error = String>,
|
||||||
|
{
|
||||||
|
fn parse_from_bytes(bytes: &Vec<u8>) -> Result<Self, String> { T::try_from(bytes) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "use_serde")]
|
||||||
|
impl<T> FromBytes for T
|
||||||
|
where
|
||||||
|
T: serde::de::DeserializeOwned + 'static,
|
||||||
|
{
|
||||||
|
fn parse_from_bytes(bytes: &Vec<u8>) -> Result<Self, String> {
|
||||||
|
let s = String::from_utf8_lossy(bytes);
|
||||||
|
match serde_json::from_str::<T>(s.as_ref()) {
|
||||||
|
Ok(data) => Ok(data),
|
||||||
|
Err(e) => Err(format!("{:?}", e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> FromRequest for Data<T>
|
||||||
|
where
|
||||||
|
T: FromBytes + 'static,
|
||||||
|
{
|
||||||
|
type Error = SystemError;
|
||||||
|
type Future = Ready<Result<Self, SystemError>>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn from_request(req: &EventRequest, payload: &mut Payload) -> Self::Future {
|
||||||
|
match payload {
|
||||||
|
Payload::None => ready(Err(unexpected_none_payload(req))),
|
||||||
|
Payload::Bytes(bytes) => match T::parse_from_bytes(bytes) {
|
||||||
|
Ok(data) => ready(Ok(Data(data))),
|
||||||
|
Err(e) => ready(Err(InternalError::new(format!("{:?}", e)).into())),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Responder for Data<T>
|
||||||
|
where
|
||||||
|
T: ToBytes,
|
||||||
|
{
|
||||||
|
fn respond_to(self, _request: &EventRequest) -> EventResponse {
|
||||||
|
match self.into_inner().into_bytes() {
|
||||||
|
Ok(bytes) => ResponseBuilder::Ok().data(bytes.to_vec()).build(),
|
||||||
|
Err(e) => {
|
||||||
|
let system_err: SystemError = InternalError::new(format!("{:?}", e)).into();
|
||||||
|
system_err.into()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> std::convert::From<T> for Data<T>
|
||||||
|
where
|
||||||
|
T: ToBytes,
|
||||||
|
{
|
||||||
|
fn from(val: T) -> Self { Data(val) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> std::convert::TryFrom<&Payload> for Data<T>
|
||||||
|
where
|
||||||
|
T: FromBytes,
|
||||||
|
{
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(payload: &Payload) -> Result<Data<T>, Self::Error> {
|
||||||
|
match payload {
|
||||||
|
Payload::None => Err(format!("Expected payload")),
|
||||||
|
Payload::Bytes(bytes) => match T::parse_from_bytes(bytes) {
|
||||||
|
Ok(data) => Ok(Data(data)),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> std::convert::TryInto<Payload> for Data<T>
|
||||||
|
where
|
||||||
|
T: ToBytes,
|
||||||
|
{
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_into(self) -> Result<Payload, Self::Error> {
|
||||||
|
let inner = self.into_inner();
|
||||||
|
let bytes = inner.into_bytes()?;
|
||||||
|
Ok(Payload::Bytes(bytes))
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,7 @@ use futures_util::task::Context;
|
|||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use pin_project::pin_project;
|
use pin_project::pin_project;
|
||||||
use std::{
|
use std::{
|
||||||
|
convert::TryInto,
|
||||||
fmt::{Debug, Display},
|
fmt::{Debug, Display},
|
||||||
future::Future,
|
future::Future,
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
@ -115,18 +116,33 @@ pub struct DispatchRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DispatchRequest {
|
impl DispatchRequest {
|
||||||
pub fn new<E>(event: E, payload: Payload) -> Self
|
pub fn new<E>(event: E) -> Self
|
||||||
where
|
where
|
||||||
E: Eq + Hash + Debug + Clone + Display,
|
E: Eq + Hash + Debug + Clone + Display,
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
payload,
|
payload: Payload::None,
|
||||||
event: event.into(),
|
event: event.into(),
|
||||||
id: uuid::Uuid::new_v4().to_string(),
|
id: uuid::Uuid::new_v4().to_string(),
|
||||||
callback: None,
|
callback: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn payload<P>(mut self, payload: P) -> Self
|
||||||
|
where
|
||||||
|
P: TryInto<Payload, Error = String>,
|
||||||
|
{
|
||||||
|
let payload = match payload.try_into() {
|
||||||
|
Ok(payload) => payload,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("{}", e);
|
||||||
|
Payload::None
|
||||||
|
},
|
||||||
|
};
|
||||||
|
self.payload = payload;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn callback(mut self, callback: BoxFutureCallback) -> Self {
|
pub fn callback(mut self, callback: BoxFutureCallback) -> Self {
|
||||||
self.callback = Some(callback);
|
self.callback = Some(callback);
|
||||||
self
|
self
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
mod error;
|
mod error;
|
||||||
|
|
||||||
pub type ResponseResult<T, E> = std::result::Result<crate::request::Data<T>, E>;
|
|
||||||
|
|
||||||
pub use error::*;
|
pub use error::*;
|
||||||
|
@ -8,9 +8,10 @@ mod rt;
|
|||||||
mod service;
|
mod service;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
|
mod data;
|
||||||
mod dispatch;
|
mod dispatch;
|
||||||
mod system;
|
mod system;
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use crate::{dispatch::*, error::*, module::*, request::*, response::*, rt::*};
|
pub use crate::{data::*, dispatch::*, error::*, module::*, request::*, response::*, rt::*};
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,16 @@ impl std::convert::Into<Payload> for Vec<u8> {
|
|||||||
fn into(self) -> Payload { Payload::Bytes(self) }
|
fn into(self) -> Payload { Payload::Bytes(self) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// = note: conflicting implementation in crate `core`:
|
||||||
|
// - impl<T, U> TryInto<U> for T where U: TryFrom<T>;
|
||||||
|
//
|
||||||
|
// impl std::convert::TryInto<Payload> for Vec<u8> {
|
||||||
|
// type Error = String;
|
||||||
|
// fn try_into(self) -> Result<Payload, Self::Error> {
|
||||||
|
// Ok(Payload::Bytes(self))
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
impl std::convert::Into<Payload> for &str {
|
impl std::convert::Into<Payload> for &str {
|
||||||
fn into(self) -> Payload { self.to_string().into() }
|
fn into(self) -> Payload { self.to_string().into() }
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ use crate::{
|
|||||||
use futures_core::ready;
|
use futures_core::ready;
|
||||||
use std::{
|
use std::{
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
ops,
|
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
@ -61,7 +60,7 @@ impl FromRequest for String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unexpected_none_payload(request: &EventRequest) -> SystemError {
|
pub fn unexpected_none_payload(request: &EventRequest) -> SystemError {
|
||||||
log::warn!("{:?} expected payload", &request.event);
|
log::warn!("{:?} expected payload", &request.event);
|
||||||
InternalError::new("Expected payload").into()
|
InternalError::new("Expected payload").into()
|
||||||
}
|
}
|
||||||
@ -99,65 +98,3 @@ where
|
|||||||
Poll::Ready(Ok(res))
|
Poll::Ready(Ok(res))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Data<T>(pub T);
|
|
||||||
|
|
||||||
impl<T> Data<T> {
|
|
||||||
pub fn into_inner(self) -> T { self.0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> ops::Deref for Data<T> {
|
|
||||||
type Target = T;
|
|
||||||
|
|
||||||
fn deref(&self) -> &T { &self.0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> ops::DerefMut for Data<T> {
|
|
||||||
fn deref_mut(&mut self) -> &mut T { &mut self.0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait FromBytes: Sized {
|
|
||||||
fn parse_from_bytes(bytes: &Vec<u8>) -> Result<Self, String>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "use_protobuf")]
|
|
||||||
impl<T> FromBytes for T
|
|
||||||
where
|
|
||||||
// https://stackoverflow.com/questions/62871045/tryfromu8-trait-bound-in-trait
|
|
||||||
T: for<'a> std::convert::TryFrom<&'a Vec<u8>, Error = String>,
|
|
||||||
{
|
|
||||||
fn parse_from_bytes(bytes: &Vec<u8>) -> Result<Self, String> { T::try_from(bytes) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "use_serde")]
|
|
||||||
impl<T> FromBytes for T
|
|
||||||
where
|
|
||||||
T: serde::de::DeserializeOwned + 'static,
|
|
||||||
{
|
|
||||||
fn parse_from_bytes(bytes: &Vec<u8>) -> Result<Self, String> {
|
|
||||||
let s = String::from_utf8_lossy(bytes);
|
|
||||||
match serde_json::from_str::<T>(s.as_ref()) {
|
|
||||||
Ok(data) => Ok(data),
|
|
||||||
Err(e) => Err(format!("{:?}", e)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> FromRequest for Data<T>
|
|
||||||
where
|
|
||||||
T: FromBytes + 'static,
|
|
||||||
{
|
|
||||||
type Error = SystemError;
|
|
||||||
type Future = Ready<Result<Self, SystemError>>;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn from_request(req: &EventRequest, payload: &mut Payload) -> Self::Future {
|
|
||||||
match payload {
|
|
||||||
Payload::None => ready(Err(unexpected_none_payload(req))),
|
|
||||||
Payload::Bytes(bytes) => match T::parse_from_bytes(bytes) {
|
|
||||||
Ok(data) => ready(Ok(Data(data))),
|
|
||||||
Err(e) => ready(Err(InternalError::new(format!("{:?}", e)).into())),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use crate::error::{InternalError, SystemError};
|
use crate::error::{InternalError, SystemError};
|
||||||
use crate::{
|
use crate::{
|
||||||
request::{Data, EventRequest},
|
request::EventRequest,
|
||||||
response::{EventResponse, ResponseBuilder},
|
response::{EventResponse, ResponseBuilder},
|
||||||
};
|
};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
@ -62,25 +62,3 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Responder for Data<T>
|
|
||||||
where
|
|
||||||
T: ToBytes,
|
|
||||||
{
|
|
||||||
fn respond_to(self, _request: &EventRequest) -> EventResponse {
|
|
||||||
match self.into_inner().into_bytes() {
|
|
||||||
Ok(bytes) => ResponseBuilder::Ok().data(bytes.to_vec()).build(),
|
|
||||||
Err(e) => {
|
|
||||||
let system_err: SystemError = InternalError::new(format!("{:?}", e)).into();
|
|
||||||
system_err.into()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> std::convert::From<T> for Data<T>
|
|
||||||
where
|
|
||||||
T: ToBytes,
|
|
||||||
{
|
|
||||||
fn from(val: T) -> Self { Data(val) }
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
|
data::Data,
|
||||||
error::SystemError,
|
error::SystemError,
|
||||||
request::{Data, EventRequest, Payload},
|
request::{EventRequest, Payload},
|
||||||
response::Responder,
|
response::Responder,
|
||||||
};
|
};
|
||||||
use std::{fmt, fmt::Formatter};
|
use std::{fmt, fmt::Formatter};
|
||||||
@ -51,6 +52,8 @@ impl Responder for EventResponse {
|
|||||||
fn respond_to(self, _: &EventRequest) -> EventResponse { self }
|
fn respond_to(self, _: &EventRequest) -> EventResponse { self }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type ResponseResult<T, E> = std::result::Result<Data<T>, E>;
|
||||||
|
|
||||||
pub fn response_ok<T, E>(data: T) -> Result<Data<T>, E>
|
pub fn response_ok<T, E>(data: T) -> Result<Data<T>, E>
|
||||||
where
|
where
|
||||||
E: Into<SystemError>,
|
E: Into<SystemError>,
|
||||||
|
@ -9,7 +9,7 @@ async fn test_init() {
|
|||||||
let event = "1";
|
let event = "1";
|
||||||
init_dispatch(|| vec![Module::new().event(event, hello)]);
|
init_dispatch(|| vec![Module::new().event(event, hello)]);
|
||||||
|
|
||||||
let request = DispatchRequest::new(event, Payload::None);
|
let request = DispatchRequest::new(event);
|
||||||
let resp = async_send(request).await;
|
let resp = async_send(request).await;
|
||||||
dbg!(&resp);
|
dbg!(&resp);
|
||||||
}
|
}
|
||||||
|
17
rust-lib/flowy-test/Cargo.toml
Normal file
17
rust-lib/flowy-test/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "flowy-test"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
flowy-sdk = { path = "../flowy-sdk"}
|
||||||
|
flowy-sys = { path = "../flowy-sys"}
|
||||||
|
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
bincode = { version = "1.3"}
|
||||||
|
protobuf = {version = "2.24.1"}
|
||||||
|
claim = "0.5.0"
|
||||||
|
tokio = { version = "1", features = ["full"]}
|
||||||
|
futures-util = "0.3.15"
|
105
rust-lib/flowy-test/src/lib.rs
Normal file
105
rust-lib/flowy-test/src/lib.rs
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
pub use flowy_sdk::*;
|
||||||
|
use flowy_sys::prelude::*;
|
||||||
|
use std::{
|
||||||
|
convert::TryFrom,
|
||||||
|
fmt::{Debug, Display},
|
||||||
|
fs,
|
||||||
|
hash::Hash,
|
||||||
|
path::PathBuf,
|
||||||
|
sync::Once,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod prelude {
|
||||||
|
pub use crate::EventTester;
|
||||||
|
pub use flowy_sys::prelude::*;
|
||||||
|
pub use std::convert::TryFrom;
|
||||||
|
}
|
||||||
|
|
||||||
|
static INIT: Once = Once::new();
|
||||||
|
pub fn init_sdk() {
|
||||||
|
let root_dir = root_dir();
|
||||||
|
|
||||||
|
INIT.call_once(|| {
|
||||||
|
FlowySDK::init_log(&root_dir);
|
||||||
|
});
|
||||||
|
FlowySDK::init(&root_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn root_dir() -> String {
|
||||||
|
// https://doc.rust-lang.org/cargo/reference/environment-variables.html
|
||||||
|
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or("./".to_owned());
|
||||||
|
let mut path_buf = fs::canonicalize(&PathBuf::from(&manifest_dir)).unwrap();
|
||||||
|
path_buf.pop(); // rust-lib
|
||||||
|
path_buf.push("flowy-test");
|
||||||
|
path_buf.push("temp");
|
||||||
|
path_buf.push("flowy");
|
||||||
|
|
||||||
|
let root_dir = path_buf.to_str().unwrap().to_string();
|
||||||
|
if !std::path::Path::new(&root_dir).exists() {
|
||||||
|
std::fs::create_dir_all(&root_dir).unwrap();
|
||||||
|
}
|
||||||
|
root_dir
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct EventTester {
|
||||||
|
request: DispatchRequest,
|
||||||
|
assert_status_code: Option<StatusCode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventTester {
|
||||||
|
pub fn new<E>(event: E) -> Self
|
||||||
|
where
|
||||||
|
E: Eq + Hash + Debug + Clone + Display,
|
||||||
|
{
|
||||||
|
init_sdk();
|
||||||
|
let request = DispatchRequest::new(event);
|
||||||
|
Self {
|
||||||
|
request,
|
||||||
|
assert_status_code: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn payload<P>(mut self, payload: P) -> Self
|
||||||
|
where
|
||||||
|
P: ToBytes,
|
||||||
|
{
|
||||||
|
self.request = self.request.payload(Data(payload));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn assert_status_code(mut self, status_code: StatusCode) -> Self {
|
||||||
|
self.assert_status_code = Some(status_code);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn async_send<R>(self) -> R
|
||||||
|
where
|
||||||
|
R: FromBytes,
|
||||||
|
{
|
||||||
|
let resp = async_send(self.request).await;
|
||||||
|
dbg!(&resp);
|
||||||
|
data_from_response(&resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sync_send<R>(self) -> R
|
||||||
|
where
|
||||||
|
R: FromBytes,
|
||||||
|
{
|
||||||
|
let resp = sync_send(self.request);
|
||||||
|
if let Some(status_code) = self.assert_status_code {
|
||||||
|
assert_eq!(resp.status_code, status_code)
|
||||||
|
}
|
||||||
|
|
||||||
|
dbg!(&resp);
|
||||||
|
data_from_response(&resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data_from_response<R>(response: &EventResponse) -> R
|
||||||
|
where
|
||||||
|
R: FromBytes,
|
||||||
|
{
|
||||||
|
let result = <Data<R>>::try_from(&response.payload).unwrap().into_inner();
|
||||||
|
result
|
||||||
|
}
|
@ -18,9 +18,12 @@ rand = { version = "0.8", features=["std_rng"] }
|
|||||||
unicode-segmentation = "1.7.1"
|
unicode-segmentation = "1.7.1"
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
protobuf = {version = "2.18.0"}
|
protobuf = {version = "2.18.0"}
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
fancy-regex = "0.5.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
quickcheck = "0.9.2"
|
quickcheck = "0.9.2"
|
||||||
quickcheck_macros = "0.9.1"
|
quickcheck_macros = "0.9.1"
|
||||||
fake = "~2.3.0"
|
fake = "~2.3.0"
|
||||||
claim = "0.4.0"
|
claim = "0.4.0"
|
||||||
|
flowy-test = { path = "../flowy-test" }
|
@ -1,6 +1,5 @@
|
|||||||
use crate::domain::{UserEmail, UserName, UserPassword};
|
use crate::domain::{UserEmail, UserName, UserPassword};
|
||||||
use flowy_derive::ProtoBuf;
|
use flowy_derive::ProtoBuf;
|
||||||
use std::convert::TryInto;
|
|
||||||
|
|
||||||
#[derive(ProtoBuf, Default)]
|
#[derive(ProtoBuf, Default)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
|
@ -5,10 +5,14 @@ pub struct UserEmail(pub String);
|
|||||||
|
|
||||||
impl UserEmail {
|
impl UserEmail {
|
||||||
pub fn parse(s: String) -> Result<UserEmail, String> {
|
pub fn parse(s: String) -> Result<UserEmail, String> {
|
||||||
|
if s.trim().is_empty() {
|
||||||
|
return Err(format!("Email can not be empty or whitespace"));
|
||||||
|
}
|
||||||
|
|
||||||
if validate_email(&s) {
|
if validate_email(&s) {
|
||||||
Ok(Self(s))
|
Ok(Self(s))
|
||||||
} else {
|
} else {
|
||||||
Err(format!("{} is not a valid subscriber email.", s))
|
Err(format!("{} is not a valid email.", s))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ impl UserName {
|
|||||||
let contains_forbidden_characters = s.chars().any(|g| forbidden_characters.contains(&g));
|
let contains_forbidden_characters = s.chars().any(|g| forbidden_characters.contains(&g));
|
||||||
|
|
||||||
if is_empty_or_whitespace || is_too_long || contains_forbidden_characters {
|
if is_empty_or_whitespace || is_too_long || contains_forbidden_characters {
|
||||||
Err(format!("{} is not a valid subscriber name.", s))
|
Err(format!("{} is not a valid name.", s))
|
||||||
} else {
|
} else {
|
||||||
Ok(Self(s))
|
Ok(Self(s))
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,43 @@
|
|||||||
|
use fancy_regex::Regex;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct UserPassword(pub String);
|
pub struct UserPassword(pub String);
|
||||||
|
|
||||||
impl UserPassword {
|
impl UserPassword {
|
||||||
pub fn parse(s: String) -> Result<UserPassword, String> { Ok(Self(s)) }
|
pub fn parse(s: String) -> Result<UserPassword, String> {
|
||||||
|
let is_empty_or_whitespace = s.trim().is_empty();
|
||||||
|
if is_empty_or_whitespace {
|
||||||
|
return Err(format!("Password can not be empty or whitespace."));
|
||||||
|
}
|
||||||
|
let is_too_long = s.graphemes(true).count() > 100;
|
||||||
|
let forbidden_characters = ['/', '(', ')', '"', '<', '>', '\\', '{', '}'];
|
||||||
|
let contains_forbidden_characters = s.chars().any(|g| forbidden_characters.contains(&g));
|
||||||
|
let is_invalid_password = !validate_password(&s);
|
||||||
|
|
||||||
|
if is_too_long || contains_forbidden_characters || is_invalid_password {
|
||||||
|
Err(format!("{} is not a valid password.", s))
|
||||||
|
} else {
|
||||||
|
Ok(Self(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
// Test it in https://regex101.com/
|
||||||
|
// https://stackoverflow.com/questions/2370015/regular-expression-for-password-validation/2370045
|
||||||
|
// Hell1!
|
||||||
|
// [invalid, less than 6]
|
||||||
|
// Hel1!
|
||||||
|
//
|
||||||
|
// Hello1!
|
||||||
|
// [invalid, must include number]
|
||||||
|
// Hello!
|
||||||
|
//
|
||||||
|
// Hello12!
|
||||||
|
// [invalid must include upper case]
|
||||||
|
// hello12!
|
||||||
|
static ref PASSWORD: Regex = Regex::new("((?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[\\W]).{6,20})").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate_password(password: &str) -> bool { PASSWORD.is_match(password).is_ok() }
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
use derive_more::Display;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash)]
|
|
||||||
pub enum UserEvent {
|
|
||||||
#[display(fmt = "AuthCheck")]
|
|
||||||
AuthCheck = 0,
|
|
||||||
#[display(fmt = "SignIn")]
|
|
||||||
SignIn = 1,
|
|
||||||
#[display(fmt = "SignUp")]
|
|
||||||
SignUp = 2,
|
|
||||||
#[display(fmt = "SignOut")]
|
|
||||||
SignOut = 3,
|
|
||||||
}
|
|
@ -1,14 +1,9 @@
|
|||||||
mod domain;
|
mod domain;
|
||||||
mod error;
|
mod error;
|
||||||
pub mod event;
|
|
||||||
mod handlers;
|
mod handlers;
|
||||||
pub mod module;
|
pub mod module;
|
||||||
mod protobuf;
|
mod protobuf;
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use crate::{
|
pub use crate::{domain::*, handlers::auth::*, module::UserEvent::*};
|
||||||
domain::*,
|
|
||||||
event::{UserEvent::*, *},
|
|
||||||
handlers::auth::*,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,23 @@
|
|||||||
use crate::{event::UserEvent::*, handlers::*};
|
use crate::handlers::*;
|
||||||
use flowy_sys::prelude::*;
|
use flowy_sys::prelude::*;
|
||||||
|
|
||||||
|
use derive_more::Display;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash)]
|
||||||
|
pub enum UserEvent {
|
||||||
|
#[display(fmt = "AuthCheck")]
|
||||||
|
AuthCheck = 0,
|
||||||
|
#[display(fmt = "SignIn")]
|
||||||
|
SignIn = 1,
|
||||||
|
#[display(fmt = "SignUp")]
|
||||||
|
SignUp = 2,
|
||||||
|
#[display(fmt = "SignOut")]
|
||||||
|
SignOut = 3,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create() -> Module {
|
pub fn create() -> Module {
|
||||||
Module::new()
|
Module::new()
|
||||||
.name("Flowy-User")
|
.name("Flowy-User")
|
||||||
.event(SignIn, user_sign_in)
|
.event(UserEvent::SignIn, user_sign_in)
|
||||||
.event(SignUp, user_sign_up)
|
.event(UserEvent::SignUp, user_sign_up)
|
||||||
}
|
}
|
||||||
|
32
rust-lib/flowy-user/tests/sign_in.rs
Normal file
32
rust-lib/flowy-user/tests/sign_in.rs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
use flowy_test::prelude::*;
|
||||||
|
use flowy_user::prelude::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn sign_in_without_password() {
|
||||||
|
let params = UserSignInParams {
|
||||||
|
email: "annie@appflowy.io".to_string(),
|
||||||
|
password: "".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = EventTester::new(SignIn)
|
||||||
|
.payload(params)
|
||||||
|
.assert_status_code(StatusCode::Err)
|
||||||
|
.sync_send::<UserSignInResult>();
|
||||||
|
dbg!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn sign_in_without_email() {
|
||||||
|
let params = UserSignInParams {
|
||||||
|
email: "".to_string(),
|
||||||
|
password: "HelloWorld!123".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = EventTester::new(SignIn)
|
||||||
|
.payload(params)
|
||||||
|
.assert_status_code(StatusCode::Err)
|
||||||
|
.sync_send::<UserSignInResult>();
|
||||||
|
dbg!(&result);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user