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

+145 -34 +/-6 browse
web: add unit tests++
1diff --git a/Cargo.lock b/Cargo.lock
2index 51e6494..3fcba1a 100644
3--- a/Cargo.lock
4+++ b/Cargo.lock
5 @@ -2035,6 +2035,7 @@ dependencies = [
6 "rand",
7 "serde",
8 "serde_json",
9+ "serde_urlencoded",
10 "tempfile",
11 "tokio",
12 "tower",
13 diff --git a/mailpot-tests/for_testing.db b/mailpot-tests/for_testing.db
14index 70ccedb..64f74ee 100644
15 Binary files a/mailpot-tests/for_testing.db and b/mailpot-tests/for_testing.db differ
16 diff --git a/rest-http/src/routes/list.rs b/rest-http/src/routes/list.rs
17index c9947f0..1897ce2 100644
18--- a/rest-http/src/routes/list.rs
19+++ b/rest-http/src/routes/list.rs
20 @@ -340,7 +340,7 @@ mod tests {
21 list: 1,
22 address: "user@example.com".to_string(),
23 name: Some("Name".to_string()),
24- account: None,
25+ account: Some(1),
26 enabled: true,
27 verified: false,
28 digest: false,
29 diff --git a/web/Cargo.toml b/web/Cargo.toml
30index 597c199..a203fc7 100644
31--- a/web/Cargo.toml
32+++ b/web/Cargo.toml
33 @@ -43,7 +43,8 @@ zstd = { version = "0.12", default-features = false }
34 [dev-dependencies]
35 hyper = { version = "0.14" }
36 mailpot-tests = { version = "^0.1", path = "../mailpot-tests" }
37- tempfile = "3.3"
38+ serde_urlencoded = { version = "^0.7" }
39+ tempfile = { version = "3.3" }
40 tower = { version = "^0.4" }
41
42 [build-dependencies]
43 diff --git a/web/src/auth.rs b/web/src/auth.rs
44index 84d3ef6..af17e30 100644
45--- a/web/src/auth.rs
46+++ b/web/src/auth.rs
47 @@ -179,7 +179,7 @@ pub async fn ssh_signin_post(
48 let (_prev_token, _) = if let Some(tok @ (_, timestamp)) =
49 session.get::<(String, i64)>(TOKEN_KEY)
50 {
51- if !(timestamp < now && now - timestamp < EXPIRY_IN_SECS) {
52+ if !(timestamp <= now && now - timestamp < EXPIRY_IN_SECS) {
53 session.add_message(Message {
54 message: "The token has expired. Please retry.".into(),
55 level: Level::Error,
56 diff --git a/web/src/main.rs b/web/src/main.rs
57index a6de0c7..000d58e 100644
58--- a/web/src/main.rs
59+++ b/web/src/main.rs
60 @@ -26,12 +26,8 @@ use minijinja::value::Value;
61 use rand::Rng;
62 use tokio::sync::RwLock;
63
64- fn create_app(conf: Configuration) -> Router {
65- let store = MemoryStore::new();
66- let secret = rand::thread_rng().gen::<[u8; 128]>();
67- let session_layer = SessionLayer::new(store, &secret).with_secure(false);
68-
69- let shared_state = Arc::new(AppState {
70+ fn new_state(conf: Configuration) -> Arc<AppState> {
71+ Arc::new(AppState {
72 conf,
73 root_url_prefix: Value::from_safe_string(
74 std::env::var("ROOT_URL_PREFIX").unwrap_or_default(),
75 @@ -42,7 +38,13 @@ fn create_app(conf: Configuration) -> Router {
76 .into(),
77 site_subtitle: std::env::var("SITE_SUBTITLE").ok().map(Into::into),
78 user_store: Arc::new(RwLock::new(HashMap::default())),
79- });
80+ })
81+ }
82+
83+ fn create_app(shared_state: Arc<AppState>) -> Router {
84+ let store = MemoryStore::new();
85+ let secret = rand::thread_rng().gen::<[u8; 128]>();
86+ let session_layer = SessionLayer::new(store, &secret).with_secure(false);
87
88 let auth_layer = AuthLayer::new(shared_state.clone(), &secret);
89
90 @@ -155,7 +157,7 @@ async fn main() {
91 return;
92 }
93 let conf = Configuration::from_file(config_path).unwrap();
94- let app = create_app(conf);
95+ let app = create_app(new_state(conf));
96
97 let hostname = std::env::var("HOSTNAME").unwrap_or_else(|_| "0.0.0.0".to_string());
98 let port = std::env::var("PORT").unwrap_or_else(|_| "3000".to_string());
99 @@ -215,7 +217,11 @@ mod tests {
100
101 use axum::{
102 body::Body,
103- http::{method::Method, Request, StatusCode},
104+ http::{
105+ header::{COOKIE, SET_COOKIE},
106+ method::Method,
107+ Request, StatusCode,
108+ },
109 };
110 use mailpot::{Configuration, Connection, SendMail};
111 use mailpot_tests::init_stderr_logging;
112 @@ -228,6 +234,26 @@ mod tests {
113 async fn test_routes() {
114 init_stderr_logging();
115
116+ macro_rules! req {
117+ (get $url:expr) => {{
118+ Request::builder()
119+ .uri($url)
120+ .method(Method::GET)
121+ .body(Body::empty())
122+ .unwrap()
123+ }};
124+ (post $url:expr, $body:expr) => {{
125+ Request::builder()
126+ .uri($url)
127+ .method(Method::POST)
128+ .header("Content-Type", "application/x-www-form-urlencoded")
129+ .body(Body::from(
130+ serde_urlencoded::to_string($body).unwrap().into_bytes(),
131+ ))
132+ .unwrap()
133+ }};
134+ }
135+
136 let tmp_dir = TempDir::new().unwrap();
137
138 let db_path = tmp_dir.path().join("mpot.db");
139 @@ -241,46 +267,129 @@ mod tests {
140 let db = Connection::open_db(config.clone()).unwrap();
141 let list = db.lists().unwrap().remove(0);
142
143+ let state = new_state(config.clone());
144+
145 // ------------------------------------------------------------
146 // list()
147
148- let cl = |url, config| async move {
149- let response = create_app(config)
150- .oneshot(
151- Request::builder()
152- .uri(&url)
153- .method(Method::GET)
154- .body(Body::empty())
155- .unwrap(),
156- )
157- .await
158- .unwrap();
159+ let cl = |url, state| async move {
160+ let response = create_app(state).oneshot(req!(get & url)).await.unwrap();
161
162 assert_eq!(response.status(), StatusCode::OK);
163
164 hyper::body::to_bytes(response.into_body()).await.unwrap()
165 };
166 assert_eq!(
167- cl(format!("/list/{}/", list.id), config.clone()).await,
168- cl(format!("/list/{}/", list.pk), config.clone()).await
169+ cl(format!("/list/{}/", list.id), state.clone()).await,
170+ cl(format!("/list/{}/", list.pk), state.clone()).await
171 );
172
173 // ------------------------------------------------------------
174 // help(), ssh_signin(), root()
175
176- for path in ["/help/", "/login/", "/"] {
177- let response = create_app(config.clone())
178- .oneshot(
179- Request::builder()
180- .uri(path)
181- .method(Method::GET)
182- .body(Body::empty())
183- .unwrap(),
184- )
185+ for path in ["/help/", "/"] {
186+ let response = create_app(state.clone())
187+ .oneshot(req!(get path))
188+ .await
189+ .unwrap();
190+
191+ assert_eq!(response.status(), StatusCode::OK);
192+ }
193+
194+ // ------------------------------------------------------------
195+ // auth.rs...
196+
197+ let login_app = create_app(state.clone());
198+ let session_cookie = {
199+ let response = login_app
200+ .clone()
201+ .oneshot(req!(get "/login/"))
202 .await
203 .unwrap();
204+ assert_eq!(response.status(), StatusCode::OK);
205+
206+ response.headers().get(SET_COOKIE).unwrap().clone()
207+ };
208+ let user = User {
209+ pk: 1,
210+ ssh_signature: String::new(),
211+ role: Role::User,
212+ public_key: None,
213+ password: String::new(),
214+ name: None,
215+ address: String::new(),
216+ enabled: true,
217+ };
218+ state.insert_user(1, user.clone()).await;
219+
220+ {
221+ let mut request = req!(post "/login/",
222+ AuthFormPayload {
223+ address: "user@example.com".into(),
224+ password: "hunter2".into()
225+ }
226+ );
227+ request
228+ .headers_mut()
229+ .insert(COOKIE, session_cookie.to_owned());
230+ let res = login_app.clone().oneshot(request).await.unwrap();
231+
232+ assert_eq!(
233+ res.headers().get(http::header::LOCATION),
234+ Some(
235+ &SettingsPath
236+ .to_uri()
237+ .to_string()
238+ .as_str()
239+ .try_into()
240+ .unwrap()
241+ )
242+ );
243+ }
244+
245+ // ------------------------------------------------------------
246+ // settings()
247+
248+ {
249+ let mut request = req!(get "/settings/");
250+ request
251+ .headers_mut()
252+ .insert(COOKIE, session_cookie.to_owned());
253+ let response = login_app.clone().oneshot(request).await.unwrap();
254
255 assert_eq!(response.status(), StatusCode::OK);
256 }
257+
258+ // ------------------------------------------------------------
259+ // settings_post()
260+
261+ {
262+ let mut request = req!(
263+ post "/settings/",
264+ crate::settings::ChangeSetting::Subscribe {
265+ list_pk: IntPOST(1),
266+ });
267+ request
268+ .headers_mut()
269+ .insert(COOKIE, session_cookie.to_owned());
270+ let res = login_app.clone().oneshot(request).await.unwrap();
271+
272+ assert_eq!(
273+ res.headers().get(http::header::LOCATION),
274+ Some(
275+ &SettingsPath
276+ .to_uri()
277+ .to_string()
278+ .as_str()
279+ .try_into()
280+ .unwrap()
281+ )
282+ );
283+ }
284+ // ------------------------------------------------------------
285+ // user_list_subscription() TODO
286+
287+ // ------------------------------------------------------------
288+ // user_list_subscription_post() TODO
289 }
290 }