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;
+}