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-protobuf/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/.dart_tool" />
|
||||
<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
|
||||
**/*.rs.bk
|
||||
**/**/*.log*
|
||||
**/**/temp
|
||||
bin/
|
@ -7,6 +7,7 @@ members = [
|
||||
"flowy-user",
|
||||
"flowy-ast",
|
||||
"flowy-derive",
|
||||
"flowy-test",
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
|
@ -93,7 +93,7 @@ impl std::convert::From<FFIRequest> for DispatchRequest {
|
||||
} else {
|
||||
Payload::None
|
||||
};
|
||||
let request = DispatchRequest::new(ffi_request.event, payload);
|
||||
let request = DispatchRequest::new(ffi_request.event).payload(payload);
|
||||
request
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
// https://docs.rs/syn/1.0.48/syn/struct.DeriveInput.html
|
||||
#![feature(str_split_once)]
|
||||
extern crate proc_macro;
|
||||
|
||||
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 pin_project::pin_project;
|
||||
use std::{
|
||||
convert::TryInto,
|
||||
fmt::{Debug, Display},
|
||||
future::Future,
|
||||
hash::Hash,
|
||||
@ -115,18 +116,33 @@ pub struct DispatchRequest {
|
||||
}
|
||||
|
||||
impl DispatchRequest {
|
||||
pub fn new<E>(event: E, payload: Payload) -> Self
|
||||
pub fn new<E>(event: E) -> Self
|
||||
where
|
||||
E: Eq + Hash + Debug + Clone + Display,
|
||||
{
|
||||
Self {
|
||||
payload,
|
||||
payload: Payload::None,
|
||||
event: event.into(),
|
||||
id: uuid::Uuid::new_v4().to_string(),
|
||||
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 {
|
||||
self.callback = Some(callback);
|
||||
self
|
||||
|
@ -1,5 +1,3 @@
|
||||
mod error;
|
||||
|
||||
pub type ResponseResult<T, E> = std::result::Result<crate::request::Data<T>, E>;
|
||||
|
||||
pub use error::*;
|
||||
|
@ -8,9 +8,10 @@ mod rt;
|
||||
mod service;
|
||||
mod util;
|
||||
|
||||
mod data;
|
||||
mod dispatch;
|
||||
mod system;
|
||||
|
||||
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) }
|
||||
}
|
||||
|
||||
// = 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 {
|
||||
fn into(self) -> Payload { self.to_string().into() }
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ use crate::{
|
||||
use futures_core::ready;
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
ops,
|
||||
pin::Pin,
|
||||
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);
|
||||
InternalError::new("Expected payload").into()
|
||||
}
|
||||
@ -99,65 +98,3 @@ where
|
||||
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)]
|
||||
use crate::error::{InternalError, SystemError};
|
||||
use crate::{
|
||||
request::{Data, EventRequest},
|
||||
request::EventRequest,
|
||||
response::{EventResponse, ResponseBuilder},
|
||||
};
|
||||
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::{
|
||||
data::Data,
|
||||
error::SystemError,
|
||||
request::{Data, EventRequest, Payload},
|
||||
request::{EventRequest, Payload},
|
||||
response::Responder,
|
||||
};
|
||||
use std::{fmt, fmt::Formatter};
|
||||
@ -51,6 +52,8 @@ impl Responder for EventResponse {
|
||||
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>
|
||||
where
|
||||
E: Into<SystemError>,
|
||||
|
@ -9,7 +9,7 @@ async fn test_init() {
|
||||
let event = "1";
|
||||
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;
|
||||
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"
|
||||
log = "0.4.14"
|
||||
protobuf = {version = "2.18.0"}
|
||||
lazy_static = "1.4.0"
|
||||
fancy-regex = "0.5.0"
|
||||
|
||||
[dev-dependencies]
|
||||
quickcheck = "0.9.2"
|
||||
quickcheck_macros = "0.9.1"
|
||||
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 flowy_derive::ProtoBuf;
|
||||
use std::convert::TryInto;
|
||||
|
||||
#[derive(ProtoBuf, Default)]
|
||||
pub struct User {
|
||||
|
@ -5,10 +5,14 @@ pub struct UserEmail(pub String);
|
||||
|
||||
impl UserEmail {
|
||||
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) {
|
||||
Ok(Self(s))
|
||||
} 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));
|
||||
|
||||
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 {
|
||||
Ok(Self(s))
|
||||
}
|
||||
|
@ -1,6 +1,43 @@
|
||||
use fancy_regex::Regex;
|
||||
use lazy_static::lazy_static;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
#[derive(Debug)]
|
||||
pub struct UserPassword(pub String);
|
||||
|
||||
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 error;
|
||||
pub mod event;
|
||||
mod handlers;
|
||||
pub mod module;
|
||||
mod protobuf;
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::{
|
||||
domain::*,
|
||||
event::{UserEvent::*, *},
|
||||
handlers::auth::*,
|
||||
};
|
||||
pub use crate::{domain::*, handlers::auth::*, module::UserEvent::*};
|
||||
}
|
||||
|
@ -1,9 +1,23 @@
|
||||
use crate::{event::UserEvent::*, handlers::*};
|
||||
use crate::handlers::*;
|
||||
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 {
|
||||
Module::new()
|
||||
.name("Flowy-User")
|
||||
.event(SignIn, user_sign_in)
|
||||
.event(SignUp, user_sign_up)
|
||||
.event(UserEvent::SignIn, user_sign_in)
|
||||
.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