Mercurial
diff seobeo/tests/seobeo_sse_server_test.c @ 257:609d3c6aff4e
[seobeo] Add persistent SSE streams
Co-authored-by: Copilot <[email protected]>
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Tue, 04 Aug 2026 16:49:11 -0700 |
| parents | |
| children | 1f9877b637e9 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/seobeo/tests/seobeo_sse_server_test.c Tue Aug 04 16:49:11 2026 -0700 @@ -0,0 +1,168 @@ +#include "seobeo/seobeo.h" + +#include <assert.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <unistd.h> + +static int reserve_port(void) +{ + int socket_fd = socket(AF_INET, SOCK_STREAM, 0); + assert(socket_fd >= 0); + struct sockaddr_in address = { + .sin_family = AF_INET, + .sin_addr.s_addr = htonl(INADDR_LOOPBACK), + .sin_port = 0, + }; + assert(bind( + socket_fd, + (struct sockaddr *)&address, + sizeof(address)) == 0); + socklen_t length = sizeof(address); + assert(getsockname( + socket_fd, + (struct sockaddr *)&address, + &length) == 0); + int port = ntohs(address.sin_port); + close(socket_fd); + return port; +} + +static int connect_to(int port) +{ + int socket_fd = socket(AF_INET, SOCK_STREAM, 0); + if (socket_fd < 0) + return -1; + struct timeval timeout = { + .tv_sec = 3, + }; + setsockopt( + socket_fd, + SOL_SOCKET, + SO_RCVTIMEO, + &timeout, + sizeof(timeout)); + struct sockaddr_in address = { + .sin_family = AF_INET, + .sin_addr.s_addr = htonl(INADDR_LOOPBACK), + .sin_port = htons((uint16)port), + }; + if (connect( + socket_fd, + (struct sockaddr *)&address, + sizeof(address)) != 0) + { + close(socket_fd); + return -1; + } + return socket_fd; +} + +static void send_request(int socket_fd, const char *path) +{ + char request[512]; + int length = snprintf( + request, + sizeof(request), + "GET %s HTTP/1.1\r\n" + "Host: 127.0.0.1\r\n" + "Connection: keep-alive\r\n" + "\r\n", + path); + assert(length > 0 && (size_t)length < sizeof(request)); + assert(send(socket_fd, request, (size_t)length, 0) == length); +} + +static size_t receive_until( + int socket_fd, + char *buffer, + size_t capacity, + const char *needle) +{ + size_t used = 0; + while (used + 1 < capacity) + { + ssize_t amount = recv( + socket_fd, + buffer + used, + capacity - used - 1, + 0); + assert(amount > 0); + used += (size_t)amount; + buffer[used] = '\0'; + if (strstr(buffer, needle)) + return used; + } + assert(FALSE && "response exceeded test buffer"); + return 0; +} + +static pid_t start_server( + const char *binary, + const char *port) +{ + pid_t pid = fork(); + assert(pid >= 0); + if (pid == 0) + { + execl(binary, binary, port, NULL); + _exit(127); + } + + int numeric_port = atoi(port); + for (int attempt = 0; attempt < 100; attempt++) + { + int socket_fd = connect_to(numeric_port); + if (socket_fd >= 0) + { + close(socket_fd); + return pid; + } + usleep(20000); + } + kill(pid, SIGTERM); + waitpid(pid, NULL, 0); + assert(FALSE && "SSE test server did not start"); + return -1; +} + +int main(int argc, char **argv) +{ + assert(argc == 2); + int port = reserve_port(); + char port_text[16]; + snprintf(port_text, sizeof(port_text), "%d", port); + pid_t server = start_server(argv[1], port_text); + + int event_socket = connect_to(port); + assert(event_socket >= 0); + send_request(event_socket, "/events"); + char events[4096] = {0}; + receive_until( + event_socket, + events, + sizeof(events), + "data: later\n\n"); + assert(strstr(events, "HTTP/1.1 200 OK\r\n")); + assert(strstr(events, "Content-Type: text/event-stream\r\n")); + assert(strstr(events, "data: connected\n\n")); + + int health_socket = connect_to(port); + assert(health_socket >= 0); + send_request(health_socket, "/health"); + assert(shutdown(health_socket, SHUT_WR) == 0); + char health[1024] = {0}; + receive_until(health_socket, health, sizeof(health), "\r\n\r\nok"); + assert(strstr(health, "HTTP/1.1 200 OK\r\n")); + + close(health_socket); + close(event_socket); + kill(server, SIGTERM); + waitpid(server, NULL, 0); + printf("Seobeo SSE server integration test passed\n"); + return 0; +}