refactor test module

This commit is contained in:
appflowy 2021-09-19 23:21:10 +08:00
parent 260060ac5c
commit 07b4113dc1
16 changed files with 124 additions and 150 deletions

View File

@ -57,6 +57,7 @@ tokio = { version = "1", features = ["full"] }
flowy-user = { path = "../rust-lib/flowy-user" } flowy-user = { path = "../rust-lib/flowy-user" }
flowy-workspace = { path = "../rust-lib/flowy-workspace" } flowy-workspace = { path = "../rust-lib/flowy-workspace" }
flowy-document = { path = "../rust-lib/flowy-document" } flowy-document = { path = "../rust-lib/flowy-document" }
flowy-ws = { path = "../rust-lib/flowy-ws" }
flowy-net = { path = "../rust-lib/flowy-net", features = ["http_server"] } flowy-net = { path = "../rust-lib/flowy-net", features = ["http_server"] }
ormx = { version = "0.7", features = ["postgres"]} ormx = { version = "0.7", features = ["postgres"]}

View File

@ -1,10 +1,8 @@
use crate::entities::token::{Claim, Token}; use crate::entities::token::{Claim, Token};
use actix_web::http::HeaderValue;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use dashmap::DashMap; use dashmap::DashMap;
use flowy_net::errors::ServerError; use flowy_net::errors::ServerError;
use actix_web::http::HeaderValue;
use lazy_static::lazy_static; use lazy_static::lazy_static;
lazy_static! { lazy_static! {

View File

@ -7,8 +7,17 @@ use std::fmt::Formatter;
pub type Socket = Recipient<ClientMessage>; pub type Socket = Recipient<ClientMessage>;
#[derive(Serialize, Deserialize, Debug, Clone, Hash, PartialEq, Eq)] #[derive(Serialize, Deserialize, Debug, Clone, Hash, PartialEq, Eq)]
pub struct SessionId { pub struct SessionId(pub String);
pub id: String,
impl<T: AsRef<str>> std::convert::From<T> for SessionId {
fn from(s: T) -> Self { SessionId(s.as_ref().to_owned()) }
}
impl std::fmt::Display for SessionId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let desc = format!("{}", &self.0);
f.write_str(&desc)
}
} }
pub struct Session { pub struct Session {
@ -25,17 +34,6 @@ impl std::convert::From<Connect> for Session {
} }
} }
impl SessionId {
pub fn new(id: String) -> Self { SessionId { id } }
}
impl std::fmt::Display for SessionId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let desc = format!("{}", &self.id);
f.write_str(&desc)
}
}
#[derive(Debug, Message, Clone)] #[derive(Debug, Message, Clone)]
#[rtype(result = "Result<(), ServerError>")] #[rtype(result = "Result<(), ServerError>")]
pub struct Connect { pub struct Connect {

View File

@ -5,33 +5,36 @@ use std::fmt::Formatter;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum MessageData { pub enum MessageData {
Text(String),
Binary(Bytes), Binary(Bytes),
Connect(SessionId), Connect(SessionId),
Disconnect(String), Disconnect(SessionId),
} }
#[derive(Debug, Message, Clone)] #[derive(Debug, Message, Clone)]
#[rtype(result = "()")] #[rtype(result = "()")]
pub struct ClientMessage { pub struct ClientMessage {
pub sid: SessionId, pub session_id: SessionId,
pub data: MessageData, pub data: MessageData,
} }
impl ClientMessage { impl ClientMessage {
pub fn new(sid: SessionId, data: MessageData) -> Self { ClientMessage { sid, data } } pub fn new<T: Into<SessionId>>(session_id: T, data: MessageData) -> Self {
ClientMessage {
session_id: session_id.into(),
data,
}
}
} }
impl std::fmt::Display for ClientMessage { impl std::fmt::Display for ClientMessage {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let content = match &self.data { let content = match &self.data {
MessageData::Text(t) => format!("[Text]: {}", t), MessageData::Binary(_) => "[Binary]".to_owned(),
MessageData::Binary(_) => "[Binary message]".to_owned(), MessageData::Connect(_) => "[Connect]".to_owned(),
MessageData::Connect(_) => "Connect".to_owned(), MessageData::Disconnect(_) => "[Disconnect]".to_owned(),
MessageData::Disconnect(_) => "Disconnect".to_owned(),
}; };
let desc = format!("{}:{}", &self.sid, content); let desc = format!("{}:{}", &self.session_id, content);
f.write_str(&desc) f.write_str(&desc)
} }
} }

