Add basic integration tests
All checks were successful
/ build (push) Successful in 1m19s

This commit is contained in:
Simon Bruder 2024-07-27 22:16:12 +02:00
parent b22588cd0d
commit bd1e7ad407
Signed by: simon
GPG key ID: 347FF8699CDA0776
5 changed files with 219 additions and 0 deletions

1
Cargo.lock generated
View file

@ -1221,6 +1221,7 @@ dependencies = [
name = "li7y"
version = "0.0.0"
dependencies = [
"actix-http",
"actix-identity",
"actix-session",
"actix-web",

View file

@ -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"

77
tests/auth.rs Normal file
View file

@ -0,0 +1,77 @@
// SPDX-FileCopyrightText: 2024 Simon Bruder <simon@sbruder.de>
//
// 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 protected_route_requires_login(pool: PgPool) {
let srv = test::init_service(li7y::app(&common::config(), &pool)).await;
let req = test::TestRequest::get().uri("/items").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"));
}

69
tests/common/mod.rs Normal file
View file

@ -0,0 +1,69 @@
// SPDX-FileCopyrightText: 2024 Simon Bruder <simon@sbruder.de>
//
// 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::<std::ffi::OsString>::new().iter())
}
#[allow(dead_code)] // for some reason rustc detects this as unused
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<actix_web::body::EitherBody<actix_web::body::BoxBody>>,
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()
}

71
tests/items.rs Normal file
View file

@ -0,0 +1,71 @@
// SPDX-FileCopyrightText: 2024 Simon Bruder <simon@sbruder.de>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use actix_web::{body::MessageBody, test};
use sqlx::{query_as, PgPool};
use uuid::Uuid;
mod common;
#[sqlx::test(fixtures("default"))]
async fn list(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());
let body = String::from_utf8(res.into_body().try_into_bytes().unwrap().to_vec()).unwrap();
let items: Vec<(Uuid, Option<String>)> = query_as("SELECT id, name FROM items")
.fetch_all(&pool)
.await
.unwrap();
for (id, name) in items {
assert!(body.contains(&format!(r#"href="/item/{id}""#)));
if let Some(name) = name {
assert!(body.contains(&format!(">{name}</a>")));
}
}
}
#[sqlx::test(fixtures("default"))]
async fn show(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("/item/663f45e6-b11a-4197-8ce4-c784ac9ee617")
.cookie(session_cookie.clone())
.to_request();
let res = test::call_service(&srv, req).await;
assert!(res.status().is_success());
let body = String::from_utf8(res.into_body().try_into_bytes().unwrap().to_vec()).unwrap();
assert!(body.contains("<h2>Item 2 <"));
assert!(body.contains("<th>UUID</th><td>663f45e6-b11a-4197-8ce4-c784ac9ee617</td>"));
assert!(body.contains("<th>Name</th><td>Item 2</td>"));
assert!(body
.contains(r#"href="/item-class/8a979306-b4c6-4ef8-900d-68f64abb2975">Subclass 1.1</a>"#));
assert!(body.contains(r#"href="/item/4fc0f5f4-4dca-4c24-844d-1f464cb32afa">Item 1</a>"#));
assert!(body.contains(r#"<li class="breadcrumb-item active">Item 2</li>"#));
assert!(body.contains(
r#"href="/item/049298e2-73db-42fb-957d-a741655648b1">Original Packaging of Item 2</a>"#
));
assert!(body.contains(">Lorem ipsum 3</td>"));
assert!(body.contains(">acquire</"));
}