From 848b6fbf78be47a4ca07a1b9375214c8005e209a Mon Sep 17 00:00:00 2001 From: Tyr Chen Date: Sat, 4 May 2024 12:08:13 -0700 Subject: [PATCH] feature: add integration test --- Cargo.lock | 266 ++++++++++++++++++++++++++- Cargo.toml | 4 +- chat_server/Cargo.toml | 10 +- chat_server/src/handlers/messages.rs | 4 +- chat_server/src/lib.rs | 10 +- chat_server/src/main.rs | 5 +- chat_test/Cargo.toml | 24 +++ chat_test/chat.yml | 13 ++ chat_test/notify.yml | 8 + chat_test/src/lib.rs | 1 + chat_test/tests/chat.rs | 200 ++++++++++++++++++++ notify_server/src/lib.rs | 10 +- notify_server/src/main.rs | 7 +- notify_server/src/notif.rs | 2 +- notify_server/src/sse.rs | 4 - 15 files changed, 531 insertions(+), 37 deletions(-) create mode 100644 chat_test/Cargo.toml create mode 100644 chat_test/chat.yml create mode 100644 chat_test/notify.yml create mode 100644 chat_test/src/lib.rs create mode 100644 chat_test/tests/chat.rs diff --git a/Cargo.lock b/Cargo.lock index 12a2c36..adb74cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -259,6 +259,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64ct" version = "1.6.0" @@ -452,6 +458,7 @@ dependencies = [ "axum", "axum-extra", "chat-core", + "chat-server", "chrono", "hex", "http-body-util", @@ -471,6 +478,23 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "chat_test" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum", + "chat-core", + "chat-server", + "futures", + "notify-server", + "reqwest", + "reqwest-eventsource", + "serde", + "serde_json", + "tokio", +] + [[package]] name = "chrono" version = "0.4.38" @@ -770,6 +794,17 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "eventsource-stream" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" +dependencies = [ + "futures-core", + "nom", + "pin-project-lite", +] + [[package]] name = "fastrand" version = "2.0.2" @@ -953,6 +988,12 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + [[package]] name = "futures-util" version = "0.3.30" @@ -1062,7 +1103,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" dependencies = [ - "base64", + "base64 0.21.7", "bytes", "headers-core", "http", @@ -1222,6 +1263,24 @@ dependencies = [ "pin-project-lite", "smallvec", "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls 0.22.4", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", ] [[package]] @@ -1231,6 +1290,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" dependencies = [ "bytes", + "futures-channel", "futures-util", "http", "http-body", @@ -1238,6 +1298,9 @@ dependencies = [ "pin-project-lite", "socket2", "tokio", + "tower", + "tower-service", + "tracing", ] [[package]] @@ -1283,6 +1346,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + [[package]] name = "itertools" version = "0.12.1" @@ -1898,6 +1967,66 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +[[package]] +name = "reqwest" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "mime_guess", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.22.4", + "rustls-pemfile 2.1.2", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "tokio", + "tokio-rustls", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots 0.26.1", + "winreg", +] + +[[package]] +name = "reqwest-eventsource" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632c55746dbb44275691640e7b40c907c16a2dc1a5842aa98aaec90da6ec6bde" +dependencies = [ + "eventsource-stream", + "futures-core", + "futures-timer", + "mime", + "nom", + "pin-project-lite", + "reqwest", + "thiserror", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -1976,19 +2105,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fecbfb7b1444f477b345853b1fce097a2c6fb637b2bfb87e6bc5db0f043fae4" dependencies = [ "ring", - "rustls-webpki", + "rustls-webpki 0.101.7", "sct", ] +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.3", + "subtle", + "zeroize", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64", + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", ] +[[package]] +name = "rustls-pki-types" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beb461507cee2c2ff151784c52762cf4d9ff6a61f3e80968600ed24fa837fa54" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -1999,6 +2158,17 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustls-webpki" +version = "0.102.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3bce581c0dd41bce533ce695a1437fa16a7ab5ac3ccfa99fe1a620a7885eabf" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.15" @@ -2256,8 +2426,8 @@ dependencies = [ "once_cell", "paste", "percent-encoding", - "rustls", - "rustls-pemfile", + "rustls 0.21.11", + "rustls-pemfile 1.0.4", "serde", "serde_json", "sha2", @@ -2268,7 +2438,7 @@ dependencies = [ "tokio-stream", "tracing", "url", - "webpki-roots", + "webpki-roots 0.25.4", ] [[package]] @@ -2331,7 +2501,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" dependencies = [ "atoi", - "base64", + "base64 0.21.7", "bitflags 2.5.0", "byteorder", "bytes", @@ -2374,7 +2544,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" dependencies = [ "atoi", - "base64", + "base64 0.21.7", "bitflags 2.5.0", "byteorder", "chrono", @@ -2579,6 +2749,17 @@ dependencies = [ "syn 2.0.60", ] +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.4", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.15" @@ -2722,6 +2903,12 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "typenum" version = "1.17.0" @@ -2828,6 +3015,15 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2874,6 +3070,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.92" @@ -2903,12 +3111,44 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +[[package]] +name = "wasm-streams" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webpki-roots" version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +[[package]] +name = "webpki-roots" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "whoami" version = "1.5.1" @@ -3089,6 +3329,16 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "zerocopy" version = "0.7.32" diff --git a/Cargo.toml b/Cargo.toml index bef23a4..e2b68ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["chat_server", "chat_core", "notify_server"] +members = ["chat_server", "chat_core", "notify_server", "chat_test"] resolver = "2" [workspace.dependencies] @@ -14,7 +14,9 @@ axum = { version = "0.7.5", features = [ axum-extra = { version = "0.9.3", features = ["typed-header"] } chrono = { version = "0.4.38", features = ["serde"] } chat-core = { path = "./chat_core" } +chat-server = { path = "./chat_server" } jwt-simple = "0.12.9" +notify-server = { path = "./notify_server" } serde = { version = "1.0.198", features = ["derive"] } serde_yaml = "0.9.34" sqlx = { version = "0.7.4", features = [ diff --git a/chat_server/Cargo.toml b/chat_server/Cargo.toml index 4964b8f..fd5eb52 100644 --- a/chat_server/Cargo.toml +++ b/chat_server/Cargo.toml @@ -3,7 +3,9 @@ name = "chat-server" version = "0.1.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +default = [] +test-util = ["http-body-util", "sqlx-db-tester"] [dependencies] anyhow = { workspace = true } @@ -13,6 +15,7 @@ axum-extra = { workspace = true } chrono = { workspace = true } chat-core = { workspace = true } hex = "0.4.3" +http-body-util = { version = "0.1.1", optional = true } jwt-simple = { workspace = true } mime_guess = "2.0.4" serde = { workspace = true } @@ -20,6 +23,7 @@ serde_json = "1.0.116" serde_yaml = { workspace = true } sha1 = "0.10.6" sqlx = { workspace = true } +sqlx-db-tester = { version = "0.4.2", optional = true } thiserror = { workspace = true } tokio = { workspace = true } tower = { workspace = true } @@ -27,7 +31,5 @@ tower-http = { workspace = true } tracing = { workspace = true } tracing-subscriber = { workspace = true } - [dev-dependencies] -http-body-util = "0.1.1" -sqlx-db-tester = "0.4.2" +chat-server = { workspace = true, features = ["test-util"] } diff --git a/chat_server/src/handlers/messages.rs b/chat_server/src/handlers/messages.rs index b384250..cff7b2f 100644 --- a/chat_server/src/handlers/messages.rs +++ b/chat_server/src/handlers/messages.rs @@ -1,6 +1,6 @@ use axum::{ extract::{Multipart, Path, Query, State}, - http::HeaderMap, + http::{HeaderMap, StatusCode}, response::IntoResponse, Extension, Json, }; @@ -18,7 +18,7 @@ pub(crate) async fn send_message_handler( ) -> Result { let msg = state.create_message(input, id, user.id as _).await?; - Ok(Json(msg)) + Ok((StatusCode::CREATED, Json(msg))) } pub(crate) async fn list_message_handler( diff --git a/chat_server/src/lib.rs b/chat_server/src/lib.rs index ff1b29d..c8e0b3f 100644 --- a/chat_server/src/lib.rs +++ b/chat_server/src/lib.rs @@ -27,21 +27,19 @@ use axum::{ pub use config::AppConfig; #[derive(Debug, Clone)] -pub(crate) struct AppState { +pub struct AppState { inner: Arc, } #[allow(unused)] -pub(crate) struct AppStateInner { +pub struct AppStateInner { pub(crate) config: AppConfig, pub(crate) dk: DecodingKey, pub(crate) ek: EncodingKey, pub(crate) pool: PgPool, } -pub async fn get_router(config: AppConfig) -> Result { - let state = AppState::try_new(config).await?; - +pub async fn get_router(state: AppState) -> Result { let chat = Router::new() .route( "/:id", @@ -118,7 +116,7 @@ impl fmt::Debug for AppStateInner { } } -#[cfg(test)] +#[cfg(feature = "test-util")] mod test_util { use super::*; use sqlx::{Executor, PgPool}; diff --git a/chat_server/src/main.rs b/chat_server/src/main.rs index d8e0ae6..d59ed04 100644 --- a/chat_server/src/main.rs +++ b/chat_server/src/main.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use chat_server::{get_router, AppConfig}; +use chat_server::{get_router, AppConfig, AppState}; use tokio::net::TcpListener; use tracing::{info, level_filters::LevelFilter}; use tracing_subscriber::{fmt::Layer, layer::SubscriberExt, util::SubscriberInitExt, Layer as _}; @@ -12,7 +12,8 @@ async fn main() -> Result<()> { let config = AppConfig::load()?; let addr = format!("0.0.0.0:{}", config.server.port); - let app = get_router(config).await?; + let state = AppState::try_new(config).await?; + let app = get_router(state).await?; let listener = TcpListener::bind(&addr).await?; info!("Listening on: {}", addr); diff --git a/chat_test/Cargo.toml b/chat_test/Cargo.toml new file mode 100644 index 0000000..82b936e --- /dev/null +++ b/chat_test/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "chat_test" +version = "0.1.0" +edition = "2021" + +[dev-dependencies] +anyhow = { workspace = true } +axum = { workspace = true } +chat-core = { workspace = true } +chat-server = { workspace = true, features = ["test-util"] } +notify-server = { workspace = true } +reqwest = { version = "0.12.4", default-features = false, features = [ + "rustls-tls", + "json", + "multipart", + "stream", +] } +reqwest-eventsource = "0.6.0" +serde = { workspace = true } +serde_json = "1.0.116" +tokio = { workspace = true } + +[dependencies] +futures = "0.3.30" diff --git a/chat_test/chat.yml b/chat_test/chat.yml new file mode 100644 index 0000000..fea32a5 --- /dev/null +++ b/chat_test/chat.yml @@ -0,0 +1,13 @@ +server: + port: 6688 + db_url: postgres://postgres:postgres@localhost:5432/chat + base_dir: /tmp/chat_server +auth: + sk: | + -----BEGIN PRIVATE KEY----- + MC4CAQAwBQYDK2VwBCIEIDnxJGEJGoW+mNKHn4vRY1V6BQ3MglSQSuZ8featmyC4 + -----END PRIVATE KEY----- + pk: | + -----BEGIN PUBLIC KEY----- + MCowBQYDK2VwAyEAfM+lwNHj6TRJ3EGP38lIJcOo9Dlt2u2JzcwWMbu7jQY= + -----END PUBLIC KEY----- diff --git a/chat_test/notify.yml b/chat_test/notify.yml new file mode 100644 index 0000000..decff5b --- /dev/null +++ b/chat_test/notify.yml @@ -0,0 +1,8 @@ +server: + port: 6687 + db_url: postgres://postgres:postgres@localhost:5432/chat +auth: + pk: | + -----BEGIN PUBLIC KEY----- + MCowBQYDK2VwAyEAfM+lwNHj6TRJ3EGP38lIJcOo9Dlt2u2JzcwWMbu7jQY= + -----END PUBLIC KEY----- diff --git a/chat_test/src/lib.rs b/chat_test/src/lib.rs new file mode 100644 index 0000000..8b1a393 --- /dev/null +++ b/chat_test/src/lib.rs @@ -0,0 +1 @@ +// empty diff --git a/chat_test/tests/chat.rs b/chat_test/tests/chat.rs new file mode 100644 index 0000000..833bc8a --- /dev/null +++ b/chat_test/tests/chat.rs @@ -0,0 +1,200 @@ +use anyhow::Result; +use chat_core::{Chat, ChatType, Message}; +use futures::StreamExt; +use reqwest::{ + multipart::{Form, Part}, + StatusCode, +}; +use reqwest_eventsource::{Event, EventSource}; +use serde::Deserialize; +use serde_json::json; +use std::{net::SocketAddr, time::Duration}; +use tokio::{net::TcpListener, time::sleep}; + +/* +test1: + name: user 1 create chat + steps: + - signin + email: tchen@acme.org + password: 123456 + - create_chat + name: test + members: [1, 2] + - create_message + chat_id: 1 + content: hello + files: [Cargo.toml] +*/ + +#[derive(Debug, Deserialize)] +struct AuthToken { + token: String, +} + +struct ChatServer { + addr: SocketAddr, + token: String, + client: reqwest::Client, +} + +struct NotifyServer; + +const WILD_ADDR: &str = "0.0.0.0:0"; + +#[tokio::test] +async fn chat_server_should_work() -> Result<()> { + let (tdb, state) = chat_server::AppState::new_for_test().await?; + let chat_server = ChatServer::new(state).await?; + let db_url = tdb.url(); + NotifyServer::new(&db_url, &chat_server.token).await?; + let chat = chat_server.create_chat().await?; + let _msg = chat_server.create_message(chat.id as u64).await?; + sleep(Duration::from_secs(1)).await; + Ok(()) +} + +impl NotifyServer { + async fn new(db_url: &str, token: &str) -> Result { + let mut config = notify_server::AppConfig::load()?; + config.server.db_url = db_url.to_string(); + let app = notify_server::get_router(config).await?; + let listener = TcpListener::bind(WILD_ADDR).await?; + let addr = listener.local_addr()?; + + tokio::spawn(async move { + axum::serve(listener, app.into_make_service()) + .await + .unwrap(); + }); + + let mut es = EventSource::get(format!("http://{}/events?access_token={}", addr, token)); + + tokio::spawn(async move { + while let Some(event) = es.next().await { + match event { + Ok(Event::Open) => println!("Connection Open!"), + Ok(Event::Message(message)) => match message.event.as_str() { + "NewChat" => { + let chat: Chat = serde_json::from_str(&message.data).unwrap(); + assert_eq!(chat.name.as_ref().unwrap(), "test"); + assert_eq!(chat.members, vec![1, 2]); + assert_eq!(chat.r#type, ChatType::PrivateChannel); + } + + "NewMessage" => { + let msg: Message = serde_json::from_str(&message.data).unwrap(); + assert_eq!(msg.content, "hello"); + assert_eq!(msg.files.len(), 1); + assert_eq!(msg.sender_id, 1); + } + _ => { + panic!("unexpected event: {:?}", message); + } + }, + Err(err) => { + println!("Error: {}", err); + es.close(); + } + } + } + }); + + Ok(Self) + } +} + +impl ChatServer { + async fn new(state: chat_server::AppState) -> Result { + let app = chat_server::get_router(state).await?; + let listener = TcpListener::bind(WILD_ADDR).await?; + let addr = listener.local_addr()?; + + tokio::spawn(async move { + axum::serve(listener, app.into_make_service()) + .await + .unwrap(); + }); + + let client = reqwest::Client::new(); + + let mut ret = Self { + addr, + client, + token: "".to_string(), + }; + + ret.token = ret.signin().await?; + + Ok(ret) + } + + async fn signin(&self) -> Result { + let res = self + .client + .post(&format!("http://{}/api/signin", self.addr)) + .header("Content-Type", "application/json") + .body(r#"{"email": "tchen@acme.org","password":"123456"}"#) + .send() + .await?; + + assert_eq!(res.status(), 200); + let ret: AuthToken = res.json().await?; + Ok(ret.token) + } + + async fn create_chat(&self) -> Result { + let res = self + .client + .post(format!("http://{}/api/chats", self.addr)) + .header("Authorization", format!("Bearer {}", self.token)) + .header("Content-Type", "application/json") + .body(r#"{"name": "test", "members": [1, 2], "public": false}"#); + let res = res.send().await?; + assert_eq!(res.status(), StatusCode::CREATED); + let chat: Chat = res.json().await?; + assert_eq!(chat.name.as_ref().unwrap(), "test"); + assert_eq!(chat.members, vec![1, 2]); + assert_eq!(chat.r#type, ChatType::PrivateChannel); + + Ok(chat) + } + + async fn create_message(&self, chat_id: u64) -> Result { + // upload file + let data = include_bytes!("../Cargo.toml"); + let files = Part::bytes(data) + .file_name("Cargo.toml") + .mime_str("text/plain")?; + let form = Form::new().part("file", files); + + let res = self + .client + .post(&format!("http://{}/api/upload", self.addr)) + .header("Authorization", format!("Bearer {}", self.token)) + .multipart(form) + .send() + .await?; + assert_eq!(res.status(), StatusCode::OK); + let ret: Vec = res.json().await?; + + let body = serde_json::to_string(&json!({ + "content": "hello", + "files": ret, + }))?; + let res = self + .client + .post(format!("http://{}/api/chats/{}", self.addr, chat_id)) + .header("Authorization", format!("Bearer {}", self.token)) + .header("Content-Type", "application/json") + .body(body); + let res = res.send().await?; + assert_eq!(res.status(), StatusCode::CREATED); + let message: Message = res.json().await?; + assert_eq!(message.content, "hello"); + assert_eq!(message.files, ret); + assert_eq!(message.sender_id, 1); + assert_eq!(message.chat_id, chat_id as i64); + Ok(message) + } +} diff --git a/notify_server/src/lib.rs b/notify_server/src/lib.rs index af4f8e2..f362a74 100644 --- a/notify_server/src/lib.rs +++ b/notify_server/src/lib.rs @@ -20,7 +20,7 @@ use tokio::sync::broadcast; pub use config::AppConfig; pub use error::AppError; -pub use notif::{setup_pg_listener, AppEvent}; +pub use notif::AppEvent; pub type UserMap = Arc>>>; @@ -35,16 +35,16 @@ pub struct AppStateInner { const INDEX_HTML: &str = include_str!("../index.html"); -pub fn get_router() -> (Router, AppState) { - let config = AppConfig::load().expect("Failed to load config"); +pub async fn get_router(config: AppConfig) -> anyhow::Result { let state = AppState::new(config); + notif::setup_pg_listener(state.clone()).await?; let app = Router::new() .route("/events", get(sse_handler)) .layer(from_fn_with_state(state.clone(), verify_token::)) .route("/", get(index_handler)) - .with_state(state.clone()); + .with_state(state); - (app, state) + Ok(app) } async fn index_handler() -> impl IntoResponse { diff --git a/notify_server/src/main.rs b/notify_server/src/main.rs index 9bbf686..06d9b4c 100644 --- a/notify_server/src/main.rs +++ b/notify_server/src/main.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use notify_server::{get_router, setup_pg_listener}; +use notify_server::{get_router, AppConfig}; use tokio::net::TcpListener; use tracing::{info, level_filters::LevelFilter}; use tracing_subscriber::{fmt::Layer, layer::SubscriberExt, util::SubscriberInitExt, Layer as _}; @@ -11,9 +11,8 @@ async fn main() -> Result<()> { let addr = "0.0.0.0:6687"; - let (app, state) = get_router(); - - setup_pg_listener(state).await?; + let config = AppConfig::load().expect("Failed to load config"); + let app = get_router(config).await?; let listener = TcpListener::bind(&addr).await?; info!("Listening on: {}", addr); diff --git a/notify_server/src/notif.rs b/notify_server/src/notif.rs index 6de4b5f..e3ebb44 100644 --- a/notify_server/src/notif.rs +++ b/notify_server/src/notif.rs @@ -8,7 +8,7 @@ use sqlx::postgres::PgListener; use tracing::{info, warn}; #[derive(Debug, Serialize, Deserialize)] -#[serde(tag = "type")] +#[serde(tag = "event")] pub enum AppEvent { NewChat(Chat), AddToChat(Chat), diff --git a/notify_server/src/sse.rs b/notify_server/src/sse.rs index 68ccc5b..382d525 100644 --- a/notify_server/src/sse.rs +++ b/notify_server/src/sse.rs @@ -4,7 +4,6 @@ use axum::{ response::{sse::Event, Sse}, Extension, }; -use axum_extra::{headers, TypedHeader}; use chat_core::User; use futures::Stream; use std::{convert::Infallible, time::Duration}; @@ -17,10 +16,7 @@ const CHANNEL_CAPACITY: usize = 256; pub(crate) async fn sse_handler( Extension(user): Extension, State(state): State, - TypedHeader(user_agent): TypedHeader, ) -> Sse>> { - info!("`{}` connected", user_agent.as_str()); - let user_id = user.id as u64; let users = &state.users;