View File

@ -20,10 +20,7 @@ pub async fn establish_ws_connection(
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
match LoggedUser::from_token(token.clone()) { match LoggedUser::from_token(token.clone()) {
Ok(user) => { Ok(user) => {
let client = WSClient::new( let client = WSClient::new(&user.user_id, server.get_ref().clone());
SessionId::new(user.user_id.clone()),
server.get_ref().clone(),
);
let result = ws::start(client, &request, payload); let result = ws::start(client, &request, payload);
match result { match result {
Ok(response) => Ok(response.into()), Ok(response) => Ok(response.into()),

View File

@ -1,20 +1,3 @@
use std::time::Instant;
use actix::{
fut,
Actor,
ActorContext,
ActorFutureExt,
Addr,
AsyncContext,
ContextFutureSpawner,
Handler,
Running,
StreamHandler,
WrapFuture,
};
use actix_web_actors::{ws, ws::Message::Text};
use crate::{ use crate::{
config::{HEARTBEAT_INTERVAL, PING_TIMEOUT}, config::{HEARTBEAT_INTERVAL, PING_TIMEOUT},
service::ws_service::{ service::ws_service::{
@ -24,37 +7,57 @@ use crate::{
WSServer, WSServer,
}, },
}; };
use actix::*;
use actix_web_actors::{ws, ws::Message::Text};
use std::time::Instant;
// Frontend │ Backend
//
// │
// ┌──────────┐ WsMessage ┌───────────┐ ClientMessage ┌──────────┐
// │ user 1 │─────────┼────▶│ws_client_1│──────────────────▶│ws_server │
// └──────────┘ └───────────┘ └──────────┘
// │ │
// WsMessage ▼
// ┌──────────┐ │ ┌───────────┐ ClientMessage Group
// │ user 2 │◀──────────────│ws_client_2│◀───────┐ ┌───────────────┐
// └──────────┘ │ └───────────┘ │ │ ws_user_1 │
// │ │ │
// │ └────────│ ws_user_2 │
// ┌──────────┐ ┌───────────┐ │ │
// │ user 3 │─────────┼────▶│ws_client_3│ └───────────────┘
// └──────────┘ └───────────┘
// │
pub struct WSClient { pub struct WSClient {
sid: SessionId, session_id: SessionId,
server: Addr<WSServer>, server: Addr<WSServer>,
hb: Instant, hb: Instant,
} }
impl WSClient { impl WSClient {
pub fn new(sid: SessionId, server: Addr<WSServer>) -> Self { pub fn new<T: Into<SessionId>>(session_id: T, server: Addr<WSServer>) -> Self {
Self { Self {
sid, session_id: session_id.into(),
hb: Instant::now(), hb: Instant::now(),
server, server,
} }
} }
fn hb(&self, ctx: &mut ws::WebsocketContext<Self>) { fn hb(&self, ctx: &mut ws::WebsocketContext<Self>) {
ctx.run_interval(HEARTBEAT_INTERVAL, |ws_session, ctx| { ctx.run_interval(HEARTBEAT_INTERVAL, |client, ctx| {
if Instant::now().duration_since(ws_session.hb) > PING_TIMEOUT { if Instant::now().duration_since(client.hb) > PING_TIMEOUT {
ws_session.server.do_send(Disconnect { client.server.do_send(Disconnect {
sid: ws_session.sid.clone(), sid: client.session_id.clone(),
}); });
ctx.stop(); ctx.stop();
return; } else {
}
ctx.ping(b""); ctx.ping(b"");
}
}); });
} }
fn send(&self, data: MessageData) { fn send(&self, data: MessageData) {
let msg = ClientMessage::new(self.sid.clone(), data); let msg = ClientMessage::new(self.session_id.clone(), data);
self.server.do_send(msg); self.server.do_send(msg);
} }
} }
@ -67,18 +70,16 @@ impl Actor for WSClient {
let socket = ctx.address().recipient(); let socket = ctx.address().recipient();
let connect = Connect { let connect = Connect {
socket, socket,
sid: self.sid.clone(), sid: self.session_id.clone(),
}; };
self.server self.server
.send(connect) .send(connect)
.into_actor(self) .into_actor(self)
.then(|res, _ws_session, _ctx| { .then(|res, client, _ctx| {
match res { match res {
Ok(Ok(_)) => {}, Ok(Ok(_)) => log::trace!("Send connect message to server success"),
Ok(Err(_e)) => { Ok(Err(e)) => log::error!("Send connect message to server failed: {:?}", e),
unimplemented!() Err(e) => log::error!("Send connect message to server failed: {:?}", e),
},
Err(_e) => unimplemented!(),
} }
fut::ready(()) fut::ready(())
}) })
@ -87,7 +88,7 @@ impl Actor for WSClient {
fn stopping(&mut self, _: &mut Self::Context) -> Running { fn stopping(&mut self, _: &mut Self::Context) -> Running {
self.server.do_send(Disconnect { self.server.do_send(Disconnect {
sid: self.sid.clone(), sid: self.session_id.clone(),
}); });
Running::Stop Running::Stop
@ -98,39 +99,33 @@ impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for WSClient {
fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) { fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
match msg { match msg {
Ok(ws::Message::Ping(msg)) => { Ok(ws::Message::Ping(msg)) => {
log::debug!("Receive {} ping {:?}", &self.sid, &msg);
self.hb = Instant::now(); self.hb = Instant::now();
ctx.pong(&msg); ctx.pong(&msg);
}, },
Ok(ws::Message::Pong(msg)) => { Ok(ws::Message::Pong(msg)) => {
log::debug!("Receive {} pong {:?}", &self.sid, &msg); log::debug!("Receive {} pong {:?}", &self.session_id, &msg);
self.send(MessageData::Connect(self.sid.clone()));
self.hb = Instant::now(); self.hb = Instant::now();
}, },
Ok(ws::Message::Binary(bin)) => { Ok(ws::Message::Binary(bin)) => {
log::debug!(" Receive {} binary", &self.sid); log::debug!(" Receive {} binary", &self.session_id);
self.send(MessageData::Binary(bin)); self.send(MessageData::Binary(bin));
}, },
Ok(Text(_)) => {
log::warn!("Receive unexpected text message");
},
Ok(ws::Message::Close(reason)) => { Ok(ws::Message::Close(reason)) => {
log::debug!("Receive {} close {:?}", &self.sid, &reason); self.send(MessageData::Disconnect(self.session_id.clone()));
ctx.close(reason); ctx.close(reason);
ctx.stop(); ctx.stop();
}, },
Ok(ws::Message::Continuation(c)) => { Ok(ws::Message::Continuation(_)) => {},
log::debug!("Receive {} continues message {:?}", &self.sid, &c); Ok(ws::Message::Nop) => {},
},
Ok(ws::Message::Nop) => {
log::debug!("Receive Nop message");
},
Ok(Text(s)) => {
log::debug!("Receive {} text {:?}", &self.sid, &s);
self.send(MessageData::Text(s.to_string()));
},
Err(e) => { Err(e) => {
let msg = format!("{} error: {:?}", &self.sid, e); log::error!(
log::error!("stream {}", msg); "[{}]: WebSocketStream protocol error {:?}",
ctx.text(msg); self.session_id,
e
);
ctx.stop(); ctx.stop();
}, },
} }
@ -142,21 +137,11 @@ impl Handler<ClientMessage> for WSClient {
fn handle(&mut self, msg: ClientMessage, ctx: &mut Self::Context) { fn handle(&mut self, msg: ClientMessage, ctx: &mut Self::Context) {
match msg.data { match msg.data {
MessageData::Text(text) => {
ctx.text(text);
},
MessageData::Binary(binary) => { MessageData::Binary(binary) => {
ctx.binary(binary); ctx.binary(binary);
}, },
MessageData::Connect(sid) => { MessageData::Connect(_) => {},
let connect_msg = format!("{} connect", &sid); MessageData::Disconnect(_) => {},
ctx.text(connect_msg);
},
MessageData::Disconnect(text) => {
log::debug!("Session start disconnecting {}", self.sid);
ctx.text(text);
ctx.stop();
},
} }
} }
} }

View File

@ -1,6 +1,7 @@
use crate::service::ws_service::{ use crate::service::ws_service::{
entities::{Connect, Disconnect, Session, SessionId}, entities::{Connect, Disconnect, Session, SessionId},
ClientMessage, ClientMessage,
MessageData,
}; };
use actix::{Actor, Context, Handler}; use actix::{Actor, Context, Handler};
use dashmap::DashMap; use dashmap::DashMap;
@ -46,7 +47,13 @@ impl Handler<Disconnect> for WSServer {
impl Handler<ClientMessage> for WSServer { impl Handler<ClientMessage> for WSServer {
type Result = (); type Result = ();
fn handle(&mut self, _msg: ClientMessage, _ctx: &mut Context<Self>) -> Self::Result {} fn handle(&mut self, msg: ClientMessage, _ctx: &mut Context<Self>) -> Self::Result {
match msg.data {
MessageData::Binary(_) => {},
MessageData::Connect(_) => {},
MessageData::Disconnect(_) => {},
}
}
} }
impl actix::Supervised for WSServer { impl actix::Supervised for WSServer {

View File

@ -1,5 +1,3 @@
mod auth; mod auth;
mod doc; mod doc;
mod helper;
mod workspace; mod workspace;
mod ws;

View File

@ -164,7 +164,7 @@ impl TestServer {
doc doc
} }
pub(crate) async fn register_user(&self) -> SignUpResponse { pub async fn register_user(&self) -> SignUpResponse {
let params = SignUpParams { let params = SignUpParams {
email: "annie@appflowy.io".to_string(), email: "annie@appflowy.io".to_string(),
name: "annie".to_string(), name: "annie".to_string(),
@ -174,15 +174,15 @@ impl TestServer {
self.register(params).await self.register(params).await
} }
pub(crate) async fn register(&self, params: SignUpParams) -> SignUpResponse { pub async fn register(&self, params: SignUpParams) -> SignUpResponse {
let url = format!("{}/api/register", self.http_addr()); let url = format!("{}/api/register", self.http_addr());
let response = user_sign_up_request(params, &url).await.unwrap(); let response = user_sign_up_request(params, &url).await.unwrap();
response response
} }
pub(crate) fn http_addr(&self) -> String { format!("http://{}", self.host) } pub fn http_addr(&self) -> String { format!("http://{}", self.host) }
pub(crate) fn ws_addr(&self) -> String { pub fn ws_addr(&self) -> String {
format!( format!(
"ws://{}/ws/{}", "ws://{}/ws/{}",
self.host, self.host,
@ -265,7 +265,7 @@ async fn drop_test_database(database_name: String) {
.expect("Failed to drop database."); .expect("Failed to drop database.");
} }
pub(crate) async fn create_test_workspace(server: &TestServer) -> Workspace { pub async fn create_test_workspace(server: &TestServer) -> Workspace {
let params = CreateWorkspaceParams { let params = CreateWorkspaceParams {
name: "My first workspace".to_string(), name: "My first workspace".to_string(),
desc: "This is my first workspace".to_string(), desc: "This is my first workspace".to_string(),
@ -275,7 +275,7 @@ pub(crate) async fn create_test_workspace(server: &TestServer) -> Workspace {
workspace workspace
} }
pub(crate) async fn create_test_app(server: &TestServer, workspace_id: &str) -> App { pub async fn create_test_app(server: &TestServer, workspace_id: &str) -> App {
let params = CreateAppParams { let params = CreateAppParams {
workspace_id: workspace_id.to_owned(), workspace_id: workspace_id.to_owned(),
name: "My first app".to_string(), name: "My first app".to_string(),
@ -287,7 +287,7 @@ pub(crate) async fn create_test_app(server: &TestServer, workspace_id: &str) ->
app app
} }
pub(crate) async fn create_test_view(application: &TestServer, app_id: &str) -> View { pub async fn create_test_view(application: &TestServer, app_id: &str) -> View {
let name = "My first view".to_string(); let name = "My first view".to_string();
let desc = "This is my first view".to_string(); let desc = "This is my first view".to_string();
let thumbnail = "http://1.png".to_string(); let thumbnail = "http://1.png".to_string();

3
backend/tests/main.rs Normal file
View File

@ -0,0 +1,3 @@
mod api;
pub mod helper;
mod ws;

View File

@ -69,15 +69,3 @@ impl WsScriptRunner {
} }
} }
} }
#[actix_rt::test]
async fn ws_connect() {
let mut ws = WsTest::new(vec![
WsScript::SendText("abc"),
WsScript::SendText("abc"),
WsScript::SendText("abc"),
WsScript::Disconnect("abc"),
])
.await;
ws.run_scripts().await
}

2
backend/tests/ws/mod.rs Normal file
View File

@ -0,0 +1,2 @@
mod helper;
mod ws;

13
backend/tests/ws/ws.rs Normal file
View File

@ -0,0 +1,13 @@
use crate::ws::helper::{WsScript, WsTest};
#[actix_rt::test]
async fn ws_connect() {
let mut ws = WsTest::new(vec![
WsScript::SendText("abc"),
WsScript::SendText("abc"),
WsScript::SendText("abc"),
WsScript::Disconnect("close by user"),
])
.await;
ws.run_scripts().await
}

View File

@ -18,10 +18,10 @@ use flowy_database::{
}; };
use flowy_infra::kv::KV; use flowy_infra::kv::KV;
use flowy_sqlite::ConnectionPool; use flowy_sqlite::ConnectionPool;
use flowy_ws::{connect::Retry, WsController, WsMessage, WsMessageHandler}; use flowy_ws::{connect::Retry, WsController, WsMessageHandler};
use parking_lot::RwLock; use parking_lot::RwLock;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{sync::Arc, time::Duration}; use std::sync::Arc;
pub struct UserSessionConfig { pub struct UserSessionConfig {
root_dir: String, root_dir: String,

View File

@ -1,19 +1,17 @@
use crate::{errors::WsError, MsgReceiver, MsgSender}; use crate::{errors::WsError, MsgReceiver, MsgSender};
use flowy_net::errors::ServerError;
use futures_core::{future::BoxFuture, ready}; use futures_core::{future::BoxFuture, ready};
use futures_util::{FutureExt, StreamExt, TryStreamExt}; use futures_util::{FutureExt, StreamExt};
use pin_project::pin_project; use pin_project::pin_project;
use std::{ use std::{
fmt, fmt,
future::Future, future::Future,
pin::Pin, pin::Pin,
sync::Arc,
task::{Context, Poll}, task::{Context, Poll},
}; };
use tokio::net::TcpStream; use tokio::net::TcpStream;
use tokio_tungstenite::{ use tokio_tungstenite::{
connect_async, connect_async,
tungstenite::{handshake::client::Response, http::StatusCode, Error, Message}, tungstenite::{handshake::client::Response, Error, Message},
MaybeTlsStream, MaybeTlsStream,
WebSocketStream, WebSocketStream,
}; };
@ -160,7 +158,7 @@ where
{ {
type Output = (); type Output = ();
fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
(self.f)(&self.addr); (self.f)(&self.addr);
Poll::Ready(()) Poll::Ready(())

View File

@ -1,28 +1,23 @@
use crate::{connect::WsConnection, errors::WsError, WsMessage}; use crate::{
connect::{Retry, WsConnection},
errors::WsError,
WsMessage,
};
use flowy_net::errors::ServerError; use flowy_net::errors::ServerError;
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender}; use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
use futures_core::{ready, Stream}; use futures_core::{future::BoxFuture, ready, Stream};
use crate::connect::Retry;
use bytes::Buf;
use futures_core::future::BoxFuture;
use pin_project::pin_project; use pin_project::pin_project;
use std::{ use std::{
collections::HashMap, collections::HashMap,
future::Future, future::Future,
marker::PhantomData,
pin::Pin, pin::Pin,
sync::Arc, sync::Arc,
task::{Context, Poll}, task::{Context, Poll},
}; };
use tokio::{sync::RwLock, task::JoinHandle}; use tokio::{sync::RwLock, task::JoinHandle};
use tokio_tungstenite::{ use tokio_tungstenite::tungstenite::{
tungstenite::{
protocol::{frame::coding::CloseCode, CloseFrame}, protocol::{frame::coding::CloseCode, CloseFrame},
Message, Message,
},
MaybeTlsStream,
WebSocketStream,
}; };
pub type MsgReceiver = UnboundedReceiver<Message>; pub type MsgReceiver = UnboundedReceiver<Message>;
@ -188,18 +183,6 @@ impl Future for WsHandlers {
} }
} }
// impl WsSender for WsController {
// fn send_msg(&self, msg: WsMessage) -> Result<(), WsError> {
// match self.ws_tx.as_ref() {
// None => Err(WsError::internal().context("Should call make_connect
// first")), Some(sender) => {
// let _ = sender.unbounded_send(msg.into()).map_err(|e|
// WsError::internal().context(e))?; Ok(())
// },
// }
// }
// }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct WsSender { pub struct WsSender {
ws_tx: MsgSender, ws_tx: MsgSender,