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,18 @@
|
||||
The sql files in this folder are all tested automatically. They are organized in
|
||||
two subdirectories:
|
||||
|
||||
## `component_rendering/`
|
||||
|
||||
Files that depend on SQLPage's HTML rendering (components, shells, redirects,
|
||||
etc.). Every file that does not start with `error_` must render a page that
|
||||
contains the text "It works !" and no occurrence of the word "error" (case
|
||||
insensitive). `error_` files should return a page containing the word "error"
|
||||
and the rest of the file name. Files may include `nosqlite`, `nomssql`,
|
||||
`nopostgres` or `nomysql` in their name to skip incompatible backends.
|
||||
|
||||
## `data/`
|
||||
|
||||
Files that only validate data-processing functions should live here. They must
|
||||
return rows with an `actual` column plus either `expected` (exact match) or
|
||||
`expected_contains` (substring match). Tests in this directory are fetched as
|
||||
JSON and validated row by row.
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
-- this test is skipped on MSSQL and postgres because their json_object function has a different syntax
|
||||
select 'columns' as component;
|
||||
select
|
||||
JSON_OBJECT('description', 'It works !') as item,
|
||||
JSON_OBJECT('description', 'It works !') as item
|
||||
;
|
||||
@@ -0,0 +1,129 @@
|
||||
select 'dynamic' as component,'{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"dynamic","properties":
|
||||
{"component":"text", "contents": "It works ! (but it shouldn''t)"}
|
||||
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
|
||||
' as properties;
|
||||
@@ -0,0 +1 @@
|
||||
select 'yes' as "It works !";
|
||||
@@ -0,0 +1,2 @@
|
||||
-- "<!DOCTYPE html><body>It works !</body>" in base64
|
||||
select 'download' as component, 'data:text/html;base64,PCFET0NUWVBFIGh0bWw+PGJvZHk+SXQgd29ya3MgITwvYm9keT4K' as data_url;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- "<!DOCTYPE html><body>It works !</body>" percent encoded
|
||||
select 'download' as component, 'data:text/html,%3C!DOCTYPE%20html%3E%3Cbody%3EIt%20works%20!%3C%2Fbody%3E' as data_url;
|
||||
@@ -0,0 +1,8 @@
|
||||
-- Checks that we can have a page with a single dynamic component containing multiple children
|
||||
select 'dynamic' as component,
|
||||
'[
|
||||
{"component":"dynamic", "properties": [
|
||||
{"component":"text"},
|
||||
{"contents":"It works !", "bold":true}
|
||||
]}
|
||||
]' as properties;
|
||||
@@ -0,0 +1,6 @@
|
||||
-- Checks that we can have a page with a single dynamic component containing multiple children
|
||||
select 'dynamic' as component,
|
||||
'{"component":"text"}' as properties,
|
||||
'{"contents":"Hello, ", "bold":true}' as properties,
|
||||
'{"component":"text"}' as properties,
|
||||
'{"contents":"It works !", "bold":true}' as properties;
|
||||
@@ -0,0 +1,2 @@
|
||||
select 'dynamic' as component,
|
||||
sqlpage.run_sql('tests/sql_test_files/component_rendering/dynamic_shell.sql') as properties;
|
||||
@@ -0,0 +1,7 @@
|
||||
-- Checks that we can have a page with a single dynamic component containing multiple children
|
||||
select 'dynamic' as component,
|
||||
'[
|
||||
{"component":"shell", "title":"It works !"},
|
||||
{"component":"text"},
|
||||
{"contents":"Yes it does !", "bold":true}
|
||||
]' as properties;
|
||||
@@ -0,0 +1,8 @@
|
||||
-- Checks that we can have a page with a single dynamic component containing multiple children
|
||||
select 'dynamic' as component,
|
||||
'[
|
||||
{"component":"text"},
|
||||
{"contents":"Hello, ", "bold":true},
|
||||
{"component":"text"},
|
||||
{"contents":"It works !", "bold":true}
|
||||
]' as properties;
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
select 'text' as component;
|
||||
select 'hello world' as contents;
|
||||
select 'cookie' as component,
|
||||
'username' as name,
|
||||
'John Doe' as value;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- https://github.com/sqlpage/SQLPage/issues/788
|
||||
copy this_table_does_not_exist (csv) from 'recon_csv_file_input' DELIMITER '*' CSV;
|
||||
@@ -0,0 +1,2 @@
|
||||
select 'dynamic' as component,
|
||||
'{ "this object": "has a forbidden comma" , }' as properties; -- this is invalid JSON, and should cause an error
|
||||
@@ -0,0 +1,4 @@
|
||||
select 'shell' as component,
|
||||
lower(sqlpage.url_encode(lower('HELLO'))) as title;
|
||||
-- this is invalid, because the sqlpage pseudo-function is sandwiched between two native SQL functions.
|
||||
-- It can't be executed neither before nor after the query is executed.
|
||||
@@ -0,0 +1,2 @@
|
||||
select 'shell' as component, 'hello' as title;
|
||||
select 'shell' as component, 'world' as title;
|
||||
+1
@@ -0,0 +1 @@
|
||||
select 'dynamic' as component; -- missing "properties" column
|
||||
@@ -0,0 +1,4 @@
|
||||
select 'debug' as component,
|
||||
sqlpage.run_sql(
|
||||
'tests/sql_test_files/component_rendering/error_too_many_nested_inclusions.sql'
|
||||
) as contents;
|
||||
@@ -0,0 +1,4 @@
|
||||
select sqlpage.fetch('{
|
||||
"url": "https://example.com",
|
||||
"invalid_field": "should fail"
|
||||
}')
|
||||
@@ -0,0 +1,6 @@
|
||||
select 'log' as component,
|
||||
'Hello, World!' as message,
|
||||
'info' as level;
|
||||
|
||||
select 'text' as component,
|
||||
'It works !' as contents;
|
||||
@@ -0,0 +1,6 @@
|
||||
-- https://github.com/sqlpage/SQLPage/discussions/600
|
||||
select 'table' as component, 'Link' AS markdown; -- uppercase Link
|
||||
select '[It works !](https://example.com)
|
||||
|
||||
[comment]: <> (This is a comment. If this is visible, there is an error in markdown rendering)' as link; -- lowercase "link".
|
||||
-- If the markdown is not rendered, the page will contain the string "error" and trigger a test failure.
|
||||
@@ -0,0 +1,2 @@
|
||||
select 'dynamic' as component,
|
||||
sqlpage.run_sql('tests/sql_test_files/component_rendering/simple.sql') as properties;
|
||||
@@ -0,0 +1,14 @@
|
||||
select
|
||||
'dynamic' as component,
|
||||
sqlpage.run_sql (
|
||||
'tests/components/display_text.sql',
|
||||
CONCAT ('{"html":"', html, '"}') -- UNSAFE. Don't do that with untrusted data. We do it here because we can't use json_object (the syntax is not the same in all supported databases)
|
||||
) as properties
|
||||
from
|
||||
(
|
||||
select
|
||||
'It ' as html
|
||||
union all
|
||||
select
|
||||
'works !'
|
||||
) t1;
|
||||
@@ -0,0 +1,4 @@
|
||||
set html = 'It ';
|
||||
select 'dynamic' as component, sqlpage.run_sql('tests/components/display_text.sql') as properties;
|
||||
set html = 'works !';
|
||||
select 'dynamic' as component, sqlpage.run_sql('tests/components/display_text.sql') as properties;
|
||||
@@ -0,0 +1,3 @@
|
||||
select 'shell' as component,
|
||||
'/' as search_target,
|
||||
'It works !' as search_value;
|
||||
@@ -0,0 +1,2 @@
|
||||
select 'shell' as component, 'Hello world !' AS title;
|
||||
select 'text' as component, 'It works !' AS contents;
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
-- Doesnt work on mssql because it does not support "create temporary table"
|
||||
create temporary table temp_t(x text);
|
||||
insert into temp_t(x) values ('It works !');
|
||||
select 'dynamic' as component, sqlpage.run_sql('tests/sql_test_files/select_temp_t.sql') AS properties;
|
||||
@@ -0,0 +1,2 @@
|
||||
select 'text' as component,
|
||||
'### It works !' AS contents_md;
|
||||
@@ -0,0 +1,2 @@
|
||||
select 'text' as component,
|
||||
'<span>It works !</span>' AS unsafe_contents_md;
|
||||
@@ -0,0 +1,2 @@
|
||||
select 'shell-empty' as component;
|
||||
select 'text' as component, '<!DOCTYPE html><html><head><title>My page</title></head><body><h1>It works !</h1></body></html>' as html;
|
||||
@@ -0,0 +1 @@
|
||||
select 'test' as expected, sqlpage.basic_auth_password() as actual;
|
||||
@@ -0,0 +1 @@
|
||||
select 'test' as expected, sqlpage.basic_auth_username() as actual;
|
||||
@@ -0,0 +1,10 @@
|
||||
-- https://github.com/sqlpage/SQLPage/issues/818
|
||||
|
||||
set success = 'It works !';
|
||||
set failure = 'You should never see this';
|
||||
|
||||
select 'It works !' as expected,
|
||||
case $success
|
||||
when $success then $success
|
||||
when $failure then $failure
|
||||
end as actual;
|
||||
@@ -0,0 +1,2 @@
|
||||
select NULL as expected,
|
||||
sqlpage.client_ip() as actual;
|
||||
@@ -0,0 +1 @@
|
||||
select 'https://example.com' as expected, sqlpage.link(coalesce($i_do_not_exist, 'https://example.com')) as actual;
|
||||
@@ -0,0 +1,3 @@
|
||||
select '%2F1' as expected, sqlpage.url_encode('/' || $x) as actual;
|
||||
select '%2F1' as expected, sqlpage.url_encode(CONCAT('/', $x)) as actual;
|
||||
select NULL as expected, sqlpage.url_encode(CONCAT('/', $thisisnull)) as actual;
|
||||
@@ -0,0 +1 @@
|
||||
select '123' as expected, sqlpage.cookie('test_cook') as actual;
|
||||
@@ -0,0 +1,6 @@
|
||||
drop table if exists my_tmp_store;
|
||||
create table my_tmp_store(x varchar(100));
|
||||
|
||||
insert into my_tmp_store(x) values ('It works !');
|
||||
|
||||
select 'It works !' as expected, x as actual from my_tmp_store;
|
||||
@@ -0,0 +1,2 @@
|
||||
set result = CAST(0.47 AS DECIMAL(3,2));
|
||||
select '0.47' as expected, $result as actual;
|
||||
@@ -0,0 +1,6 @@
|
||||
drop table if exists files_to_read;
|
||||
create table files_to_read(filepath varchar(100));
|
||||
insert into files_to_read(filepath) values ('tests/it_works.txt');
|
||||
select 'It works !' as expected,
|
||||
sqlpage.read_file_as_text(filepath) as actual
|
||||
from files_to_read;
|
||||
@@ -0,0 +1,2 @@
|
||||
select NULL as expected,
|
||||
sqlpage.environment_variable('I_DO_NOT_EXIST') as actual;
|
||||
@@ -0,0 +1,6 @@
|
||||
set res = sqlpage.fetch('{
|
||||
"url": "http://localhost:' || $echo_port || '/hello_world",
|
||||
"response_encoding": "base64"
|
||||
}');
|
||||
select 'R0VUIC9oZWxsb193b3Js' as expected_contains,
|
||||
$res as actual;
|
||||
@@ -0,0 +1,6 @@
|
||||
set res = sqlpage.fetch('{
|
||||
"url": "http://localhost:' || $echo_port || '/hello_world",
|
||||
"response_encoding": "hex"
|
||||
}');
|
||||
select '474554202f68656c6c6f5f776f726c64' as expected_contains,
|
||||
$res as actual;
|
||||
@@ -0,0 +1,10 @@
|
||||
set url = 'http://localhost:' || $echo_port || '/post';
|
||||
set res = sqlpage.fetch(json_object(
|
||||
'method', 'POST',
|
||||
'url', $url,
|
||||
'headers', json_object('x-custom', '1'),
|
||||
'body', json_array('hello', 'world')
|
||||
));
|
||||
set expected = 'POST /post|accept-encoding: br, gzip, deflate, zstd|content-length: 17|content-type: application/json|host: localhost:' || $echo_port || '|user-agent: sqlpage|x-custom: 1|["hello","world"]';
|
||||
select $expected as expected,
|
||||
$res as actual;
|
||||
@@ -0,0 +1 @@
|
||||
select null as expected, sqlpage.fetch(null) as actual;
|
||||
@@ -0,0 +1,6 @@
|
||||
set url = 'http://localhost:' || $echo_port || '/post';
|
||||
set fetch_request = '{"method": "POST", "url": "' || $url || '", "headers": {"x-custom": "1"}, "body": {"hello": "world"}}';
|
||||
set res = sqlpage.fetch($fetch_request);
|
||||
set expected = 'POST /post|accept-encoding: br, gzip, deflate, zstd|content-length: 18|content-type: application/json|host: localhost:' || $echo_port || '|user-agent: sqlpage|x-custom: 1|{"hello": "world"}';
|
||||
select $expected as expected,
|
||||
$res as actual;
|
||||
@@ -0,0 +1,4 @@
|
||||
set url = 'http://localhost:' || $echo_port || '/hello_world';
|
||||
set res = sqlpage.fetch($url);
|
||||
select 'GET /hello_world' as expected_contains,
|
||||
$res as actual;
|
||||
@@ -0,0 +1,3 @@
|
||||
set res = sqlpage.fetch_with_meta('http://not-a-real-url');
|
||||
|
||||
select '"error":"Request failed' as expected_contains, $res as actual;
|
||||
@@ -0,0 +1 @@
|
||||
select null as expected, sqlpage.fetch_with_meta(null) as actual;
|
||||
@@ -0,0 +1,14 @@
|
||||
set url = 'http://localhost:' || $echo_port || '/hello_world';
|
||||
set fetch_req = '{
|
||||
"method": "PUT",
|
||||
"url": "' || $url || '",
|
||||
"headers": {
|
||||
"user-agent": "myself"
|
||||
}
|
||||
}';
|
||||
set res = sqlpage.fetch_with_meta($fetch_req);
|
||||
|
||||
select '"status":200' as expected_contains,
|
||||
'"headers":{' as expected_contains,
|
||||
'"body":"' as expected_contains,
|
||||
$res as actual;
|
||||
@@ -0,0 +1,2 @@
|
||||
select '$argon2id$' as expected_contains,
|
||||
coalesce(sqlpage.hash_password($x), 'NULL') as actual;
|
||||
@@ -0,0 +1 @@
|
||||
select NULL as expected, sqlpage.hash_password(null) as actual;
|
||||
@@ -0,0 +1 @@
|
||||
select 'test_cook=123' as expected, sqlpage.header('cookie') as actual;
|
||||
@@ -0,0 +1 @@
|
||||
select '"cookie":"test_cook=123"' as expected_contains, sqlpage.headers() as actual;
|
||||
@@ -0,0 +1,2 @@
|
||||
select '97yD9DBThCSxMpjmqm+xQ+9NWaFJRhdZl0edvC0aPNg=' as expected,
|
||||
sqlpage.hmac('The quick brown fox jumps over the lazy dog', 'key', 'sha256-base64') as actual;
|
||||
@@ -0,0 +1,2 @@
|
||||
select sqlpage.hmac('test data', 'test key', 'sha256') as expected,
|
||||
sqlpage.hmac('test data', 'test key') as actual;
|
||||
@@ -0,0 +1,2 @@
|
||||
select 'f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8' as expected,
|
||||
sqlpage.hmac('The quick brown fox jumps over the lazy dog', 'key', 'sha256') as actual;
|
||||
@@ -0,0 +1,2 @@
|
||||
select 'b42af09057bac1e2d41708e48a902e09b5ff7f12ab428a4fe86653c73dd248fb82f948a549f7b791a5b41915ee4d1ec3935357e4e2317250d0372afa2ebeeb3a' as expected,
|
||||
sqlpage.hmac('The quick brown fox jumps over the lazy dog', 'key', 'sha512') as actual;
|
||||
@@ -0,0 +1,7 @@
|
||||
-- Test Shopify webhook HMAC validation with base64 output
|
||||
select 'QNyObTlKbMx2qDlPF/ZOZcBqg5OgPg+2oky3zldc0Gw=' as expected,
|
||||
sqlpage.hmac(
|
||||
'{"id":1234567890,"email":"customer@example.com","total_price":"123.45"}',
|
||||
'test-webhook-secret',
|
||||
'sha256-base64'
|
||||
) as actual;
|
||||
@@ -0,0 +1,10 @@
|
||||
set x = 'set_value';
|
||||
set set_only = 'only_in_set';
|
||||
|
||||
select 'set_value' as expected, $x as actual;
|
||||
select 'only_in_set' as expected, $set_only as actual;
|
||||
select '{"x":"1"}' as expected, sqlpage.variables('get') as actual;
|
||||
select '"x":"set_value"' as expected_contains, sqlpage.variables('set') as actual;
|
||||
select '"set_only":"only_in_set"' as expected_contains, sqlpage.variables('set') as actual;
|
||||
select '"x":"set_value"' as expected_contains, sqlpage.variables() as actual;
|
||||
select '"set_only":"only_in_set"' as expected_contains, sqlpage.variables() as actual;
|
||||
@@ -0,0 +1 @@
|
||||
select 'test.sql?x=123' as expected, sqlpage.link('test.sql', json_object('x', 123)) as actual;
|
||||
@@ -0,0 +1 @@
|
||||
select 'test.sql' as expected, sqlpage.link('test.sql', json_object('x', null)) as actual;
|
||||
@@ -0,0 +1,4 @@
|
||||
-- in SQLite, we provide our own unicode-aware lower function
|
||||
-- see https://github.com/sqlpage/SQLPage/issues/452
|
||||
select 'It works !' as expected,
|
||||
coalesce(lower(NULL), 'It works !') as actual;
|
||||
@@ -0,0 +1 @@
|
||||
select 'It%20works%20%21' as expected, sqlpage.url_encode(sqlpage.read_file_as_text('tests/it_works.txt')) as actual;
|
||||
@@ -0,0 +1,2 @@
|
||||
select '/tests/sql_test_files/data/path.sql' as expected,
|
||||
sqlpage.path() as actual;
|
||||
@@ -0,0 +1 @@
|
||||
select 2 as expected, $x::decimal + 1 as actual;
|
||||
@@ -0,0 +1,2 @@
|
||||
select 'http' as expected,
|
||||
sqlpage.protocol() as actual;
|
||||
@@ -0,0 +1 @@
|
||||
select '?x=1' as expected, sqlpage.link('', sqlpage.variables('get')) as actual;
|
||||
@@ -0,0 +1 @@
|
||||
select '' as expected, sqlpage.random_string(0) as actual;
|
||||
@@ -0,0 +1,3 @@
|
||||
set actual = sqlpage.read_file_as_data_url('tests/it_works.txt');
|
||||
select 'data:text/plain;base64,SXQgd29ya3MgIQ==' as expected,
|
||||
coalesce($actual, 'NULL') as actual;
|
||||
@@ -0,0 +1,2 @@
|
||||
select 'It works !' as expected,
|
||||
sqlpage.read_file_as_text('tests/it_works.txt') as actual;
|
||||
@@ -0,0 +1,8 @@
|
||||
set route = sqlpage.regex_match(
|
||||
'/categories/(?<category>[A-Za-z0-9_]+)/post/(?<id>[0-9]+)',
|
||||
'/categories/sql/post/42'
|
||||
);
|
||||
|
||||
select
|
||||
'{"0":"/categories/sql/post/42","category":"sql","id":"42"}' as expected,
|
||||
$route as actual;
|
||||
@@ -0,0 +1,3 @@
|
||||
set actual = sqlpage.request_method();
|
||||
select 'GET' as expected,
|
||||
coalesce($actual, 'NULL') as actual;
|
||||
@@ -0,0 +1,2 @@
|
||||
set msg = case when 1=1 then 'It works !' else 'It failed !' end;
|
||||
select 'It works !' as expected, $msg as actual;
|
||||
@@ -0,0 +1,3 @@
|
||||
set what_does_it_do = 'wo' || 'rks';
|
||||
select 'It works !' as expected,
|
||||
'It ' || $what_does_it_do || ' !' as actual;
|
||||
@@ -0,0 +1,2 @@
|
||||
set result = sqlpage.set_variable('y', '2');
|
||||
select '?x=1&y=2' as expected, $result as actual;
|
||||
@@ -0,0 +1,2 @@
|
||||
set result = sqlpage.set_variable('x', null);
|
||||
select '?' as expected, $result as actual;
|
||||
@@ -0,0 +1,2 @@
|
||||
set result = sqlpage.set_variable('x', '2');
|
||||
select '?x=2' as expected, $result as actual;
|
||||
@@ -0,0 +1,2 @@
|
||||
SET result = 123456789123456789123456789;
|
||||
select '123456789123456789123456789' as expected, $result as actual;
|
||||
@@ -0,0 +1,3 @@
|
||||
set two = 2;
|
||||
select '2' as expected,
|
||||
$two as actual;
|
||||
@@ -0,0 +1,3 @@
|
||||
set i_am_null = NULL;
|
||||
select NULL as expected,
|
||||
$i_am_null as actual;
|
||||
@@ -0,0 +1,2 @@
|
||||
set x = NULL;
|
||||
select NULL as expected, $x as actual;
|
||||
@@ -0,0 +1,4 @@
|
||||
SET x = 42;
|
||||
SET y = $x;
|
||||
SET z = $y;
|
||||
select '42' as expected, $z as actual;
|
||||
@@ -0,0 +1,3 @@
|
||||
set my_var = sqlpage.url_encode(' ');
|
||||
select '%20' as expected,
|
||||
$my_var as actual;
|
||||
@@ -0,0 +1,3 @@
|
||||
set my_var = sqlpage.url_encode(UPPER('a'));
|
||||
select 'A' as expected,
|
||||
$my_var as actual;
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Checks that the UPPER function is working correctly with unicode characters.
|
||||
select 'É' as expected,
|
||||
UPPER('é') as actual;
|
||||
@@ -0,0 +1,2 @@
|
||||
set result = sqrt(9.0);
|
||||
select CAST(3.0 AS FLOAT) as expected, cast($result as FLOAT) as actual;
|
||||
@@ -0,0 +1,2 @@
|
||||
set actual = sqlpage.uploaded_file_path('my_file');
|
||||
select null as expected, $actual as actual;
|
||||
@@ -0,0 +1,2 @@
|
||||
select '%2F' as expected,
|
||||
sqlpage.url_encode('/') as actual;
|
||||
@@ -0,0 +1,4 @@
|
||||
-- syntax is valid in SQLite, PostgreSQL and SQLServer
|
||||
-- no cast as varchar in MySQL
|
||||
select 'It works !' as expected,
|
||||
CAST('It works !' as varchar(100)) as actual;
|
||||
@@ -0,0 +1,2 @@
|
||||
select '{"x":"1"}' as expected,
|
||||
sqlpage.variables('get') as actual;
|
||||
@@ -0,0 +1,2 @@
|
||||
select '{"x":"1"}' as expected,
|
||||
sqlpage.variables() as actual;
|
||||
@@ -0,0 +1,2 @@
|
||||
set x = '2';
|
||||
select '{"x":"2"}' as expected, sqlpage.variables() as actual;
|
||||
@@ -0,0 +1 @@
|
||||
select '{}' as expected, sqlpage.variables('post') as actual;
|
||||
@@ -0,0 +1,263 @@
|
||||
use actix_web::test;
|
||||
use sqlpage::AppState;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::oneshot;
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
#[actix_web::test]
|
||||
async fn run_all_sql_test_files() {
|
||||
let app_data = crate::common::make_app_data().await;
|
||||
let test_files = get_sql_test_cases();
|
||||
|
||||
let (shutdown_tx, shutdown_rx) = oneshot::channel();
|
||||
let (echo_handle, port) = crate::common::start_echo_server(shutdown_rx);
|
||||
wait_for_echo_server(port).await;
|
||||
|
||||
for test_file in test_files {
|
||||
run_sql_test(&test_file, &app_data, &echo_handle, port).await;
|
||||
}
|
||||
|
||||
let _ = shutdown_tx.send(());
|
||||
let _ = tokio::time::timeout(Duration::from_secs(2), echo_handle).await;
|
||||
}
|
||||
|
||||
async fn wait_for_echo_server(port: u16) {
|
||||
let client = awc::Client::default();
|
||||
let start = std::time::Instant::now();
|
||||
while start.elapsed() < Duration::from_secs(5) {
|
||||
if client
|
||||
.get(format!("http://localhost:{port}/"))
|
||||
.send()
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
return;
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
panic!("Echo server did not become ready");
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum SqlTestFormat {
|
||||
Html,
|
||||
Json,
|
||||
}
|
||||
|
||||
struct SqlTestCase {
|
||||
path: std::path::PathBuf,
|
||||
format: SqlTestFormat,
|
||||
}
|
||||
|
||||
fn get_sql_test_cases() -> Vec<SqlTestCase> {
|
||||
let mut tests = Vec::new();
|
||||
tests.extend(read_sql_tests_in_dir(
|
||||
"tests/sql_test_files/component_rendering",
|
||||
SqlTestFormat::Html,
|
||||
));
|
||||
tests.extend(read_sql_tests_in_dir(
|
||||
"tests/sql_test_files/data",
|
||||
SqlTestFormat::Json,
|
||||
));
|
||||
tests
|
||||
}
|
||||
|
||||
fn read_sql_tests_in_dir(dir: &str, format: SqlTestFormat) -> Vec<SqlTestCase> {
|
||||
std::fs::read_dir(dir)
|
||||
.unwrap()
|
||||
.filter_map(|e| {
|
||||
let path = e.ok()?.path();
|
||||
if path.is_dir() || path.extension()? != "sql" {
|
||||
return None;
|
||||
}
|
||||
Some(SqlTestCase { path, format })
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn run_sql_test(
|
||||
test_case: &SqlTestCase,
|
||||
app_data: &actix_web::web::Data<AppState>,
|
||||
_echo_handle: &JoinHandle<()>,
|
||||
port: u16,
|
||||
) {
|
||||
let test_file = &test_case.path;
|
||||
let test_file_path = test_file.to_string_lossy().replace('\\', "/");
|
||||
let stem = test_file.file_stem().unwrap().to_str().unwrap();
|
||||
|
||||
let db_type = format!("{:?}", app_data.db.info.database_type).to_lowercase();
|
||||
if stem.contains(&format!("_no{db_type}")) {
|
||||
println!("Skipped {}: {}", test_file.display(), db_type);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut query_params = "x=1".to_string();
|
||||
if test_file_path.contains("fetch") {
|
||||
query_params.push_str(&format!("&echo_port={port}"));
|
||||
}
|
||||
let req_str = format!("/{test_file_path}?{query_params}");
|
||||
|
||||
let use_json = matches!(test_case.format, SqlTestFormat::Json);
|
||||
|
||||
let resp = tokio::time::timeout(Duration::from_secs(5), async {
|
||||
if use_json {
|
||||
crate::common::req_path_with_app_data_json(&req_str, app_data.clone()).await
|
||||
} else {
|
||||
crate::common::req_path_with_app_data(&req_str, app_data.clone()).await
|
||||
}
|
||||
})
|
||||
.await
|
||||
.unwrap_or_else(|_| panic!("Test timeout: {}", test_file.display()))
|
||||
.unwrap_or_else(|e| panic!("Request failed: {}: {}", test_file.display(), e));
|
||||
|
||||
let body = String::from_utf8(test::read_body(resp).await.to_vec())
|
||||
.unwrap_or_else(|_| panic!("Invalid UTF-8: {}", test_file.display()));
|
||||
|
||||
if use_json {
|
||||
assert_json_test(&body, test_file);
|
||||
} else {
|
||||
assert_html_test(&body, test_file, stem);
|
||||
}
|
||||
}
|
||||
|
||||
fn format_error(obj: &serde_json::Map<String, serde_json::Value>) -> Option<String> {
|
||||
if obj.get("component").and_then(|v| v.as_str()) != Some("error") {
|
||||
return None;
|
||||
}
|
||||
let mut msg = String::new();
|
||||
if let Some(desc) = obj.get("description").and_then(|v| v.as_str()) {
|
||||
msg.push_str(desc);
|
||||
}
|
||||
if let Some(bt) = obj.get("backtrace").and_then(|v| v.as_array()) {
|
||||
for frame in bt {
|
||||
if let Some(s) = frame.as_str() {
|
||||
msg.push_str(&format!("\n {}", s));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(msg)
|
||||
}
|
||||
|
||||
fn assert_json_test(body: &str, test_file: &std::path::Path) {
|
||||
let rows: Vec<serde_json::Value> = serde_json::from_str(body)
|
||||
.unwrap_or_else(|_| panic!("Invalid JSON: {}", test_file.display()));
|
||||
|
||||
assert!(
|
||||
!rows.is_empty(),
|
||||
"No rows returned: {}",
|
||||
test_file.display()
|
||||
);
|
||||
|
||||
for row in rows {
|
||||
let obj = match row.as_object() {
|
||||
Some(o) => o,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
if let Some(err) = format_error(obj) {
|
||||
panic!("Error in {}:\n{}", test_file.display(), err);
|
||||
}
|
||||
|
||||
let actual = obj
|
||||
.get("actual")
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::Value::Null);
|
||||
let actual_str = json_to_string(&actual);
|
||||
|
||||
let expected: Vec<serde_json::Value> = obj
|
||||
.get("expected")
|
||||
.map(|v| match v {
|
||||
serde_json::Value::Array(arr) => arr.clone(),
|
||||
_ => vec![v.clone()],
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let expected_contains: Vec<String> = obj
|
||||
.get("expected_contains")
|
||||
.map(|v| match v {
|
||||
serde_json::Value::Array(arr) => arr.iter().map(json_to_string).collect(),
|
||||
_ => vec![json_to_string(v)],
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
if expected.is_empty() && expected_contains.is_empty() {
|
||||
panic!(
|
||||
"No expected values found in {}: {}",
|
||||
test_file.display(),
|
||||
row
|
||||
);
|
||||
}
|
||||
|
||||
let exact_ok = expected.is_empty() || expected.iter().any(|e| e == &actual);
|
||||
let contains_ok = expected_contains.is_empty()
|
||||
|| expected_contains.iter().all(|e| actual_str.contains(e));
|
||||
|
||||
if !exact_ok || !contains_ok {
|
||||
let mut msg = format!("Test failed: {}\n", test_file.display());
|
||||
if !expected.is_empty() {
|
||||
let expected_strs: Vec<String> = expected.iter().map(|d| d.to_string()).collect();
|
||||
msg.push_str(&format!("Expected: {}\n", expected_strs.join(" or ")));
|
||||
}
|
||||
if !expected_contains.is_empty() {
|
||||
msg.push_str(&format!(
|
||||
"Expected to contain: {}\n",
|
||||
expected_contains.join(", ")
|
||||
));
|
||||
}
|
||||
msg.push_str(&format!("Actual: {}\n", actual));
|
||||
panic!("{}", msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_html_test(body: &str, test_file: &std::path::Path, stem: &str) {
|
||||
assert!(
|
||||
body.starts_with("<!DOCTYPE html>"),
|
||||
"Not HTML: {}",
|
||||
test_file.display()
|
||||
);
|
||||
|
||||
if stem.starts_with("error_") {
|
||||
let expected = stem.strip_prefix("error_").unwrap().replace('_', " ");
|
||||
assert!(
|
||||
body.to_lowercase().contains(&expected.to_lowercase()),
|
||||
"Should contain '{}': {}",
|
||||
expected,
|
||||
test_file.display()
|
||||
);
|
||||
} else {
|
||||
if let Some(error) = extract_error(body) {
|
||||
panic!("Error in {}: {}", test_file.display(), error);
|
||||
}
|
||||
assert!(
|
||||
body.contains("It works !"),
|
||||
"Should contain 'It works !': {}",
|
||||
test_file.display()
|
||||
);
|
||||
assert!(
|
||||
!body.to_lowercase().contains("error"),
|
||||
"Unexpected error: {}",
|
||||
test_file.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_error(body: &str) -> Option<String> {
|
||||
body.split("<code class=\"sqlpage-error-description\">")
|
||||
.nth(1)?
|
||||
.split("</code>")
|
||||
.next()
|
||||
.map(str::trim)
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(String::from)
|
||||
}
|
||||
|
||||
fn json_to_string(v: &serde_json::Value) -> String {
|
||||
match v {
|
||||
serde_json::Value::Null => "NULL".to_string(),
|
||||
serde_json::Value::String(s) => s.clone(),
|
||||
serde_json::Value::Number(n) => n.to_string(),
|
||||
serde_json::Value::Bool(b) => b.to_string(),
|
||||
_ => v.to_string(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
-- see tests/sql_test_files/component_rendering/temp_table_accessible_in_run_sql_nomssql.sql
|
||||
select 'text' as component, x as contents from temp_t;
|
||||
Reference in New Issue
Block a user