LCOV - code coverage report
Current view: top level - src/handler - tests.rs (source / functions) Coverage Total Hit
Test: bliki.lcov Lines: 99.3 % 139 138
Test Date: 2025-11-27 15:46:07 Functions: 100.0 % 20 20

            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 : }
        

Generated by: LCOV version 2.0-1