Files

285 lines
9.4 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use actix_web::{http::StatusCode, test};
use sqlpage::{
AppState,
webserver::{self, make_placeholder},
};
use sqlx::executor::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::<Vec<_>>();
let results = futures_util::future::join_all(reqs).await;
for result in results {
let resp = result.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let body = test::read_body(resp).await;
assert!(
body.starts_with(b"<!DOCTYPE html>"),
"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_datagrid_description_presence_controls_placeholder() {
let resp = req_path("/tests/components/datagrid_icon_only.sql")
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let body = String::from_utf8(test::read_body(resp).await.to_vec()).unwrap();
assert!(body.contains("Facebook"), "{body}");
assert!(body.contains("Empty"), "{body}");
assert!(body.contains("Missing"), "{body}");
assert!(body.contains("<svg"), "{body}");
assert_eq!(body.matches('').count(), 1, "{body}");
}
#[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,
webserver::database::SupportedDatabase::Oracle
) {
return;
}
let create_table_sql =
sqlpage::filesystem::DbFsQueries::get_create_table_sql(state.db.info.database_type);
// Other tests share this database, so never drop a table their initialized state may use.
if state
.db
.connection
.execute("SELECT 1 FROM sqlpage_files WHERE 1 = 0")
.await
.is_err()
{
state.db.connection.execute(create_table_sql).await.unwrap();
}
state
.db
.connection
.execute("DELETE FROM sqlpage_files WHERE path = 'on_db.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::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(
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::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 = 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 = 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#"<button type="submit" form="poem" formaction="?action"#),
"{body_str}\nexpected to contain a button with formaction"
);
}
#[actix_web::test]
async fn test_official_website_basic_auth_example() {
let resp = req_path_with_app_data(
"/examples/authentication/basic_auth.sql",
make_app_data_for_official_website().await,
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
let body = test::read_body(resp).await;
let body_str = String::from_utf8(body.to_vec()).unwrap();
assert!(
body_str.contains("Unauthorized"),
"{body_str}\nexpected to contain Unauthorized"
);
}
async fn make_app_data_for_official_website() -> actix_web::web::Data<AppState> {
crate::common::init_log();
let config_path = std::path::Path::new("examples/official-site/sqlpage");
let mut app_config = sqlpage::app_config::load_from_directory(config_path).unwrap();
app_config.web_root = std::path::PathBuf::from("examples/official-site");
app_config.database_url = "sqlite::memory:".to_string();
let app_state = make_app_data_from_config(app_config.clone()).await;
webserver::database::migrations::apply(&app_config, &app_state.db)
.await
.unwrap();
app_state
}