diff --git a/Cargo.lock b/Cargo.lock index 2a10f0c..fed73ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -120,6 +120,8 @@ version = "0.1.0" dependencies = [ "axum", "axum-extra", + "axum-test", + "bytes", "chrono", "color-eyre", "dotenvy", @@ -131,7 +133,6 @@ dependencies = [ "sea-orm", "serde", "serde_json", - "serial_test", "tokio", "tokio-stream", "tower", @@ -424,6 +425,35 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-test" +version = "20.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a86bfe2ef15bee102ac34912f7f4542b0bb37dc464fa55461763999c4d625e7" +dependencies = [ + "anyhow", + "axum", + "bytes", + "bytesize", + "cookie", + "expect-json", + "http", + "http-body-util", + "hyper", + "hyper-util", + "mime", + "pretty_assertions", + "reqwest 0.13.2", + "reserve-port", + "rust-multipart-rfc7578_2", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tower", + "url", +] + [[package]] name = "backtrace" version = "0.3.76" @@ -572,6 +602,12 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +[[package]] +name = "bytesize" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bd91ee7b2422bcb158d90ef4d14f75ef67f340943fc4149891dcce8f8b972a3" + [[package]] name = "cc" version = "1.2.58" @@ -602,6 +638,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + [[package]] name = "chrono" version = "0.4.44" @@ -738,6 +785,35 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206" +dependencies = [ + "cookie", + "document-features", + "idna", + "log", + "publicsuffix", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -773,6 +849,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc" version = "3.4.0" @@ -890,6 +975,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.10.7" @@ -913,6 +1004,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + [[package]] name = "dotenvy" version = "0.15.7" @@ -934,6 +1034,15 @@ dependencies = [ "serde", ] +[[package]] +name = "email_address" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" +dependencies = [ + "serde", +] + [[package]] name = "embedded-hal" version = "1.0.0" @@ -955,6 +1064,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "erased-serde" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + [[package]] name = "errno" version = "0.3.14" @@ -1014,6 +1134,35 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "expect-json" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "869f97f4abe8e78fc812a94ad6b721d72c4fb5532877c79610f2c238d7ccf6c4" +dependencies = [ + "chrono", + "email_address", + "expect-json-macros", + "num", + "regex", + "serde", + "serde_json", + "thiserror 2.0.18", + "typetag", + "uuid", +] + +[[package]] +name = "expect-json-macros" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e6fdf550180a6c29a28cb9aac262dc0064c25735641d2317f670075e9a469d9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "eyre" version = "0.6.12" @@ -1259,6 +1408,7 @@ dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", + "rand_core 0.10.1", "wasip2", "wasip3", ] @@ -1708,6 +1858,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "inventory" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" +dependencies = [ + "rustversion", +] + [[package]] name = "ipnet" version = "2.12.0" @@ -1901,6 +2060,12 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + [[package]] name = "lock_api" version = "0.4.14" @@ -1990,6 +2155,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -2065,6 +2240,20 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -2091,6 +2280,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-conv" version = "0.2.1" @@ -2117,6 +2315,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -2388,6 +2597,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "prettyplease" version = "0.2.37" @@ -2451,6 +2670,12 @@ dependencies = [ "yansi", ] +[[package]] +name = "psl-types" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" + [[package]] name = "ptr_meta" version = "0.1.4" @@ -2471,6 +2696,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "publicsuffix" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf" +dependencies = [ + "idna", + "psl-types", +] + [[package]] name = "quinn" version = "0.11.9" @@ -2575,6 +2810,17 @@ dependencies = [ "rand_core 0.9.5", ] +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.1", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -2613,6 +2859,12 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "redox_syscall" version = "0.5.18" @@ -2720,6 +2972,8 @@ checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" dependencies = [ "base64", "bytes", + "cookie", + "cookie_store", "encoding_rs", "futures-core", "futures-util", @@ -2733,6 +2987,7 @@ dependencies = [ "js-sys", "log", "mime", + "mime_guess", "percent-encoding", "pin-project-lite", "quinn", @@ -2771,6 +3026,15 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "reserve-port" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94070964579245eb2f76e62a7668fe87bd9969ed6c41256f3bf614e3323dd3cc" +dependencies = [ + "thiserror 2.0.18", +] + [[package]] name = "ring" version = "0.17.14" @@ -2857,6 +3121,21 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rust-multipart-rfc7578_2" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00bdaa068902270ca7fa8619775e1838e23a63620abac0947ce0f715819b8cec" +dependencies = [ + "bytes", + "futures-core", + "futures-util", + "http", + "mime", + "rand 0.10.1", + "thiserror 2.0.18", +] + [[package]] name = "rust_decimal" version = "1.41.0" @@ -3004,15 +3283,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scc" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" -dependencies = [ - "sdd", -] - [[package]] name = "schannel" version = "0.1.29" @@ -3028,12 +3298,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sdd" -version = "3.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" - [[package]] name = "sea-bae" version = "0.2.1" @@ -3300,32 +3564,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serial_test" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "911bd979bf1070a3f3aa7b691a3b3e9968f339ceeec89e08c280a8a22207a32f" -dependencies = [ - "futures-executor", - "futures-util", - "log", - "once_cell", - "parking_lot", - "scc", - "serial_test_derive", -] - -[[package]] -name = "serial_test_derive" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a7d91949b85b0d2fb687445e448b40d322b6b3e4af6b44a29b21d9a5f33e6d9" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "sha1" version = "0.10.6" @@ -3333,7 +3571,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -3344,7 +3582,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -4105,6 +4343,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + [[package]] name = "typenum" version = "1.20.0" @@ -4121,6 +4365,30 @@ dependencies = [ "uuid", ] +[[package]] +name = "typetag" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be2212c8a9b9bcfca32024de14998494cf9a5dfa59ea1b829de98bac374b86bf" +dependencies = [ + "erased-serde", + "inventory", + "once_cell", + "serde", + "typetag-impl", +] + +[[package]] +name = "typetag-impl" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27a7a9b72ba121f6f1f6c3632b85604cac41aedb5ddc70accbebb6cac83de846" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "ufmt" version = "0.2.0" @@ -4148,6 +4416,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e87a2ed6b42ec5e28cc3b94c09982969e9227600b2e3dcbc1db927a84c06bd69" +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + [[package]] name = "unicode-bidi" version = "0.3.18" diff --git a/api/Cargo.toml b/api/Cargo.toml index eb27064..8cbcace 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -34,4 +34,5 @@ reqwest = { version = "0.13.2", features = ["json", "stream"] } jsonwebtoken = { version = "10.3.0", features = ["aws_lc_rs"] } [dev-dependencies] -serial_test = "3.4.0" +axum-test = { version = "20.0.0", features = ["reqwest"] } +bytes = "1.11.1" diff --git a/api/src/auth.rs b/api/src/auth.rs index b68a401..f324dab 100644 --- a/api/src/auth.rs +++ b/api/src/auth.rs @@ -25,8 +25,12 @@ impl FromRequestParts for User { async fn from_request_parts( parts: &mut Parts, - _state: &AppState, + state: &AppState, ) -> Result { + if let Some(user) = &state.test_user { + return Ok(user.clone()); + } + let TypedHeader(Authorization(bearer)) = parts .extract::>>() .await diff --git a/api/src/lib.rs b/api/src/lib.rs new file mode 100644 index 0000000..ecffefd --- /dev/null +++ b/api/src/lib.rs @@ -0,0 +1,67 @@ +use axum::Router; +use color_eyre::eyre; +use migration::{Migrator, MigratorTrait}; +use sea_orm::{ConnectionTrait, Database, DatabaseConnection}; +use tower::ServiceBuilder; +use tower_http::{ + cors::{Any, CorsLayer}, + trace::{self, TraceLayer}, +}; +use tracing::Level; + +use crate::{auth::User, services::events::EventService}; + +pub mod auth; +mod entities; +mod routes; +mod services; + +#[derive(Clone)] +struct AppState { + db_connection: DatabaseConnection, + event_service: EventService, + test_user: Option, +} + +pub async fn app(db_url: &str, test_user: Option) -> eyre::Result { + let state = AppState { + db_connection: init_db(db_url).await?, + event_service: EventService::new(), + test_user, + }; + + let app = Router::new() + .with_state(state.clone()) + .nest("/api", routes::routes(state.clone())) + .layer( + ServiceBuilder::new() + .layer( + TraceLayer::new_for_http() + .make_span_with(trace::DefaultMakeSpan::new().level(Level::INFO)) + .on_response(trace::DefaultOnResponse::new().level(Level::INFO)), + ) + .layer( + CorsLayer::new() + .allow_headers(Any) + .allow_origin(Any) + .allow_methods(Any), + ), + ); + + Ok(app) +} + +async fn init_db(url: &str) -> eyre::Result { + let connection = Database::connect(url) + .await + .expect("Database connection failed"); + + let connection = match connection.get_database_backend() { + sea_orm::DatabaseBackend::Sqlite => connection, + _ => panic!("Unsupported database backend"), + }; + + Migrator::up(&connection, None).await.unwrap(); + + Ok(connection) +} diff --git a/api/src/main.rs b/api/src/main.rs index 9f71acb..1bebcb2 100644 --- a/api/src/main.rs +++ b/api/src/main.rs @@ -1,28 +1,7 @@ -use axum::{Router, extract::State, routing::post}; +use api::app; use color_eyre::eyre; -use http::StatusCode; -use migration::{Migrator, MigratorTrait}; -use sea_orm::{ConnectionTrait, Database, DatabaseConnection}; use std::env; -use tower::ServiceBuilder; -use tower_http::{ - cors::{Any, CorsLayer}, - trace::{self, TraceLayer}, -}; -use tracing::{Level, info}; - -use crate::services::events::EventService; - -mod auth; -mod entities; -mod routes; -mod services; - -#[derive(Clone)] -struct AppState { - db_connection: DatabaseConnection, - event_service: EventService, -} +use tracing::Level; #[tokio::main] async fn main() -> eyre::Result<()> { @@ -33,31 +12,7 @@ async fn main() -> eyre::Result<()> { env::var("DATABASE_URL").unwrap_or("sqlite://../.data/data_db.sqlite?mode=rwc".to_string()); let port = env::var("PORT").unwrap_or("8080".to_string()); - let db_connection = init_db(&db_url).await?; - - let state = AppState { - db_connection, - event_service: EventService::new(), - }; - - let app = Router::new() - .route("/reset_db", post(reset_db)) - .with_state(state.clone()) - .nest("/api", routes::routes(state.clone())) - .layer( - ServiceBuilder::new() - .layer( - TraceLayer::new_for_http() - .make_span_with(trace::DefaultMakeSpan::new().level(Level::INFO)) - .on_response(trace::DefaultOnResponse::new().level(Level::INFO)), - ) - .layer( - CorsLayer::new() - .allow_headers(Any) - .allow_origin(Any) - .allow_methods(Any), - ), - ); + let app = app(&db_url, None).await?; let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{port}")) .await @@ -66,26 +21,3 @@ async fn main() -> eyre::Result<()> { Ok(()) } - -async fn init_db(url: &str) -> eyre::Result { - let connection = Database::connect(url) - .await - .expect("Database connection failed"); - - let connection = match connection.get_database_backend() { - sea_orm::DatabaseBackend::Sqlite => connection, - _ => panic!("Unsupported database backend"), - }; - - Migrator::up(&connection, None).await.unwrap(); - - Ok(connection) -} - -// TODO: Remove -async fn reset_db(state: State) -> (StatusCode, &'static str) { - let response = Migrator::fresh(&state.db_connection).await; - info!(?response); - - (StatusCode::OK, "failed to fetch tags") -} diff --git a/api/src/routes/categories.rs b/api/src/routes/categories.rs index d8cd6d5..877b077 100644 --- a/api/src/routes/categories.rs +++ b/api/src/routes/categories.rs @@ -124,7 +124,7 @@ async fn sse_handler( }; match event { - events::Event::Category(user_uuid) => user_uuid == &user_id, + events::Event::Category(user) => user == &user_id, _ => false, } }) @@ -147,7 +147,7 @@ async fn sse_handler( Sse::new(stream).keep_alive( axum::response::sse::KeepAlive::new() - .interval(Duration::from_secs(1)) + .interval(Duration::from_secs(5)) .text("keep-alive-text"), ) } diff --git a/api/src/routes/tags.rs b/api/src/routes/tags.rs index 4265559..6a17ac5 100644 --- a/api/src/routes/tags.rs +++ b/api/src/routes/tags.rs @@ -108,7 +108,7 @@ async fn sse_handler( }; match event { - events::Event::Tag(user_uuid) => user_uuid == &user_id, + events::Event::Tag(user) => user == &user_id, _ => false, } }) @@ -131,7 +131,7 @@ async fn sse_handler( Sse::new(stream).keep_alive( axum::response::sse::KeepAlive::new() - .interval(Duration::from_secs(1)) + .interval(Duration::from_secs(5)) .text("keep-alive-text"), ) } diff --git a/api/src/routes/todo.rs b/api/src/routes/todo.rs index 6feacb6..e7e6a6c 100644 --- a/api/src/routes/todo.rs +++ b/api/src/routes/todo.rs @@ -131,7 +131,7 @@ async fn sse_handler( }; match event { - events::Event::Todo(user_uuid) => user_uuid == &user_id, + events::Event::Todo(user) => user == &user_id, _ => false, } }) @@ -154,7 +154,7 @@ async fn sse_handler( Sse::new(stream).keep_alive( axum::response::sse::KeepAlive::new() - .interval(Duration::from_secs(1)) + .interval(Duration::from_secs(5)) .text("keep-alive-text"), ) } diff --git a/api/tests/categories/add.rs b/api/tests/categories/add.rs index 325e77b..e0fcd2a 100644 --- a/api/tests/categories/add.rs +++ b/api/tests/categories/add.rs @@ -1,14 +1,11 @@ -use crate::common::{client::get_client, db::clear_db}; +use crate::common::client::get_client; use serde_json::json; -use serial_test::serial; use types::{Category, HexColor}; use uuid::Uuid; #[tokio::test] -#[serial] async fn add_category() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let category_uuid = Uuid::new_v4(); let category = Category { @@ -29,10 +26,8 @@ async fn add_category() { )) .await; - assert_eq!(response.status().as_str(), "200"); - - let json = response.json::().await.unwrap(); - assert_eq!(json, category); + response.assert_status_success(); + response.assert_json(&category); let categories = client.get_categories_json().await; assert_eq!(categories.len(), 1); @@ -40,10 +35,8 @@ async fn add_category() { } #[tokio::test] -#[serial] async fn add_invalid_category() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let category_uuid = Uuid::new_v4(); let response = client @@ -55,7 +48,7 @@ async fn add_invalid_category() { } )) .await; - assert_eq!(response.status().as_str(), "422"); + response.assert_status_unprocessable_entity(); let response = client .add_category(json!( @@ -67,5 +60,5 @@ async fn add_invalid_category() { } )) .await; - assert_eq!(response.status().as_str(), "422"); + response.assert_status_unprocessable_entity(); } diff --git a/api/tests/categories/delete.rs b/api/tests/categories/delete.rs index ed65b2d..6ef1af7 100644 --- a/api/tests/categories/delete.rs +++ b/api/tests/categories/delete.rs @@ -1,14 +1,11 @@ -use crate::common::{client::get_client, db::clear_db}; +use crate::common::client::get_client; use serde_json::json; -use serial_test::serial; use types::Category; use uuid::Uuid; #[tokio::test] -#[serial] async fn delete_category() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let category_uuid = Uuid::new_v4(); client @@ -23,9 +20,8 @@ async fn delete_category() { let response = client.delete_category(category_uuid).await; - assert_eq!(response.status().as_str(), "200"); - let category: Category = response.json().await.unwrap(); - + response.assert_status_success(); + let category: Category = response.json(); assert_eq!(category.uuid, category_uuid); let categories = client.get_categories_json().await; @@ -33,13 +29,11 @@ async fn delete_category() { } #[tokio::test] -#[serial] async fn delete_unknown_category() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let category_uuid = Uuid::new_v4(); let response = client.delete_category(category_uuid).await; - assert_eq!(response.status().as_str(), "404"); + response.assert_status_not_found(); } diff --git a/api/tests/categories/get.rs b/api/tests/categories/get.rs index cee05b0..7cb9d1f 100644 --- a/api/tests/categories/get.rs +++ b/api/tests/categories/get.rs @@ -1,16 +1,13 @@ -use crate::common::{client::get_client, db::clear_db}; -use serial_test::serial; +use crate::common::client::get_client; use types::Category; #[tokio::test] -#[serial] async fn get_empty_categories() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let response = client.get_categories().await; - assert_eq!(response.status().as_str(), "200"); - let json = response.json::>().await.unwrap(); + response.assert_status_success(); + let json = response.json::>(); assert_eq!(json.len(), 0); } diff --git a/api/tests/categories/sse.rs b/api/tests/categories/sse.rs index 4f865c2..f6c0583 100644 --- a/api/tests/categories/sse.rs +++ b/api/tests/categories/sse.rs @@ -1,49 +1,52 @@ use std::time::Duration; -use crate::common::{client::get_client, db::clear_db}; +use crate::common::client::get_client; +use axum_test::TestServer; +use bytes::Bytes; +use futures::Stream; +use reqwest::Error; use serde_json::json; -use serial_test::serial; -use tokio::{task::JoinHandle, time::timeout}; +use tokio::time::timeout; use tokio_stream::StreamExt; use types::Category; use uuid::Uuid; -async fn get_categories() -> JoinHandle> { - let handle = tokio::spawn(async move { - let client = get_client(); - let sse_url = format!("{}/api/categories/sse", client.base_url); - - let response = client.api.get(sse_url).send().await.unwrap(); - let mut stream = response.bytes_stream(); +pub async fn connect_categories_sse( + server: &TestServer, +) -> impl Stream> { + let response = server + .reqwest_get("/api/categories/sse") + .send() + .await + .unwrap(); - let mut received = String::new(); - while let Some(chunk) = stream.next().await { - let chunk = chunk.expect("stream error"); - received.push_str(&String::from_utf8_lossy(&chunk)); + response.bytes_stream() +} - if received.contains("\n\n") { - break; - } - } +pub async fn read_first_event( + mut stream: impl Stream> + std::marker::Unpin, +) -> Vec { + let mut received = String::new(); - let cleaned_json = received.replace("data: ", "").trim().to_string(); - let categories: Vec = serde_json::from_str(&cleaned_json).unwrap(); + while let Some(chunk) = stream.next().await { + let chunk = chunk.expect("stream error"); + received.push_str(&String::from_utf8_lossy(&chunk)); - categories - }); + if received.contains("\n\n") { + break; + } + } - tokio::time::sleep(std::time::Duration::from_millis(100)).await; + let cleaned = received.replace("data: ", "").trim().to_string(); - handle + serde_json::from_str(&cleaned).unwrap() } #[tokio::test] -#[serial] async fn sends_events_on_add() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; - let handle = get_categories().await; + let stream = connect_categories_sse(&client.server).await; let category_uuid = Uuid::new_v4(); client @@ -57,9 +60,8 @@ async fn sends_events_on_add() { )) .await; - let categories = timeout(Duration::from_secs(2), handle) + let categories = timeout(Duration::from_secs(2), read_first_event(stream)) .await - .unwrap() .unwrap(); assert_eq!(categories.len(), 1); @@ -67,10 +69,8 @@ async fn sends_events_on_add() { } #[tokio::test] -#[serial] async fn sends_events_on_update() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let category_uuid = Uuid::new_v4(); client @@ -84,7 +84,7 @@ async fn sends_events_on_update() { )) .await; - let handle = get_categories().await; + let stream = connect_categories_sse(&client.server).await; client .add_category_json(json!({ @@ -95,9 +95,8 @@ async fn sends_events_on_update() { })) .await; - let categories = timeout(Duration::from_secs(2), handle) + let categories = timeout(Duration::from_secs(2), read_first_event(stream)) .await - .unwrap() .unwrap(); assert_eq!(categories.len(), 1); assert_eq!(categories.first().unwrap().uuid, category_uuid); @@ -105,10 +104,8 @@ async fn sends_events_on_update() { } #[tokio::test] -#[serial] async fn sends_events_on_remove() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let category_uuid = Uuid::new_v4(); client @@ -122,13 +119,12 @@ async fn sends_events_on_remove() { )) .await; - let handle = get_categories().await; + let stream = connect_categories_sse(&client.server).await; client.delete_category_json(category_uuid).await; - let categories = timeout(Duration::from_secs(2), handle) + let categories = timeout(Duration::from_secs(2), read_first_event(stream)) .await - .unwrap() .unwrap(); assert_eq!(categories.len(), 0); diff --git a/api/tests/categories/update.rs b/api/tests/categories/update.rs index 2305a49..d9e51da 100644 --- a/api/tests/categories/update.rs +++ b/api/tests/categories/update.rs @@ -1,14 +1,11 @@ -use crate::common::{client::get_client, db::clear_db}; +use crate::common::client::get_client; use serde_json::json; -use serial_test::serial; use types::HexColor; use uuid::Uuid; #[tokio::test] -#[serial] async fn update_category() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let category_uuid = Uuid::new_v4(); client @@ -42,10 +39,8 @@ async fn update_category() { } #[tokio::test] -#[serial] async fn update_invalid_category() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let category_uuid = Uuid::new_v4(); client @@ -68,7 +63,7 @@ async fn update_invalid_category() { } )) .await; - assert_eq!(response.status().as_str(), "422"); + response.assert_status_unprocessable_entity(); let categories = client.get_categories_json().await; assert_eq!(categories.len(), 1); diff --git a/api/tests/common/client.rs b/api/tests/common/client.rs index 7e899df..1f2eb85 100644 --- a/api/tests/common/client.rs +++ b/api/tests/common/client.rs @@ -1,156 +1,134 @@ #![allow(unused)] -use reqwest::{Response, header}; +use api::{app, auth::User}; +use axum_test::{TestResponse, TestServer}; use serde::de::DeserializeOwned; use types::{Category, Tag, Todo}; use uuid::Uuid; pub struct Client { - pub api: reqwest::Client, - pub base_url: String, + pub server: TestServer, } impl Client { - async fn get(&self, path: &str) -> Response { - self.api - .get(&format!("{}/api/{}", self.base_url, path)) - .send() - .await - .unwrap() + async fn get(&self, path: &str) -> TestResponse { + self.server.get(&format!("/api/{}", path)).await } - async fn post(&self, path: &str) -> Response { - self.api - .post(&format!("{}/api/{}", self.base_url, path)) - .send() - .await - .unwrap() + async fn post(&self, path: &str) -> TestResponse { + self.server.post(&format!("/api/{}", path)).await } - async fn post_json(&self, path: &str, body: serde_json::Value) -> Response { - self.api - .post(&format!("{}/api/{}", self.base_url, path)) + async fn post_json(&self, path: &str, body: serde_json::Value) -> TestResponse { + self.server + .post(&format!("/api/{}", path)) .json(&body) - .send() .await - .unwrap() } - async fn delete(&self, path: &str) -> Response { - self.api - .delete(&format!("{}/api/{}", self.base_url, path)) - .send() - .await - .unwrap() + async fn delete(&self, path: &str) -> TestResponse { + self.server.delete(&format!("/api/{}", path)).await } - async fn json(response: Response) -> T { - assert_eq!(response.status().as_str(), "200"); - response.json().await.unwrap() + fn json(response: TestResponse) -> T { + response.assert_status_success(); + response.json() } - pub async fn get_todos(&self) -> Response { + pub async fn get_todos(&self) -> TestResponse { self.get("todos").await } pub async fn get_todos_json(&self) -> Vec { - Self::json(self.get_todos().await).await + Self::json(self.get_todos().await) } - pub async fn add_todo(&self, todo: serde_json::Value) -> Response { + pub async fn add_todo(&self, todo: serde_json::Value) -> TestResponse { self.post_json("todos", todo).await } pub async fn add_todo_json(&self, todo: serde_json::Value) -> Todo { - Self::json(self.add_todo(todo).await).await + Self::json(self.add_todo(todo).await) } - pub async fn delete_todo(&self, todo_uuid: Uuid) -> Response { + pub async fn delete_todo(&self, todo_uuid: Uuid) -> TestResponse { self.delete(&format!("todos/{}", todo_uuid)).await } pub async fn delete_todo_json(&self, todo_uuid: Uuid) -> Todo { - Self::json(self.delete_todo(todo_uuid).await).await + Self::json(self.delete_todo(todo_uuid).await) } - pub async fn check_todo(&self, todo_uuid: Uuid) -> Response { + pub async fn check_todo(&self, todo_uuid: Uuid) -> TestResponse { self.post(&format!("todos/check/{}", todo_uuid)).await } - pub async fn remove_check_todo(&self, todo_uuid: Uuid) -> Response { + pub async fn remove_check_todo(&self, todo_uuid: Uuid) -> TestResponse { self.delete(&format!("todos/check/{}", todo_uuid)).await } - pub async fn get_tags(&self) -> Response { + pub async fn get_tags(&self) -> TestResponse { self.get("tags").await } pub async fn get_tags_json(&self) -> Vec { - Self::json(self.get_tags().await).await + Self::json(self.get_tags().await) } - pub async fn add_tag(&self, tag: serde_json::Value) -> Response { + pub async fn add_tag(&self, tag: serde_json::Value) -> TestResponse { self.post_json("tags", tag).await } pub async fn add_tag_json(&self, tag: serde_json::Value) -> Tag { - Self::json(self.add_tag(tag).await).await + Self::json(self.add_tag(tag).await) } - pub async fn delete_tag(&self, tag_uuid: Uuid) -> Response { + pub async fn delete_tag(&self, tag_uuid: Uuid) -> TestResponse { self.delete(&format!("tags/{}", tag_uuid)).await } pub async fn delete_tag_json(&self, tag_uuid: Uuid) -> Tag { - Self::json(self.delete_tag(tag_uuid).await).await + Self::json(self.delete_tag(tag_uuid).await) } - pub async fn get_categories(&self) -> Response { + pub async fn get_categories(&self) -> TestResponse { self.get("categories").await } pub async fn get_categories_json(&self) -> Vec { - Self::json(self.get_categories().await).await + Self::json(self.get_categories().await) } - pub async fn add_category(&self, category: serde_json::Value) -> Response { + pub async fn add_category(&self, category: serde_json::Value) -> TestResponse { self.post_json("categories", category).await } pub async fn add_category_json(&self, category: serde_json::Value) -> Category { - Self::json(self.add_category(category).await).await + Self::json(self.add_category(category).await) } - pub async fn delete_category(&self, category_uuid: Uuid) -> Response { + pub async fn delete_category(&self, category_uuid: Uuid) -> TestResponse { self.delete(&format!("categories/{}", category_uuid)).await } pub async fn delete_category_json(&self, category_uuid: Uuid) -> Category { - Self::json(self.delete_category(category_uuid).await).await + Self::json(self.delete_category(category_uuid).await) } - pub async fn move_todo(&self, value: serde_json::Value) -> Response { + pub async fn move_todo(&self, value: serde_json::Value) -> TestResponse { self.post_json("todos/move", value).await } } -pub fn get_client() -> Client { - dotenvy::dotenv().unwrap(); - let token = std::env::var("API_TOKEN").expect("no API_TOKEN set"); - let base_url = std::env::var("BASE_URL").expect("no BASE_URL set"); - - let mut headers = header::HeaderMap::new(); - headers.insert( - "Authorization", - header::HeaderValue::from_str(&format!("Bearer {}", token)).unwrap(), - ); +pub async fn get_client() -> Client { + let user = User { + id: Uuid::new_v4().to_string(), + }; - let client = reqwest::ClientBuilder::new() - .default_headers(headers) - .build() + let app = app("sqlite://../.data/data_db.sqlite?mode=rwc", Some(user)) + .await .unwrap(); Client { - api: client, - base_url, + server: TestServer::builder().http_transport().build(app), } } diff --git a/api/tests/common/db.rs b/api/tests/common/db.rs deleted file mode 100644 index 3243535..0000000 --- a/api/tests/common/db.rs +++ /dev/null @@ -1,26 +0,0 @@ -use std::path::PathBuf; - -use tokio::fs; - -use crate::common::client::Client; - -pub async fn clear_db(client: &Client) { - let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../.data"); - - if path.join("categories").exists() { - fs::remove_dir_all(path.join("categories")).await.unwrap(); - } - if path.join("tags").exists() { - fs::remove_dir_all(path.join("tags")).await.unwrap(); - } - if path.join("todos").exists() { - fs::remove_dir_all(path.join("todos")).await.unwrap(); - } - - client - .api - .post(&format!("{}/reset_db", client.base_url)) - .send() - .await - .unwrap(); -} diff --git a/api/tests/common/mod.rs b/api/tests/common/mod.rs index 0c69e9b..b9babe5 100644 --- a/api/tests/common/mod.rs +++ b/api/tests/common/mod.rs @@ -1,2 +1 @@ pub mod client; -pub mod db; diff --git a/api/tests/tags/add.rs b/api/tests/tags/add.rs index dcd23b4..7737ff3 100644 --- a/api/tests/tags/add.rs +++ b/api/tests/tags/add.rs @@ -1,14 +1,11 @@ -use crate::common::{client::get_client, db::clear_db}; +use crate::common::client::get_client; use serde_json::json; -use serial_test::serial; use types::{HexColor, Tag}; use uuid::Uuid; #[tokio::test] -#[serial] async fn add_tag() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let tag_uuid = Uuid::new_v4(); let tag = Tag { @@ -27,10 +24,9 @@ async fn add_tag() { )) .await; - assert_eq!(response.status().as_str(), "200"); + response.assert_status_success(); - let json = response.json::().await.unwrap(); - assert_eq!(json, tag); + response.assert_json(&tag); let tags = client.get_tags_json().await; assert_eq!(tags.len(), 1); @@ -38,10 +34,8 @@ async fn add_tag() { } #[tokio::test] -#[serial] async fn add_invalid_tag() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let tag_uuid = Uuid::new_v4(); let response = client @@ -52,7 +46,7 @@ async fn add_invalid_tag() { } )) .await; - assert_eq!(response.status().as_str(), "422"); + response.assert_status_unprocessable_entity(); let response = client .add_tag(json!( @@ -63,5 +57,5 @@ async fn add_invalid_tag() { } )) .await; - assert_eq!(response.status().as_str(), "422"); + response.assert_status_unprocessable_entity(); } diff --git a/api/tests/tags/delete.rs b/api/tests/tags/delete.rs index 8cdccbf..3d43d99 100644 --- a/api/tests/tags/delete.rs +++ b/api/tests/tags/delete.rs @@ -1,14 +1,11 @@ -use crate::common::{client::get_client, db::clear_db}; +use crate::common::client::get_client; use serde_json::json; -use serial_test::serial; use types::Tag; use uuid::Uuid; #[tokio::test] -#[serial] async fn delete_tag() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let tag_uuid = Uuid::new_v4(); client @@ -22,9 +19,9 @@ async fn delete_tag() { let response = client.delete_tag(tag_uuid).await; - assert_eq!(response.status().as_str(), "200"); - let tag: Tag = response.json().await.unwrap(); + response.assert_status_success(); + let tag: Tag = response.json(); assert_eq!(tag.uuid, tag_uuid); let tags = client.get_tags_json().await; @@ -32,13 +29,11 @@ async fn delete_tag() { } #[tokio::test] -#[serial] async fn delete_unknown_tag() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let tag_uuid = Uuid::new_v4(); let response = client.delete_tag(tag_uuid).await; - assert_eq!(response.status().as_str(), "404"); + response.assert_status_not_found(); } diff --git a/api/tests/tags/get.rs b/api/tests/tags/get.rs index 3925458..7c77b5e 100644 --- a/api/tests/tags/get.rs +++ b/api/tests/tags/get.rs @@ -1,16 +1,13 @@ -use crate::common::{client::get_client, db::clear_db}; -use serial_test::serial; +use crate::common::client::get_client; use types::Tag; #[tokio::test] -#[serial] async fn get_empty_tags() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let response = client.get_tags().await; - assert_eq!(response.status().as_str(), "200"); - let json = response.json::>().await.unwrap(); + response.assert_status_success(); + let json = response.json::>(); assert_eq!(json.len(), 0); } diff --git a/api/tests/tags/sse.rs b/api/tests/tags/sse.rs index cfeb93d..1493739 100644 --- a/api/tests/tags/sse.rs +++ b/api/tests/tags/sse.rs @@ -1,49 +1,47 @@ use std::time::Duration; -use crate::common::{client::get_client, db::clear_db}; +use crate::common::client::get_client; +use bytes::Bytes; +use futures::Stream; use serde_json::json; -use serial_test::serial; -use tokio::{task::JoinHandle, time::timeout}; +use tokio::time::timeout; use tokio_stream::StreamExt; use types::Tag; use uuid::Uuid; -async fn get_tags() -> JoinHandle> { - let handle = tokio::spawn(async move { - let client = get_client(); - let sse_url = format!("{}/api/tags/sse", client.base_url); +use axum_test::TestServer; +use reqwest::Error; - let response = client.api.get(sse_url).send().await.unwrap(); - let mut stream = response.bytes_stream(); +pub async fn connect_tags_sse(server: &TestServer) -> impl Stream> { + let response = server.reqwest_get("/api/tags/sse").send().await.unwrap(); - let mut received = String::new(); - while let Some(chunk) = stream.next().await { - let chunk = chunk.expect("stream error"); - received.push_str(&String::from_utf8_lossy(&chunk)); + response.bytes_stream() +} - if received.contains("\n\n") { - break; - } - } +pub async fn read_first_event( + mut stream: impl Stream> + std::marker::Unpin, +) -> Vec { + let mut received = String::new(); - let cleaned_json = received.replace("data: ", "").trim().to_string(); - let todos: Vec = serde_json::from_str(&cleaned_json).unwrap(); + while let Some(chunk) = stream.next().await { + let chunk = chunk.expect("stream error"); + received.push_str(&String::from_utf8_lossy(&chunk)); - todos - }); + if received.contains("\n\n") { + break; + } + } - tokio::time::sleep(std::time::Duration::from_millis(100)).await; + let cleaned = received.replace("data: ", "").trim().to_string(); - handle + serde_json::from_str(&cleaned).unwrap() } #[tokio::test] -#[serial] async fn sends_events_on_add() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; - let handle = get_tags().await; + let stream = connect_tags_sse(&client.server).await; let tag_uuid = Uuid::new_v4(); client @@ -56,9 +54,8 @@ async fn sends_events_on_add() { )) .await; - let tags = timeout(Duration::from_secs(2), handle) + let tags = timeout(Duration::from_secs(2), read_first_event(stream)) .await - .unwrap() .unwrap(); assert_eq!(tags.len(), 1); @@ -66,10 +63,8 @@ async fn sends_events_on_add() { } #[tokio::test] -#[serial] async fn sends_events_on_update() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let tag_uuid = Uuid::new_v4(); client @@ -82,7 +77,7 @@ async fn sends_events_on_update() { )) .await; - let handle = get_tags().await; + let stream = connect_tags_sse(&client.server).await; client .add_tag_json(json!({ @@ -92,9 +87,8 @@ async fn sends_events_on_update() { })) .await; - let tags = timeout(Duration::from_secs(2), handle) + let tags = timeout(Duration::from_secs(2), read_first_event(stream)) .await - .unwrap() .unwrap(); assert_eq!(tags.len(), 1); @@ -103,10 +97,8 @@ async fn sends_events_on_update() { } #[tokio::test] -#[serial] async fn sends_events_on_remove() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let tag_uuid = Uuid::new_v4(); client @@ -119,13 +111,12 @@ async fn sends_events_on_remove() { )) .await; - let handle = get_tags().await; + let stream = connect_tags_sse(&client.server).await; client.delete_tag_json(tag_uuid).await; - let tags = timeout(Duration::from_secs(2), handle) + let tags = timeout(Duration::from_secs(2), read_first_event(stream)) .await - .unwrap() .unwrap(); assert_eq!(tags.len(), 0); diff --git a/api/tests/tags/update.rs b/api/tests/tags/update.rs index 520bd0f..b90b448 100644 --- a/api/tests/tags/update.rs +++ b/api/tests/tags/update.rs @@ -1,14 +1,11 @@ -use crate::common::{client::get_client, db::clear_db}; +use crate::common::client::get_client; use serde_json::json; -use serial_test::serial; use types::HexColor; use uuid::Uuid; #[tokio::test] -#[serial] async fn update_tag() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let tag_uuid = Uuid::new_v4(); client @@ -39,10 +36,8 @@ async fn update_tag() { } #[tokio::test] -#[serial] async fn update_invalid_tag() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let tag_uuid = Uuid::new_v4(); client @@ -63,7 +58,7 @@ async fn update_invalid_tag() { } )) .await; - assert_eq!(response.status().as_str(), "422"); + response.assert_status_unprocessable_entity(); let tags = client.get_tags_json().await; assert_eq!(tags.len(), 1); diff --git a/api/tests/todos/add.rs b/api/tests/todos/add.rs index 422b3e1..2a7234a 100644 --- a/api/tests/todos/add.rs +++ b/api/tests/todos/add.rs @@ -1,15 +1,12 @@ -use crate::common::{client::get_client, db::clear_db}; +use crate::common::client::get_client; use chrono::{DateTime, NaiveDate, NaiveTime, Utc}; use serde_json::json; -use serial_test::serial; use types::{TimePoint, TimeRange, TimeRecurring, Todo}; use uuid::Uuid; #[tokio::test] -#[serial] async fn add_todo() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo_uuid = Uuid::new_v4(); let todo = Todo { @@ -35,10 +32,9 @@ async fn add_todo() { })) .await; - assert_eq!(response.status().as_str(), "200"); + response.assert_status_success(); - let json = response.json::().await.unwrap(); - assert_eq!(json, todo); + response.assert_json(&todo); let todos = client.get_todos_json().await; assert_eq!(todos.len(), 1); @@ -46,10 +42,8 @@ async fn add_todo() { } #[tokio::test] -#[serial] async fn add_todo_with_time_point() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo_uuid = Uuid::new_v4(); let todo = Todo { @@ -88,10 +82,8 @@ async fn add_todo_with_time_point() { })) .await; - assert_eq!(response.status().as_str(), "200"); - - let json = response.json::().await.unwrap(); - assert_eq!(json, todo); + response.assert_status_success(); + response.assert_json(&todo); let todos = client.get_todos_json().await; assert_eq!(todos.len(), 1); @@ -99,10 +91,8 @@ async fn add_todo_with_time_point() { } #[tokio::test] -#[serial] async fn add_todo_with_time_range() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo_uuid = Uuid::new_v4(); let todo = Todo { @@ -138,10 +128,8 @@ async fn add_todo_with_time_range() { })) .await; - assert_eq!(response.status().as_str(), "200"); - - let json = response.json::().await.unwrap(); - assert_eq!(json, todo); + response.assert_status_success(); + response.assert_json(&todo); let todos = client.get_todos_json().await; assert_eq!(todos.len(), 1); @@ -149,10 +137,8 @@ async fn add_todo_with_time_range() { } #[tokio::test] -#[serial] async fn add_todo_with_time_recurring_weekly() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo_uuid = Uuid::new_v4(); let todo = Todo { @@ -186,10 +172,8 @@ async fn add_todo_with_time_recurring_weekly() { })) .await; - assert_eq!(response.status().as_str(), "200"); - - let json = response.json::().await.unwrap(); - assert_eq!(json, todo); + response.assert_status_success(); + response.assert_json(&todo); let todos = client.get_todos_json().await; assert_eq!(todos.len(), 1); @@ -197,10 +181,8 @@ async fn add_todo_with_time_recurring_weekly() { } #[tokio::test] -#[serial] async fn add_todo_with_time_recurring_daily() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo_uuid = Uuid::new_v4(); let todo = Todo { @@ -232,8 +214,7 @@ async fn add_todo_with_time_recurring_daily() { })) .await; - let json = response.json::().await.unwrap(); - assert_eq!(json, todo); + response.assert_json(&todo); let todos = client.get_todos_json().await; assert_eq!(todos.len(), 1); @@ -241,10 +222,8 @@ async fn add_todo_with_time_recurring_daily() { } #[tokio::test] -#[serial] async fn add_invalid_todo() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo_uuid = Uuid::new_v4(); let response = client @@ -257,7 +236,7 @@ async fn add_invalid_todo() { } })) .await; - assert_eq!(response.status().as_str(), "422"); + response.assert_status_unprocessable_entity(); let response = client .add_todo(json!( @@ -268,7 +247,7 @@ async fn add_invalid_todo() { } )) .await; - assert_eq!(response.status().as_str(), "422"); + response.assert_status_unprocessable_entity(); let response = client .add_todo(json!( @@ -282,17 +261,15 @@ async fn add_invalid_todo() { } })) .await; - assert_eq!(response.status().as_str(), "422"); + response.assert_status_unprocessable_entity(); let todos = client.get_todos_json().await; assert_eq!(todos.len(), 0); } #[tokio::test] -#[serial] async fn add_todo_with_labels() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo_uuid = Uuid::new_v4(); let tag_uuid = Uuid::new_v4(); @@ -348,14 +325,12 @@ async fn add_todo_with_labels() { let todos = client.get_todos_json().await; assert_eq!(todos.len(), 1); - assert_eq!(todos[1], todo); + assert_eq!(todos[0], todo); } #[tokio::test] -#[serial] async fn add_mutliple_todos() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo1_uuid = Uuid::new_v4(); let todo2_uuid = Uuid::new_v4(); @@ -410,10 +385,8 @@ async fn add_mutliple_todos() { } #[tokio::test] -#[serial] async fn add_mutliple_bottom_todos() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo1_uuid = Uuid::new_v4(); let todo2_uuid = Uuid::new_v4(); @@ -471,10 +444,8 @@ async fn add_mutliple_bottom_todos() { } #[tokio::test] -#[serial] async fn add_mutliple_top_todos() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo1_uuid = Uuid::new_v4(); let todo2_uuid = Uuid::new_v4(); @@ -532,10 +503,8 @@ async fn add_mutliple_top_todos() { } #[tokio::test] -#[serial] async fn add_mutliple_mixed_todos() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo1_uuid = Uuid::new_v4(); let todo2_uuid = Uuid::new_v4(); @@ -592,10 +561,8 @@ async fn add_mutliple_mixed_todos() { } #[tokio::test] -#[serial] async fn add_mutliple_referencing_todos() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo1_uuid = Uuid::new_v4(); let todo2_uuid = Uuid::new_v4(); diff --git a/api/tests/todos/check.rs b/api/tests/todos/check.rs index 1cfa5ec..ab53e91 100644 --- a/api/tests/todos/check.rs +++ b/api/tests/todos/check.rs @@ -1,14 +1,11 @@ -use crate::common::{client::get_client, db::clear_db}; +use crate::common::client::get_client; use serde_json::json; -use serial_test::serial; use types::Todo; use uuid::Uuid; #[tokio::test] -#[serial] async fn check_todo() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo_uuid = Uuid::new_v4(); client @@ -26,8 +23,8 @@ async fn check_todo() { let response = client.check_todo(todo_uuid).await; - assert_eq!(response.status().as_str(), "200"); - let todo: Todo = response.json().await.unwrap(); + response.assert_status_success(); + let todo: Todo = response.json(); assert_eq!(todo.uuid, todo_uuid); assert_eq!(todo.checks.len(), 1); @@ -39,22 +36,18 @@ async fn check_todo() { } #[tokio::test] -#[serial] async fn check_unknown_todo() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo_uuid = Uuid::new_v4(); let response = client.check_todo(todo_uuid).await; - assert_eq!(response.status().as_str(), "404"); + response.assert_status_not_found(); } #[tokio::test] -#[serial] async fn remove_check_todo() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo_uuid = Uuid::new_v4(); client @@ -72,29 +65,27 @@ async fn remove_check_todo() { let response = client.check_todo(todo_uuid).await; - assert_eq!(response.status().as_str(), "200"); - let todo: Todo = response.json().await.unwrap(); + response.assert_status_success(); + let todo: Todo = response.json(); assert_eq!(todo.uuid, todo_uuid); assert_eq!(todo.checks.len(), 1); let response = client.remove_check_todo(todo_uuid).await; - assert_eq!(response.status().as_str(), "200"); - let todo: Todo = response.json().await.unwrap(); + response.assert_status_success(); + let todo: Todo = response.json(); assert_eq!(todo.uuid, todo_uuid); assert_eq!(todo.checks.len(), 0); } #[tokio::test] -#[serial] async fn remove_check_unknown_todo() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo_uuid = Uuid::new_v4(); let response = client.remove_check_todo(todo_uuid).await; - assert_eq!(response.status().as_str(), "404"); + response.assert_status_not_found(); } diff --git a/api/tests/todos/delete.rs b/api/tests/todos/delete.rs index 053f1e6..1bf3d37 100644 --- a/api/tests/todos/delete.rs +++ b/api/tests/todos/delete.rs @@ -1,14 +1,11 @@ -use crate::common::{client::get_client, db::clear_db}; +use crate::common::client::get_client; use serde_json::json; -use serial_test::serial; use types::Todo; use uuid::Uuid; #[tokio::test] -#[serial] async fn delete_todo() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo_uuid = Uuid::new_v4(); client @@ -26,9 +23,9 @@ async fn delete_todo() { let response = client.delete_todo(todo_uuid).await; - assert_eq!(response.status().as_str(), "200"); - let todo: Todo = response.json().await.unwrap(); + response.assert_status_success(); + let todo: Todo = response.json(); assert_eq!(todo.uuid, todo_uuid); let todos = client.get_todos_json().await; @@ -36,13 +33,11 @@ async fn delete_todo() { } #[tokio::test] -#[serial] async fn delete_unknown_todo() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo_uuid = Uuid::new_v4(); let response = client.delete_todo(todo_uuid).await; - assert_eq!(response.status().as_str(), "404"); + response.assert_status_not_found(); } diff --git a/api/tests/todos/get.rs b/api/tests/todos/get.rs index 2c43b48..5bd894a 100644 --- a/api/tests/todos/get.rs +++ b/api/tests/todos/get.rs @@ -1,16 +1,13 @@ -use crate::common::{client::get_client, db::clear_db}; -use serial_test::serial; +use crate::common::client::get_client; use types::Todo; #[tokio::test] -#[serial] async fn get_empty_todos() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let response = client.get_todos().await; - assert_eq!(response.status().as_str(), "200"); - let json = response.json::>().await.unwrap(); + response.assert_status_success(); + let json = response.json::>(); assert_eq!(json.len(), 0); } diff --git a/api/tests/todos/postion.rs b/api/tests/todos/postion.rs index 025fb37..4ec9646 100644 --- a/api/tests/todos/postion.rs +++ b/api/tests/todos/postion.rs @@ -1,13 +1,10 @@ -use crate::common::{client::get_client, db::clear_db}; +use crate::common::client::get_client; use serde_json::json; -use serial_test::serial; use uuid::Uuid; #[tokio::test] -#[serial] async fn move_todo() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo1_uuid = Uuid::new_v4(); let todo2_uuid = Uuid::new_v4(); @@ -85,10 +82,8 @@ async fn move_todo() { } #[tokio::test] -#[serial] async fn invalid_move_todo() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo1_uuid = Uuid::new_v4(); let todo2_uuid = Uuid::new_v4(); @@ -139,8 +134,8 @@ async fn invalid_move_todo() { let response = client .move_todo(json!({ "toMove": unknown_uuid, "to": todo3_uuid })) .await; - assert_eq!(response.status().as_str(), "404"); + response.assert_status_not_found(); let response = client.move_todo(json!({ "toMove": "unknown" })).await; - assert_eq!(response.status().as_str(), "422"); + response.assert_status_unprocessable_entity(); } diff --git a/api/tests/todos/sse.rs b/api/tests/todos/sse.rs index cb82a95..8d545c3 100644 --- a/api/tests/todos/sse.rs +++ b/api/tests/todos/sse.rs @@ -1,49 +1,46 @@ use std::time::Duration; -use crate::common::{client::get_client, db::clear_db}; +use crate::common::client::get_client; +use axum_test::TestServer; +use bytes::Bytes; +use futures::Stream; +use reqwest::Error; use serde_json::json; -use serial_test::serial; -use tokio::{task::JoinHandle, time::timeout}; +use tokio::time::timeout; use tokio_stream::StreamExt; use types::Todo; use uuid::Uuid; -async fn get_todos() -> JoinHandle> { - let handle = tokio::spawn(async move { - let client = get_client(); - let sse_url = format!("{}/api/todos/sse", client.base_url); +pub async fn connect_todos_sse(server: &TestServer) -> impl Stream> { + let response = server.reqwest_get("/api/todos/sse").send().await.unwrap(); - let response = client.api.get(sse_url).send().await.unwrap(); - let mut stream = response.bytes_stream(); - - let mut received = String::new(); - while let Some(chunk) = stream.next().await { - let chunk = chunk.expect("stream error"); - received.push_str(&String::from_utf8_lossy(&chunk)); + response.bytes_stream() +} - if received.contains("\n\n") { - break; - } - } +pub async fn read_first_event( + mut stream: impl Stream> + std::marker::Unpin, +) -> Vec { + let mut received = String::new(); - let cleaned_json = received.replace("data: ", "").trim().to_string(); - let todos: Vec = serde_json::from_str(&cleaned_json).unwrap(); + while let Some(chunk) = stream.next().await { + let chunk = chunk.expect("stream error"); + received.push_str(&String::from_utf8_lossy(&chunk)); - todos - }); + if received.contains("\n\n") { + break; + } + } - tokio::time::sleep(std::time::Duration::from_millis(100)).await; + let cleaned = received.replace("data: ", "").trim().to_string(); - handle + serde_json::from_str(&cleaned).unwrap() } #[tokio::test] -#[serial] async fn sends_events_on_add() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; - let handle = get_todos().await; + let stream = connect_todos_sse(&client.server).await; let todo_uuid = Uuid::new_v4(); client @@ -58,9 +55,8 @@ async fn sends_events_on_add() { })) .await; - let todos = timeout(Duration::from_secs(2), handle) + let todos = timeout(Duration::from_secs(2), read_first_event(stream)) .await - .unwrap() .unwrap(); assert_eq!(todos.len(), 1); @@ -68,10 +64,8 @@ async fn sends_events_on_add() { } #[tokio::test] -#[serial] async fn sends_events_on_update() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo_uuid = Uuid::new_v4(); client @@ -86,7 +80,7 @@ async fn sends_events_on_update() { })) .await; - let handle = get_todos().await; + let stream = connect_todos_sse(&client.server).await; client .add_todo_json(json!({ @@ -100,9 +94,8 @@ async fn sends_events_on_update() { })) .await; - let todos = timeout(Duration::from_secs(2), handle) + let todos = timeout(Duration::from_secs(2), read_first_event(stream)) .await - .unwrap() .unwrap(); assert_eq!(todos.len(), 1); assert_eq!(todos.first().unwrap().uuid, todo_uuid); @@ -110,10 +103,8 @@ async fn sends_events_on_update() { } #[tokio::test] -#[serial] async fn sends_events_on_remove() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo_uuid = Uuid::new_v4(); client @@ -128,22 +119,19 @@ async fn sends_events_on_remove() { })) .await; - let handle = get_todos().await; + let stream = connect_todos_sse(&client.server).await; client.delete_todo_json(todo_uuid).await; - let todos = timeout(Duration::from_secs(2), handle) + let todos = timeout(Duration::from_secs(2), read_first_event(stream)) .await - .unwrap() .unwrap(); assert_eq!(todos.len(), 0); } #[tokio::test] -#[serial] async fn sends_events_on_check() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo_uuid = Uuid::new_v4(); client @@ -158,22 +146,19 @@ async fn sends_events_on_check() { })) .await; - let handle = get_todos().await; + let stream = connect_todos_sse(&client.server).await; client.check_todo(todo_uuid).await; - let todos = timeout(Duration::from_secs(2), handle) + let todos = timeout(Duration::from_secs(2), read_first_event(stream)) .await - .unwrap() .unwrap(); assert_eq!(todos.len(), 1); } #[tokio::test] -#[serial] async fn sends_events_on_check_remove() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo_uuid = Uuid::new_v4(); client @@ -190,13 +175,12 @@ async fn sends_events_on_check_remove() { client.check_todo(todo_uuid).await; - let handle = get_todos().await; + let stream = connect_todos_sse(&client.server).await; client.remove_check_todo(todo_uuid).await; - let todos = timeout(Duration::from_secs(2), handle) + let todos = timeout(Duration::from_secs(2), read_first_event(stream)) .await - .unwrap() .unwrap(); assert_eq!(todos.len(), 1); } diff --git a/api/tests/todos/update.rs b/api/tests/todos/update.rs index 25d9aa5..5a7cc59 100644 --- a/api/tests/todos/update.rs +++ b/api/tests/todos/update.rs @@ -1,15 +1,12 @@ -use crate::common::{client::get_client, db::clear_db}; +use crate::common::client::get_client; use chrono::{DateTime, NaiveDate, NaiveTime, Utc}; use serde_json::json; -use serial_test::serial; use types::{TimePoint, TimeRange, TimeRecurring, Todo}; use uuid::Uuid; #[tokio::test] -#[serial] async fn update_todo() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo_uuid = Uuid::new_v4(); @@ -50,10 +47,8 @@ async fn update_todo() { } #[tokio::test] -#[serial] async fn update_todo_with_labels() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo_uuid = Uuid::new_v4(); let tag_uuid = Uuid::new_v4(); @@ -140,10 +135,8 @@ async fn update_todo_with_labels() { } #[tokio::test] -#[serial] async fn update_todo_with_time_point() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo_uuid = Uuid::new_v4(); let todo = Todo { @@ -195,10 +188,9 @@ async fn update_todo_with_time_point() { })) .await; - assert_eq!(response.status().as_str(), "200"); + response.assert_status_success(); - let json = response.json::().await.unwrap(); - assert_eq!(json, todo); + response.assert_json(&todo); let todos = client.get_todos_json().await; assert_eq!(todos.len(), 1); @@ -206,10 +198,8 @@ async fn update_todo_with_time_point() { } #[tokio::test] -#[serial] async fn update_todo_with_time_range() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo_uuid = Uuid::new_v4(); let todo = Todo { @@ -258,10 +248,8 @@ async fn update_todo_with_time_range() { })) .await; - assert_eq!(response.status().as_str(), "200"); - - let json = response.json::().await.unwrap(); - assert_eq!(json, todo); + response.assert_status_success(); + response.assert_json(&todo); let todos = client.get_todos_json().await; assert_eq!(todos.len(), 1); @@ -269,10 +257,8 @@ async fn update_todo_with_time_range() { } #[tokio::test] -#[serial] async fn update_todo_with_time_recurring_weekly() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo_uuid = Uuid::new_v4(); let todo = Todo { @@ -319,10 +305,8 @@ async fn update_todo_with_time_recurring_weekly() { })) .await; - assert_eq!(response.status().as_str(), "200"); - - let json = response.json::().await.unwrap(); - assert_eq!(json, todo); + response.assert_status_success(); + response.assert_json(&todo); let todos = client.get_todos_json().await; assert_eq!(todos.len(), 1); @@ -330,10 +314,8 @@ async fn update_todo_with_time_recurring_weekly() { } #[tokio::test] -#[serial] async fn update_todo_with_time_recurring_daily() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo_uuid = Uuid::new_v4(); let todo = Todo { @@ -378,10 +360,8 @@ async fn update_todo_with_time_recurring_daily() { })) .await; - assert_eq!(response.status().as_str(), "200"); - - let json = response.json::().await.unwrap(); - assert_eq!(json, todo); + response.assert_status_success(); + response.assert_json(&todo); let todos = client.get_todos_json().await; assert_eq!(todos.len(), 1); @@ -389,10 +369,8 @@ async fn update_todo_with_time_recurring_daily() { } #[tokio::test] -#[serial] async fn update_todo_with_remove_time() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo_uuid = Uuid::new_v4(); let todo = Todo { @@ -435,10 +413,8 @@ async fn update_todo_with_remove_time() { })) .await; - assert_eq!(response.status().as_str(), "200"); - - let json = response.json::().await.unwrap(); - assert_eq!(json, todo); + response.assert_status_success(); + response.assert_json(&todo); let todos = client.get_todos_json().await; assert_eq!(todos.len(), 1); @@ -446,10 +422,8 @@ async fn update_todo_with_remove_time() { } #[tokio::test] -#[serial] async fn update_invalid_todos() { - let client = get_client(); - clear_db(&client).await; + let client = get_client().await; let todo_uuid = Uuid::new_v4(); @@ -477,7 +451,7 @@ async fn update_invalid_todos() { } )) .await; - assert_eq!(response.status().as_str(), "422"); + response.assert_status_unprocessable_entity(); let todos = client.get_todos_json().await;