Line data Source code
1 : use crate::db::setup_db_with_arcmutex as setup_db;
2 : use axum::{Router, body::Body};
3 : use axum_extra::extract::cookie::Key;
4 : use googletest::prelude::*;
5 : use http::{Request, StatusCode};
6 : use http_body_util::BodyExt;
7 : use rusqlite::Connection;
8 : use std::{
9 : collections::HashMap,
10 : fs,
11 : sync::{Arc, Mutex},
12 : };
13 : use tower::util::ServiceExt;
14 :
15 9 : fn cookie_key() -> Key {
16 9 : Key::from(fs::read("./testdata/cookie.key").unwrap().as_slice())
17 9 : }
18 :
19 9 : fn setup_app(db: Arc<Mutex<Connection>>) -> Router {
20 9 : let key = cookie_key();
21 9 : crate::new(db, "./templates/", "./public/", "/tmp/", key)
22 9 : }
23 :
24 : #[gtest]
25 : #[tokio::test]
26 : async fn test_sign_in() -> Result<()> {
27 : let dbmux = setup_db(Some("testdata/test_sign_in.sql".into()));
28 : let app = setup_app(dbmux.clone());
29 : let mut params = HashMap::new();
30 : params.insert("username", "admin");
31 : params.insert("password", "pass");
32 : let body = serde_urlencoded::to_string(params).unwrap();
33 : let req = Request::builder()
34 : .method("POST")
35 : .uri("/sign-in/")
36 : .header("Content-Type", "application/x-www-form-urlencoded")
37 : .body(body)?;
38 : let res = app.oneshot(req).await?;
39 : expect_that!(res.status(), eq(StatusCode::SEE_OTHER));
40 : let location_header = res.headers().get("location").unwrap();
41 : let mut location_iter = location_header.to_str().unwrap().split('=');
42 : assert_eq!(location_iter.next(), Some("/"));
43 :
44 : let db = dbmux.lock()?;
45 : let mut stmt = db.prepare("SELECT session_id, user_id FROM sessions LIMIT 1")?;
46 1 : let (_session_id, user_id): (String, i64) = stmt.query_row([], |row| {
47 1 : let sid = row.get("session_id")?;
48 1 : let uid = row.get("user_id")?;
49 1 : Ok((sid, uid))
50 1 : })?;
51 : assert_eq!(user_id, 1);
52 : let set_cookie_header = res.headers().get("set-cookie").unwrap();
53 : let mut cookie_iter = set_cookie_header.to_str().unwrap().split('=');
54 : assert_eq!(cookie_iter.next(), Some("sid"));
55 : Ok(())
56 : }
57 :
58 : #[gtest]
59 : #[tokio::test]
60 : async fn test_sign_in_wrong_password() -> Result<()> {
61 : let dbmux = setup_db(Some("testdata/test_sign_in.sql".into()));
62 : let app = setup_app(dbmux.clone());
63 : let mut params = HashMap::new();
64 : params.insert("username", "admin");
65 : params.insert("password", "wrongpass");
66 : let body = serde_urlencoded::to_string(params).unwrap();
67 : let req = Request::builder()
68 : .method("POST")
69 : .uri("/sign-in/")
70 : .header("Content-Type", "application/x-www-form-urlencoded")
71 : .body(body)?;
72 : let res = app.oneshot(req).await?;
73 : expect_that!(res.status(), eq(StatusCode::UNAUTHORIZED));
74 : Ok(())
75 : }
76 :
77 : #[tokio::test]
78 1 : async fn test_index() {
79 1 : let dbmux = setup_db(None);
80 1 : let app = setup_app(dbmux.clone());
81 1 : let req = Request::builder().method("GET").uri("/").body(Body::empty()).unwrap();
82 1 : let res = app.oneshot(req).await.unwrap();
83 :
84 1 : assert_eq!(res.status(), StatusCode::OK);
85 1 : }
86 :
87 : /// Sign-in as `username` using `pasword` and returns the session ID cookie.
88 3 : async fn sign_in(app: Router, username: &str, password: &str) -> Result<String> {
89 3 : let mut params = HashMap::new();
90 3 : params.insert("username", username);
91 3 : params.insert("password", password);
92 3 : let body = serde_urlencoded::to_string(params).unwrap();
93 3 : let req = Request::builder()
94 3 : .method("POST")
95 3 : .uri("/sign-in/")
96 3 : .header("Content-Type", "application/x-www-form-urlencoded")
97 3 : .body(body)?;
98 3 : let res = app.oneshot(req).await?;
99 3 : assert_eq!(res.status(), StatusCode::SEE_OTHER);
100 3 : let set_cookie_header = res.headers().get("set-cookie").unwrap();
101 3 : let sid = set_cookie_header.to_str()?.split('=').nth(1).unwrap();
102 3 : Ok(sid.to_string())
103 3 : }
104 :
105 : #[tokio::test]
106 1 : async fn test_new_article() -> Result<()> {
107 1 : let dbmux = setup_db(Some("testdata/signed_in_user.sql".into()));
108 1 : let app = setup_app(dbmux.clone());
109 1 : let sid = sign_in(app.clone(), "admin", "pass").await?;
110 1 : let req = Request::builder()
111 1 : .method("GET")
112 1 : .uri("/article/new")
113 1 : .header("Cookie", format!("sid={}", sid))
114 1 : .body(Body::empty())
115 1 : .unwrap();
116 1 : let res = app.oneshot(req).await.unwrap();
117 1 : assert_eq!(res.status(), StatusCode::OK);
118 2 : Ok(())
119 1 : }
120 :
121 : #[tokio::test]
122 1 : async fn test_create_article() -> Result<()> {
123 1 : let dbmux = setup_db(Some("testdata/signed_in_user.sql".into()));
124 1 : let app = setup_app(dbmux.clone());
125 1 : let mut params = HashMap::new();
126 1 : params.insert("title", "Some Title");
127 1 : params.insert("content", "Cool post");
128 1 : let body = serde_urlencoded::to_string(params).unwrap();
129 1 : let sid = sign_in(app.clone(), "admin", "pass").await?;
130 1 : let req = Request::builder()
131 1 : .method("POST")
132 1 : .uri("/article/")
133 1 : .header("Content-Type", "application/x-www-form-urlencoded")
134 1 : .header("Cookie", format!("sid={}", sid))
135 1 : .body(body)
136 1 : .unwrap();
137 1 : let res = app.oneshot(req).await.unwrap();
138 :
139 1 : assert_eq!(res.status(), StatusCode::SEE_OTHER);
140 1 : let db = dbmux.lock().unwrap();
141 1 : let mut stmt = db.prepare("SELECT p.page_id, p.title, r.content FROM pages p NATURAL JOIN revisions r ORDER BY r.rev_id DESC LIMIT 1").unwrap();
142 1 : let mut rows = stmt.query([]).unwrap();
143 1 : if let Some(row) = rows.next().unwrap() {
144 1 : assert_eq!(row.get("page_id"), Ok(1));
145 1 : assert_eq!(row.get("title"), Ok("Some Title".to_string()));
146 1 : assert_eq!(row.get("content"), Ok("Cool post".to_string()));
147 1 : }
148 1 : Ok(())
149 1 : }
150 :
151 : #[tokio::test]
152 1 : async fn test_show_article() {
153 1 : let dbmux = setup_db(Some("testdata/test_show_article.sql".into()));
154 1 : let app = setup_app(dbmux.clone());
155 1 : let req = Request::builder().method("GET").uri("/article/1/").body(Body::empty()).unwrap();
156 1 : let res = app.oneshot(req).await.unwrap();
157 :
158 1 : assert_eq!(res.status(), StatusCode::OK)
159 1 : }
160 :
161 : #[tokio::test]
162 1 : async fn test_show_article_not_found() {
163 1 : let dbmux = setup_db(None);
164 1 : let app = setup_app(dbmux.clone());
165 1 : let req = Request::builder().method("GET").uri("/article/100/").body(Body::empty()).unwrap();
166 1 : let res = app.oneshot(req).await.unwrap();
167 :
168 1 : assert_eq!(res.status(), StatusCode::NOT_FOUND)
169 1 : }
170 :
171 : #[tokio::test]
172 1 : async fn test_upload() -> Result<()> {
173 1 : let dbmux = setup_db(Some("testdata/signed_in_user.sql".into()));
174 1 : let app = setup_app(dbmux.clone());
175 1 : let form =
176 1 : reqwest::multipart::Form::new().file("favicon", "./public/favicon.ico").await.unwrap();
177 1 : let boundary = form.boundary().to_string();
178 1 : let stream = form.into_stream();
179 1 : let body = Body::from_stream(stream);
180 1 : let sid = sign_in(app.clone(), "admin", "pass").await?;
181 1 : let req = Request::builder()
182 1 : .method("POST")
183 1 : .uri("/upload/")
184 1 : .header("Content-Type", format!("multipart/form-data; boundary={}", boundary))
185 1 : .header("Cookie", format!("sid={}", sid))
186 1 : .body(body)
187 1 : .unwrap();
188 1 : let res = app.oneshot(req).await.unwrap();
189 :
190 1 : assert_eq!(res.status(), StatusCode::OK);
191 1 : let bytes = res.into_body().collect().await?.to_bytes();
192 1 : let buf = String::from_utf8(bytes.to_vec())?;
193 1 : let json: serde_json::Value = serde_json::from_str(buf.as_str())?;
194 1 : assert_eq!(json["file_id"], 1);
195 1 : assert_eq!(json["filename"], "favicon.ico".to_string());
196 1 : assert_eq!(json["url"], "/media/1".to_string());
197 :
198 1 : let db = dbmux.lock()?;
199 1 : let mut stmt = db.prepare("SELECT file_id, digest FROM files LIMIT 1").unwrap();
200 1 : let (file_id, digest): (i64, String) = stmt.query_row([], |row| {
201 1 : let file_id = row.get("file_id")?;
202 1 : let digest = row.get("digest")?;
203 1 : Ok((file_id, digest))
204 1 : })?;
205 1 : assert_eq!(file_id, 1);
206 1 : assert_eq!(
207 : digest,
208 1 : "C9D25FC03F47AA1062C344781A66E680D558F0563CFB5522FF57863F027637BA".to_string()
209 : );
210 2 : Ok(())
211 1 : }
212 :
213 : #[tokio::test]
214 1 : async fn test_media_show() -> Result<()> {
215 1 : let dbmux = setup_db(None);
216 : {
217 1 : let db = dbmux.lock()?;
218 1 : let mut stmt = db.prepare("INSERT INTO files (filename, mimetype, digest) VALUES ('favicon.ico', 'image/x-icon', 'C9D25FC03F47AA1062C344781A66E680D558F0563CFB5522FF57863F027637BA')").unwrap();
219 1 : let _ = stmt.execute([]);
220 1 : fs::copy(
221 : "./public/favicon.ico",
222 : "/tmp/C9D25FC03F47AA1062C344781A66E680D558F0563CFB5522FF57863F027637BA",
223 0 : )?;
224 : }
225 1 : let app = setup_app(dbmux.clone());
226 1 : let req = Request::builder().method("GET").uri("/media/1").body(Body::empty()).unwrap();
227 1 : let res = app.oneshot(req).await.unwrap();
228 1 : assert_eq!(res.status(), StatusCode::OK);
229 2 : Ok(())
230 1 : }
|