diff seobeo/tests/seobeo_sse_test.c @ 264:04fee26ecce0

add authenticated JRPG conversation platform Add reusable auth/session storage, owned conversation recovery, guest quotas, admin workflows, URL-routed conversation UI, mobile frame support, and parallel browser acceptance. Co-authored-by: Copilot <[email protected]>
author MrJuneJune <me@mrjunejune.com>
date Fri, 07 Aug 2026 07:34:12 -0700
parents 1f9877b637e9
children
line wrap: on
line diff
--- a/seobeo/tests/seobeo_sse_test.c	Thu Aug 06 11:31:30 2026 -0700
+++ b/seobeo/tests/seobeo_sse_test.c	Fri Aug 07 07:34:12 2026 -0700
@@ -8,6 +8,17 @@
 #include <sys/socket.h>
 #include <unistd.h>
 
+/* Counter incremented by the detach callback. */
+static _Atomic int g_detach_count = 0;
+static Seobeo_SSE_Stream *g_detach_stream = NULL;
+
+static void test_detach_cb(Seobeo_SSE_Stream *p_stream, void *ctx)
+{
+  (void)ctx;
+  g_detach_stream = p_stream;
+  atomic_fetch_add(&g_detach_count, 1);
+}
+
 static void read_expected(int socket_fd, const char *expected)
 {
   size_t expected_length = strlen(expected);
@@ -186,6 +197,45 @@
   close(sockets[0]);
   close(sockets[1]);
 
+  /*
+   * Detach callback test: verify callback fires after handle detach and
+   * that the callback is called outside g_sse_mutex (no deadlock).
+   */
+  {
+    int cbs[2];
+    assert(socketpair(AF_UNIX, SOCK_STREAM, 0, cbs) == 0);
+    uint8 cb_write_buf[4096] = {0};
+    Seobeo_Handle cb_handle;
+    initialize_handle(&cb_handle, cbs[0], cb_write_buf, sizeof(cb_write_buf));
+    Seobeo_SSE_Stream *p_cb_stream = Seobeo_SSE_Server_Attach(&cb_handle, "/cb");
+    assert(p_cb_stream);
+    assert(Seobeo_SSE_Retain(p_cb_stream)); /* extra reference */
+
+    /* Register detach callback. */
+    atomic_store(&g_detach_count, 0);
+    g_detach_stream = NULL;
+    Seobeo_SSE_Set_Detach_Callback(p_cb_stream, test_detach_cb, NULL);
+
+    /* Detach: fires callback outside the mutex. */
+    Seobeo_SSE_Server_Detach_Handle(&cb_handle);
+
+    assert(atomic_load(&g_detach_count) == 1);
+    assert(g_detach_stream == p_cb_stream);
+    assert(!Seobeo_SSE_Is_Open(p_cb_stream));
+
+    /* Second detach: no stream found, callback NOT called again. */
+    Seobeo_SSE_Server_Detach_Handle(&cb_handle);
+    assert(atomic_load(&g_detach_count) == 1);
+
+    /* Clear the callback and verify it isn't called on a subsequent release. */
+    Seobeo_SSE_Set_Detach_Callback(p_cb_stream, NULL, NULL);
+    Seobeo_SSE_Release(p_cb_stream); /* release extra reference */
+    Seobeo_SSE_Server_Destroy();
+    close(cbs[0]);
+    close(cbs[1]);
+    printf("  detach callback fires once, outside mutex             PASS\n");
+  }
+
   Seobeo_Router_Init();
   Seobeo_Router_Register_SSE("/events/:topic", route_handler);
   Seobeo_Router_Register_SSE("/events/:topic/fixed", route_handler);
@@ -193,8 +243,7 @@
       "POST",
       "/events/:topic",
       post_route_handler);
-  Dowa_Arena *p_arena = Dowa_Arena_Create(4096);
-  Seobeo_Request_Entry *p_request = NULL;
+  Dowa_Arena *p_arena = Dowa_Arena_Create(4096);  Seobeo_Request_Entry *p_request = NULL;
   Seobeo_SSE_Handler handler = Seobeo_Router_Find_SSE_Handler(
       "GET",
       "/events/builds",