/* * test_netio_on_connected.c — TDD tests for on_connected callback in endpoint_t. * * Task 1.2: election-iomgr-refactor spec * Boundary: netio.c / netio.h * * Tests verify: * 1. on_connected field exists in endpoint_t (compile-time check) * 2. endpoint_set_on_connected() stores the callback * 3. Callback fires exactly once when ssl_handshake_done is set to 1 * 4. Callback pointer is nulled after firing (fire-once semantics) * 5. handle_ssl_handshake path: invokes on_connected via io_handler mock */ #define _GNU_SOURCE #include #include #include #include #include #include #include "netio.h" /* ── Static state for callback tracking ──────────────────────────── */ static int g_cb_count = 0; static endpoint_t *g_cb_ep = NULL; static void on_connected_stub(endpoint_t *ep) { g_cb_count++; g_cb_ep = ep; } /* Reset per-test globals */ static void reset_cb_state(void) { g_cb_count = 0; g_cb_ep = NULL; } /* ── Test 1: Field exists and is zero-initialised after endpoint_get ── */ static void test_on_connected_field_exists_and_is_null(void) { endpoint_t *ep = endpoint_get(); TEST_ASSERT_NOT_NULL(ep); /* Field must be accessible and NULL after fresh allocation */ TEST_ASSERT_NULL(ep->on_connected); free(ep); printf("test_on_connected_field_exists_and_is_null: PASS\n"); } /* ── Test 2: endpoint_set_on_connected stores the callback ──────── */ static void test_endpoint_set_on_connected_stores_callback(void) { endpoint_t *ep = endpoint_get(); TEST_ASSERT_NOT_NULL(ep); int rc = endpoint_set_on_connected(ep, on_connected_stub); TEST_ASSERT_EQUAL_INT(0, rc); TEST_ASSERT_EQUAL_PTR(on_connected_stub, ep->on_connected); free(ep); printf("test_endpoint_set_on_connected_stores_callback: PASS\n"); } /* ── Test 3: endpoint_set_on_connected with NULL ep returns -EINVAL */ static void test_endpoint_set_on_connected_null_ep(void) { int rc = endpoint_set_on_connected(NULL, on_connected_stub); TEST_ASSERT_EQUAL_INT(-EINVAL, rc); printf("test_endpoint_set_on_connected_null_ep: PASS\n"); } /* ── Test 4: endpoint_set_on_connected accepts NULL callback ────── */ static void test_endpoint_set_on_connected_null_cb(void) { endpoint_t *ep = endpoint_get(); TEST_ASSERT_NOT_NULL(ep); /* Setting to NULL is valid — it clears a prior callback */ int rc = endpoint_set_on_connected(ep, NULL); TEST_ASSERT_EQUAL_INT(0, rc); TEST_ASSERT_NULL(ep->on_connected); free(ep); printf("test_endpoint_set_on_connected_null_cb: PASS\n"); } /* * ── Test 5: Direct simulation of ssl_handshake_done path ───────── * * Because io_handler requires a live SSL+epoll context, we simulate * the exact code path specified by the design: * * ep->ssl_handshake_done = 1; * if (ep->on_connected) { ep->on_connected(ep); ep->on_connected = NULL; } * * This tests that the fire-once contract holds when the hook is invoked * immediately after ssl_handshake_done is set. */ static void test_on_connected_fires_once_after_handshake_done(void) { reset_cb_state(); endpoint_t *ep = endpoint_get(); TEST_ASSERT_NOT_NULL(ep); endpoint_set_on_connected(ep, on_connected_stub); TEST_ASSERT_EQUAL_PTR(on_connected_stub, ep->on_connected); /* Simulate the moment handle_ssl_handshake sets ssl_handshake_done = 1 * and the subsequent on_connected invocation in io_handler. * This mirrors the exact logic added to netio.c. */ ep->ssl_handshake_done = 1; if (ep->on_connected) { ep->on_connected(ep); ep->on_connected = NULL; } /* Callback must have fired exactly once */ TEST_ASSERT_EQUAL_INT(1, g_cb_count); /* Callback must receive the correct endpoint pointer */ TEST_ASSERT_EQUAL_PTR(ep, g_cb_ep); /* Pointer must be nulled (fire-once) */ TEST_ASSERT_NULL(ep->on_connected); /* Simulate a second epoll event — must NOT fire again */ if (ep->on_connected) { ep->on_connected(ep); ep->on_connected = NULL; } TEST_ASSERT_EQUAL_INT(1, g_cb_count); /* still exactly 1 */ free(ep); printf("test_on_connected_fires_once_after_handshake_done: PASS\n"); } /* ── Test 6: No callback set — no crash when handshake completes ── */ static void test_no_callback_handshake_done_no_crash(void) { endpoint_t *ep = endpoint_get(); TEST_ASSERT_NOT_NULL(ep); /* No on_connected set */ ep->ssl_handshake_done = 1; if (ep->on_connected) { ep->on_connected(ep); ep->on_connected = NULL; } /* Must reach here without crash */ TEST_ASSERT_NULL(ep->on_connected); free(ep); printf("test_no_callback_handshake_done_no_crash: PASS\n"); } /* ── Test suite entry point ───────────────────────────────────────── */ void test_netio_on_connected(void) { printf("\n>> netio on_connected callback tests\n\n"); RUN_TEST(test_on_connected_field_exists_and_is_null); RUN_TEST(test_endpoint_set_on_connected_stores_callback); RUN_TEST(test_endpoint_set_on_connected_null_ep); RUN_TEST(test_endpoint_set_on_connected_null_cb); RUN_TEST(test_on_connected_fires_once_after_handshake_done); RUN_TEST(test_no_callback_handshake_done_no_crash); }