use actix_web::{http::StatusCode, test}; use sqlpage::{ AppState, webserver::{self, make_placeholder}, }; use sqlx::Executor as _; use crate::common::{make_app_data_from_config, req_path, req_path_with_app_data, test_config}; #[actix_web::test] async fn test_concurrent_requests() { let components = [ "table", "form", "card", "datagrid", "hero", "list", "timeline", ]; let app_data = make_app_data_from_config(test_config()).await; let reqs = (0..64) .map(|i| { let component = components[i % components.len()]; req_path_with_app_data( format!("/tests/components/any_component.sql?component={component}"), app_data.clone(), ) }) .collect::>(); let results = futures_util::future::join_all(reqs).await; for result in results.into_iter() { let resp = result.unwrap(); assert_eq!(resp.status(), StatusCode::OK); let body = test::read_body(resp).await; assert!( body.starts_with(b""), "Expected html doctype" ); let body = String::from_utf8(body.to_vec()).unwrap(); assert!( body.contains("It works !"), "Expected to contain: It works !, but got: {body}" ); assert!(!body.contains("error")); } } #[actix_web::test] async fn test_routing_with_db_fs() { let mut config = test_config(); if config.database_url.contains("memory") { return; } config.site_prefix = "/prefix/".to_string(); let state = AppState::init(&config).await.unwrap(); if matches!( state.db.info.database_type, sqlpage::webserver::database::SupportedDatabase::Oracle ) { return; } let drop_sql = "DROP TABLE IF EXISTS sqlpage_files"; state.db.connection.execute(drop_sql).await.unwrap(); let create_table_sql = sqlpage::filesystem::DbFsQueries::get_create_table_sql(state.db.info.database_type); state.db.connection.execute(create_table_sql).await.unwrap(); let insert_sql = format!( "INSERT INTO sqlpage_files(path, contents) VALUES ('on_db.sql', {})", make_placeholder(state.db.info.kind, 1) ); sqlx::query(&insert_sql) .bind("select ''text'' as component, ''Hi from db !'' AS contents;".as_bytes()) .execute(&state.db.connection) .await .unwrap(); let state = AppState::init(&config).await.unwrap(); let app_data = actix_web::web::Data::new(state); let resp = req_path_with_app_data("/prefix/on_db.sql", app_data.clone()) .await .unwrap(); assert_eq!(resp.status(), StatusCode::OK); let body = test::read_body(resp).await; let body_str = String::from_utf8(body.to_vec()).unwrap(); assert!( body_str.contains("Hi from db !"), "{body_str}\nexpected to contain: Hi from db !" ); } #[cfg(unix)] #[actix_web::test] async fn test_non_unicode_static_path_returns_bad_request_with_db_fs() { let mut config = test_config(); if !config.database_url.starts_with("sqlite") { return; } config.database_url = "sqlite://file:test_non_unicode_static_path?mode=memory&cache=shared".to_string(); let state = AppState::init(&config).await.unwrap(); let expected_db_path = "\u{FFFD}.txt"; let mut conn = state.db.connection.acquire().await.unwrap(); (&mut *conn) .execute(sqlpage::filesystem::DbFsQueries::get_create_table_sql( sqlpage::webserver::database::SupportedDatabase::Sqlite, )) .await .unwrap(); let insert_sql = format!( "INSERT INTO sqlpage_files(path, contents) VALUES ({}, {})", make_placeholder(state.db.info.kind, 1), make_placeholder(state.db.info.kind, 2) ); sqlx::query(&insert_sql) .bind(expected_db_path) .bind("file from db fs".as_bytes()) .execute(&mut *conn) .await .unwrap(); drop(conn); let state = AppState::init(&config).await.unwrap(); let app_data = actix_web::web::Data::new(state); let req = test::TestRequest::get() .uri("/%FF.txt") .app_data(app_data) .to_srv_request(); let err = sqlpage::webserver::http::main_handler(req) .await .expect_err("non-unicode path should not panic and must return bad request"); assert_eq!( err.as_response_error().status_code(), StatusCode::BAD_REQUEST ); } #[actix_web::test] async fn test_routing_with_prefix() { let mut config = test_config(); config.site_prefix = "/prefix/".to_string(); let state = AppState::init(&config).await.unwrap(); let app_data = actix_web::web::Data::new(state); let resp = req_path_with_app_data( "/prefix/tests/sql_test_files/component_rendering/simple.sql", app_data.clone(), ) .await .unwrap(); assert_eq!(resp.status(), StatusCode::OK); let body = test::read_body(resp).await; let body_str = String::from_utf8(body.to_vec()).unwrap(); assert!( body_str.contains("It works !"), "{body_str}\nexpected to contain: It works !" ); assert!( body_str.contains("href=\"/prefix/"), "{body_str}\nexpected to contain links with site prefix" ); let resp = req_path_with_app_data("/prefix/nonexistent.sql", app_data.clone()) .await .expect("should handle 404"); let body = test::read_body(resp).await; let body_str = String::from_utf8(body.to_vec()).unwrap(); assert!( body_str.contains("404"), "Response should contain \"404\", but got:\n{body_str}" ); let resp = req_path_with_app_data("/prefix/sqlpage/migrations/0001_init.sql", app_data.clone()) .await .expect_err("Expected forbidden error") .to_string(); assert!(resp.to_lowercase().contains("forbidden"), "{resp}"); let resp = req_path_with_app_data( "/tests/sql_test_files/component_rendering/simple.sql", app_data, ) .await .unwrap(); assert_eq!(resp.status(), StatusCode::MOVED_PERMANENTLY); let location = resp .headers() .get("location") .expect("location header should be present"); assert_eq!(location.to_str().unwrap(), "/prefix/"); } #[actix_web::test] async fn test_hidden_files() { let resp_result = req_path("/tests/core/.hidden.sql").await; assert!( resp_result.is_err(), "Accessing a hidden file should be forbidden, but received success: {resp_result:?}" ); let resp = resp_result.unwrap_err().error_response(); assert_eq!(resp.status(), StatusCode::FORBIDDEN); let srv_resp = actix_web::test::TestRequest::default().to_srv_response(resp); let body = test::read_body(srv_resp).await; assert!( String::from_utf8_lossy(&body) .to_lowercase() .contains("forbidden"), ); } #[actix_web::test] async fn test_official_website_documentation() { let app_data = make_app_data_for_official_website().await; let resp = req_path_with_app_data("/component.sql?component=button", app_data) .await .unwrap_or_else(|e| { panic!("Failed to get response for /component.sql?component=button: {e}") }); assert_eq!(resp.status(), StatusCode::OK); let body = test::read_body(resp).await; let body_str = String::from_utf8(body.to_vec()).unwrap(); assert!( body_str.contains(r#"