#include "test_framework.h"
#include "../src/types.h"
#include "../src/config.h"
#include <stdio.h>
#include <unistd.h>
static const char *TEST_CONFIG_FILE = "/tmp/test_proxy_config.json";
static void create_test_config(const char *content) {
FILE *f = fopen(TEST_CONFIG_FILE, "w");
if (f) {
fprintf(f, "%s", content);
fclose(f);
}
}
static void cleanup_test_config(void) {
unlink(TEST_CONFIG_FILE);
}
void test_config_load_valid(void) {
TEST_SUITE_BEGIN("Config Load Valid Configuration");
const char *valid_config =
"{\n"
" \"port\": 9090,\n"
" \"reverse_proxy\": [\n"
" {\n"
" \"hostname\": \"test.example.com\",\n"
" \"upstream_host\": \"127.0.0.1\",\n"
" \"upstream_port\": 3000,\n"
" \"use_ssl\": false,\n"
" \"rewrite_host\": true\n"
" },\n"
" {\n"
" \"hostname\": \"api.example.com\",\n"
" \"upstream_host\": \"192.168.1.100\",\n"
" \"upstream_port\": 443,\n"
" \"use_ssl\": true,\n"
" \"rewrite_host\": false\n"
" }\n"
" ]\n"
"}\n";
create_test_config(valid_config);
int result = config_load(TEST_CONFIG_FILE);
TEST_ASSERT_EQ(1, result, "Config loaded successfully");
TEST_ASSERT_EQ(9090, config->port, "Port is 9090");
TEST_ASSERT_EQ(2, config->route_count, "Two routes configured");
route_config_t *route1 = config_find_route("test.example.com");
TEST_ASSERT(route1 != NULL, "Route for test.example.com found");
if (route1) {
TEST_ASSERT_STR_EQ("127.0.0.1", route1->upstream_host, "First route upstream host");
TEST_ASSERT_EQ(3000, route1->upstream_port, "First route upstream port");
TEST_ASSERT_EQ(0, route1->use_ssl, "First route SSL disabled");
TEST_ASSERT_EQ(1, route1->rewrite_host, "First route host rewrite enabled");
}
route_config_t *route2 = config_find_route("api.example.com");
TEST_ASSERT(route2 != NULL, "Route for api.example.com found");
if (route2) {
TEST_ASSERT_STR_EQ("192.168.1.100", route2->upstream_host, "Second route upstream host");
TEST_ASSERT_EQ(443, route2->upstream_port, "Second route upstream port");
TEST_ASSERT_EQ(1, route2->use_ssl, "Second route SSL enabled");
TEST_ASSERT_EQ(0, route2->rewrite_host, "Second route host rewrite disabled");
}
config_free();
cleanup_test_config();
TEST_SUITE_END();
}
void test_config_find_route_case_insensitive(void) {
TEST_SUITE_BEGIN("Config Find Route Case Insensitive");
const char *config_content =
"{\n"
" \"port\": 8080,\n"
" \"reverse_proxy\": [\n"
" {\n"
" \"hostname\": \"Test.Example.COM\",\n"
" \"upstream_host\": \"localhost\",\n"
" \"upstream_port\": 3000,\n"
" \"use_ssl\": false,\n"
" \"rewrite_host\": false\n"
" }\n"
" ]\n"
"}\n";
create_test_config(config_content);
config_load(TEST_CONFIG_FILE);
route_config_t *route1 = config_find_route("test.example.com");
TEST_ASSERT(route1 != NULL, "Lowercase hostname matches");
route_config_t *route2 = config_find_route("TEST.EXAMPLE.COM");
TEST_ASSERT(route2 != NULL, "Uppercase hostname matches");
route_config_t *route3 = config_find_route("TeSt.ExAmPlE.cOm");
TEST_ASSERT(route3 != NULL, "Mixed case hostname matches");
config_free();
cleanup_test_config();
TEST_SUITE_END();
}
void test_config_find_route_nonexistent(void) {
TEST_SUITE_BEGIN("Config Find Nonexistent Route");
const char *config_content =
"{\n"
" \"port\": 8080,\n"
" \"reverse_proxy\": [\n"
" {\n"
" \"hostname\": \"existing.com\",\n"
" \"upstream_host\": \"localhost\",\n"
" \"upstream_port\": 3000,\n"
" \"use_ssl\": false,\n"
" \"rewrite_host\": false\n"
" }\n"
" ]\n"
"}\n";
create_test_config(config_content);
config_load(TEST_CONFIG_FILE);
route_config_t *route = config_find_route("nonexistent.com");
TEST_ASSERT(route == NULL, "Nonexistent route returns NULL");
route = config_find_route(NULL);
TEST_ASSERT(route == NULL, "NULL hostname returns NULL");
config_free();
cleanup_test_config();
TEST_SUITE_END();
}
void test_config_default_port(void) {
TEST_SUITE_BEGIN("Config Default Port");
const char *config_content =
"{\n"
" \"reverse_proxy\": [\n"
" {\n"
" \"hostname\": \"test.com\",\n"
" \"upstream_host\": \"localhost\",\n"
" \"upstream_port\": 3000,\n"
" \"use_ssl\": false,\n"
" \"rewrite_host\": false\n"
" }\n"
" ]\n"
"}\n";
create_test_config(config_content);
config_load(TEST_CONFIG_FILE);
TEST_ASSERT_EQ(8080, config->port, "Default port is 8080 when not specified");
config_free();
cleanup_test_config();
TEST_SUITE_END();
}
void test_config_invalid_json(void) {
TEST_SUITE_BEGIN("Config Invalid JSON");
const char *invalid_config = "{ invalid json }";
create_test_config(invalid_config);
int result = config_load(TEST_CONFIG_FILE);
TEST_ASSERT_EQ(0, result, "Invalid JSON returns 0");
cleanup_test_config();
TEST_SUITE_END();
}
void test_config_missing_file(void) {
TEST_SUITE_BEGIN("Config Missing File");
unlink("/tmp/nonexistent_config.json");
int result = config_load("/tmp/nonexistent_config.json");
TEST_ASSERT_EQ(0, result, "Missing file returns 0");
TEST_SUITE_END();
}
void test_config_ssl_rewrite_host_options(void) {
TEST_SUITE_BEGIN("Config SSL and Rewrite Host Options");
const char *config_content =
"{\n"
" \"port\": 8080,\n"
" \"reverse_proxy\": [\n"
" {\n"
" \"hostname\": \"https-rewrite.com\",\n"
" \"upstream_host\": \"secure.example.com\",\n"
" \"upstream_port\": 443,\n"
" \"use_ssl\": true,\n"
" \"rewrite_host\": true\n"
" },\n"
" {\n"
" \"hostname\": \"http-norewrite.com\",\n"
" \"upstream_host\": \"plain.example.com\",\n"
" \"upstream_port\": 80,\n"
" \"use_ssl\": false,\n"
" \"rewrite_host\": false\n"
" }\n"
" ]\n"
"}\n";
create_test_config(config_content);
config_load(TEST_CONFIG_FILE);
route_config_t *ssl_route = config_find_route("https-rewrite.com");
TEST_ASSERT(ssl_route != NULL, "SSL route found");
if (ssl_route) {
TEST_ASSERT_EQ(1, ssl_route->use_ssl, "SSL enabled for https route");
TEST_ASSERT_EQ(1, ssl_route->rewrite_host, "Host rewrite enabled for https route");
}
route_config_t *plain_route = config_find_route("http-norewrite.com");
TEST_ASSERT(plain_route != NULL, "Plain route found");
if (plain_route) {
TEST_ASSERT_EQ(0, plain_route->use_ssl, "SSL disabled for http route");
TEST_ASSERT_EQ(0, plain_route->rewrite_host, "Host rewrite disabled for http route");
}
config_free();
cleanup_test_config();
TEST_SUITE_END();
}
void test_config_invalid_port(void) {
TEST_SUITE_BEGIN("Config Invalid Port");
const char *config_content =
"{\n"
" \"port\": 99999,\n"
" \"reverse_proxy\": [\n"
" {\n"
" \"hostname\": \"test.com\",\n"
" \"upstream_host\": \"localhost\",\n"
" \"upstream_port\": 3000,\n"
" \"use_ssl\": false,\n"
" \"rewrite_host\": false\n"
" }\n"
" ]\n"
"}\n";
create_test_config(config_content);
int result = config_load(TEST_CONFIG_FILE);
TEST_ASSERT_EQ(0, result, "Invalid port (99999) rejected");
cleanup_test_config();
const char *config_zero =
"{\n"
" \"port\": 0,\n"
" \"reverse_proxy\": [\n"
" {\n"
" \"hostname\": \"test.com\",\n"
" \"upstream_host\": \"localhost\",\n"
" \"upstream_port\": 3000,\n"
" \"use_ssl\": false,\n"
" \"rewrite_host\": false\n"
" }\n"
" ]\n"
"}\n";
create_test_config(config_zero);
result = config_load(TEST_CONFIG_FILE);
TEST_ASSERT_EQ(0, result, "Invalid port (0) rejected");
cleanup_test_config();
TEST_SUITE_END();
}
void test_config_empty_routes(void) {
TEST_SUITE_BEGIN("Config Empty Routes");
const char *config_content =
"{\n"
" \"port\": 8080,\n"
" \"reverse_proxy\": []\n"
"}\n";
create_test_config(config_content);
int result = config_load(TEST_CONFIG_FILE);
TEST_ASSERT_EQ(0, result, "Empty routes array rejected");
cleanup_test_config();
TEST_SUITE_END();
}
void test_config_with_auth(void) {
TEST_SUITE_BEGIN("Config With Authentication");
const char *config_content =
"{\n"
" \"port\": 8080,\n"
" \"reverse_proxy\": [\n"
" {\n"
" \"hostname\": \"secure.example.com\",\n"
" \"upstream_host\": \"backend.local\",\n"
" \"upstream_port\": 3000,\n"
" \"use_ssl\": false,\n"
" \"rewrite_host\": false,\n"
" \"use_auth\": true,\n"
" \"username\": \"admin\",\n"
" \"password\": \"secret123\"\n"
" }\n"
" ]\n"
"}\n";
create_test_config(config_content);
int result = config_load(TEST_CONFIG_FILE);
TEST_ASSERT_EQ(1, result, "Config with auth loaded");
route_config_t *route = config_find_route("secure.example.com");
TEST_ASSERT(route != NULL, "Auth route found");
if (route) {
TEST_ASSERT_EQ(1, route->use_auth, "Auth is enabled");
TEST_ASSERT_STR_EQ("admin", route->username, "Username is admin");
TEST_ASSERT(strlen(route->password_hash) > 0, "Password hash is set");
}
config_free();
cleanup_test_config();
TEST_SUITE_END();
}
void test_config_with_patches(void) {
TEST_SUITE_BEGIN("Config With Patch Rules");
const char *config_content =
"{\n"
" \"port\": 8080,\n"
" \"reverse_proxy\": [\n"
" {\n"
" \"hostname\": \"patched.example.com\",\n"
" \"upstream_host\": \"backend.local\",\n"
" \"upstream_port\": 3000,\n"
" \"use_ssl\": false,\n"
" \"rewrite_host\": false,\n"
" \"patch\": {\n"
" \"old-text\": \"new-text\",\n"
" \"remove-this\": null\n"
" }\n"
" }\n"
" ]\n"
"}\n";
create_test_config(config_content);
int result = config_load(TEST_CONFIG_FILE);
TEST_ASSERT_EQ(1, result, "Config with patches loaded");
route_config_t *route = config_find_route("patched.example.com");
TEST_ASSERT(route != NULL, "Patched route found");
if (route) {
TEST_ASSERT_EQ(2, route->patches.rule_count, "Two patch rules loaded");
}
config_free();
cleanup_test_config();
TEST_SUITE_END();
}
void test_config_create_default(void) {
TEST_SUITE_BEGIN("Config Create Default");
const char *default_file = "/tmp/test_default_config.json";
unlink(default_file);
config_create_default(default_file);
FILE *f = fopen(default_file, "r");
TEST_ASSERT(f != NULL, "Default config file created");
if (f) {
fclose(f);
}
config_create_default(default_file);
int result = config_load(default_file);
TEST_ASSERT_EQ(1, result, "Default config is valid");
config_free();
unlink(default_file);
TEST_SUITE_END();
}
void test_config_check_file_changed(void) {
TEST_SUITE_BEGIN("Config Check File Changed");
const char *config_content =
"{\n"
" \"port\": 8080,\n"
" \"reverse_proxy\": [\n"
" {\n"
" \"hostname\": \"test.com\",\n"
" \"upstream_host\": \"localhost\",\n"
" \"upstream_port\": 3000,\n"
" \"use_ssl\": false,\n"
" \"rewrite_host\": false\n"
" }\n"
" ]\n"
"}\n";
create_test_config(config_content);
int first_check = config_check_file_changed(TEST_CONFIG_FILE);
TEST_ASSERT_EQ(0, first_check, "First check returns 0 (initializes mtime)");
int second_check = config_check_file_changed(TEST_CONFIG_FILE);
TEST_ASSERT_EQ(0, second_check, "Second check returns 0 (unchanged)");
int missing_check = config_check_file_changed("/tmp/nonexistent_file.json");
TEST_ASSERT_EQ(0, missing_check, "Missing file returns 0");
cleanup_test_config();
TEST_SUITE_END();
}
void test_config_hot_reload(void) {
TEST_SUITE_BEGIN("Config Hot Reload");
const char *initial_config =
"{\n"
" \"port\": 8080,\n"
" \"reverse_proxy\": [\n"
" {\n"
" \"hostname\": \"initial.com\",\n"
" \"upstream_host\": \"localhost\",\n"
" \"upstream_port\": 3000,\n"
" \"use_ssl\": false,\n"
" \"rewrite_host\": false\n"
" }\n"
" ]\n"
"}\n";
create_test_config(initial_config);
config_load(TEST_CONFIG_FILE);
route_config_t *route1 = config_find_route("initial.com");
TEST_ASSERT(route1 != NULL, "Initial route found");
const char *updated_config =
"{\n"
" \"port\": 9090,\n"
" \"reverse_proxy\": [\n"
" {\n"
" \"hostname\": \"updated.com\",\n"
" \"upstream_host\": \"newhost\",\n"
" \"upstream_port\": 4000,\n"
" \"use_ssl\": true,\n"
" \"rewrite_host\": true\n"
" }\n"
" ]\n"
"}\n";
create_test_config(updated_config);
int result = config_hot_reload(TEST_CONFIG_FILE);
TEST_ASSERT_EQ(1, result, "Hot reload succeeded");
route_config_t *route2 = config_find_route("updated.com");
TEST_ASSERT(route2 != NULL, "Updated route found after hot reload");
if (route2) {
TEST_ASSERT_STR_EQ("newhost", route2->upstream_host, "New upstream host");
TEST_ASSERT_EQ(4000, route2->upstream_port, "New upstream port");
TEST_ASSERT_EQ(1, route2->use_ssl, "SSL enabled after reload");
}
config_free();
cleanup_test_config();
TEST_SUITE_END();
}
void test_config_hot_reload_invalid(void) {
TEST_SUITE_BEGIN("Config Hot Reload Invalid");
const char *valid_config =
"{\n"
" \"port\": 8080,\n"
" \"reverse_proxy\": [\n"
" {\n"
" \"hostname\": \"test.com\",\n"
" \"upstream_host\": \"localhost\",\n"
" \"upstream_port\": 3000,\n"
" \"use_ssl\": false,\n"
" \"rewrite_host\": false\n"
" }\n"
" ]\n"
"}\n";
create_test_config(valid_config);
config_load(TEST_CONFIG_FILE);
create_test_config("{ invalid json }");
int result = config_hot_reload(TEST_CONFIG_FILE);
TEST_ASSERT_EQ(0, result, "Hot reload with invalid JSON fails");
route_config_t *route = config_find_route("test.com");
TEST_ASSERT(route != NULL, "Original config preserved after failed reload");
config_free();
cleanup_test_config();
TEST_SUITE_END();
}
void test_config_hot_reload_invalid_port(void) {
TEST_SUITE_BEGIN("Config Hot Reload Invalid Port");
const char *valid_config =
"{\n"
" \"port\": 8080,\n"
" \"reverse_proxy\": [\n"
" {\n"
" \"hostname\": \"test.com\",\n"
" \"upstream_host\": \"localhost\",\n"
" \"upstream_port\": 3000,\n"
" \"use_ssl\": false,\n"
" \"rewrite_host\": false\n"
" }\n"
" ]\n"
"}\n";
create_test_config(valid_config);
config_load(TEST_CONFIG_FILE);
const char *invalid_port_config =
"{\n"
" \"port\": 99999,\n"
" \"reverse_proxy\": [\n"
" {\n"
" \"hostname\": \"new.com\",\n"
" \"upstream_host\": \"localhost\",\n"
" \"upstream_port\": 3000,\n"
" \"use_ssl\": false,\n"
" \"rewrite_host\": false\n"
" }\n"
" ]\n"
"}\n";
create_test_config(invalid_port_config);
int result = config_hot_reload(TEST_CONFIG_FILE);
TEST_ASSERT_EQ(0, result, "Hot reload with invalid port fails");
config_free();
cleanup_test_config();
TEST_SUITE_END();
}
void test_config_ref_counting(void) {
TEST_SUITE_BEGIN("Config Reference Counting");
const char *config_content =
"{\n"
" \"port\": 8080,\n"
" \"reverse_proxy\": [\n"
" {\n"
" \"hostname\": \"test.com\",\n"
" \"upstream_host\": \"localhost\",\n"
" \"upstream_port\": 3000,\n"
" \"use_ssl\": false,\n"
" \"rewrite_host\": false\n"
" }\n"
" ]\n"
"}\n";
create_test_config(config_content);
config_load(TEST_CONFIG_FILE);
TEST_ASSERT(config != NULL, "Config loaded");
TEST_ASSERT_EQ(1, config->ref_count, "Initial ref count is 1");
config_ref_inc(config);
TEST_ASSERT_EQ(2, config->ref_count, "Ref count incremented to 2");
config_ref_inc(config);
TEST_ASSERT_EQ(3, config->ref_count, "Ref count incremented to 3");
config_ref_dec(config);
TEST_ASSERT_EQ(2, config->ref_count, "Ref count decremented to 2");
config_ref_dec(config);
TEST_ASSERT_EQ(1, config->ref_count, "Ref count decremented to 1");
config_ref_inc(NULL);
config_ref_dec(NULL);
TEST_ASSERT(1, "NULL ref operations don't crash");
config_free();
cleanup_test_config();
TEST_SUITE_END();
}
void test_config_hot_reload_with_auth_and_patches(void) {
TEST_SUITE_BEGIN("Config Hot Reload With Auth And Patches");
const char *initial_config =
"{\n"
" \"port\": 8080,\n"
" \"reverse_proxy\": [\n"
" {\n"
" \"hostname\": \"test.com\",\n"
" \"upstream_host\": \"localhost\",\n"
" \"upstream_port\": 3000,\n"
" \"use_ssl\": false,\n"
" \"rewrite_host\": false\n"
" }\n"
" ]\n"
"}\n";
create_test_config(initial_config);
config_load(TEST_CONFIG_FILE);
const char *updated_config =
"{\n"
" \"port\": 8080,\n"
" \"reverse_proxy\": [\n"
" {\n"
" \"hostname\": \"secure.com\",\n"
" \"upstream_host\": \"backend\",\n"
" \"upstream_port\": 443,\n"
" \"use_ssl\": true,\n"
" \"rewrite_host\": true,\n"
" \"use_auth\": true,\n"
" \"username\": \"user\",\n"
" \"password\": \"pass\",\n"
" \"patch\": {\n"
" \"find\": \"replace\"\n"
" }\n"
" }\n"
" ]\n"
"}\n";
create_test_config(updated_config);
int result = config_hot_reload(TEST_CONFIG_FILE);
TEST_ASSERT_EQ(1, result, "Hot reload with auth and patches succeeded");
route_config_t *route = config_find_route("secure.com");
TEST_ASSERT(route != NULL, "Route with auth found");
if (route) {
TEST_ASSERT_EQ(1, route->use_auth, "Auth enabled after reload");
TEST_ASSERT_EQ(1, route->patches.rule_count, "Patch rule loaded after reload");
}
config_free();
cleanup_test_config();
TEST_SUITE_END();
}
void run_config_tests(void) {
test_config_load_valid();
test_config_find_route_case_insensitive();
test_config_find_route_nonexistent();
test_config_default_port();
test_config_invalid_json();
test_config_missing_file();
test_config_ssl_rewrite_host_options();
test_config_invalid_port();
test_config_empty_routes();
test_config_with_auth();
test_config_with_patches();
test_config_create_default();
test_config_check_file_changed();
test_config_hot_reload();
test_config_hot_reload_invalid();
test_config_hot_reload_invalid_port();
test_config_ref_counting();
test_config_hot_reload_with_auth_and_patches();
}