#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 #include #include 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, "") != 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, "") != 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(); }