diff --git a/Cargo.lock b/Cargo.lock index d8c3046..5198028 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1221,6 +1221,7 @@ dependencies = [ name = "li7y" version = "0.0.0" dependencies = [ + "actix-http", "actix-identity", "actix-session", "actix-web", diff --git a/Cargo.toml b/Cargo.toml index 302e756..d6559b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ time = { version = "0.3.36", features = ["parsing", "serde"] } uuid = { version = "1.9.0", features = ["serde", "v4"] } [dev-dependencies] +actix-http = "3.8.0" quickcheck = "1.0.3" quickcheck_macros = "1.0.0" diff --git a/tests/common/mod.rs b/tests/common/mod.rs new file mode 100644 index 0000000..b008db7 --- /dev/null +++ b/tests/common/mod.rs @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: 2024 Simon Bruder +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +use std::env; + +use actix_http::header; +use actix_web::{cookie::Cookie, dev::ServiceResponse, test}; +use clap::Parser; +use serde::Serialize; + +use li7y::Config; + +pub const SUPERUSER_PASSWORD: &str = "correct horse battery staple"; + +#[derive(Serialize)] +pub struct LoginForm { + password: String, +} + +impl Default for LoginForm { + fn default() -> Self { + Self { + password: SUPERUSER_PASSWORD.to_string(), + } + } +} + +pub fn config() -> Config { + env::set_var("SUPERUSER_PASSWORD", SUPERUSER_PASSWORD); + Config::parse_from(Vec::::new().iter()) +} + +pub fn assert_redirect(res: ServiceResponse) -> String { + assert!(res.status().is_redirection()); + + res.headers() + .get(header::LOCATION) + .expect("No location header set when expected") + .to_str() + .expect("Location header is not valid UTF-8") + .to_string() +} + +pub async fn session_cookie<'a>( + srv: &impl actix_web::dev::Service< + actix_http::Request, + Response = ServiceResponse>, + Error = actix_web::Error, + >, +) -> Cookie<'a> { + let req = test::TestRequest::post() + .uri("/login") + .set_form(LoginForm::default()) + .to_request(); + + Cookie::parse_encoded( + test::call_service(&srv, req) + .await + .headers() + .get(header::SET_COOKIE) + .unwrap() + .to_str() + .unwrap() + .to_string(), + ) + .unwrap() +} diff --git a/tests/smoke.rs b/tests/smoke.rs new file mode 100644 index 0000000..21f1643 --- /dev/null +++ b/tests/smoke.rs @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: 2024 Simon Bruder +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +use actix_http::header; +use actix_web::{cookie::Cookie, test}; +use sqlx::PgPool; + +mod common; + +#[sqlx::test] +async fn index(pool: PgPool) { + let srv = test::init_service(li7y::app(&common::config(), &pool)).await; + let req = test::TestRequest::get().uri("/").to_request(); + let res = test::call_service(&srv, req).await; + + assert!(common::assert_redirect(res.map_into_boxed_body()).starts_with("/login")); +} + +#[sqlx::test] +async fn login(pool: PgPool) { + let srv = test::init_service(li7y::app(&common::config(), &pool)).await; + + // This is identical to common::session_cookie, + // but copied here explicitly to ensure the right functionality is tested. + let req = test::TestRequest::post() + .uri("/login") + .set_form(common::LoginForm::default()) + .to_request(); + + let res = test::call_service(&srv, req).await; + let session = Cookie::parse_encoded( + res.headers() + .clone() + .get(header::SET_COOKIE) + .unwrap() + .to_str() + .unwrap() + .to_string(), + ) + .unwrap(); + + assert!(common::assert_redirect(res.map_into_boxed_body()).starts_with("/")); + + let req = test::TestRequest::get() + .uri("/") + .cookie(session.clone()) + .to_request(); + + let res = test::call_service(&srv, req).await; + + assert!(res.status().is_success()); +} + +#[ignore = "actix_session::CookieSessionStore does not support invalidating sessions"] +#[sqlx::test] +async fn logout(pool: PgPool) { + let srv = test::init_service(li7y::app(&common::config(), &pool)).await; + + let session_cookie = common::session_cookie(&srv).await; + + let req = test::TestRequest::post() + .uri("/logout") + .cookie(session_cookie.clone()) + .to_request(); + + test::call_service(&srv, req).await; + + let req = test::TestRequest::get() + .uri("/items") + .cookie(session_cookie.clone()) + .to_request(); + + let res = test::call_service(&srv, req).await; + + assert!(common::assert_redirect(res.map_into_boxed_body()).starts_with("/login")); +} + +#[sqlx::test] +async fn list_items(pool: PgPool) { + let srv = test::init_service(li7y::app(&common::config(), &pool)).await; + + let session_cookie = common::session_cookie(&srv).await; + + let req = test::TestRequest::get() + .uri("/items") + .cookie(session_cookie.clone()) + .to_request(); + + let res = test::call_service(&srv, req).await; + + assert!(res.status().is_success()); +}