test(websocket): expand branch coverage for websocket_server.cpp#1059
Merged
Conversation
Add tests/unit/websocket_server_branch_test.cpp exercising public-API surfaces of messaging_ws_server that remained uncovered after Issue #989 baseline measurement (line 39.9% / branch 19.7%). Coverage targets: - ws_server_config full-field round-trip (port / max_connections / ping_interval / max_message_size at zero / 1 / max boundaries, path with empty / 1024-char / binary-byte values, auto_pong toggle) - messaging_ws_server construction with empty / 512-char / binary-byte server IDs and string_view literal variant - Default-state queries on never-started server (is_running, connection_count, get_connection, get_all_connections) - stop_server() / i_websocket_server::stop() returning ok() on prepare_stop() early-return branch - broadcast_text() / broadcast_binary() session_mgr_ == nullptr early-return for empty / small / 64 KiB payloads - Interface (i_websocket_server) callback adapters for connection / disconnection / text / binary / error with null callbacks (empty- function branch), populated lambdas, triple replacement, shared_ptr- captured state, and null-then-populated-then-null toggle - Concurrent state queries and callback replacement under shared_ptr - Multi-instance independent state and type alias coverage - Destructor cleanup paths Tests are hermetic: no live TCP peer, no WebSocket handshake. The impl- level methods that exchange frames remain reachable only with a transport fixture; this PR documents them in an honest scope statement. Closes #1053
Contributor
Coverage Report
Coverage DetailsFull HTML report is available as a build artifact. |
This was referenced Apr 26, 2026
kcenon
added a commit
that referenced
this pull request
Apr 27, 2026
* test(websocket): expand websocket_server.cpp coverage Append 33 unit tests to tests/test_messaging_ws_server.cpp targeting public-API surfaces of messaging_ws_server reachable without a live WebSocket peer. Complements tests/unit/websocket_server_branch_test.cpp (added in #1059) by exercising additional angles: - ws_message struct as_text() / as_binary() round-trip and reference semantics used by invoke_message_callback() routing - ws_close_code enum value invariants per RFC 6455 Section 7.4 used by ws_connection::close(uint16_t, string_view) and on_close() - Constructor variations (c-string, std::string, string_view, substring string_view) and server_id() reference stability - Default-state queries via interfaces::i_websocket_server pointer (is_running, connection_count, stop) - get_connection() lookup for empty / 2048-char / IPv4-style ids on never-started server returning nullptr - get_all_connections() empty-vector stability on never-started server - broadcast_text / broadcast_binary with registered text / binary callbacks but no peers does not invoke the callbacks - Interface callback adapters: lambda capture is stored not invoked immediately for connection / disconnection / error - All five interface callbacks replaceable with default-constructed std::function (covers the empty-function adapter branch) - Multi-instance state isolation across 32 sequential constructions - Two servers with the same id are independent instances - wait_for_stop on never-started server returns under one second - stop_server idempotency across 10 calls and via interface pointer These tests are hermetic and rely solely on state reachable without a live io_context loop or TCP peer. The acceptance criteria of #1067 (line >= 80% / branch >= 70%) cannot be met from unit tests alone because the post-handshake frame I/O path requires an in-process WebSocket loopback fixture that does not exist yet; the gap is tracked under #953. Part of #1067 Part of #953 * test(websocket): drop protected is_running() call via interface pointer i_network_component::is_running() is declared protected in the base interface; only derived class messaging_ws_server publishes it publicly. Calling is_running() through std::shared_ptr<i_websocket_server> fails to compile with 'is protected within this context'. Replace the failing test with an interface-pointer stop()/connection_count() idempotency variant that uses only public surface. --------- Co-authored-by: Raphael Shin <raphael.shin@flonics.co.kr>
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Summary
Add branch coverage tests for
src/http/websocket_server.cppexercising public-API surfaces ofmessaging_ws_serverthat remained uncovered after Issue #989 baseline measurement (line 39.9% / branch 19.7% on develop @ 05c1b7b, 2026-04-26). All 69 test cases across 10 test suites are hermetic and operate purely on the public API; no live TCP peer or WebSocket handshake is required.Change Type
Why
Related Issues
Motivation
src/http/websocket_server.cppsits among the lowest-coverage files in the network_system source tree. The uncovered region concentrates in error and boundary paths — exactly the surfaces that regress during the ongoingResult<T>migration and API stabilization. Bringing this file over the 70% line / 60% branch bar contributes directly to the #953 acceptance criteria. This PR completes the 5-issue sequential batch of branch-coverage sub-issues under epic #953 (sibling PRs: #1054 http2_client, #1055 grpc_client, #1056 http2_server, #1057 quic_socket, #1058 quic_server).Where
Files Changed
tests/unit/websocket_server_branch_test.cpp(new, ~976 lines, 69 test cases)tests/CMakeLists.txt(registration entry mirroringnetwork_quic_server_branch_test)CHANGELOG.md(Unreleased > Added entry)docs/CHANGELOG.md(Unreleased > Added detailed entry)How
Implementation Highlights
Test groups (suite names):
WsServerConfigRoundTrip(11 tests) —ws_server_configdefaults, port / max_connections / ping_interval / max_message_size boundary values (zero / one / max), empty / 1024-char / binary-byte path values, auto_pong toggle, full-config copy and assignment round-tripsWsServerConstruction(5 tests) — empty / 512-char / binary-byte server IDs, 16-iteration short-lived construction, server_id() reference stability, std::string_view literal constructor variantWsServerDefaults(6 tests) — is_running()==false, connection_count()==0, empty get_all_connections(), get_connection() returning nullptr for unknown / empty / 2048-char / binary-byte ids, repeated default-state stabilityWsServerStop(4 tests) — stop_server() / i_websocket_server::stop() returning ok() on the prepare_stop() early-return branch when never started, repeated stop idempotency, is_running() invarianceWsServerBroadcast(8 tests) — broadcast_text() and broadcast_binary() session_mgr_ == nullptr early-return for empty / small / 64 KiB payloads with binary-byte text strings and 4-iteration repeated invocationWsServerInterfaceCallbacks(16 tests) — everyi_websocket_servercallback adapter (connection / disconnection / text / binary / error) accepting null callbacks (empty-function branch), populated lambdas (wrap-and-store branch), triple replacement, combined registration, shared_ptr-captured state, and null-then-populated-then-null toggle exercising the if-callback / else-empty branch in every adapter twiceWsServerConcurrency(5 tests) — 4 threads x 200 iterations of is_running() / connection_count() / server_id() / get_all_connections() under shared_ptr lifetime, plus a writer/reader pair on set_error_callback() / is_running()WsServerMultiInstance(3 tests) — 8 servers with independent default state, broadcast-on-one-does-not-affect-another, stop-one-affects-noneWsServerTypeAlias(3 tests) —ws_serverandsecure_ws_serverinstantiation, interop assignment between them via shared_ptrWsServerDestructor(4 tests) — destructor cleanup on never-started server, after stop, with all five interface callbacks registered, and after broadcast invocationThe test file follows the pattern established by sibling PR #1058 (
quic_server_branch_test.cpp). The namespace aliaswsiface = kcenon::network::interfacesis used in place ofifaceto avoid the Linux glibc<net/if.h>collision (struct iface ifa_ifp) that bit PR #1058 mid-CI.Honest scope statement
The impl-level methods that physically accept TCP connections, complete the WebSocket HTTP/1.1 upgrade handshake, exchange frames with a peer, and dispatch per-message callbacks remain reachable only with a live TCP client that speaks the WebSocket wire format end-to-end. Specifically, the
messaging_ws_serverprivate surfaces:do_start_impl()success path pastasio::ip::tcp::acceptorconstruction (the bind_failed branches foraddress_in_use/access_deniedare reachable only by binding to a privileged or in-use port and would not be hermetic; the success path would also leave the io_context loop running asynchronously inside the test process and pollute global state)do_stop_impl()— only reachable after a successful startdo_accept()— async TCP accept completion, reachable only with a liveio_context::run()loophandle_new_connection()— TCP socket construction +websocket_socketwrapping +ws_connection_implconstruction + per-connection callback wiring +ws_socket->async_accepthandshake initiation, reachable only after a TCP connect that completes the WebSocket HTTP/1.1 upgrade handshakeon_message()— reachable only after async_read of a complete frameon_close()— reachable only after a peer-initiated close frame or localclose()on_error()— reachable only after a transport-level error during read/writeinvoke_*_callback()helpers — reachable only from frame-driven paths inside the io_contextThe success branches of
broadcast_text()/broadcast_binary()with at least one populated session, and the non-null branch ofget_connection()for a known id, also require a live WebSocket client that completes the handshake, sincesession_mgr_->add_connection()inhandle_new_connection()is the only path that populates the session map and that call sits behind thews_socket->async_acceptcallback. Thews_connection_implprivate methods (send_text/send_binary/close/is_connected/get_socket/invalidate) are reachable only when at least one connection has been added throughhandle_new_connection()and so are also blocked by the same handshake requirement.Testing those branches hermetically would require either a mock thread pool that does not actually run
io_context::run, a friend-declared injection point insidemessaging_ws_server, or a transport fixture that drives the WebSocket upgrade handshake from a test-side client.Testing Done
Breaking Changes
None — test-only additions.