#include "test_framework.h" #include "../src/types.h" #include "../src/http.h" #include "../src/config.h" #include #include static const char *TEST_CONFIG_FILE = "/tmp/test_routing_config.json"; static void create_routing_config(void) { FILE *f = fopen(TEST_CONFIG_FILE, "w"); if (f) { fprintf(f, "{\n" " \"port\": 8080,\n" " \"reverse_proxy\": [\n" " {\n" " \"hostname\": \"api.local\",\n" " \"upstream_host\": \"backend-api\",\n" " \"upstream_port\": 3000,\n" " \"use_ssl\": false,\n" " \"rewrite_host\": true\n" " },\n" " {\n" " \"hostname\": \"secure.local\",\n" " \"upstream_host\": \"backend-secure\",\n" " \"upstream_port\": 443,\n" " \"use_ssl\": true,\n" " \"rewrite_host\": true\n" " },\n" " {\n" " \"hostname\": \"passthrough.local\",\n" " \"upstream_host\": \"backend-pass\",\n" " \"upstream_port\": 8000,\n" " \"use_ssl\": false,\n" " \"rewrite_host\": false\n" " }\n" " ]\n" "}\n"); fclose(f); } } static void cleanup_routing_config(void) { unlink(TEST_CONFIG_FILE); } void test_routing_host_rewrite(void) { TEST_SUITE_BEGIN("Routing Host Rewrite Logic"); create_routing_config(); config_load(TEST_CONFIG_FILE); route_config_t *route = config_find_route("api.local"); TEST_ASSERT(route != NULL, "Route for api.local found"); if (route) { TEST_ASSERT_EQ(1, route->rewrite_host, "Host rewrite enabled for api.local"); TEST_ASSERT_STR_EQ("backend-api", route->upstream_host, "Upstream host is backend-api"); TEST_ASSERT_EQ(3000, route->upstream_port, "Upstream port is 3000"); } route = config_find_route("passthrough.local"); TEST_ASSERT(route != NULL, "Route for passthrough.local found"); if (route) { TEST_ASSERT_EQ(0, route->rewrite_host, "Host rewrite disabled for passthrough.local"); } config_free(); cleanup_routing_config(); TEST_SUITE_END(); } void test_routing_ssl_upstream(void) { TEST_SUITE_BEGIN("Routing SSL Upstream"); create_routing_config(); config_load(TEST_CONFIG_FILE); route_config_t *route = config_find_route("secure.local"); TEST_ASSERT(route != NULL, "Route for secure.local found"); if (route) { TEST_ASSERT_EQ(1, route->use_ssl, "SSL enabled for secure.local"); TEST_ASSERT_EQ(443, route->upstream_port, "SSL port is 443"); } route = config_find_route("api.local"); TEST_ASSERT(route != NULL, "Route for api.local found"); if (route) { TEST_ASSERT_EQ(0, route->use_ssl, "SSL disabled for api.local"); } config_free(); cleanup_routing_config(); TEST_SUITE_END(); } void test_routing_dashboard_detection(void) { TEST_SUITE_BEGIN("Routing Dashboard Detection"); http_request_t req; const char *dashboard_req = "GET /rproxy/dashboard HTTP/1.1\r\nHost: anyhost.com\r\n\r\n"; http_parse_request(dashboard_req, strlen(dashboard_req), &req); int is_dashboard = (strncmp(req.uri, "/rproxy/dashboard", 17) == 0); TEST_ASSERT_EQ(1, is_dashboard, "Dashboard URI detected"); const char *api_req = "GET /rproxy/api/stats HTTP/1.1\r\nHost: anyhost.com\r\n\r\n"; http_parse_request(api_req, strlen(api_req), &req); int is_api = (strncmp(req.uri, "/rproxy/api/stats", 17) == 0); TEST_ASSERT_EQ(1, is_api, "API stats URI detected"); const char *regular_req = "GET /some/other/path HTTP/1.1\r\nHost: api.local\r\n\r\n"; http_parse_request(regular_req, strlen(regular_req), &req); int is_regular = (strncmp(req.uri, "/rproxy/", 8) != 0); TEST_ASSERT_EQ(1, is_regular, "Regular path not matched as internal"); TEST_SUITE_END(); } void test_routing_keep_alive_handling(void) { TEST_SUITE_BEGIN("Routing Keep-Alive Handling"); http_request_t req; const char *ka_req = "GET /api HTTP/1.1\r\nHost: api.local\r\nConnection: keep-alive\r\n\r\n"; http_parse_request(ka_req, strlen(ka_req), &req); TEST_ASSERT_EQ(1, req.keep_alive, "Keep-alive flag set when Connection: keep-alive"); const char *close_req = "GET /api HTTP/1.1\r\nHost: api.local\r\nConnection: close\r\n\r\n"; http_parse_request(close_req, strlen(close_req), &req); TEST_ASSERT_EQ(0, req.keep_alive, "Keep-alive flag not set when Connection: close"); const char *default_req = "GET /api HTTP/1.1\r\nHost: api.local\r\n\r\n"; http_parse_request(default_req, strlen(default_req), &req); TEST_ASSERT_EQ(1, req.keep_alive, "Keep-alive default for HTTP/1.1"); const char *http10_req = "GET /api HTTP/1.0\r\nHost: api.local\r\n\r\n"; http_parse_request(http10_req, strlen(http10_req), &req); TEST_ASSERT_EQ(0, req.keep_alive, "Keep-alive default off for HTTP/1.0"); TEST_SUITE_END(); } void test_routing_all_methods_accepted(void) { TEST_SUITE_BEGIN("Routing All HTTP Methods Accepted"); http_request_t req; int result; const char *methods[] = { "GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS", "PATCH", "TRACE", "CONNECT", "PROPFIND", "PROPPATCH", "MKCOL", "MOVE", "COPY", "LOCK", "UNLOCK", "SEARCH", "REPORT", "MKACTIVITY", "CHECKOUT", "MERGE", "NOTIFY", "SUBSCRIBE", "UNSUBSCRIBE", "CUSTOMMETHOD", "FOOBAR", "MYPROTO" }; int num_methods = sizeof(methods) / sizeof(methods[0]); for (int i = 0; i < num_methods; i++) { char request[256]; snprintf(request, sizeof(request), "%s /path HTTP/1.1\r\nHost: test.com\r\n\r\n", methods[i]); result = http_parse_request(request, strlen(request), &req); char msg[128]; snprintf(msg, sizeof(msg), "Method %s accepted", methods[i]); TEST_ASSERT_EQ(1, result, msg); TEST_ASSERT_STR_EQ(methods[i], req.method, msg); } TEST_SUITE_END(); } void test_routing_pipelined_requests(void) { TEST_SUITE_BEGIN("Routing Pipelined Request Detection"); http_request_t req1, req2; const char *first_req = "GET /first HTTP/1.1\r\nHost: api.local\r\nConnection: keep-alive\r\n\r\n"; int result1 = http_parse_request(first_req, strlen(first_req), &req1); TEST_ASSERT_EQ(1, result1, "First request parsed"); TEST_ASSERT_STR_EQ("/first", req1.uri, "First request URI"); TEST_ASSERT_EQ(1, req1.keep_alive, "First request keep-alive"); const char *second_req = "GET /second HTTP/1.1\r\nHost: api.local\r\n\r\n"; int result2 = http_parse_request(second_req, strlen(second_req), &req2); TEST_ASSERT_EQ(1, result2, "Second request parsed"); TEST_ASSERT_STR_EQ("/second", req2.uri, "Second request URI"); int is_new_request = http_is_request_start(second_req, strlen(second_req)); TEST_ASSERT_EQ(1, is_new_request, "New request detected for pipelining"); TEST_SUITE_END(); } void test_routing_websocket_upgrade(void) { TEST_SUITE_BEGIN("Routing WebSocket Upgrade"); http_request_t req; const char *ws_req = "GET /ws/chat HTTP/1.1\r\n" "Host: api.local\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n"; int result = http_parse_request(ws_req, strlen(ws_req), &req); TEST_ASSERT_EQ(1, result, "WebSocket upgrade request parsed"); TEST_ASSERT_EQ(1, req.is_websocket, "WebSocket flag set"); TEST_ASSERT_STR_EQ("/ws/chat", req.uri, "WebSocket URI parsed"); TEST_SUITE_END(); } void test_routing_chunked_transfer(void) { TEST_SUITE_BEGIN("Routing Chunked Transfer Encoding"); http_request_t req; const char *chunked_req = "POST /upload HTTP/1.1\r\n" "Host: api.local\r\n" "Transfer-Encoding: chunked\r\n" "\r\n"; int result = http_parse_request(chunked_req, strlen(chunked_req), &req); TEST_ASSERT_EQ(1, result, "Chunked request parsed"); TEST_ASSERT_EQ(1, req.is_chunked, "Chunked flag set"); const char *non_chunked_req = "POST /upload HTTP/1.1\r\n" "Host: api.local\r\n" "Content-Length: 100\r\n" "\r\n"; result = http_parse_request(non_chunked_req, strlen(non_chunked_req), &req); TEST_ASSERT_EQ(1, result, "Non-chunked request parsed"); TEST_ASSERT_EQ(0, req.is_chunked, "Chunked flag not set"); TEST_ASSERT_EQ(100, req.content_length, "Content-Length parsed"); TEST_SUITE_END(); } void run_routing_tests(void) { test_routing_host_rewrite(); test_routing_ssl_upstream(); test_routing_dashboard_detection(); test_routing_keep_alive_handling(); test_routing_all_methods_accepted(); test_routing_pipelined_requests(); test_routing_websocket_upgrade(); test_routing_chunked_transfer(); }