Skip to content
use actix_web::{
    HttpRequest, HttpResponse, Responder, get,
    http::header::{CACHE_CONTROL, HeaderValue},
    web,
};

use crate::config;

const HTMX: &str = include_str!("../assets/h.js");
const DAISYUI: &str = include_str!("../assets/daisyui.css");
const APP_CSS: &str = include_str!("../assets/app.css");

#[get("/assets/{filename:.*}")]
pub async fn assets(req: HttpRequest, config: web::Data<config::Config>) -> impl Responder {
    let path = req.match_info().query("filename");
    let cache_control = config
        .cache_control
        .clone()
        .unwrap_or_else(|| HeaderValue::from_static(config::DEFAULT_CACHE_CONTROL));

    match path {
        "h.js" => HttpResponse::Ok()
            .content_type("application/javascript; charset=utf-8")
            .insert_header((CACHE_CONTROL, cache_control))
            .body(HTMX),
        "daisyui.css" | "daisyui" => HttpResponse::Ok()
            .content_type("text/css; charset=utf-8")
            .insert_header((CACHE_CONTROL, cache_control))
            .body(DAISYUI),
        "app.css" => HttpResponse::Ok()
            .content_type("text/css; charset=utf-8")
            .insert_header((CACHE_CONTROL, cache_control))
            .body(APP_CSS),
        _ => HttpResponse::NotFound().body("Not found"),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use actix_web::{http::header::HeaderValue, test as aw_test, web};

    #[actix_web::test]
    async fn test_assets_handler_known_files() {
        let config = config::Config::new(
            8080,
            HeaderValue::from_static(config::DEFAULT_CACHE_CONTROL),
            "notify.db".to_string(),
            false,
            "123456".to_string(),
        );
        let app = aw_test::init_service(
            actix_web::App::new()
                .app_data(web::Data::new(config))
                .service(assets),
        )
        .await;

        for (path, expected_type) in [
            ("/assets/h.js", "application/javascript"),
            ("/assets/daisyui.css", "text/css"),
            ("/assets/daisyui", "text/css"),
            ("/assets/app.css", "text/css"),
        ] {
            let req = aw_test::TestRequest::get().uri(path).to_request();
            let resp = aw_test::call_service(&app, req).await;
            assert!(resp.status().is_success(), "{path} should succeed");
            assert!(
                resp.headers()
                    .get("content-type")
                    .unwrap()
                    .to_str()
                    .unwrap()
                    .starts_with(expected_type),
                "{path} should have content type {expected_type}"
            );
            assert_eq!(
                resp.headers()
                    .get("cache-control")
                    .unwrap()
                    .to_str()
                    .unwrap(),
                config::DEFAULT_CACHE_CONTROL
            );
        }
    }

    #[actix_web::test]
    async fn test_assets_handler_when_cache_control_is_none() {
        let config = config::Config::new(
            8080,
            None,
            "notify.db".to_string(),
            false,
            "123456".to_string(),
        );
        let app = aw_test::init_service(
            actix_web::App::new()
                .app_data(web::Data::new(config))
                .service(assets),
        )
        .await;

        let req = aw_test::TestRequest::get().uri("/assets/h.js").to_request();
        let resp = aw_test::call_service(&app, req).await;
        assert!(resp.status().is_success());
        assert_eq!(
            resp.headers()
                .get("cache-control")
                .unwrap()
                .to_str()
                .unwrap(),
            config::DEFAULT_CACHE_CONTROL
        );
    }

    #[actix_web::test]
    async fn test_assets_handler_when_cache_control_is_no_cache() {
        let config = config::Config::new(
            8080,
            HeaderValue::from_static("no-cache"),
            "notify.db".to_string(),
            false,
            "123456".to_string(),
        );
        let app = aw_test::init_service(
            actix_web::App::new()
                .app_data(web::Data::new(config))
                .service(assets),
        )
        .await;

        let req = aw_test::TestRequest::get()
            .uri("/assets/app.css")
            .to_request();
        let resp = aw_test::call_service(&app, req).await;
        assert!(resp.status().is_success());
        assert_eq!(
            resp.headers()
                .get("cache-control")
                .unwrap()
                .to_str()
                .unwrap(),
            "no-cache"
        );
    }

    #[actix_web::test]
    async fn test_assets_handler_unknown_file_returns_404() {
        let config = config::Config::new(
            8080,
            HeaderValue::from_static(config::DEFAULT_CACHE_CONTROL),
            "notify.db".to_string(),
            false,
            "123456".to_string(),
        );
        let app = aw_test::init_service(
            actix_web::App::new()
                .app_data(web::Data::new(config))
                .service(assets),
        )
        .await;

        let req = aw_test::TestRequest::get()
            .uri("/assets/unknown.txt")
            .to_request();
        let resp = aw_test::call_service(&app, req).await;
        assert_eq!(resp.status(), actix_web::http::StatusCode::NOT_FOUND);
    }

    #[test]
    fn test_app_css_contains_necessary_utility_classes() {
        assert!(APP_CSS.contains(".send-feedback-visible"));
        assert!(APP_CSS.contains(".size-4"));
        assert!(APP_CSS.contains(".size-12"));
        assert!(APP_CSS.contains(".shrink-0"));
        assert!(APP_CSS.contains(".grid"));
        assert!(APP_CSS.contains(".md\\:grid-cols-2"));
        assert!(APP_CSS.contains(".max-w-5xl"));
        assert!(APP_CSS.contains(".bg-primary\\/10"));
        assert!(APP_CSS.contains(".text-base-content\\/70"));
    }
}