chore: import upstream snapshot with attribution
CI / compile_and_lint (push) Failing after 0s
CI / docker_build (linux/amd64, -linux-amd64-duckdb, duckdb) (push) Failing after 0s
CI / docker_build (linux/arm64, -linux-arm64, minimal) (push) Failing after 2s
CI / docker_build (linux/arm64, -linux-arm64-duckdb, duckdb) (push) Failing after 1s
CI / docker_build (linux/amd64, minimal) (push) Failing after 1s
CI / test (, sqlite, sqlite::memory:) (push) Has been skipped
CI / test (mssql, mssql, mssql://root:Password123!@127.0.0.1/sqlpage) (push) Has been skipped
CI / test (mysql, mysql, mysql://root:Password123!@127.0.0.1/sqlpage) (push) Has been skipped
CI / test (oracle, oracle, Driver=Oracle 21 ODBC driver;Dbq=//127.0.0.1:1521/FREEPDB1;Uid=root;Pwd=Password123!) (push) Has been skipped
CI / test (postgres, odbc, Driver=PostgreSQL Unicode;Server=127.0.0.1;Port=5432;Database=sqlpage;UID=root;PWD=Password123!, true) (push) Has been skipped
CI / test (postgres, postgres, postgres://root:Password123!@127.0.0.1/sqlpage) (push) Has been skipped
CI / playwright (push) Has been skipped
CI / docker_build (linux/arm/v7, -linux-arm-v7, minimal) (push) Failing after 0s
CI / hurl_examples (push) Failing after 8s
deploy website / deploy_official_site (push) Failing after 1s
CI / hurl (${{ matrix.example }}) (push) Has been skipped
CI / docker_push (duckdb) (push) Has been cancelled
CI / docker_push (minimal) (push) Has been cancelled
CI / windows_test (push) Has been cancelled
CI / compile_and_lint (push) Failing after 0s
CI / docker_build (linux/amd64, -linux-amd64-duckdb, duckdb) (push) Failing after 0s
CI / docker_build (linux/arm64, -linux-arm64, minimal) (push) Failing after 2s
CI / docker_build (linux/arm64, -linux-arm64-duckdb, duckdb) (push) Failing after 1s
CI / docker_build (linux/amd64, minimal) (push) Failing after 1s
CI / test (, sqlite, sqlite::memory:) (push) Has been skipped
CI / test (mssql, mssql, mssql://root:Password123!@127.0.0.1/sqlpage) (push) Has been skipped
CI / test (mysql, mysql, mysql://root:Password123!@127.0.0.1/sqlpage) (push) Has been skipped
CI / test (oracle, oracle, Driver=Oracle 21 ODBC driver;Dbq=//127.0.0.1:1521/FREEPDB1;Uid=root;Pwd=Password123!) (push) Has been skipped
CI / test (postgres, odbc, Driver=PostgreSQL Unicode;Server=127.0.0.1;Port=5432;Database=sqlpage;UID=root;PWD=Password123!, true) (push) Has been skipped
CI / test (postgres, postgres, postgres://root:Password123!@127.0.0.1/sqlpage) (push) Has been skipped
CI / playwright (push) Has been skipped
CI / docker_build (linux/arm/v7, -linux-arm-v7, minimal) (push) Failing after 0s
CI / hurl_examples (push) Failing after 8s
deploy website / deploy_official_site (push) Failing after 1s
CI / hurl (${{ matrix.example }}) (push) Has been skipped
CI / docker_push (duckdb) (push) Has been cancelled
CI / docker_push (minimal) (push) Has been cancelled
CI / windows_test (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
use actix_web::{http::StatusCode, test};
|
||||
use serde_json::json;
|
||||
use sqlpage::webserver::http::main_handler;
|
||||
|
||||
use crate::common::get_request_to;
|
||||
|
||||
#[actix_web::test]
|
||||
async fn test_request_body() -> actix_web::Result<()> {
|
||||
let req = get_request_to("/tests/requests/request_body_test.sql")
|
||||
.await?
|
||||
.insert_header(("content-type", "text/plain"))
|
||||
.set_payload("Hello, world!")
|
||||
.to_srv_request();
|
||||
let resp = main_handler(req).await?;
|
||||
|
||||
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("Hello, world!"),
|
||||
"{body_str}\nexpected to contain: Hello, world!"
|
||||
);
|
||||
|
||||
// Test with form data - should return NULL
|
||||
let req = get_request_to("/tests/requests/request_body_test.sql")
|
||||
.await?
|
||||
.insert_header(("content-type", "application/x-www-form-urlencoded"))
|
||||
.set_payload("key=value")
|
||||
.to_srv_request();
|
||||
let resp = main_handler(req).await?;
|
||||
|
||||
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("NULL"),
|
||||
"{body_str}\nexpected NULL for form data"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
async fn test_request_body_base64() -> actix_web::Result<()> {
|
||||
let binary_data = (0u8..=255u8).collect::<Vec<_>>();
|
||||
let expected_base64 =
|
||||
base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &binary_data);
|
||||
|
||||
let req = get_request_to("/tests/requests/request_body_base64_test.sql")
|
||||
.await?
|
||||
.insert_header(("content-type", "application/octet-stream"))
|
||||
.set_payload(binary_data)
|
||||
.to_srv_request();
|
||||
let resp = main_handler(req).await?;
|
||||
|
||||
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(&expected_base64),
|
||||
"{body_str}\nexpected to contain base64: {expected_base64}"
|
||||
);
|
||||
|
||||
// Test with form data - should return NULL
|
||||
let req = get_request_to("/tests/requests/request_body_base64_test.sql")
|
||||
.await?
|
||||
.insert_header(("content-type", "application/x-www-form-urlencoded"))
|
||||
.set_payload("key=value")
|
||||
.to_srv_request();
|
||||
let resp = main_handler(req).await?;
|
||||
|
||||
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("NULL"),
|
||||
"{body_str}\nexpected NULL for form data"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
async fn test_download_data_url() -> actix_web::Result<()> {
|
||||
let req = get_request_to("/tests/requests/request_download_test.sql")
|
||||
.await?
|
||||
.to_srv_request();
|
||||
let resp = main_handler(req).await?;
|
||||
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let ct = resp.headers().get("content-type").unwrap();
|
||||
assert_eq!(ct, "text/plain");
|
||||
let content_disposition = resp.headers().get("content-disposition").unwrap();
|
||||
assert_eq!(
|
||||
content_disposition,
|
||||
"attachment; filename=\"my text file.txt\""
|
||||
);
|
||||
let body = test::read_body(resp).await;
|
||||
assert_eq!(&body, &b"Hello download!"[..]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
async fn test_large_form_field_roundtrip() -> actix_web::Result<()> {
|
||||
let long_string = "a".repeat(123454);
|
||||
let req = get_request_to("/tests/components/display_form_field.sql")
|
||||
.await?
|
||||
.insert_header(("content-type", "application/x-www-form-urlencoded"))
|
||||
.set_payload(["x=", &long_string].concat()) // total size is 123454 + 2 = 123456 bytes
|
||||
.to_srv_request();
|
||||
let resp = main_handler(req).await?;
|
||||
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("error"),
|
||||
"{body_str}\nshouldn't have errors"
|
||||
);
|
||||
assert!(
|
||||
body_str.contains(&long_string),
|
||||
"{body_str}\nexpected to contain long string submitted"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
async fn test_variables_function() -> actix_web::Result<()> {
|
||||
let url = "/tests/requests/variables.sql?common=get_value&get_only=get_val";
|
||||
let req_body = "common=post_value&post_only=post_val";
|
||||
let req = get_request_to(url)
|
||||
.await?
|
||||
.insert_header(("content-type", "application/x-www-form-urlencoded"))
|
||||
.insert_header(("accept", "application/json"))
|
||||
.set_payload(req_body)
|
||||
.to_srv_request();
|
||||
let resp = main_handler(req).await?;
|
||||
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let body_json: serde_json::Value = test::read_body_json(resp).await;
|
||||
|
||||
let expected = [
|
||||
[
|
||||
(
|
||||
"all_vars",
|
||||
json!({"get_only": "get_val", "common": "get_value", "post_only": "post_val", "common": "post_value"}),
|
||||
),
|
||||
(
|
||||
"get_vars",
|
||||
json!({"get_only": "get_val", "common": "get_value"}),
|
||||
),
|
||||
(
|
||||
"post_vars",
|
||||
json!({"post_only": "post_val", "common": "post_value"}),
|
||||
),
|
||||
("set_vars", json!({})),
|
||||
],
|
||||
[
|
||||
(
|
||||
"all_vars",
|
||||
json!({"get_only": "get_val", "common": "set_common_value", "post_only": "post_val", "my_set_var": "set_value"}),
|
||||
),
|
||||
(
|
||||
"get_vars",
|
||||
json!({"get_only": "get_val", "common": "get_value"}),
|
||||
),
|
||||
(
|
||||
"post_vars",
|
||||
json!({"post_only": "post_val", "common": "post_value"}),
|
||||
),
|
||||
(
|
||||
"set_vars",
|
||||
json!({"common": "set_common_value", "my_set_var": "set_value"}),
|
||||
),
|
||||
],
|
||||
];
|
||||
|
||||
let actual_array = body_json.as_array().expect("response is nota json array");
|
||||
for (i, expected_step) in expected.into_iter().enumerate() {
|
||||
let actual = &actual_array[i];
|
||||
for (key, expected_value) in expected_step {
|
||||
let actual_decoded: serde_json::Value =
|
||||
serde_json::from_str(actual[key].as_str().unwrap()).unwrap();
|
||||
assert_eq!(
|
||||
actual_decoded, expected_value,
|
||||
"step {i}: {key} mismatch: {actual_decoded:#} != {expected_value:#}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
async fn test_invalid_utf8_multipart_text_field_returns_bad_request() -> actix_web::Result<()> {
|
||||
let req = get_request_to("/tests/requests/variables.sql")
|
||||
.await?
|
||||
.insert_header(("content-type", "multipart/form-data; boundary=1234567890"))
|
||||
.set_payload(
|
||||
b"--1234567890\r\n\
|
||||
Content-Disposition: form-data; name=\"x\"\r\n\
|
||||
Content-Type: text/plain\r\n\
|
||||
\r\n\
|
||||
\xff\r\n\
|
||||
--1234567890--\r\n"
|
||||
.as_slice(),
|
||||
)
|
||||
.to_srv_request();
|
||||
let status = match main_handler(req).await {
|
||||
Ok(resp) => resp.status(),
|
||||
Err(err) => err.as_response_error().status_code(),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
status,
|
||||
StatusCode::BAD_REQUEST,
|
||||
"assertion error, expected 400 bad request on invalid utf8 payload, got {}",
|
||||
status
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
async fn test_missing_multipart_content_disposition_returns_bad_request() -> actix_web::Result<()> {
|
||||
let req = get_request_to("/tests/requests/variables.sql")
|
||||
.await?
|
||||
.insert_header(("content-type", "multipart/form-data; boundary=1234567890"))
|
||||
.set_payload(
|
||||
b"--1234567890\r\n\
|
||||
Content-Type: text/plain\r\n\
|
||||
\r\n\
|
||||
hello\r\n\
|
||||
--1234567890--\r\n"
|
||||
.as_slice(),
|
||||
)
|
||||
.to_srv_request();
|
||||
let status = match main_handler(req).await {
|
||||
Ok(resp) => resp.status(),
|
||||
Err(err) => err.as_response_error().status_code(),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
status,
|
||||
StatusCode::BAD_REQUEST,
|
||||
"expected 400 bad request on malformed multipart payload, got {}",
|
||||
status
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
mod webhook_hmac;
|
||||
@@ -0,0 +1,2 @@
|
||||
select 'shell-empty' as component,
|
||||
coalesce(sqlpage.request_body_base64(), 'NULL') as html;
|
||||
@@ -0,0 +1,2 @@
|
||||
select 'shell-empty' as component,
|
||||
coalesce(sqlpage.request_body(), 'NULL') as html;
|
||||
@@ -0,0 +1,6 @@
|
||||
select 'download' as component,
|
||||
'data:text/plain;base64,SGVsbG8gZG93bmxvYWQh' as data_url,
|
||||
'my text file.txt' as filename;
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
select
|
||||
sqlpage.variables() as all_vars,
|
||||
sqlpage.variables('get') as get_vars,
|
||||
sqlpage.variables('post') as post_vars,
|
||||
sqlpage.variables('set') as set_vars;
|
||||
|
||||
set my_set_var = 'set_value';
|
||||
set common = 'set_common_value';
|
||||
|
||||
select
|
||||
sqlpage.variables() as all_vars,
|
||||
sqlpage.variables('get') as get_vars,
|
||||
sqlpage.variables('post') as post_vars,
|
||||
sqlpage.variables('set') as set_vars;
|
||||
@@ -0,0 +1,99 @@
|
||||
use actix_web::{http::StatusCode, test};
|
||||
use sqlpage::webserver::http::main_handler;
|
||||
|
||||
use crate::common::get_request_to;
|
||||
|
||||
#[actix_web::test]
|
||||
async fn test_webhook_hmac_invalid_signature() -> actix_web::Result<()> {
|
||||
// Set up environment variable for webhook secret
|
||||
unsafe {
|
||||
std::env::set_var("WEBHOOK_SECRET", "test-secret-key");
|
||||
}
|
||||
|
||||
let webhook_body = r#"{"order_id":12345,"total":"99.99"}"#;
|
||||
let invalid_signature = "96a5f6f65c85a2d4d1f3a37813ab2c0b44041bdc17691fbb0884e3eb52b7c54b";
|
||||
|
||||
let req = get_request_to("/tests/webhook_hmac_validation.sql")
|
||||
.await?
|
||||
.insert_header(("content-type", "application/json"))
|
||||
.insert_header(("X-Webhook-Signature", invalid_signature))
|
||||
.set_payload(webhook_body)
|
||||
.to_srv_request();
|
||||
|
||||
let resp = main_handler(req).await?;
|
||||
|
||||
// Should redirect to error page when signature is invalid
|
||||
assert!(
|
||||
resp.status() == StatusCode::FOUND || resp.status() == StatusCode::SEE_OTHER,
|
||||
"Expected redirect (302 or 303) for invalid signature, got: {}",
|
||||
resp.status()
|
||||
);
|
||||
|
||||
let location = resp
|
||||
.headers()
|
||||
.get("location")
|
||||
.expect("Should have Location header")
|
||||
.to_str()
|
||||
.unwrap();
|
||||
assert_eq!(location, "/error.sql?err=bad_webhook_signature");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
async fn test_webhook_hmac_valid_signature() -> actix_web::Result<()> {
|
||||
// Set up environment variable for webhook secret
|
||||
unsafe {
|
||||
std::env::set_var("WEBHOOK_SECRET", "test-secret-key");
|
||||
}
|
||||
|
||||
let webhook_body = r#"{"order_id":12345,"total":"99.99"}"#;
|
||||
let valid_signature = "260b3b5ead84843645588af82d5d2c3fe24c598a950d36c45438c3a5f5bb941c";
|
||||
|
||||
let req = get_request_to("/tests/webhook_hmac_validation.sql")
|
||||
.await?
|
||||
.insert_header(("content-type", "application/json"))
|
||||
.insert_header(("X-Webhook-Signature", valid_signature))
|
||||
.set_payload(webhook_body)
|
||||
.to_srv_request();
|
||||
|
||||
let resp = main_handler(req).await?;
|
||||
|
||||
// Should return success when signature is valid
|
||||
assert_eq!(resp.status(), StatusCode::OK, "200 resp for signed req");
|
||||
assert!(!resp.headers().contains_key("location"), "no redirect");
|
||||
|
||||
assert_eq!(
|
||||
test::read_body_json::<serde_json::Value, _>(resp).await,
|
||||
serde_json::json! ({"msg": "Webhook signature is valid !"})
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
async fn test_webhook_hmac_missing_signature() -> actix_web::Result<()> {
|
||||
// Set up environment variable for webhook secret
|
||||
unsafe {
|
||||
std::env::set_var("WEBHOOK_SECRET", "test-secret-key");
|
||||
}
|
||||
|
||||
let webhook_body = r#"{"order_id":12345,"total":"99.99"}"#;
|
||||
|
||||
// Don't include the X-Webhook-Signature header
|
||||
let req = get_request_to("/tests/webhook_hmac_validation.sql")
|
||||
.await?
|
||||
.insert_header(("content-type", "application/json"))
|
||||
.set_payload(webhook_body)
|
||||
.to_srv_request();
|
||||
|
||||
let resp = main_handler(req).await?;
|
||||
|
||||
let location = resp
|
||||
.headers()
|
||||
.get("location")
|
||||
.expect("Should have Location header")
|
||||
.to_str()
|
||||
.unwrap();
|
||||
assert_eq!(location, "/error.sql?err=bad_webhook_signature");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user