259 lines
8.9 KiB
C
Raw Normal View History

2025-11-29 01:49:14 +01:00
#include "test_framework.h"
#include "../src/types.h"
#include "../src/http.h"
#include "../src/config.h"
#include <stdio.h>
#include <unistd.h>
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();
}