mirror of
https://github.com/Blah-IM/blahrs.git
synced 2025-05-01 00:31:09 +00:00
test: add for WS
This commit is contained in:
parent
4bca196df3
commit
883fac02ae
3 changed files with 142 additions and 6 deletions
35
Cargo.lock
generated
35
Cargo.lock
generated
|
@ -167,7 +167,7 @@ dependencies = [
|
||||||
"sha1",
|
"sha1",
|
||||||
"sync_wrapper 1.0.1",
|
"sync_wrapper 1.0.1",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-tungstenite",
|
"tokio-tungstenite 0.21.0",
|
||||||
"tower",
|
"tower",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
|
@ -337,6 +337,7 @@ dependencies = [
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
|
"tokio-tungstenite 0.24.0",
|
||||||
"toml",
|
"toml",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
@ -2438,7 +2439,19 @@ dependencies = [
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"log",
|
"log",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tungstenite",
|
"tungstenite 0.21.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-tungstenite"
|
||||||
|
version = "0.24.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9"
|
||||||
|
dependencies = [
|
||||||
|
"futures-util",
|
||||||
|
"log",
|
||||||
|
"tokio",
|
||||||
|
"tungstenite 0.24.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2615,6 +2628,24 @@ dependencies = [
|
||||||
"utf-8",
|
"utf-8",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tungstenite"
|
||||||
|
version = "0.24.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"bytes",
|
||||||
|
"data-encoding",
|
||||||
|
"http",
|
||||||
|
"httparse",
|
||||||
|
"log",
|
||||||
|
"rand",
|
||||||
|
"sha1",
|
||||||
|
"thiserror",
|
||||||
|
"utf-8",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.17.0"
|
version = "1.17.0"
|
||||||
|
|
|
@ -47,6 +47,7 @@ reqwest = { version = "0.12.7", features = ["json"] }
|
||||||
rstest = { version = "0.22.0", default-features = false }
|
rstest = { version = "0.22.0", default-features = false }
|
||||||
scopeguard = "1.2.0"
|
scopeguard = "1.2.0"
|
||||||
tempfile = "3.12.0"
|
tempfile = "3.12.0"
|
||||||
|
tokio-tungstenite = "0.24.0"
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
|
@ -19,7 +19,7 @@ use blah_types::{
|
||||||
use blahd::{ApiError, AppState, Database, RoomList, RoomMsgs};
|
use blahd::{ApiError, AppState, Database, RoomList, RoomMsgs};
|
||||||
use ed25519_dalek::SigningKey;
|
use ed25519_dalek::SigningKey;
|
||||||
use futures_util::future::BoxFuture;
|
use futures_util::future::BoxFuture;
|
||||||
use futures_util::TryFutureExt;
|
use futures_util::{SinkExt, Stream, StreamExt, TryFutureExt};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use reqwest::{header, Method, StatusCode};
|
use reqwest::{header, Method, StatusCode};
|
||||||
use rstest::{fixture, rstest};
|
use rstest::{fixture, rstest};
|
||||||
|
@ -34,12 +34,16 @@ const LOCALHOST: &str = "localhost";
|
||||||
const REGISTER_DIFFICULTY: u8 = 1;
|
const REGISTER_DIFFICULTY: u8 = 1;
|
||||||
|
|
||||||
const TIME_TOLERANCE: Duration = Duration::from_millis(100);
|
const TIME_TOLERANCE: Duration = Duration::from_millis(100);
|
||||||
|
const WS_CONNECT_TIMEOUT: Duration = Duration::from_millis(1500);
|
||||||
|
|
||||||
const CONFIG: fn(u16) -> String = |port| {
|
const CONFIG: fn(u16) -> String = |port| {
|
||||||
format!(
|
format!(
|
||||||
r#"
|
r#"
|
||||||
base_url="http://{LOCALHOST}:{port}"
|
base_url="http://{LOCALHOST}:{port}"
|
||||||
|
|
||||||
|
[ws]
|
||||||
|
auth_timeout_sec = 1
|
||||||
|
|
||||||
[register]
|
[register]
|
||||||
enable_public = true
|
enable_public = true
|
||||||
difficulty = {REGISTER_DIFFICULTY}
|
difficulty = {REGISTER_DIFFICULTY}
|
||||||
|
@ -114,6 +118,14 @@ impl fmt::Display for ApiErrorWithHeaders {
|
||||||
|
|
||||||
impl std::error::Error for ApiErrorWithHeaders {}
|
impl std::error::Error for ApiErrorWithHeaders {}
|
||||||
|
|
||||||
|
// TODO: Hoist this into types crate.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum WsEvent {
|
||||||
|
// TODO: Include cid?
|
||||||
|
Msg(SignedChatMsg),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Server {
|
struct Server {
|
||||||
port: u16,
|
port: u16,
|
||||||
|
@ -122,11 +134,34 @@ struct Server {
|
||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
fn url(&self, rhs: impl fmt::Display) -> String {
|
fn url(&self, rhs: impl fmt::Display) -> String {
|
||||||
format!("{}/_blah{}", self.domain(), rhs)
|
format!("http://{}/_blah{}", self.domain(), rhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn domain(&self) -> String {
|
fn domain(&self) -> String {
|
||||||
format!("http://{}:{}", LOCALHOST, self.port)
|
format!("{}:{}", LOCALHOST, self.port)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn connect_ws(
|
||||||
|
&self,
|
||||||
|
auth_user: Option<&User>,
|
||||||
|
) -> Result<impl Stream<Item = Result<WsEvent>> + Unpin> {
|
||||||
|
let url = format!("ws://{}/_blah/ws", self.domain());
|
||||||
|
let (mut ws, _) = tokio_tungstenite::connect_async(url).await.unwrap();
|
||||||
|
if let Some(user) = auth_user {
|
||||||
|
ws.send(tokio_tungstenite::tungstenite::Message::Text(auth(user)))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
Ok(ws
|
||||||
|
.map(|ret| {
|
||||||
|
let wsmsg = ret?;
|
||||||
|
if wsmsg.is_close() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let event = serde_json::from_slice::<WsEvent>(&wsmsg.into_data())?;
|
||||||
|
Ok(Some(event))
|
||||||
|
})
|
||||||
|
.filter_map(|ret| std::future::ready(ret.transpose())))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn request<Req: Serialize, Resp: DeserializeOwned>(
|
fn request<Req: Serialize, Resp: DeserializeOwned>(
|
||||||
|
@ -943,7 +978,7 @@ async fn register(server: Server) {
|
||||||
register_fast(&req)
|
register_fast(&req)
|
||||||
.await
|
.await
|
||||||
.expect_api_err(StatusCode::BAD_REQUEST, "invalid_server_url");
|
.expect_api_err(StatusCode::BAD_REQUEST, "invalid_server_url");
|
||||||
req.server_url = server.domain().parse().unwrap();
|
req.server_url = format!("http://{}", server.domain()).parse().unwrap();
|
||||||
|
|
||||||
register_fast(&req)
|
register_fast(&req)
|
||||||
.await
|
.await
|
||||||
|
@ -1078,3 +1113,72 @@ async fn register(server: Server) {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn event(server: Server) {
|
||||||
|
let rid1 = server
|
||||||
|
.create_room(&ALICE, RoomAttrs::PUBLIC_JOINABLE, "room1")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut ws = server.connect_ws(None).await.unwrap();
|
||||||
|
let msg = tokio::time::timeout(WS_CONNECT_TIMEOUT, ws.next())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert!(msg.is_none(), "auth should timeout");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut ws = server.connect_ws(Some(&CAROL)).await.unwrap();
|
||||||
|
assert!(
|
||||||
|
ws.next().await.is_none(),
|
||||||
|
"should close unauthorized connection",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ok.
|
||||||
|
let mut ws = server.connect_ws(Some(&ALICE)).await.unwrap();
|
||||||
|
// TODO: Synchronize with the server so that following msgs will be received.
|
||||||
|
|
||||||
|
// Should receive msgs from self-post.
|
||||||
|
{
|
||||||
|
let chat = server.post_chat(rid1, &ALICE, "alice1").await.unwrap();
|
||||||
|
let got = ws.next().await.unwrap().unwrap();
|
||||||
|
assert_eq!(got, WsEvent::Msg(chat.msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should receive msgs from other user.
|
||||||
|
{
|
||||||
|
server
|
||||||
|
.join_room(rid1, &BOB, MemberPermission::MAX_SELF_ADD)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let chat = server.post_chat(rid1, &BOB, "bob1").await.unwrap();
|
||||||
|
let got = ws.next().await.unwrap().unwrap();
|
||||||
|
assert_eq!(got, WsEvent::Msg(chat.msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should receive msgs from new room.
|
||||||
|
let rid2 = server
|
||||||
|
.create_room(&ALICE, RoomAttrs::PUBLIC_JOINABLE, "room2")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
{
|
||||||
|
let chat = server.post_chat(rid2, &ALICE, "alice2").await.unwrap();
|
||||||
|
let got = ws.next().await.unwrap().unwrap();
|
||||||
|
assert_eq!(got, WsEvent::Msg(chat.msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each streams should receive each message once.
|
||||||
|
{
|
||||||
|
let mut ws2 = server.connect_ws(Some(&ALICE)).await.unwrap();
|
||||||
|
|
||||||
|
let chat = server.post_chat(rid1, &ALICE, "alice1").await.unwrap();
|
||||||
|
let got1 = ws.next().await.unwrap().unwrap();
|
||||||
|
assert_eq!(got1, WsEvent::Msg(chat.msg.clone()));
|
||||||
|
let got2 = ws2.next().await.unwrap().unwrap();
|
||||||
|
assert_eq!(got2, WsEvent::Msg(chat.msg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue