fix: replace pthread primitives with atomics and add log rotation in config and monitor

- Migrate config.c from pthread_rwlock and __sync builtins to stdatomic.h atomic_fetch_add/sub for ref counting, removing global config_lock
- Replace pthread_mutex_t in health_check.c and monitor.c with lock-free patterns, eliminating vhost_stats_mutex and health_mutex
- Add log file rotation in logging.c with 10MB max size and 5 rotation backups, triggered on fstat size check
- Introduce SSL handshake timeout (SSL_HANDSHAKE_TIMEOUT_SEC) in connection.c with elapsed time tracking via ssl_handshake_start
- Extend upstream connection setup to copy config reference and call config_ref_inc in connection_connect_to_upstream
- Add WAL journal mode and synchronous NORMAL pragmas to monitor.c SQLite init, plus data retention constant and vhost_totals table schema
- Update Makefile with -Werror, -O3, -march=native, -flto flags, separate CFLAGS_DEBUG, add valgrind phony target, and lower min coverage to 60% with new test modules
- Expand .gitignore to cover CLAUDE.md, *.db-wal, and *.db-shm files
- Refactor health_check.c to use snprintf instead of strncpy for hostname/upstream_host with HOSTNAME_MAX_LEN bounds
This commit is contained in:
2025-12-12 21:02:24 +00:00
parent ef3cb0fc54
commit 1ec7101289
16 changed files with 1719 additions and 114 deletions
+482
View File
@@ -0,0 +1,482 @@
#include "test_framework.h"
#include "../src/types.h"
#include "../src/connection.h"
#include "../src/buffer.h"
#include "../src/monitor.h"
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
extern connection_t connections[MAX_FDS];
extern int epoll_fd;
void test_connection_init_all(void) {
TEST_SUITE_BEGIN("Connection Init All");
connection_init_all();
TEST_ASSERT_EQ(CONN_TYPE_UNUSED, connections[0].type, "Connection 0 is unused");
TEST_ASSERT_EQ(CONN_TYPE_UNUSED, connections[100].type, "Connection 100 is unused");
TEST_SUITE_END();
}
void test_connection_set_non_blocking(void) {
TEST_SUITE_BEGIN("Connection Set Non-Blocking");
int pipefd[2];
int result = pipe(pipefd);
TEST_ASSERT_EQ(0, result, "Pipe created");
result = connection_set_non_blocking(pipefd[0]);
TEST_ASSERT_EQ(0, result, "Set non-blocking returns 0");
int flags = fcntl(pipefd[0], F_GETFL, 0);
TEST_ASSERT((flags & O_NONBLOCK) != 0, "O_NONBLOCK flag is set");
close(pipefd[0]);
close(pipefd[1]);
result = connection_set_non_blocking(-1);
TEST_ASSERT_EQ(-1, result, "Invalid fd returns -1");
TEST_SUITE_END();
}
void test_connection_close_unused(void) {
TEST_SUITE_BEGIN("Connection Close Unused");
connection_init_all();
connection_close(-1);
TEST_ASSERT(1, "Close invalid fd doesn't crash");
connection_close(MAX_FDS + 100);
TEST_ASSERT(1, "Close out of range fd doesn't crash");
TEST_SUITE_END();
}
void test_connection_do_read_null(void) {
TEST_SUITE_BEGIN("Connection Do Read NULL");
int result = connection_do_read(NULL);
TEST_ASSERT_EQ(-1, result, "NULL connection returns -1");
TEST_SUITE_END();
}
void test_connection_do_write_null(void) {
TEST_SUITE_BEGIN("Connection Do Write NULL");
int result = connection_do_write(NULL);
TEST_ASSERT_EQ(-1, result, "NULL connection returns -1");
TEST_SUITE_END();
}
void test_connection_do_read_basic(void) {
TEST_SUITE_BEGIN("Connection Do Read Basic");
int sockfd[2];
int result = socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd);
TEST_ASSERT_EQ(0, result, "Socket pair created");
connection_set_non_blocking(sockfd[0]);
connection_set_non_blocking(sockfd[1]);
connection_t conn;
memset(&conn, 0, sizeof(conn));
conn.fd = sockfd[0];
conn.type = CONN_TYPE_CLIENT;
buffer_init(&conn.read_buf, 4096);
const char *test_data = "Hello, World!";
ssize_t written = write(sockfd[1], test_data, strlen(test_data));
TEST_ASSERT(written > 0, "Data written to socket");
int bytes = connection_do_read(&conn);
TEST_ASSERT(bytes > 0, "Read returned positive bytes");
TEST_ASSERT(buffer_available_read(&conn.read_buf) > 0, "Data in read buffer");
buffer_free(&conn.read_buf);
close(sockfd[0]);
close(sockfd[1]);
TEST_SUITE_END();
}
void test_connection_do_write_basic(void) {
TEST_SUITE_BEGIN("Connection Do Write Basic");
int sockfd[2];
int result = socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd);
TEST_ASSERT_EQ(0, result, "Socket pair created");
connection_set_non_blocking(sockfd[0]);
connection_set_non_blocking(sockfd[1]);
connection_t conn;
memset(&conn, 0, sizeof(conn));
conn.fd = sockfd[0];
conn.type = CONN_TYPE_CLIENT;
buffer_init(&conn.write_buf, 4096);
const char *test_data = "Hello, World!";
memcpy(conn.write_buf.data, test_data, strlen(test_data));
conn.write_buf.tail = strlen(test_data);
int bytes = connection_do_write(&conn);
TEST_ASSERT(bytes > 0, "Write returned positive bytes");
char recv_buf[100] = {0};
int recv_bytes = read(sockfd[1], recv_buf, sizeof(recv_buf));
TEST_ASSERT(recv_bytes > 0, "Data received on other end");
buffer_free(&conn.write_buf);
close(sockfd[0]);
close(sockfd[1]);
TEST_SUITE_END();
}
void test_connection_tcp_keepalive(void) {
TEST_SUITE_BEGIN("Connection TCP Keepalive");
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
TEST_ASSERT(sockfd >= 0, "Socket created");
connection_set_tcp_keepalive(sockfd);
int optval;
socklen_t optlen = sizeof(optval);
getsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &optval, &optlen);
TEST_ASSERT_EQ(1, optval, "SO_KEEPALIVE is enabled");
close(sockfd);
TEST_SUITE_END();
}
void test_connection_buffer_operations(void) {
TEST_SUITE_BEGIN("Connection Buffer Operations");
connection_t conn;
memset(&conn, 0, sizeof(conn));
int result = buffer_init(&conn.read_buf, 1024);
TEST_ASSERT_EQ(0, result, "Read buffer initialized");
result = buffer_init(&conn.write_buf, 1024);
TEST_ASSERT_EQ(0, result, "Write buffer initialized");
const char *data = "Test data for buffer";
memcpy(conn.write_buf.data, data, strlen(data));
conn.write_buf.tail = strlen(data);
TEST_ASSERT_EQ(strlen(data), buffer_available_read(&conn.write_buf), "Write buffer has data");
buffer_consume(&conn.write_buf, 5);
TEST_ASSERT_EQ(strlen(data) - 5, buffer_available_read(&conn.write_buf), "Data consumed from buffer");
buffer_free(&conn.read_buf);
buffer_free(&conn.write_buf);
TEST_SUITE_END();
}
void test_connection_send_error_response(void) {
TEST_SUITE_BEGIN("Connection Send Error Response");
connection_init_all();
int old_epoll = epoll_fd;
epoll_fd = epoll_create1(0);
TEST_ASSERT(epoll_fd >= 0, "Epoll created");
int sockfd[2];
int result = socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd);
TEST_ASSERT_EQ(0, result, "Socket pair created");
connection_t *conn = &connections[sockfd[0]];
memset(conn, 0, sizeof(connection_t));
conn->fd = sockfd[0];
conn->type = CONN_TYPE_CLIENT;
conn->state = CLIENT_STATE_READING_HEADERS;
buffer_init(&conn->read_buf, 4096);
buffer_init(&conn->write_buf, 4096);
struct epoll_event ev = { .events = EPOLLIN, .data.fd = sockfd[0] };
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sockfd[0], &ev);
connection_send_error_response(conn, 404, "Not Found", "Resource not found");
TEST_ASSERT(conn->write_buf.tail > 0, "Error response written");
TEST_ASSERT(strstr((char*)conn->write_buf.data, "404") != NULL, "404 in response");
buffer_free(&conn->read_buf);
buffer_free(&conn->write_buf);
close(sockfd[0]);
close(sockfd[1]);
close(epoll_fd);
epoll_fd = old_epoll;
TEST_SUITE_END();
}
void test_connection_epoll_operations(void) {
TEST_SUITE_BEGIN("Connection Epoll Operations");
int old_epoll = epoll_fd;
epoll_fd = epoll_create1(0);
TEST_ASSERT(epoll_fd >= 0, "Epoll created");
int sockfd[2];
int result = socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd);
TEST_ASSERT_EQ(0, result, "Socket pair created");
connection_add_to_epoll(sockfd[0], EPOLLIN);
TEST_ASSERT(1, "Add to epoll doesn't crash");
connection_modify_epoll(sockfd[0], EPOLLIN | EPOLLOUT);
TEST_ASSERT(1, "Modify epoll doesn't crash");
close(sockfd[0]);
close(sockfd[1]);
close(epoll_fd);
epoll_fd = old_epoll;
TEST_SUITE_END();
}
void test_connection_send_auth_required(void) {
TEST_SUITE_BEGIN("Connection Send Auth Required");
connection_init_all();
int old_epoll = epoll_fd;
epoll_fd = epoll_create1(0);
TEST_ASSERT(epoll_fd >= 0, "Epoll created");
int sockfd[2];
int result = socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd);
TEST_ASSERT_EQ(0, result, "Socket pair created");
connection_t *conn = &connections[sockfd[0]];
memset(conn, 0, sizeof(connection_t));
conn->fd = sockfd[0];
conn->type = CONN_TYPE_CLIENT;
conn->state = CLIENT_STATE_READING_HEADERS;
buffer_init(&conn->read_buf, 4096);
buffer_init(&conn->write_buf, 4096);
struct epoll_event ev = { .events = EPOLLIN, .data.fd = sockfd[0] };
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sockfd[0], &ev);
connection_send_auth_required(conn, "RProxy Dashboard");
TEST_ASSERT(conn->write_buf.tail > 0, "Auth response written");
TEST_ASSERT(strstr((char*)conn->write_buf.data, "401") != NULL, "401 status in response");
TEST_ASSERT(strstr((char*)conn->write_buf.data, "WWW-Authenticate") != NULL, "WWW-Authenticate header");
buffer_free(&conn->read_buf);
buffer_free(&conn->write_buf);
close(sockfd[0]);
close(sockfd[1]);
close(epoll_fd);
epoll_fd = old_epoll;
TEST_SUITE_END();
}
void test_connection_close_active(void) {
TEST_SUITE_BEGIN("Connection Close Active");
connection_init_all();
int old_epoll = epoll_fd;
epoll_fd = epoll_create1(0);
TEST_ASSERT(epoll_fd >= 0, "Epoll created");
int sockfd[2];
int result = socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd);
TEST_ASSERT_EQ(0, result, "Socket pair created");
connection_t *conn = &connections[sockfd[0]];
memset(conn, 0, sizeof(connection_t));
conn->fd = sockfd[0];
conn->type = CONN_TYPE_CLIENT;
conn->state = CLIENT_STATE_READING_HEADERS;
buffer_init(&conn->read_buf, 4096);
buffer_init(&conn->write_buf, 4096);
struct epoll_event ev = { .events = EPOLLIN, .data.fd = sockfd[0] };
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sockfd[0], &ev);
connection_close(sockfd[0]);
TEST_ASSERT_EQ(CONN_TYPE_UNUSED, conn->type, "Connection type reset to unused");
close(sockfd[1]);
close(epoll_fd);
epoll_fd = old_epoll;
TEST_SUITE_END();
}
void test_connection_cleanup_idle(void) {
TEST_SUITE_BEGIN("Connection Cleanup Idle");
connection_init_all();
int old_epoll = epoll_fd;
epoll_fd = epoll_create1(0);
TEST_ASSERT(epoll_fd >= 0, "Epoll created");
connection_cleanup_idle();
TEST_ASSERT(1, "Cleanup idle doesn't crash on empty connections");
close(epoll_fd);
epoll_fd = old_epoll;
TEST_SUITE_END();
}
void test_connection_do_read_eof(void) {
TEST_SUITE_BEGIN("Connection Do Read EOF");
int sockfd[2];
int result = socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd);
TEST_ASSERT_EQ(0, result, "Socket pair created");
connection_set_non_blocking(sockfd[0]);
connection_t conn;
memset(&conn, 0, sizeof(conn));
conn.fd = sockfd[0];
conn.type = CONN_TYPE_CLIENT;
buffer_init(&conn.read_buf, 4096);
close(sockfd[1]);
int bytes = connection_do_read(&conn);
TEST_ASSERT_EQ(0, bytes, "Read returns 0 on EOF");
buffer_free(&conn.read_buf);
close(sockfd[0]);
TEST_SUITE_END();
}
void test_connection_do_write_full_buffer(void) {
TEST_SUITE_BEGIN("Connection Do Write Full Buffer");
int sockfd[2];
int result = socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd);
TEST_ASSERT_EQ(0, result, "Socket pair created");
connection_set_non_blocking(sockfd[0]);
connection_set_non_blocking(sockfd[1]);
int sndbuf = 4096;
setsockopt(sockfd[0], SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));
connection_t conn;
memset(&conn, 0, sizeof(conn));
conn.fd = sockfd[0];
conn.type = CONN_TYPE_CLIENT;
buffer_init(&conn.write_buf, 4096);
memset(conn.write_buf.data, 'A', 4096);
conn.write_buf.tail = 4096;
int bytes = connection_do_write(&conn);
TEST_ASSERT(bytes > 0, "Write returns positive bytes");
buffer_free(&conn.write_buf);
close(sockfd[0]);
close(sockfd[1]);
TEST_SUITE_END();
}
void test_connection_multiple_error_responses(void) {
TEST_SUITE_BEGIN("Connection Multiple Error Responses");
connection_init_all();
int old_epoll = epoll_fd;
epoll_fd = epoll_create1(0);
int sockfd[2];
socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd);
connection_t *conn = &connections[sockfd[0]];
memset(conn, 0, sizeof(connection_t));
conn->fd = sockfd[0];
conn->type = CONN_TYPE_CLIENT;
buffer_init(&conn->read_buf, 4096);
buffer_init(&conn->write_buf, 4096);
struct epoll_event ev = { .events = EPOLLIN, .data.fd = sockfd[0] };
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sockfd[0], &ev);
connection_send_error_response(conn, 500, "Internal Server Error", "Server error");
TEST_ASSERT(strstr((char*)conn->write_buf.data, "500") != NULL, "500 in response");
buffer_free(&conn->read_buf);
buffer_free(&conn->write_buf);
close(sockfd[0]);
close(sockfd[1]);
close(epoll_fd);
epoll_fd = old_epoll;
TEST_SUITE_END();
}
void test_connection_do_read_eagain(void) {
TEST_SUITE_BEGIN("Connection Do Read EAGAIN");
int sockfd[2];
int result = socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd);
TEST_ASSERT_EQ(0, result, "Socket pair created");
connection_set_non_blocking(sockfd[0]);
connection_set_non_blocking(sockfd[1]);
connection_t conn;
memset(&conn, 0, sizeof(conn));
conn.fd = sockfd[0];
conn.type = CONN_TYPE_CLIENT;
buffer_init(&conn.read_buf, 4096);
int bytes = connection_do_read(&conn);
TEST_ASSERT(bytes <= 0, "Read returns <= 0 when no data");
buffer_free(&conn.read_buf);
close(sockfd[0]);
close(sockfd[1]);
TEST_SUITE_END();
}
void run_connection_tests(void) {
test_connection_init_all();
test_connection_set_non_blocking();
test_connection_close_unused();
test_connection_do_read_null();
test_connection_do_write_null();
test_connection_do_read_basic();
test_connection_do_write_basic();
test_connection_tcp_keepalive();
test_connection_buffer_operations();
test_connection_send_error_response();
test_connection_epoll_operations();
test_connection_send_auth_required();
test_connection_close_active();
test_connection_cleanup_idle();
test_connection_do_read_eof();
test_connection_do_write_full_buffer();
test_connection_multiple_error_responses();
test_connection_do_read_eagain();
}
+273
View File
@@ -0,0 +1,273 @@
#include "test_framework.h"
#include "../src/types.h"
#include "../src/dashboard.h"
#include "../src/monitor.h"
#include "../src/buffer.h"
#include "../src/connection.h"
#include "../src/auth.h"
#include <string.h>
#include <unistd.h>
#include <sys/epoll.h>
extern connection_t connections[MAX_FDS];
extern int epoll_fd;
static connection_t* create_test_connection(void) {
static int test_fd = 100;
int fd = test_fd++;
connection_t *conn = &connections[fd];
memset(conn, 0, sizeof(connection_t));
conn->fd = fd;
conn->type = CONN_TYPE_CLIENT;
conn->state = CLIENT_STATE_SERVING_INTERNAL;
buffer_init(&conn->read_buf, 4096);
buffer_init(&conn->write_buf, 65536);
conn->request.keep_alive = 1;
return conn;
}
static void cleanup_test_connection(connection_t *conn) {
buffer_free(&conn->read_buf);
buffer_free(&conn->write_buf);
memset(conn, 0, sizeof(connection_t));
}
void test_dashboard_serve_null(void) {
TEST_SUITE_BEGIN("Dashboard Serve NULL Safety");
dashboard_serve(NULL, "GET /", 5);
TEST_ASSERT(1, "NULL connection doesn't crash");
dashboard_serve_stats_api(NULL, "GET /", 5);
TEST_ASSERT(1, "NULL connection for stats doesn't crash");
TEST_SUITE_END();
}
void test_dashboard_serve_html(void) {
TEST_SUITE_BEGIN("Dashboard Serve HTML");
epoll_fd = epoll_create1(0);
TEST_ASSERT(epoll_fd >= 0, "Epoll created");
monitor_init(NULL);
auth_init(NULL, NULL);
connection_t *conn = create_test_connection();
struct epoll_event ev = { .events = EPOLLIN, .data.fd = conn->fd };
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn->fd, &ev);
const char *request = "GET /rproxy/dashboard HTTP/1.1\r\nHost: localhost\r\n\r\n";
dashboard_serve(conn, request, strlen(request));
TEST_ASSERT(conn->write_buf.tail > 0, "Response written to buffer");
TEST_ASSERT(strstr((char*)conn->write_buf.data, "HTTP/1.1 200") != NULL, "HTTP 200 response");
TEST_ASSERT(strstr((char*)conn->write_buf.data, "text/html") != NULL, "Content-Type is HTML");
TEST_ASSERT(strstr((char*)conn->write_buf.data, "<!DOCTYPE html>") != NULL, "HTML content present");
cleanup_test_connection(conn);
monitor_cleanup();
close(epoll_fd);
TEST_SUITE_END();
}
void test_dashboard_serve_stats_api(void) {
TEST_SUITE_BEGIN("Dashboard Serve Stats API");
epoll_fd = epoll_create1(0);
monitor_init(NULL);
auth_init(NULL, NULL);
vhost_stats_t *stats = monitor_get_or_create_vhost_stats("test.example.com");
monitor_record_request_start(stats, 0);
monitor_record_bytes(stats, 1000, 500);
connection_t *conn = create_test_connection();
struct epoll_event ev = { .events = EPOLLIN, .data.fd = conn->fd };
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn->fd, &ev);
const char *request = "GET /rproxy/api/stats HTTP/1.1\r\nHost: localhost\r\n\r\n";
dashboard_serve_stats_api(conn, request, strlen(request));
TEST_ASSERT(conn->write_buf.tail > 0, "Stats response written");
TEST_ASSERT(strstr((char*)conn->write_buf.data, "HTTP/1.1 200") != NULL, "HTTP 200 response");
TEST_ASSERT(strstr((char*)conn->write_buf.data, "application/json") != NULL, "Content-Type is JSON");
TEST_ASSERT(strstr((char*)conn->write_buf.data, "cpu_percent") != NULL, "CPU data in response");
TEST_ASSERT(strstr((char*)conn->write_buf.data, "memory_gb") != NULL, "Memory data in response");
TEST_ASSERT(strstr((char*)conn->write_buf.data, "active_connections") != NULL, "Connections data in response");
TEST_ASSERT(strstr((char*)conn->write_buf.data, "processes") != NULL, "Processes array in response");
TEST_ASSERT(strstr((char*)conn->write_buf.data, "test.example.com") != NULL, "Vhost in response");
cleanup_test_connection(conn);
monitor_cleanup();
close(epoll_fd);
TEST_SUITE_END();
}
void test_dashboard_with_auth_disabled(void) {
TEST_SUITE_BEGIN("Dashboard With Auth Disabled");
epoll_fd = epoll_create1(0);
monitor_init(NULL);
auth_init(NULL, NULL);
connection_t *conn = create_test_connection();
struct epoll_event ev = { .events = EPOLLIN, .data.fd = conn->fd };
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn->fd, &ev);
const char *request = "GET /rproxy/dashboard HTTP/1.1\r\nHost: localhost\r\n\r\n";
dashboard_serve(conn, request, strlen(request));
TEST_ASSERT(strstr((char*)conn->write_buf.data, "HTTP/1.1 200") != NULL, "Dashboard accessible without auth when disabled");
cleanup_test_connection(conn);
monitor_cleanup();
close(epoll_fd);
TEST_SUITE_END();
}
void test_dashboard_with_auth_enabled_no_header(void) {
TEST_SUITE_BEGIN("Dashboard With Auth Enabled No Header");
epoll_fd = epoll_create1(0);
monitor_init(NULL);
auth_init("admin", "secret");
connection_t *conn = create_test_connection();
struct epoll_event ev = { .events = EPOLLIN, .data.fd = conn->fd };
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn->fd, &ev);
const char *request = "GET /rproxy/dashboard HTTP/1.1\r\nHost: localhost\r\n\r\n";
dashboard_serve(conn, request, strlen(request));
TEST_ASSERT(strstr((char*)conn->write_buf.data, "HTTP/1.1 401") != NULL, "401 response when auth required");
TEST_ASSERT(strstr((char*)conn->write_buf.data, "WWW-Authenticate") != NULL, "WWW-Authenticate header present");
TEST_ASSERT(conn->state == CLIENT_STATE_ERROR, "Connection state is ERROR");
cleanup_test_connection(conn);
auth_init(NULL, NULL);
monitor_cleanup();
close(epoll_fd);
TEST_SUITE_END();
}
void test_dashboard_with_valid_auth(void) {
TEST_SUITE_BEGIN("Dashboard With Valid Auth");
epoll_fd = epoll_create1(0);
monitor_init(NULL);
auth_init("admin", "secret");
connection_t *conn = create_test_connection();
struct epoll_event ev = { .events = EPOLLIN, .data.fd = conn->fd };
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn->fd, &ev);
const char *request = "GET /rproxy/dashboard HTTP/1.1\r\nHost: localhost\r\nAuthorization: Basic YWRtaW46c2VjcmV0\r\n\r\n";
dashboard_serve(conn, request, strlen(request));
TEST_ASSERT(strstr((char*)conn->write_buf.data, "HTTP/1.1 200") != NULL, "200 response with valid auth");
TEST_ASSERT(strstr((char*)conn->write_buf.data, "<!DOCTYPE html>") != NULL, "HTML content served");
cleanup_test_connection(conn);
auth_init(NULL, NULL);
monitor_cleanup();
close(epoll_fd);
TEST_SUITE_END();
}
void test_dashboard_stats_with_history(void) {
TEST_SUITE_BEGIN("Dashboard Stats With History Data");
epoll_fd = epoll_create1(0);
monitor_init(NULL);
auth_init(NULL, NULL);
for (int i = 0; i < 5; i++) {
monitor_update();
}
vhost_stats_t *stats1 = monitor_get_or_create_vhost_stats("api.example.com");
vhost_stats_t *stats2 = monitor_get_or_create_vhost_stats("web.example.com");
for (int i = 0; i < 10; i++) {
monitor_record_request_start(stats1, 0);
monitor_record_request_start(stats2, 1);
}
monitor_record_bytes(stats1, 50000, 25000);
monitor_record_bytes(stats2, 30000, 15000);
connection_t *conn = create_test_connection();
struct epoll_event ev = { .events = EPOLLIN, .data.fd = conn->fd };
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn->fd, &ev);
const char *request = "GET /rproxy/api/stats HTTP/1.1\r\nHost: localhost\r\n\r\n";
dashboard_serve_stats_api(conn, request, strlen(request));
TEST_ASSERT(strstr((char*)conn->write_buf.data, "api.example.com") != NULL, "First vhost in response");
TEST_ASSERT(strstr((char*)conn->write_buf.data, "web.example.com") != NULL, "Second vhost in response");
TEST_ASSERT(strstr((char*)conn->write_buf.data, "cpu_history") != NULL, "CPU history in response");
TEST_ASSERT(strstr((char*)conn->write_buf.data, "memory_history") != NULL, "Memory history in response");
TEST_ASSERT(strstr((char*)conn->write_buf.data, "network_rx_history") != NULL, "Network RX history in response");
TEST_ASSERT(strstr((char*)conn->write_buf.data, "disk_read_history") != NULL, "Disk read history in response");
cleanup_test_connection(conn);
monitor_cleanup();
close(epoll_fd);
TEST_SUITE_END();
}
void test_dashboard_keep_alive(void) {
TEST_SUITE_BEGIN("Dashboard Keep-Alive Header");
epoll_fd = epoll_create1(0);
monitor_init(NULL);
auth_init(NULL, NULL);
connection_t *conn = create_test_connection();
conn->request.keep_alive = 1;
struct epoll_event ev = { .events = EPOLLIN, .data.fd = conn->fd };
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn->fd, &ev);
const char *request = "GET /rproxy/dashboard HTTP/1.1\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n";
dashboard_serve(conn, request, strlen(request));
TEST_ASSERT(strstr((char*)conn->write_buf.data, "Connection: keep-alive") != NULL, "Keep-alive in response");
cleanup_test_connection(conn);
conn = create_test_connection();
conn->request.keep_alive = 0;
ev.data.fd = conn->fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn->fd, &ev);
dashboard_serve(conn, request, strlen(request));
TEST_ASSERT(strstr((char*)conn->write_buf.data, "Connection: close") != NULL, "Connection close in response");
cleanup_test_connection(conn);
monitor_cleanup();
close(epoll_fd);
TEST_SUITE_END();
}
void run_dashboard_tests(void) {
test_dashboard_serve_null();
test_dashboard_serve_html();
test_dashboard_serve_stats_api();
test_dashboard_with_auth_disabled();
test_dashboard_with_auth_enabled_no_header();
test_dashboard_with_valid_auth();
test_dashboard_stats_with_history();
test_dashboard_keep_alive();
}
+210
View File
@@ -0,0 +1,210 @@
#include "test_framework.h"
#include "../src/types.h"
#include "../src/health_check.h"
#include "../src/config.h"
#include <string.h>
#include <stdio.h>
#include <unistd.h>
extern app_config_t *config;
static void setup_test_config(int route_count) {
if (config) {
if (config->routes) free(config->routes);
free(config);
}
config = calloc(1, sizeof(app_config_t));
config->port = 8080;
config->route_count = route_count;
if (route_count > 0) {
config->routes = calloc(route_count, sizeof(route_config_t));
for (int i = 0; i < route_count; i++) {
snprintf(config->routes[i].hostname, sizeof(config->routes[i].hostname), "host%d.example.com", i);
snprintf(config->routes[i].upstream_host, sizeof(config->routes[i].upstream_host), "127.0.0.1");
config->routes[i].upstream_port = 3000 + i;
}
}
}
static void cleanup_test_config(void) {
if (config) {
if (config->routes) free(config->routes);
free(config);
config = NULL;
}
}
void test_health_check_init_no_routes(void) {
TEST_SUITE_BEGIN("Health Check Init No Routes");
setup_test_config(0);
health_check_init();
int healthy = health_check_is_healthy("nonexistent.com");
TEST_ASSERT_EQ(1, healthy, "Nonexistent host returns healthy (fail-open)");
health_check_cleanup();
cleanup_test_config();
TEST_SUITE_END();
}
void test_health_check_init_with_routes(void) {
TEST_SUITE_BEGIN("Health Check Init With Routes");
setup_test_config(3);
health_check_init();
int healthy0 = health_check_is_healthy("host0.example.com");
TEST_ASSERT_EQ(1, healthy0, "Host0 initially healthy");
int healthy1 = health_check_is_healthy("host1.example.com");
TEST_ASSERT_EQ(1, healthy1, "Host1 initially healthy");
int healthy2 = health_check_is_healthy("host2.example.com");
TEST_ASSERT_EQ(1, healthy2, "Host2 initially healthy");
int unknown = health_check_is_healthy("unknown.example.com");
TEST_ASSERT_EQ(1, unknown, "Unknown host returns healthy (fail-open)");
health_check_cleanup();
cleanup_test_config();
TEST_SUITE_END();
}
void test_health_check_null_hostname(void) {
TEST_SUITE_BEGIN("Health Check NULL Hostname");
setup_test_config(1);
health_check_init();
int healthy = health_check_is_healthy(NULL);
TEST_ASSERT_EQ(1, healthy, "NULL hostname returns healthy (fail-open)");
health_check_cleanup();
cleanup_test_config();
TEST_SUITE_END();
}
void test_health_check_case_insensitive(void) {
TEST_SUITE_BEGIN("Health Check Case Insensitive");
setup_test_config(1);
health_check_init();
int lower = health_check_is_healthy("host0.example.com");
TEST_ASSERT_EQ(1, lower, "Lowercase matches");
int upper = health_check_is_healthy("HOST0.EXAMPLE.COM");
TEST_ASSERT_EQ(1, upper, "Uppercase matches");
int mixed = health_check_is_healthy("Host0.Example.COM");
TEST_ASSERT_EQ(1, mixed, "Mixed case matches");
health_check_cleanup();
cleanup_test_config();
TEST_SUITE_END();
}
void test_health_check_cleanup(void) {
TEST_SUITE_BEGIN("Health Check Cleanup");
setup_test_config(2);
health_check_init();
health_check_cleanup();
int healthy = health_check_is_healthy("host0.example.com");
TEST_ASSERT_EQ(1, healthy, "After cleanup returns healthy (disabled)");
cleanup_test_config();
TEST_SUITE_END();
}
void test_health_check_run_no_init(void) {
TEST_SUITE_BEGIN("Health Check Run Without Init");
health_check_run();
TEST_ASSERT(1, "Run without init doesn't crash");
TEST_SUITE_END();
}
void test_health_check_run_with_init(void) {
TEST_SUITE_BEGIN("Health Check Run With Init");
setup_test_config(1);
health_check_init();
health_check_run();
TEST_ASSERT(1, "Health check run completes");
int healthy = health_check_is_healthy("host0.example.com");
TEST_ASSERT(healthy == 0 || healthy == 1, "Health status is valid");
health_check_cleanup();
cleanup_test_config();
TEST_SUITE_END();
}
void test_health_check_multiple_init(void) {
TEST_SUITE_BEGIN("Health Check Multiple Init");
setup_test_config(2);
health_check_init();
int h1 = health_check_is_healthy("host0.example.com");
TEST_ASSERT_EQ(1, h1, "First init: host0 healthy");
setup_test_config(3);
health_check_init();
int h2 = health_check_is_healthy("host2.example.com");
TEST_ASSERT_EQ(1, h2, "Second init: host2 healthy");
health_check_cleanup();
cleanup_test_config();
TEST_SUITE_END();
}
void test_health_check_disabled_state(void) {
TEST_SUITE_BEGIN("Health Check Disabled State");
int healthy_before = health_check_is_healthy("any.host.com");
TEST_ASSERT_EQ(1, healthy_before, "Disabled: any host is healthy");
setup_test_config(1);
health_check_init();
int healthy_enabled = health_check_is_healthy("host0.example.com");
TEST_ASSERT_EQ(1, healthy_enabled, "Enabled: known host is healthy");
health_check_cleanup();
int healthy_after = health_check_is_healthy("any.host.com");
TEST_ASSERT_EQ(1, healthy_after, "After cleanup: any host is healthy");
cleanup_test_config();
TEST_SUITE_END();
}
void run_health_check_tests(void) {
test_health_check_init_no_routes();
test_health_check_init_with_routes();
test_health_check_null_hostname();
test_health_check_case_insensitive();
test_health_check_cleanup();
test_health_check_run_no_init();
test_health_check_run_with_init();
test_health_check_multiple_init();
test_health_check_disabled_state();
}
+10
View File
@@ -25,6 +25,11 @@ extern void run_http_helper_tests(void);
extern void run_patch_tests(void);
extern void run_auth_tests(void);
extern void run_rate_limit_tests(void);
extern void run_monitor_tests(void);
extern void run_dashboard_tests(void);
extern void run_health_check_tests(void);
extern void run_ssl_handler_tests(void);
extern void run_connection_tests(void);
int main(int argc, char *argv[]) {
(void)argc;
@@ -44,6 +49,11 @@ int main(int argc, char *argv[]) {
run_patch_tests();
run_auth_tests();
run_rate_limit_tests();
run_monitor_tests();
run_dashboard_tests();
run_health_check_tests();
run_ssl_handler_tests();
run_connection_tests();
test_summary();
+244
View File
@@ -0,0 +1,244 @@
#include "test_framework.h"
#include "../src/types.h"
#include "../src/monitor.h"
#include <string.h>
#include <unistd.h>
void test_history_deque_init(void) {
TEST_SUITE_BEGIN("History Deque Initialization");
history_deque_t dq;
history_deque_init(&dq, 10);
TEST_ASSERT(dq.points != NULL, "Deque points allocated");
TEST_ASSERT_EQ(10, dq.capacity, "Capacity is 10");
TEST_ASSERT_EQ(0, dq.count, "Count is 0");
TEST_ASSERT_EQ(0, dq.head, "Head is 0");
free(dq.points);
TEST_SUITE_END();
}
void test_history_deque_push(void) {
TEST_SUITE_BEGIN("History Deque Push");
history_deque_t dq;
history_deque_init(&dq, 5);
history_deque_push(&dq, 1.0, 100.0);
TEST_ASSERT_EQ(1, dq.count, "Count is 1 after first push");
history_deque_push(&dq, 2.0, 200.0);
history_deque_push(&dq, 3.0, 300.0);
TEST_ASSERT_EQ(3, dq.count, "Count is 3 after three pushes");
history_deque_push(&dq, 4.0, 400.0);
history_deque_push(&dq, 5.0, 500.0);
TEST_ASSERT_EQ(5, dq.count, "Count is 5 at capacity");
history_deque_push(&dq, 6.0, 600.0);
TEST_ASSERT_EQ(5, dq.count, "Count stays at capacity after overflow");
free(dq.points);
TEST_SUITE_END();
}
void test_network_history_deque(void) {
TEST_SUITE_BEGIN("Network History Deque");
network_history_deque_t dq;
network_history_deque_init(&dq, 5);
TEST_ASSERT(dq.points != NULL, "Network deque points allocated");
TEST_ASSERT_EQ(5, dq.capacity, "Capacity is 5");
TEST_ASSERT_EQ(0, dq.count, "Count is 0");
network_history_deque_push(&dq, 1.0, 100.0, 50.0);
TEST_ASSERT_EQ(1, dq.count, "Count is 1 after push");
network_history_deque_push(&dq, 2.0, 200.0, 100.0);
network_history_deque_push(&dq, 3.0, 300.0, 150.0);
TEST_ASSERT_EQ(3, dq.count, "Count is 3");
free(dq.points);
TEST_SUITE_END();
}
void test_disk_history_deque(void) {
TEST_SUITE_BEGIN("Disk History Deque");
disk_history_deque_t dq;
disk_history_deque_init(&dq, 5);
TEST_ASSERT(dq.points != NULL, "Disk deque points allocated");
TEST_ASSERT_EQ(5, dq.capacity, "Capacity is 5");
disk_history_deque_push(&dq, 1.0, 10.0, 5.0);
TEST_ASSERT_EQ(1, dq.count, "Count is 1 after push");
disk_history_deque_push(&dq, 2.0, 20.0, 10.0);
TEST_ASSERT_EQ(2, dq.count, "Count is 2");
free(dq.points);
TEST_SUITE_END();
}
void test_request_time_deque(void) {
TEST_SUITE_BEGIN("Request Time Deque");
request_time_deque_t dq;
request_time_deque_init(&dq, 10);
TEST_ASSERT(dq.times != NULL, "Request time deque allocated");
TEST_ASSERT_EQ(10, dq.capacity, "Capacity is 10");
TEST_ASSERT_EQ(0, dq.count, "Count is 0");
request_time_deque_push(&dq, 15.5);
TEST_ASSERT_EQ(1, dq.count, "Count is 1 after push");
request_time_deque_push(&dq, 20.0);
request_time_deque_push(&dq, 25.5);
TEST_ASSERT_EQ(3, dq.count, "Count is 3");
for (int i = 0; i < 10; i++) {
request_time_deque_push(&dq, (double)i);
}
TEST_ASSERT_EQ(10, dq.count, "Count stays at capacity");
free(dq.times);
TEST_SUITE_END();
}
void test_monitor_init_cleanup(void) {
TEST_SUITE_BEGIN("Monitor Init and Cleanup");
monitor_init(NULL);
TEST_ASSERT(monitor.cpu_history.points != NULL, "CPU history initialized");
TEST_ASSERT(monitor.memory_history.points != NULL, "Memory history initialized");
TEST_ASSERT(monitor.network_history.points != NULL, "Network history initialized");
TEST_ASSERT(monitor.disk_history.points != NULL, "Disk history initialized");
monitor_cleanup();
TEST_ASSERT(monitor.cpu_history.points == NULL, "CPU history cleaned up");
TEST_ASSERT(monitor.memory_history.points == NULL, "Memory history cleaned up");
TEST_SUITE_END();
}
void test_monitor_vhost_stats(void) {
TEST_SUITE_BEGIN("Monitor Vhost Stats");
monitor_init(NULL);
vhost_stats_t *stats1 = monitor_get_or_create_vhost_stats("test.example.com");
TEST_ASSERT(stats1 != NULL, "First vhost stats created");
TEST_ASSERT_STR_EQ("test.example.com", stats1->vhost_name, "Vhost name correct");
TEST_ASSERT_EQ(0, stats1->total_requests, "Initial total requests is 0");
vhost_stats_t *stats2 = monitor_get_or_create_vhost_stats("test.example.com");
TEST_ASSERT(stats1 == stats2, "Same vhost returns same stats");
vhost_stats_t *stats3 = monitor_get_or_create_vhost_stats("other.example.com");
TEST_ASSERT(stats3 != NULL, "Second vhost stats created");
TEST_ASSERT(stats1 != stats3, "Different vhosts have different stats");
vhost_stats_t *null_stats = monitor_get_or_create_vhost_stats(NULL);
TEST_ASSERT(null_stats == NULL, "NULL vhost returns NULL");
vhost_stats_t *empty_stats = monitor_get_or_create_vhost_stats("");
TEST_ASSERT(empty_stats == NULL, "Empty vhost returns NULL");
monitor_cleanup();
TEST_SUITE_END();
}
void test_monitor_record_request(void) {
TEST_SUITE_BEGIN("Monitor Record Request");
monitor_init(NULL);
vhost_stats_t *stats = monitor_get_or_create_vhost_stats("api.test.com");
TEST_ASSERT(stats != NULL, "Stats created");
monitor_record_request_start(stats, 0);
TEST_ASSERT_EQ(1, stats->total_requests, "Total requests incremented");
TEST_ASSERT_EQ(1, stats->http_requests, "HTTP requests incremented");
TEST_ASSERT_EQ(0, stats->websocket_requests, "WebSocket requests unchanged");
monitor_record_request_start(stats, 1);
TEST_ASSERT_EQ(2, stats->total_requests, "Total requests is 2");
TEST_ASSERT_EQ(1, stats->http_requests, "HTTP requests still 1");
TEST_ASSERT_EQ(1, stats->websocket_requests, "WebSocket requests is 1");
monitor_record_request_start(NULL, 0);
TEST_ASSERT(1, "NULL stats doesn't crash");
monitor_cleanup();
TEST_SUITE_END();
}
void test_monitor_record_bytes(void) {
TEST_SUITE_BEGIN("Monitor Record Bytes");
monitor_init(NULL);
vhost_stats_t *stats = monitor_get_or_create_vhost_stats("bytes.test.com");
TEST_ASSERT(stats != NULL, "Stats created");
TEST_ASSERT_EQ(0, stats->bytes_sent, "Initial bytes sent is 0");
TEST_ASSERT_EQ(0, stats->bytes_recv, "Initial bytes recv is 0");
monitor_record_bytes(stats, 1000, 500);
TEST_ASSERT_EQ(1000, stats->bytes_sent, "Bytes sent recorded");
TEST_ASSERT_EQ(500, stats->bytes_recv, "Bytes recv recorded");
monitor_record_bytes(stats, 500, 250);
TEST_ASSERT_EQ(1500, stats->bytes_sent, "Bytes sent accumulated");
TEST_ASSERT_EQ(750, stats->bytes_recv, "Bytes recv accumulated");
monitor_record_bytes(NULL, 100, 100);
TEST_ASSERT(1, "NULL stats doesn't crash");
monitor_cleanup();
TEST_SUITE_END();
}
void test_monitor_update(void) {
TEST_SUITE_BEGIN("Monitor Update");
monitor_init(NULL);
monitor_update();
TEST_ASSERT(monitor.cpu_history.count >= 0, "CPU history has data after update");
TEST_ASSERT(monitor.memory_history.count >= 0, "Memory history has data after update");
monitor_update();
monitor_update();
TEST_ASSERT(1, "Multiple updates don't crash");
monitor_cleanup();
TEST_SUITE_END();
}
void run_monitor_tests(void) {
test_history_deque_init();
test_history_deque_push();
test_network_history_deque();
test_disk_history_deque();
test_request_time_deque();
test_monitor_init_cleanup();
test_monitor_vhost_stats();
test_monitor_record_request();
test_monitor_record_bytes();
test_monitor_update();
}
+205
View File
@@ -0,0 +1,205 @@
#include "test_framework.h"
#include "../src/types.h"
#include "../src/ssl_handler.h"
#include <string.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
extern SSL_CTX *ssl_ctx;
void test_ssl_init_cleanup(void) {
TEST_SUITE_BEGIN("SSL Init and Cleanup");
ssl_init();
TEST_ASSERT(ssl_ctx != NULL, "SSL context created");
ssl_cleanup();
TEST_ASSERT(ssl_ctx == NULL, "SSL context cleaned up");
TEST_SUITE_END();
}
void test_ssl_multiple_init(void) {
TEST_SUITE_BEGIN("SSL Multiple Init");
ssl_init();
SSL_CTX *first_ctx = ssl_ctx;
TEST_ASSERT(first_ctx != NULL, "First init creates context");
ssl_cleanup();
ssl_init();
SSL_CTX *second_ctx = ssl_ctx;
TEST_ASSERT(second_ctx != NULL, "Second init creates context");
ssl_cleanup();
TEST_SUITE_END();
}
void test_ssl_set_verify(void) {
TEST_SUITE_BEGIN("SSL Set Verify");
ssl_set_verify(1);
TEST_ASSERT(1, "Set verify enabled doesn't crash");
ssl_set_verify(0);
TEST_ASSERT(1, "Set verify disabled doesn't crash");
ssl_init();
TEST_ASSERT(ssl_ctx != NULL, "Context created after set_verify");
ssl_cleanup();
TEST_SUITE_END();
}
void test_ssl_set_ca_file(void) {
TEST_SUITE_BEGIN("SSL Set CA File");
ssl_set_ca_file(NULL);
TEST_ASSERT(1, "NULL CA file doesn't crash");
ssl_set_ca_file("/nonexistent/path/ca.crt");
TEST_ASSERT(1, "Nonexistent CA file doesn't crash");
ssl_set_ca_file("/etc/ssl/certs/ca-certificates.crt");
TEST_ASSERT(1, "System CA file path set");
ssl_init();
TEST_ASSERT(ssl_ctx != NULL, "Context created after set_ca_file");
ssl_cleanup();
TEST_SUITE_END();
}
void test_ssl_set_ca_path(void) {
TEST_SUITE_BEGIN("SSL Set CA Path");
ssl_set_ca_path(NULL);
TEST_ASSERT(1, "NULL CA path doesn't crash");
ssl_set_ca_path("/nonexistent/path");
TEST_ASSERT(1, "Nonexistent CA path doesn't crash");
ssl_set_ca_path("/etc/ssl/certs");
TEST_ASSERT(1, "System CA path set");
ssl_init();
TEST_ASSERT(ssl_ctx != NULL, "Context created after set_ca_path");
ssl_cleanup();
TEST_SUITE_END();
}
void test_ssl_context_options(void) {
TEST_SUITE_BEGIN("SSL Context Options");
ssl_set_verify(0);
ssl_init();
TEST_ASSERT(ssl_ctx != NULL, "Context created");
if (ssl_ctx) {
long options = SSL_CTX_get_options(ssl_ctx);
TEST_ASSERT((options & SSL_OP_NO_SSLv3) != 0, "SSLv3 disabled");
}
ssl_cleanup();
TEST_SUITE_END();
}
void test_ssl_create_connection(void) {
TEST_SUITE_BEGIN("SSL Create Connection Object");
ssl_set_verify(0);
ssl_init();
if (ssl_ctx) {
SSL *ssl = SSL_new(ssl_ctx);
TEST_ASSERT(ssl != NULL, "SSL object created from context");
if (ssl) {
SSL_free(ssl);
}
} else {
TEST_ASSERT(1, "Skipped - no SSL context");
}
ssl_cleanup();
TEST_SUITE_END();
}
void test_ssl_handshake_null(void) {
TEST_SUITE_BEGIN("SSL Handshake NULL Safety");
int result = ssl_do_handshake(NULL);
TEST_ASSERT_EQ(-1, result, "NULL connection returns -1");
connection_t conn;
memset(&conn, 0, sizeof(conn));
conn.ssl = NULL;
result = ssl_do_handshake(&conn);
TEST_ASSERT_EQ(-1, result, "Connection without SSL returns -1");
TEST_SUITE_END();
}
void test_ssl_read_write_null(void) {
TEST_SUITE_BEGIN("SSL Read/Write NULL Safety");
char buf[100];
int read_result = ssl_read(NULL, buf, sizeof(buf));
TEST_ASSERT_EQ(-1, read_result, "NULL connection read returns -1");
int write_result = ssl_write(NULL, buf, sizeof(buf));
TEST_ASSERT_EQ(-1, write_result, "NULL connection write returns -1");
connection_t conn;
memset(&conn, 0, sizeof(conn));
conn.ssl = NULL;
read_result = ssl_read(&conn, buf, sizeof(buf));
TEST_ASSERT_EQ(-1, read_result, "Connection without SSL read returns -1");
write_result = ssl_write(&conn, buf, sizeof(buf));
TEST_ASSERT_EQ(-1, write_result, "Connection without SSL write returns -1");
TEST_SUITE_END();
}
void test_ssl_reinit(void) {
TEST_SUITE_BEGIN("SSL Reinitialize");
ssl_set_verify(0);
ssl_init();
TEST_ASSERT(ssl_ctx != NULL, "First init with verify=0");
ssl_cleanup();
ssl_set_verify(1);
ssl_set_ca_path("/etc/ssl/certs");
ssl_init();
TEST_ASSERT(ssl_ctx != NULL, "Second init with verify=1");
ssl_cleanup();
TEST_SUITE_END();
}
void run_ssl_handler_tests(void) {
test_ssl_init_cleanup();
test_ssl_multiple_init();
test_ssl_set_verify();
test_ssl_set_ca_file();
test_ssl_set_ca_path();
test_ssl_context_options();
test_ssl_create_connection();
test_ssl_handshake_null();
test_ssl_read_write_null();
test_ssl_reinit();
}