Author: Manos Pitsidianakis [manos@pitsidianak.is]
Hash: c4f7ea1822268043251bbf7bc17057c82a917167
Timestamp: Sun, 14 May 2023 06:47:54 +0000 (1 year ago)

+116 -53 +/-6 browse
web: add unit tests
1diff --git a/Cargo.lock b/Cargo.lock
2index cf366b1..51e6494 100644
3--- a/Cargo.lock
4+++ b/Cargo.lock
5 @@ -2019,14 +2019,17 @@ dependencies = [
6 "axum-sessions",
7 "build-info",
8 "build-info-build",
9+ "cfg-if 1.0.0",
10 "chrono",
11 "convert_case",
12 "dyn-clone",
13 "eyre",
14 "http",
15+ "hyper",
16 "indexmap",
17 "lazy_static",
18 "mailpot",
19+ "mailpot-tests",
20 "minijinja",
21 "percent-encoding",
22 "rand",
23 @@ -2034,6 +2037,7 @@ dependencies = [
24 "serde_json",
25 "tempfile",
26 "tokio",
27+ "tower",
28 "tower-http 0.3.5",
29 "tower-service",
30 "zstd",
31 diff --git a/web/Cargo.toml b/web/Cargo.toml
32index 57c48e5..597c199 100644
33--- a/web/Cargo.toml
34+++ b/web/Cargo.toml
35 @@ -14,16 +14,13 @@ categories = ["email"]
36 name = "mpot-web"
37 path = "src/main.rs"
38
39- [features]
40- default = ["zstd"]
41- zstd = ["dep:zstd"]
42-
43 [dependencies]
44 axum = { version = "^0.6" }
45 axum-extra = { version = "^0.7", features = ["typed-routing"] }
46 axum-login = { version = "^0.5" }
47 axum-sessions = { version = "^0.5" }
48 build-info = { version = "0.0.31" }
49+ cfg-if = { version = "1" }
50 chrono = { version = "^0.4" }
51 convert_case = { version = "^0.4" }
52 dyn-clone = { version = "^1" }
53 @@ -41,8 +38,14 @@ tempfile = { version = "^3.5" }
54 tokio = { version = "1", features = ["full"] }
55 tower-http = { version = "^0.3" }
56 tower-service = { version = "^0.3" }
57- zstd = { version = "0.12.3", optional = true, default-features = false }
58+ zstd = { version = "0.12", default-features = false }
59+
60+ [dev-dependencies]
61+ hyper = { version = "0.14" }
62+ mailpot-tests = { version = "^0.1", path = "../mailpot-tests" }
63+ tempfile = "3.3"
64+ tower = { version = "^0.4" }
65
66 [build-dependencies]
67 build-info-build = { version = "0.0.31" }
68- zstd = { version = "0.12.3", optional = true, default-features = false }
69+ zstd = { version = "0.12", default-features = false }
70 diff --git a/web/README.md b/web/README.md
71index 5495c3d..c54e80c 100644
72--- a/web/README.md
73+++ b/web/README.md
74 @@ -4,9 +4,7 @@
75 cargo run --bin mpot-web -- /path/to/conf.toml
76 ```
77
78- By default on release builds templates are compressed with `zstd` and bundled in the binary.
79-
80- You can disable this behavior by disabling the `zstd` feature: `cargo build --release --no-default-features`
81+ Templates are compressed with `zstd` and bundled in the binary.
82
83 ## Configuration
84
85 diff --git a/web/build.rs b/web/build.rs
86index bd29123..5008bdc 100644
87--- a/web/build.rs
88+++ b/web/build.rs
89 @@ -44,7 +44,6 @@ fn commit_sha() {
90 }
91 }
92
93- #[cfg(feature = "zstd")]
94 fn main() -> Result<(), Box<dyn std::error::Error>> {
95 // Embed HTML templates as zstd compressed byte slices into binary.
96 // [tag:embed_templates]
97 @@ -104,8 +103,3 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
98 commit_sha();
99 Ok(())
100 }
101-
102- #[cfg(not(feature = "zstd"))]
103- fn main() {
104- commit_sha();
105- }
106 diff --git a/web/src/main.rs b/web/src/main.rs
107index b0c3223..a6de0c7 100644
108--- a/web/src/main.rs
109+++ b/web/src/main.rs
110 @@ -26,19 +26,7 @@ use minijinja::value::Value;
111 use rand::Rng;
112 use tokio::sync::RwLock;
113
114- #[tokio::main]
115- async fn main() {
116- let config_path = std::env::args()
117- .nth(1)
118- .expect("Expected configuration file path as first argument.");
119- if ["-v", "--version", "info"].contains(&config_path.as_str()) {
120- println!("{}", crate::get_git_sha());
121- println!("{CLI_INFO}");
122-
123- return;
124- }
125- let conf = Configuration::from_file(config_path).unwrap();
126-
127+ fn create_app(conf: Configuration) -> Router {
128 let store = MemoryStore::new();
129 let secret = rand::thread_rng().gen::<[u8; 128]>();
130 let session_layer = SessionLayer::new(store, &secret).with_secure(false);
131 @@ -60,7 +48,7 @@ async fn main() {
132
133 let login_url =
134 Arc::new(format!("{}{}", shared_state.root_url_prefix, LoginPath.to_crumb()).into());
135- let app = Router::new()
136+ Router::new()
137 .route("/", get(root))
138 .typed_get(list)
139 .typed_get(list_post)
140 @@ -152,7 +140,22 @@ async fn main() {
141 )
142 .layer(auth_layer)
143 .layer(session_layer)
144- .with_state(shared_state);
145+ .with_state(shared_state)
146+ }
147+
148+ #[tokio::main]
149+ async fn main() {
150+ let config_path = std::env::args()
151+ .nth(1)
152+ .expect("Expected configuration file path as first argument.");
153+ if ["-v", "--version", "info"].contains(&config_path.as_str()) {
154+ println!("{}", crate::get_git_sha());
155+ println!("{CLI_INFO}");
156+
157+ return;
158+ }
159+ let conf = Configuration::from_file(config_path).unwrap();
160+ let app = create_app(conf);
161
162 let hostname = std::env::var("HOSTNAME").unwrap_or_else(|_| "0.0.0.0".to_string());
163 let port = std::env::var("PORT").unwrap_or_else(|_| "3000".to_string());
164 @@ -206,3 +209,78 @@ async fn root(
165 };
166 Ok(Html(TEMPLATES.get_template("lists.html")?.render(context)?))
167 }
168+
169+ #[cfg(test)]
170+ mod tests {
171+
172+ use axum::{
173+ body::Body,
174+ http::{method::Method, Request, StatusCode},
175+ };
176+ use mailpot::{Configuration, Connection, SendMail};
177+ use mailpot_tests::init_stderr_logging;
178+ use tempfile::TempDir;
179+ use tower::ServiceExt;
180+
181+ use super::*;
182+
183+ #[tokio::test]
184+ async fn test_routes() {
185+ init_stderr_logging();
186+
187+ let tmp_dir = TempDir::new().unwrap();
188+
189+ let db_path = tmp_dir.path().join("mpot.db");
190+ std::fs::copy("../mailpot-tests/for_testing.db", &db_path).unwrap();
191+ let config = Configuration {
192+ send_mail: SendMail::ShellCommand("/usr/bin/false".to_string()),
193+ db_path,
194+ data_path: tmp_dir.path().to_path_buf(),
195+ administrators: vec![],
196+ };
197+ let db = Connection::open_db(config.clone()).unwrap();
198+ let list = db.lists().unwrap().remove(0);
199+
200+ // ------------------------------------------------------------
201+ // list()
202+
203+ let cl = |url, config| async move {
204+ let response = create_app(config)
205+ .oneshot(
206+ Request::builder()
207+ .uri(&url)
208+ .method(Method::GET)
209+ .body(Body::empty())
210+ .unwrap(),
211+ )
212+ .await
213+ .unwrap();
214+
215+ assert_eq!(response.status(), StatusCode::OK);
216+
217+ hyper::body::to_bytes(response.into_body()).await.unwrap()
218+ };
219+ assert_eq!(
220+ cl(format!("/list/{}/", list.id), config.clone()).await,
221+ cl(format!("/list/{}/", list.pk), config.clone()).await
222+ );
223+
224+ // ------------------------------------------------------------
225+ // help(), ssh_signin(), root()
226+
227+ for path in ["/help/", "/login/", "/"] {
228+ let response = create_app(config.clone())
229+ .oneshot(
230+ Request::builder()
231+ .uri(path)
232+ .method(Method::GET)
233+ .body(Body::empty())
234+ .unwrap(),
235+ )
236+ .await
237+ .unwrap();
238+
239+ assert_eq!(response.status(), StatusCode::OK);
240+ }
241+ }
242+ }
243 diff --git a/web/src/minijinja_utils.rs b/web/src/minijinja_utils.rs
244index 684ffb6..04da2d1 100644
245--- a/web/src/minijinja_utils.rs
246+++ b/web/src/minijinja_utils.rs
247 @@ -21,8 +21,6 @@
248
249 use super::*;
250
251- #[cfg(feature = "zstd")]
252- #[cfg(not(debug_assertions))]
253 mod compressed;
254
255 lazy_static::lazy_static! {
256 @@ -54,28 +52,16 @@ lazy_static::lazy_static! {
257 post_eml_path
258 );
259 add!(filter pluralize);
260- #[cfg(not(feature = "zstd"))]
261- #[cfg(debug_assertions)]
262- env.set_source(minijinja::Source::from_path("web/src/templates/"));
263- #[cfg(feature = "zstd")]
264- #[cfg(debug_assertions)]
265- env.set_source(minijinja::Source::from_path("web/src/templates/"));
266- #[cfg(not(feature = "zstd"))]
267- #[cfg(not(debug_assertions))]
268- env.set_source(minijinja::Source::from_path("web/src/templates/"));
269- #[cfg(feature = "zstd")]
270- #[cfg(not(debug_assertions))]
271- {
272- // Load compressed templates. They are constructed in build.rs. See
273- // [ref:embed_templates]
274- let mut source = minijinja::Source::new();
275- for (name, bytes) in compressed::COMPRESSED {
276- let mut de_bytes = vec![];
277- zstd::stream::copy_decode(*bytes,&mut de_bytes).unwrap();
278- source.add_template(*name, String::from_utf8(de_bytes).unwrap()).unwrap();
279- }
280- env.set_source(source);
281+ // Load compressed templates. They are constructed in build.rs. See
282+ // [ref:embed_templates]
283+ let mut source = minijinja::Source::new();
284+ for (name, bytes) in compressed::COMPRESSED {
285+ let mut de_bytes = vec![];
286+ zstd::stream::copy_decode(*bytes,&mut de_bytes).unwrap();
287+ source.add_template(*name, String::from_utf8(de_bytes).unwrap()).unwrap();
288 }
289+ env.set_source(source);
290+
291 env.add_global("root_url_prefix", Value::from_safe_string( std::env::var("ROOT_URL_PREFIX").unwrap_or_default()));
292 env.add_global("public_url",Value::from_safe_string(std::env::var("PUBLIC_URL").unwrap_or_default()));
293 env.add_global("site_title", Value::from_safe_string(std::env::var("SITE_TITLE").unwrap_or_else(|_| "mailing list archive".to_string())));