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:
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
Reference in New Issue
Block a user