summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorOskari Timperi <oskari.timperi@iki.fi>2022-09-15 19:35:27 +0300
committerOskari Timperi <oskari.timperi@iki.fi>2022-09-15 19:39:50 +0300
commitff34cebaa50ebac63643a5e58989e416e09de4b9 (patch)
tree5118bf17a9ca08cad49388b8847c2c0e0f3d035e /tests
downloadsqlite-http-c-ff34cebaa50ebac63643a5e58989e416e09de4b9.tar.gz
sqlite-http-c-ff34cebaa50ebac63643a5e58989e416e09de4b9.zip
Initial commit
Diffstat (limited to 'tests')
-rw-r--r--tests/CMakeLists.txt13
-rw-r--r--tests/init.c11
-rw-r--r--tests/t_http.c221
-rw-r--r--tests/t_http_next_header.c353
-rw-r--r--tests/test.h77
-rw-r--r--tests/tests.py72
6 files changed, 747 insertions, 0 deletions
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
new file mode 100644
index 0000000..8ae9d98
--- /dev/null
+++ b/tests/CMakeLists.txt
@@ -0,0 +1,13 @@
+add_library(sqlite3 STATIC "${sqlite_SOURCE_DIR}/sqlite3.c")
+target_include_directories(sqlite3 PUBLIC "${sqlite_SOURCE_DIR}")
+
+add_executable(t_http_next_header t_http_next_header.c ../http.c)
+target_link_libraries(t_http_next_header PRIVATE sqlite3)
+target_compile_definitions(t_http_next_header PRIVATE HTTP_BACKEND_DUMMY)
+add_test(NAME http_next_header COMMAND t_http_next_header)
+
+add_executable(t_http t_http.c ../http.c)
+target_link_libraries(t_http PRIVATE sqlite3)
+target_compile_definitions(t_http PRIVATE HTTP_BACKEND_DUMMY SQLITE_CORE)
+target_include_directories(t_http PRIVATE ../src)
+add_test(NAME http COMMAND t_http)
diff --git a/tests/init.c b/tests/init.c
new file mode 100644
index 0000000..7fec8f1
--- /dev/null
+++ b/tests/init.c
@@ -0,0 +1,11 @@
+#include <sqlite3.h>
+#include <stdio.h>
+
+typedef void (*entrypoint)(void);
+
+int sqlite3_http_init(sqlite3*, char**, const sqlite3_api_routines*);
+
+void test_init() {
+ sqlite3_initialize();
+ sqlite3_auto_extension((entrypoint)sqlite3_http_init);
+}
diff --git a/tests/t_http.c b/tests/t_http.c
new file mode 100644
index 0000000..5251dc7
--- /dev/null
+++ b/tests/t_http.c
@@ -0,0 +1,221 @@
+#include "http.h"
+
+#include <sqlite3.h>
+
+#include "test.h"
+
+SQLITE_EXTENSION_INIT3
+
+void http_backend_dummy_set_errmsg(const char* zErrMsg);
+void http_backend_dummy_set_response(http_response* response);
+void http_backend_dummy_reset_request();
+const http_request* http_backend_dummy_get_last_request();
+
+int sqlite3_http_init(sqlite3*, char**, const sqlite3_api_routines*);
+
+void new_text_response(http_response* response,
+ const char* zBody,
+ const char* zHeaders,
+ int iStatusCode,
+ const char* zStatus) {
+ memset(response, 0, sizeof(*response));
+ assert(zStatus != NULL);
+ if (zBody) {
+ response->pBody = sqlite3_mprintf("%s", zBody);
+ response->szBody = strlen(zBody);
+ }
+ if (zHeaders) {
+ response->zHeaders = sqlite3_mprintf("%s", zHeaders);
+ response->szHeaders = strlen(zHeaders);
+ }
+ response->iStatusCode = iStatusCode;
+ response->zStatus = sqlite3_mprintf("%s", zStatus);
+}
+
+static sqlite3* db;
+
+void test_http_get() {
+ sqlite3_stmt* stmt;
+ http_response response;
+ new_text_response(&response, "hello, world!", "Foo: Bar\r\n\r\n", 200, "HTTP/1.0 200 OK");
+ http_backend_dummy_set_response(&response);
+ ASSERT_INT_EQ(
+ sqlite3_prepare_v2(db, "select * from http_get('http://example.com')", -1, &stmt, NULL),
+ SQLITE_OK);
+ ASSERT_INT_EQ(sqlite3_step(stmt), SQLITE_ROW);
+ ASSERT_INT_EQ(sqlite3_column_count(stmt), 4);
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 0), "HTTP/1.0 200 OK");
+ ASSERT_INT_EQ(sqlite3_column_int(stmt, 1), 200);
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 2), "Foo: Bar\r\n\r\n");
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 3), "hello, world!");
+ ASSERT_INT_EQ(sqlite3_finalize(stmt), SQLITE_OK);
+}
+
+void test_http_get_hidden_columns() {
+ sqlite3_stmt* stmt;
+ http_response response;
+ new_text_response(&response, "hello, world!", "Foo: Bar\r\n\r\n", 200, "HTTP/1.0 200 OK");
+ http_backend_dummy_set_response(&response);
+ ASSERT_INT_EQ(
+ sqlite3_prepare_v2(db,
+ "select response_status, response_status_code, response_headers, "
+ "response_body, request_method, request_url, request_headers, "
+ "request_body from http_get('http://example.com/foo')",
+ -1,
+ &stmt,
+ NULL),
+ SQLITE_OK);
+ ASSERT_INT_EQ(sqlite3_step(stmt), SQLITE_ROW);
+ ASSERT_INT_EQ(sqlite3_column_count(stmt), 8);
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 0), "HTTP/1.0 200 OK");
+ ASSERT_INT_EQ(sqlite3_column_int(stmt, 1), 200);
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 2), "Foo: Bar\r\n\r\n");
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 3), "hello, world!");
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 4), "GET");
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 5), "http://example.com/foo");
+ ASSERT_NULL(sqlite3_column_text(stmt, 6));
+ ASSERT_NULL(sqlite3_column_text(stmt, 7));
+ ASSERT_INT_EQ(sqlite3_finalize(stmt), SQLITE_OK);
+}
+
+void test_http_get_request_headers() {
+ sqlite3_stmt* stmt;
+ http_response response;
+ new_text_response(&response, "hello, world!", "Foo: Bar\r\n\r\n", 200, "HTTP/1.0 200 OK");
+ http_backend_dummy_set_response(&response);
+ ASSERT_INT_EQ(
+ sqlite3_prepare_v2(db,
+ "select response_status, response_status_code, response_headers, "
+ "response_body, request_method, request_url, request_headers, "
+ "request_body from http_get('http://example.com/foo', "
+ "http_headers('Req1', 'Val1', 'Req2', 'Val2'))",
+ -1,
+ &stmt,
+ NULL),
+ SQLITE_OK);
+ ASSERT_INT_EQ(sqlite3_step(stmt), SQLITE_ROW);
+ ASSERT_INT_EQ(sqlite3_column_count(stmt), 8);
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 0), "HTTP/1.0 200 OK");
+ ASSERT_INT_EQ(sqlite3_column_int(stmt, 1), 200);
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 2), "Foo: Bar\r\n\r\n");
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 3), "hello, world!");
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 4), "GET");
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 5), "http://example.com/foo");
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 6), "Req1: Val1\r\nReq2: Val2\r\n");
+ ASSERT_NULL(sqlite3_column_text(stmt, 7));
+ ASSERT_INT_EQ(sqlite3_finalize(stmt), SQLITE_OK);
+}
+
+void test_http_post() {
+ sqlite3_stmt* stmt;
+ http_response response;
+ new_text_response(&response, "hello, world!", "Foo: Bar\r\n\r\n", 200, "HTTP/1.0 200 OK");
+ http_backend_dummy_set_response(&response);
+ ASSERT_INT_EQ(
+ sqlite3_prepare_v2(db, "select * from http_post('http://example.com')", -1, &stmt, NULL),
+ SQLITE_OK);
+ ASSERT_INT_EQ(sqlite3_step(stmt), SQLITE_ROW);
+ ASSERT_INT_EQ(sqlite3_column_count(stmt), 4);
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 0), "HTTP/1.0 200 OK");
+ ASSERT_INT_EQ(sqlite3_column_int(stmt, 1), 200);
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 2), "Foo: Bar\r\n\r\n");
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 3), "hello, world!");
+ ASSERT_INT_EQ(sqlite3_finalize(stmt), SQLITE_OK);
+}
+
+void test_http_post_hidden_columns() {
+ sqlite3_stmt* stmt;
+ http_response response;
+ new_text_response(&response, "hello, world!", "Foo: Bar\r\n\r\n", 200, "HTTP/1.0 200 OK");
+ http_backend_dummy_set_response(&response);
+ ASSERT_INT_EQ(
+ sqlite3_prepare_v2(db,
+ "select response_status, response_status_code, response_headers, "
+ "response_body, request_method, request_url, request_headers, "
+ "request_body from http_post('http://example.com/foo')",
+ -1,
+ &stmt,
+ NULL),
+ SQLITE_OK);
+ ASSERT_INT_EQ(sqlite3_step(stmt), SQLITE_ROW);
+ ASSERT_INT_EQ(sqlite3_column_count(stmt), 8);
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 0), "HTTP/1.0 200 OK");
+ ASSERT_INT_EQ(sqlite3_column_int(stmt, 1), 200);
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 2), "Foo: Bar\r\n\r\n");
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 3), "hello, world!");
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 4), "POST");
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 5), "http://example.com/foo");
+ ASSERT_NULL(sqlite3_column_text(stmt, 6));
+ ASSERT_NULL(sqlite3_column_text(stmt, 7));
+ ASSERT_INT_EQ(sqlite3_finalize(stmt), SQLITE_OK);
+}
+
+void test_http_post_request_headers() {
+ sqlite3_stmt* stmt;
+ http_response response;
+ new_text_response(&response, "hello, world!", "Foo: Bar\r\n\r\n", 200, "HTTP/1.0 200 OK");
+ http_backend_dummy_set_response(&response);
+ ASSERT_INT_EQ(
+ sqlite3_prepare_v2(db,
+ "select response_status, response_status_code, response_headers, "
+ "response_body, request_method, request_url, request_headers, "
+ "request_body from http_post('http://example.com/foo', "
+ "http_headers('Req1', 'Val1', 'Req2', 'Val2'))",
+ -1,
+ &stmt,
+ NULL),
+ SQLITE_OK);
+ ASSERT_INT_EQ(sqlite3_step(stmt), SQLITE_ROW);
+ ASSERT_INT_EQ(sqlite3_column_count(stmt), 8);
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 0), "HTTP/1.0 200 OK");
+ ASSERT_INT_EQ(sqlite3_column_int(stmt, 1), 200);
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 2), "Foo: Bar\r\n\r\n");
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 3), "hello, world!");
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 4), "POST");
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 5), "http://example.com/foo");
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 6), "Req1: Val1\r\nReq2: Val2\r\n");
+ ASSERT_NULL(sqlite3_column_text(stmt, 7));
+ ASSERT_INT_EQ(sqlite3_finalize(stmt), SQLITE_OK);
+}
+
+void test_http_post_request_body() {
+ sqlite3_stmt* stmt;
+ http_response response;
+ new_text_response(&response, "hello, world!", "Foo: Bar\r\n\r\n", 200, "HTTP/1.0 200 OK");
+ http_backend_dummy_set_response(&response);
+ ASSERT_INT_EQ(
+ sqlite3_prepare_v2(db,
+ "select response_status, response_status_code, response_headers, "
+ "response_body, request_method, request_url, request_headers, "
+ "request_body from http_post('http://example.com/foo', "
+ "http_headers('Req1', 'Val1', 'Req2', 'Val2'), 'hello')",
+ -1,
+ &stmt,
+ NULL),
+ SQLITE_OK);
+ ASSERT_INT_EQ(sqlite3_step(stmt), SQLITE_ROW);
+ ASSERT_INT_EQ(sqlite3_column_count(stmt), 8);
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 0), "HTTP/1.0 200 OK");
+ ASSERT_INT_EQ(sqlite3_column_int(stmt, 1), 200);
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 2), "Foo: Bar\r\n\r\n");
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 3), "hello, world!");
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 4), "POST");
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 5), "http://example.com/foo");
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 6), "Req1: Val1\r\nReq2: Val2\r\n");
+ ASSERT_STR_EQ(sqlite3_column_text(stmt, 7), "hello");
+ ASSERT_INT_EQ(sqlite3_finalize(stmt), SQLITE_OK);
+}
+
+int main(int argc, char const* argv[]) {
+ sqlite3_initialize();
+ sqlite3_auto_extension((void (*)(void))sqlite3_http_init);
+ ASSERT_INT_EQ(sqlite3_open(":memory:", &db), SQLITE_OK);
+ test_http_get();
+ test_http_get_hidden_columns();
+ test_http_get_request_headers();
+ test_http_post();
+ test_http_post_hidden_columns();
+ test_http_post_request_headers();
+ test_http_post_request_body();
+ return 0;
+}
diff --git a/tests/t_http_next_header.c b/tests/t_http_next_header.c
new file mode 100644
index 0000000..2f101af
--- /dev/null
+++ b/tests/t_http_next_header.c
@@ -0,0 +1,353 @@
+#include <sqlite3.h>
+
+#include "test.h"
+
+int http_next_header(const char* headers,
+ int size,
+ int* pParsed,
+ const char** ppName,
+ int* pNameSize,
+ const char** ppValue,
+ int* pValueSize);
+
+void single_header() {
+ const char* headers = "Foo: Bar\r\n";
+ int nParsed;
+ const char* name;
+ int nameSize;
+ const char* value;
+ int valueSize;
+ int rc =
+ http_next_header(headers, strlen(headers), &nParsed, &name, &nameSize, &value, &valueSize);
+ ASSERT_INT_EQ(rc, SQLITE_ROW);
+ ASSERT_INT_EQ(nameSize, 3);
+ ASSERT_MEM_EQ(name, "Foo", 3);
+ ASSERT_INT_EQ(valueSize, 3);
+ ASSERT_MEM_EQ(value, "Bar", 3);
+ ASSERT_INT_EQ(nParsed, 10);
+}
+
+void empty_string() {
+ const char* headers = "";
+ int nParsed;
+ const char* name;
+ int nameSize;
+ const char* value;
+ int valueSize;
+ int rc =
+ http_next_header(headers, strlen(headers), &nParsed, &name, &nameSize, &value, &valueSize);
+ ASSERT_INT_EQ(rc, SQLITE_DONE);
+ ASSERT_INT_EQ(nameSize, 0);
+ ASSERT_NULL(name);
+ ASSERT_INT_EQ(valueSize, 0);
+ ASSERT_NULL(value);
+ ASSERT_INT_EQ(nParsed, 0);
+}
+
+void terminating_crnl() {
+ const char* headers = "\r\n";
+ int nParsed;
+ const char* name;
+ int nameSize;
+ const char* value;
+ int valueSize;
+ int rc =
+ http_next_header(headers, strlen(headers), &nParsed, &name, &nameSize, &value, &valueSize);
+ ASSERT_INT_EQ(rc, SQLITE_DONE);
+ ASSERT_INT_EQ(nameSize, 0);
+ ASSERT_NULL(name);
+ ASSERT_INT_EQ(valueSize, 0);
+ ASSERT_NULL(value);
+ ASSERT_INT_EQ(nParsed, 2);
+}
+
+void multiple_headers() {
+ const char* headers = "Content-Type: application/json\r\nContent-Length: "
+ "343\r\nAccess-Control-Allow-Origin: *\r\n\r\n";
+ int nParsed;
+ const char* name;
+ int nameSize;
+ const char* value;
+ int valueSize;
+ int rc;
+
+ rc = http_next_header(headers, strlen(headers), &nParsed, &name, &nameSize, &value, &valueSize);
+ ASSERT_INT_EQ(rc, SQLITE_ROW);
+ ASSERT_INT_EQ(nameSize, 12);
+ ASSERT_MEM_EQ(name, "Content-Type", 12);
+ ASSERT_INT_EQ(valueSize, 16);
+ ASSERT_MEM_EQ(value, "application/json", 16);
+ ASSERT_INT_EQ(nParsed, 32);
+
+ headers += nParsed;
+
+ rc = http_next_header(headers, strlen(headers), &nParsed, &name, &nameSize, &value, &valueSize);
+ ASSERT_INT_EQ(rc, SQLITE_ROW);
+ ASSERT_INT_EQ(nameSize, 14);
+ ASSERT_MEM_EQ(name, "Content-Length", 14);
+ ASSERT_INT_EQ(valueSize, 3);
+ ASSERT_MEM_EQ(value, "343", 3);
+ ASSERT_INT_EQ(nParsed, 21);
+
+ headers += nParsed;
+
+ rc = http_next_header(headers, strlen(headers), &nParsed, &name, &nameSize, &value, &valueSize);
+ ASSERT_INT_EQ(rc, SQLITE_ROW);
+ ASSERT_INT_EQ(nameSize, 27);
+ ASSERT_MEM_EQ(name, "Access-Control-Allow-Origin", 27);
+ ASSERT_INT_EQ(valueSize, 1);
+ ASSERT_MEM_EQ(value, "*", 1);
+ ASSERT_INT_EQ(nParsed, 32);
+
+ headers += nParsed;
+
+ rc = http_next_header(headers, strlen(headers), &nParsed, &name, &nameSize, &value, &valueSize);
+ ASSERT_INT_EQ(rc, SQLITE_DONE);
+ ASSERT_INT_EQ(nameSize, 0);
+ ASSERT_NULL(name);
+ ASSERT_INT_EQ(valueSize, 0);
+ ASSERT_NULL(value);
+ ASSERT_INT_EQ(nParsed, 2);
+}
+
+void empty_value() {
+ const char* headers = "Foo:\r\n";
+ int nParsed;
+ const char* name;
+ int nameSize;
+ const char* value;
+ int valueSize;
+ int rc =
+ http_next_header(headers, strlen(headers), &nParsed, &name, &nameSize, &value, &valueSize);
+ ASSERT_INT_EQ(rc, SQLITE_ROW);
+ ASSERT_INT_EQ(nameSize, 3);
+ ASSERT_MEM_EQ(name, "Foo", 3);
+ ASSERT_INT_EQ(valueSize, 0);
+ ASSERT_NULL(value);
+ ASSERT_INT_EQ(nParsed, 6);
+}
+
+void empty_value_leading_ws() {
+ const char* headers = "Foo: \r\n";
+ int nParsed;
+ const char* name;
+ int nameSize;
+ const char* value;
+ int valueSize;
+ int rc =
+ http_next_header(headers, strlen(headers), &nParsed, &name, &nameSize, &value, &valueSize);
+ ASSERT_INT_EQ(rc, SQLITE_ROW);
+ ASSERT_INT_EQ(nameSize, 3);
+ ASSERT_MEM_EQ(name, "Foo", 3);
+ ASSERT_INT_EQ(valueSize, 0);
+ ASSERT_NULL(value);
+ ASSERT_INT_EQ(nParsed, 11);
+}
+
+void value_with_leading_ws() {
+ const char* headers = "Foo: \t Bar\r\n";
+ int nParsed;
+ const char* name;
+ int nameSize;
+ const char* value;
+ int valueSize;
+ int rc =
+ http_next_header(headers, strlen(headers), &nParsed, &name, &nameSize, &value, &valueSize);
+ ASSERT_INT_EQ(rc, SQLITE_ROW);
+ ASSERT_INT_EQ(nameSize, 3);
+ ASSERT_MEM_EQ(name, "Foo", 3);
+ ASSERT_INT_EQ(valueSize, 3);
+ ASSERT_MEM_EQ(value, "Bar", 3);
+ ASSERT_INT_EQ(nParsed, 16);
+}
+
+void value_with_trailing_ws() {
+ const char* headers = "Foo: Bar \t \r\n";
+ int nParsed;
+ const char* name;
+ int nameSize;
+ const char* value;
+ int valueSize;
+ int rc =
+ http_next_header(headers, strlen(headers), &nParsed, &name, &nameSize, &value, &valueSize);
+ ASSERT_INT_EQ(rc, SQLITE_ROW);
+ ASSERT_INT_EQ(nameSize, 3);
+ ASSERT_MEM_EQ(name, "Foo", 3);
+ ASSERT_INT_EQ(valueSize, 3);
+ ASSERT_MEM_EQ(value, "Bar", 3);
+ ASSERT_INT_EQ(nParsed, strlen(headers));
+}
+
+void value_with_leading_and_trailing_ws() {
+ const char* headers = "Foo: Bar \t \r\n";
+ int nParsed;
+ const char* name;
+ int nameSize;
+ const char* value;
+ int valueSize;
+ int rc =
+ http_next_header(headers, strlen(headers), &nParsed, &name, &nameSize, &value, &valueSize);
+ ASSERT_INT_EQ(rc, SQLITE_ROW);
+ ASSERT_INT_EQ(nameSize, 3);
+ ASSERT_MEM_EQ(name, "Foo", 3);
+ ASSERT_INT_EQ(valueSize, 3);
+ ASSERT_MEM_EQ(value, "Bar", 3);
+ ASSERT_INT_EQ(nParsed, strlen(headers));
+}
+
+void value_with_no_leading_ws() {
+ const char* headers = "Foo:Bar\r\n";
+ int nParsed;
+ const char* name;
+ int nameSize;
+ const char* value;
+ int valueSize;
+ int rc =
+ http_next_header(headers, strlen(headers), &nParsed, &name, &nameSize, &value, &valueSize);
+ ASSERT_INT_EQ(rc, SQLITE_ROW);
+ ASSERT_INT_EQ(nameSize, 3);
+ ASSERT_MEM_EQ(name, "Foo", 3);
+ ASSERT_INT_EQ(valueSize, 3);
+ ASSERT_MEM_EQ(value, "Bar", 3);
+ ASSERT_INT_EQ(nParsed, strlen(headers));
+}
+
+void value_with_spaces_inside() {
+ const char* headers = "Foo: This is a value\r\n";
+ int nParsed;
+ const char* name;
+ int nameSize;
+ const char* value;
+ int valueSize;
+ int rc =
+ http_next_header(headers, strlen(headers), &nParsed, &name, &nameSize, &value, &valueSize);
+ ASSERT_INT_EQ(rc, SQLITE_ROW);
+ ASSERT_INT_EQ(nameSize, 3);
+ ASSERT_MEM_EQ(name, "Foo", 3);
+ ASSERT_INT_EQ(valueSize, 20);
+ ASSERT_MEM_EQ(value, "This is a value", 20);
+ ASSERT_INT_EQ(nParsed, strlen(headers));
+}
+
+void value_with_spaces_inside_trailing_ws() {
+ const char* headers = "Foo: This is a value \r\n";
+ int nParsed;
+ const char* name;
+ int nameSize;
+ const char* value;
+ int valueSize;
+ int rc =
+ http_next_header(headers, strlen(headers), &nParsed, &name, &nameSize, &value, &valueSize);
+ ASSERT_INT_EQ(rc, SQLITE_ROW);
+ ASSERT_INT_EQ(nameSize, 3);
+ ASSERT_MEM_EQ(name, "Foo", 3);
+ ASSERT_INT_EQ(valueSize, 20);
+ ASSERT_MEM_EQ(value, "This is a value", 20);
+ ASSERT_INT_EQ(nParsed, strlen(headers));
+}
+
+void value_with_spaces_inside_leading_ws() {
+ const char* headers = "Foo: This is a value\r\n";
+ int nParsed;
+ const char* name;
+ int nameSize;
+ const char* value;
+ int valueSize;
+ int rc =
+ http_next_header(headers, strlen(headers), &nParsed, &name, &nameSize, &value, &valueSize);
+ ASSERT_INT_EQ(rc, SQLITE_ROW);
+ ASSERT_INT_EQ(nameSize, 3);
+ ASSERT_MEM_EQ(name, "Foo", 3);
+ ASSERT_INT_EQ(valueSize, 20);
+ ASSERT_MEM_EQ(value, "This is a value", 20);
+ ASSERT_INT_EQ(nParsed, strlen(headers));
+}
+
+void value_with_spaces_inside_leading_and_trailing_ws() {
+ const char* headers = "Foo: This is a value \r\n";
+ int nParsed;
+ const char* name;
+ int nameSize;
+ const char* value;
+ int valueSize;
+ int rc =
+ http_next_header(headers, strlen(headers), &nParsed, &name, &nameSize, &value, &valueSize);
+ ASSERT_INT_EQ(rc, SQLITE_ROW);
+ ASSERT_INT_EQ(nameSize, 3);
+ ASSERT_MEM_EQ(name, "Foo", 3);
+ ASSERT_INT_EQ(valueSize, 20);
+ ASSERT_MEM_EQ(value, "This is a value", 20);
+ ASSERT_INT_EQ(nParsed, strlen(headers));
+}
+
+void value_with_spaces_inside_no_leading_ws() {
+ const char* headers = "Foo:This is a value\r\n";
+ int nParsed;
+ const char* name;
+ int nameSize;
+ const char* value;
+ int valueSize;
+ int rc =
+ http_next_header(headers, strlen(headers), &nParsed, &name, &nameSize, &value, &valueSize);
+ ASSERT_INT_EQ(rc, SQLITE_ROW);
+ ASSERT_INT_EQ(nameSize, 3);
+ ASSERT_MEM_EQ(name, "Foo", 3);
+ ASSERT_INT_EQ(valueSize, 20);
+ ASSERT_MEM_EQ(value, "This is a value", 20);
+ ASSERT_INT_EQ(nParsed, strlen(headers));
+}
+
+void trailing_ws_after_name_is_ignored() {
+ const char* headers = "Foo\t : Bar\r\n";
+ int nParsed;
+ const char* name;
+ int nameSize;
+ const char* value;
+ int valueSize;
+ int rc =
+ http_next_header(headers, strlen(headers), &nParsed, &name, &nameSize, &value, &valueSize);
+ ASSERT_INT_EQ(rc, SQLITE_ROW);
+ ASSERT_INT_EQ(nameSize, 3);
+ ASSERT_MEM_EQ(name, "Foo", 3);
+ ASSERT_INT_EQ(valueSize, 3);
+ ASSERT_MEM_EQ(value, "Bar", 3);
+ ASSERT_INT_EQ(nParsed, strlen(headers));
+}
+
+void folding() {
+ const char* headers = "Foo: Bar\r\n Baz\r\n\tAnd more\r\n";
+ int nParsed;
+ const char* name;
+ int nameSize;
+ const char* value;
+ int valueSize;
+ int rc =
+ http_next_header(headers, strlen(headers), &nParsed, &name, &nameSize, &value, &valueSize);
+ ASSERT_INT_EQ(rc, SQLITE_ROW);
+ ASSERT_INT_EQ(nameSize, 3);
+ ASSERT_MEM_EQ(name, "Foo", 3);
+ ASSERT_INT_EQ(valueSize, 21);
+ ASSERT_MEM_EQ(value, "Bar\r\n Baz\r\n\tAnd more", 21);
+ ASSERT_INT_EQ(nParsed, strlen(headers));
+}
+
+int main(int argc, char const* argv[]) {
+ single_header();
+ empty_string();
+ terminating_crnl();
+ multiple_headers();
+ empty_value();
+ empty_value_leading_ws();
+ value_with_leading_ws();
+ value_with_trailing_ws();
+ value_with_leading_and_trailing_ws();
+ value_with_no_leading_ws();
+ value_with_spaces_inside();
+ value_with_spaces_inside_trailing_ws();
+ value_with_spaces_inside_leading_ws();
+ value_with_spaces_inside_leading_and_trailing_ws();
+ value_with_spaces_inside_no_leading_ws();
+ trailing_ws_after_name_is_ignored();
+ folding();
+ return 0;
+}
diff --git a/tests/test.h b/tests/test.h
new file mode 100644
index 0000000..38f5d6e
--- /dev/null
+++ b/tests/test.h
@@ -0,0 +1,77 @@
+#ifndef TEST_H
+#define TEST_H
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define ASSERT_INT_EQ(X, Y) \
+ do { \
+ int xxx = (X); \
+ int yyy = (Y); \
+ if (xxx != yyy) { \
+ fprintf(stderr, \
+ "*** %s ***\n%s is not equal to %s\nwhere\n%s is %d\nand\n%s " \
+ "is %d\n", \
+ __FUNCTION__, \
+ #X, \
+ #Y, \
+ #X, \
+ xxx, \
+ #Y, \
+ yyy); \
+ exit(1); \
+ } \
+ } while (0)
+
+#define ASSERT_STR_EQ(X, Y) \
+ do { \
+ const char* xxx = (X); \
+ const char* yyy = (Y); \
+ if (strcmp(xxx, yyy) != 0) { \
+ fprintf(stderr, \
+ "*** %s ***\n%s is not equal to %s\nwhere\n%s is %s\nand\n%s " \
+ "is %s\n", \
+ __FUNCTION__, \
+ #X, \
+ #Y, \
+ #X, \
+ xxx, \
+ #Y, \
+ yyy); \
+ exit(1); \
+ } \
+ } while (0)
+
+#define ASSERT_MEM_EQ(X, Y, SIZE) \
+ do { \
+ const char* xxx = (X); \
+ const char* yyy = (Y); \
+ if (memcmp(xxx, yyy, SIZE) != 0) { \
+ fprintf(stderr, \
+ "*** %s ***\n%s is not equal to %s\nwhere\n%s is %.*s\nand\n%s " \
+ "is %.*s\n", \
+ __FUNCTION__, \
+ #X, \
+ #Y, \
+ #X, \
+ SIZE, \
+ xxx, \
+ #Y, \
+ SIZE, \
+ yyy); \
+ exit(1); \
+ } \
+ } while (0)
+
+#define ASSERT_NULL(X) \
+ do { \
+ const void* xxx = (X); \
+ if (xxx != NULL) { \
+ fprintf(stderr, "*** %s ***\n%s is not NULL (was %p)\n", __FUNCTION__, #X, xxx); \
+ exit(1); \
+ } \
+ } while (0)
+
+#endif
diff --git a/tests/tests.py b/tests/tests.py
new file mode 100644
index 0000000..34d4f13
--- /dev/null
+++ b/tests/tests.py
@@ -0,0 +1,72 @@
+import http.server
+import subprocess
+import threading
+import sqlite3
+import json
+
+tests = (
+ {
+ "request_url": "http://localhost:8080/foobar",
+ "request_method": "GET",
+ "response_body": "foobar",
+ "response_headers": "Content-Size: 6\r\n",
+ "response_status_code": 200,
+ },
+)
+
+test_idx = 0
+
+class Handler(http.server.BaseHTTPRequestHandler):
+ def version_string(self):
+ return "testclient/1.0"
+
+ def date_time_string(self, timestamp=None):
+ return "Mon, 05 Sep 2022 19:26:53 GMT"
+
+ def do_GET(self):
+ self._handle_test("GET")
+
+ def do_POST(self):
+ self._handle_test("POST")
+
+ def _handle_test(self, method):
+ global test_idx
+ data = tests[test_idx]
+ test_idx += 1
+
+ try:
+ if method != data["request_method"]:
+ self.send_response(400)
+ self.end_headers()
+ else:
+ self.send_response(data["response_status_code"])
+ self.flush_headers()
+ self.wfile.write(data["response_headers"].encode("latin1", "strict"))
+ self.end_headers()
+ self.wfile.write(data["response_body"].encode("utf-8"))
+ except:
+ self.send_response(500)
+ self.end_headers()
+
+ threading.Thread(target=server.shutdown).start()
+
+
+server = http.server.HTTPServer(('', 8080), Handler)
+server_thread = threading.Thread(target=server.serve_forever)
+server_thread.start()
+
+for test in tests:
+ if test["request_method"] == "GET":
+ table = "http_get"
+ elif test["request_method"] == "POST":
+ table = "http_post"
+ request_url = test["request_url"]
+ sql = f".mode json\nselect request_method, request_url, request_headers, request_body, * from {table}('{request_url}')"
+ cp = subprocess.run(["./build/tests/Debug/sqlite3.exe", ":memory:"], input=sql, text=True, capture_output=True)
+ j = json.loads(cp.stdout)
+ print(j)
+ assert j[0]["response_status_code"] == test["response_status_code"]
+ assert j[0]["response_body"] == test["response_body"]
+ assert j[0]["response_headers"] == "Date: Mon, 05 Sep 2022 19:26:53 GMT\r\nServer: testclient/1.0\r\n" + test["response_headers"] + "\r\n"
+
+server_thread.join()