tests for web routes and config toggles
This commit is contained in:
parent
5e71ee1be6
commit
1f688e23a9
3 changed files with 175 additions and 0 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
|
@ -457,10 +457,13 @@ dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
"chrono",
|
"chrono",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
|
"http-body-util",
|
||||||
"serde",
|
"serde",
|
||||||
"teloxide",
|
"teloxide",
|
||||||
|
"tempfile",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
|
"tower",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
|
|
||||||
|
|
@ -14,3 +14,8 @@ uuid = { version = "1", features = ["v4"] }
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tower = { version = "0.5", features = ["util"] }
|
||||||
|
http-body-util = "0.1"
|
||||||
|
tempfile = "3"
|
||||||
|
|
|
||||||
167
src/web.rs
167
src/web.rs
|
|
@ -116,3 +116,170 @@ async fn submit(
|
||||||
async fn style() -> impl IntoResponse {
|
async fn style() -> impl IntoResponse {
|
||||||
([(header::CONTENT_TYPE, "text/css")], STYLE_CSS)
|
([(header::CONTENT_TYPE, "text/css")], STYLE_CSS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use axum::body::Body;
|
||||||
|
use axum::http::{Request, StatusCode};
|
||||||
|
use http_body_util::BodyExt;
|
||||||
|
use tower::ServiceExt;
|
||||||
|
|
||||||
|
fn test_config(dir: &std::path::Path) -> Config {
|
||||||
|
Config {
|
||||||
|
port: 0,
|
||||||
|
data_dir: dir.to_path_buf(),
|
||||||
|
site_title: "test".into(),
|
||||||
|
site_url: "https://test.rs".into(),
|
||||||
|
telegram_bot_token: "fake".into(),
|
||||||
|
telegram_chat_id: 0,
|
||||||
|
honeypot: true,
|
||||||
|
max_name_length: 50,
|
||||||
|
max_message_length: 1000,
|
||||||
|
max_website_length: 100,
|
||||||
|
open_registration: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_app(config: Config) -> (Router, tokio::sync::mpsc::Receiver<Entry>) {
|
||||||
|
let (tx, rx) = tokio::sync::mpsc::channel(32);
|
||||||
|
let state = Arc::new(AppState { config, tx });
|
||||||
|
(router(state), rx)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn post_form(app: &Router, body: &str) -> (StatusCode, String) {
|
||||||
|
let req = Request::builder()
|
||||||
|
.method("POST")
|
||||||
|
.uri("/submit")
|
||||||
|
.header("content-type", "application/x-www-form-urlencoded")
|
||||||
|
.body(Body::from(body.to_string()))
|
||||||
|
.unwrap();
|
||||||
|
let resp = app.clone().oneshot(req).await.unwrap();
|
||||||
|
let status = resp.status();
|
||||||
|
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
|
||||||
|
(status, String::from_utf8(bytes.to_vec()).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_index(app: &Router) -> String {
|
||||||
|
let req = Request::builder()
|
||||||
|
.uri("/")
|
||||||
|
.body(Body::empty())
|
||||||
|
.unwrap();
|
||||||
|
let resp = app.clone().oneshot(req).await.unwrap();
|
||||||
|
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
|
||||||
|
String::from_utf8(bytes.to_vec()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_open_registration_shows_form() {
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
let config = test_config(dir.path());
|
||||||
|
let (app, _rx) = test_app(config);
|
||||||
|
let html = get_index(&app).await;
|
||||||
|
assert!(html.contains("action=\"/submit\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_closed_registration_hides_form() {
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
let mut config = test_config(dir.path());
|
||||||
|
config.open_registration = false;
|
||||||
|
let (app, _rx) = test_app(config);
|
||||||
|
let html = get_index(&app).await;
|
||||||
|
assert!(!html.contains("action=\"/submit\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_closed_registration_rejects_submit() {
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
let mut config = test_config(dir.path());
|
||||||
|
config.open_registration = false;
|
||||||
|
let (app, _rx) = test_app(config);
|
||||||
|
let (status, body) = post_form(&app, "name=test&message=hello").await;
|
||||||
|
assert_eq!(status, StatusCode::OK);
|
||||||
|
assert!(body.contains("Submissions are closed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_honeypot_discards() {
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
let config = test_config(dir.path());
|
||||||
|
let (app, _rx) = test_app(config);
|
||||||
|
let (_, body) = post_form(&app, "name=bot&message=spam&url=http://spam.com").await;
|
||||||
|
assert!(body.contains("Thanks!"));
|
||||||
|
// No entry file should exist
|
||||||
|
let entries: Vec<_> = std::fs::read_dir(dir.path().join("entries"))
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.collect();
|
||||||
|
assert!(entries.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_honeypot_disabled_allows_url_field() {
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
let mut config = test_config(dir.path());
|
||||||
|
config.honeypot = false;
|
||||||
|
let (app, _rx) = test_app(config);
|
||||||
|
let (_, body) = post_form(&app, "name=user&message=hello&url=http://mysite.com").await;
|
||||||
|
assert!(body.contains("pending approval"));
|
||||||
|
let count = std::fs::read_dir(dir.path().join("entries"))
|
||||||
|
.unwrap()
|
||||||
|
.count();
|
||||||
|
assert_eq!(count, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_max_name_length() {
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
let mut config = test_config(dir.path());
|
||||||
|
config.max_name_length = 5;
|
||||||
|
let (app, _rx) = test_app(config);
|
||||||
|
let (_, body) = post_form(&app, "name=toolong&message=hi").await;
|
||||||
|
assert!(body.contains("too long"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_max_name_length_zero_unlimited() {
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
let mut config = test_config(dir.path());
|
||||||
|
config.max_name_length = 0;
|
||||||
|
let (app, _rx) = test_app(config);
|
||||||
|
let long_name = "a".repeat(200);
|
||||||
|
let (_, body) = post_form(&app, &format!("name={long_name}&message=hi")).await;
|
||||||
|
assert!(body.contains("pending approval"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_max_message_length() {
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
let mut config = test_config(dir.path());
|
||||||
|
config.max_message_length = 10;
|
||||||
|
let (app, _rx) = test_app(config);
|
||||||
|
let (_, body) = post_form(&app, "name=test&message=this+message+is+way+too+long").await;
|
||||||
|
assert!(body.contains("too long"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_max_website_length() {
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
let mut config = test_config(dir.path());
|
||||||
|
config.max_website_length = 5;
|
||||||
|
let (app, _rx) = test_app(config);
|
||||||
|
let (_, body) = post_form(&app, "name=test&message=hi&website=http://toolong.com").await;
|
||||||
|
assert!(body.contains("too long"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_valid_submission_creates_entry() {
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
let config = test_config(dir.path());
|
||||||
|
let (app, _rx) = test_app(config);
|
||||||
|
let (_, body) = post_form(&app, "name=alice&message=hello").await;
|
||||||
|
assert!(body.contains("pending approval"));
|
||||||
|
let count = std::fs::read_dir(dir.path().join("entries"))
|
||||||
|
.unwrap()
|
||||||
|
.count();
|
||||||
|
assert_eq!(count, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue