@@ -764,21 +764,21 @@ LT_BEGIN_AUTO_TEST(ws_start_stop_suite, ipv6_webserver)
764764 std::string s;
765765 CURL *curl = curl_easy_init ();
766766 CURLcode res;
767- curl_easy_setopt (curl, CURLOPT_URL , " http://[::1]:" STR (PORT + 20 ) " /base" );
767+ // TASK-076 fix: STR(PORT + 20) stringifies to "8080 + 20" (not
768+ // "8100"), so we build the URL at runtime with std::to_string.
769+ std::string ipv6_url = " http://[::1]:" + std::to_string (PORT + 20 ) + " /base" ;
770+ curl_easy_setopt (curl, CURLOPT_URL , ipv6_url.c_str ());
768771 curl_easy_setopt (curl, CURLOPT_HTTPGET , 1L );
769772 curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION , writefunc);
770773 curl_easy_setopt (curl, CURLOPT_WRITEDATA , &s);
771774 res = curl_easy_perform (curl);
772- if (res == 0 ) {
773- LT_CHECK_EQ (s, " OK" );
774- }
775+ // Once the server is confirmed running, a curl failure is a
776+ // genuine test failure — not an environmental skip.
777+ LT_ASSERT_EQ (res, 0 );
778+ LT_CHECK_EQ (s, " OK" );
775779 curl_easy_cleanup (curl);
776780 ws.stop ();
777781 }
778- // TASK-076: deleted the duplicate tail-position tautological
779- // assertion. The catch above now emits SKIP; the success path's
780- // body-content `LT_CHECK_EQ` inside the curl block carries the
781- // real signal.
782782LT_END_AUTO_TEST (ipv6_webserver)
783783
784784LT_BEGIN_AUTO_TEST (ws_start_stop_suite, dual_stack_webserver)
@@ -798,19 +798,21 @@ LT_BEGIN_AUTO_TEST(ws_start_stop_suite, dual_stack_webserver)
798798 std::string s;
799799 CURL *curl = curl_easy_init ();
800800 CURLcode res;
801- curl_easy_setopt (curl, CURLOPT_URL , " localhost:" STR (PORT + 21 ) " /base" );
801+ // TASK-076 fix: STR(PORT + 21) stringifies to "8080 + 21" (not
802+ // "8101"), so we build the URL at runtime with std::to_string.
803+ std::string ds_url = " localhost:" + std::to_string (PORT + 21 ) + " /base" ;
804+ curl_easy_setopt (curl, CURLOPT_URL , ds_url.c_str ());
802805 curl_easy_setopt (curl, CURLOPT_HTTPGET , 1L );
803806 curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION , writefunc);
804807 curl_easy_setopt (curl, CURLOPT_WRITEDATA , &s);
805808 res = curl_easy_perform (curl);
806- if (res == 0 ) {
807- LT_CHECK_EQ (s, " OK" );
808- }
809+ // Once the server is confirmed running, a curl failure is a
810+ // genuine test failure — not an environmental skip.
811+ LT_ASSERT_EQ (res, 0 );
812+ LT_CHECK_EQ (s, " OK" );
809813 curl_easy_cleanup (curl);
810814 ws.stop ();
811815 }
812- // TASK-076: deleted the duplicate tail-position tautological
813- // assertion (see ipv6_webserver above).
814816LT_END_AUTO_TEST (dual_stack_webserver)
815817
816818LT_BEGIN_AUTO_TEST (ws_start_stop_suite, bind_address_ipv4)
@@ -840,39 +842,42 @@ LT_END_AUTO_TEST(bind_address_ipv4)
840842// Test bind_address with IPv6 address string (covers IPv6 branch in create_webserver.cpp)
841843LT_BEGIN_AUTO_TEST (ws_start_stop_suite, bind_address_ipv6_string)
842844 int port = PORT + 31 ;
843- // This tests the IPv6 branch in bind_address
844- // Note: This may fail if IPv6 is not available on the system
845+ // TASK-076 fix: webserver is non-movable so we use a shared_ptr to
846+ // separate construction / start (environmental SKIP) from the curl
847+ // block (unconditional assertion). Previously a single catch(...)
848+ // swallowed assert_unattended and turned a curl failure into a SKIP.
849+ std::shared_ptr<httpserver::webserver> ws_ptr;
845850 try {
846- httpserver::webserver ws{httpserver::create_webserver (port).bind_address (" ::1" )};
847- auto ok = std::make_shared<ok_resource>();
848- ws.register_path (" base" , ok);
849- ws.start (false );
850- if (ws.is_running ()) {
851- curl_global_init (CURL_GLOBAL_ALL );
852- std::string s;
853- CURL *curl = curl_easy_init ();
854- CURLcode res;
855- std::string url = " http://[::1]:" + std::to_string (port) + " /base" ;
856- curl_easy_setopt (curl, CURLOPT_URL , url.c_str ());
857- curl_easy_setopt (curl, CURLOPT_HTTPGET , 1L );
858- curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION , writefunc);
859- curl_easy_setopt (curl, CURLOPT_WRITEDATA , &s);
860- res = curl_easy_perform (curl);
861- if (res == 0 ) {
862- LT_CHECK_EQ (s, " OK" );
863- }
864- curl_easy_cleanup (curl);
865- ws.stop ();
866- }
851+ ws_ptr = std::make_shared<httpserver::webserver>(
852+ httpserver::create_webserver (port).bind_address (" ::1" ));
867853 } catch (...) {
868- // TASK-076: IPv6 may not be available on the host; emit SKIP
869- // rather than tautological-pass.
870- LT_SKIP_IF (true , " IPv6 bind unavailable on this host" );
854+ LT_SKIP_IF (true , " IPv6 bind: construction failed on this host" );
855+ }
856+ auto ok = std::make_shared<ok_resource>();
857+ ws_ptr->register_path (" base" , ok);
858+ try {
859+ ws_ptr->start (false );
860+ } catch (...) {
861+ LT_SKIP_IF (true , " IPv6 bind: start failed on this host" );
862+ }
863+ if (ws_ptr->is_running ()) {
864+ curl_global_init (CURL_GLOBAL_ALL );
865+ std::string s;
866+ CURL *curl = curl_easy_init ();
867+ CURLcode res;
868+ std::string url = " http://[::1]:" + std::to_string (port) + " /base" ;
869+ curl_easy_setopt (curl, CURLOPT_URL , url.c_str ());
870+ curl_easy_setopt (curl, CURLOPT_HTTPGET , 1L );
871+ curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION , writefunc);
872+ curl_easy_setopt (curl, CURLOPT_WRITEDATA , &s);
873+ res = curl_easy_perform (curl);
874+ // Once the server is confirmed running, a curl failure is a
875+ // genuine test failure — not an environmental skip.
876+ LT_ASSERT_EQ (res, 0 );
877+ LT_CHECK_EQ (s, " OK" );
878+ curl_easy_cleanup (curl);
879+ ws_ptr->stop ();
871880 }
872- // TASK-076: deleted the tail-position tautological assertion. The
873- // catch above now distinguishes "IPv6 absent" (SKIP) from
874- // "implementation broken" (the catch wouldn't fire; the
875- // inside-`is_running()` LT_CHECK_EQ on /base carries the signal).
876881LT_END_AUTO_TEST (bind_address_ipv6_string)
877882
878883#ifdef HAVE_GNUTLS
@@ -1504,6 +1509,41 @@ bool is_psk_unsupported_error(const std::exception& e) {
15041509 msg.find (" disabled" ) != std::string::npos;
15051510}
15061511
1512+ // TASK-076: pin is_psk_unsupported_error triage logic so a future
1513+ // change in MHD's error strings or in the helper predicate is caught
1514+ // before it silently masks/surfaces PSK failures.
1515+ //
1516+ // Helper: wrap a literal string as a std::exception so we can pass it
1517+ // to is_psk_unsupported_error without starting a webserver.
1518+ namespace {
1519+ struct fake_exception : public std ::exception {
1520+ explicit fake_exception (const char * msg) : msg_(msg) {}
1521+ const char * what () const noexcept override { return msg_; }
1522+ const char * msg_;
1523+ };
1524+ } // namespace
1525+
1526+ // (a) "PSK not supported" → true
1527+ LT_BEGIN_AUTO_TEST (ws_start_stop_suite, is_psk_unsupported_error_psk_not_supported)
1528+ LT_CHECK_EQ (is_psk_unsupported_error(fake_exception(" PSK not supported" )), true);
1529+ LT_END_AUTO_TEST (is_psk_unsupported_error_psk_not_supported)
1530+
1531+ // (b) "psk disabled" → true (lowercase variant)
1532+ LT_BEGIN_AUTO_TEST (ws_start_stop_suite, is_psk_unsupported_error_psk_disabled)
1533+ LT_CHECK_EQ (is_psk_unsupported_error(fake_exception(" psk disabled" )), true);
1534+ LT_END_AUTO_TEST (is_psk_unsupported_error_psk_disabled)
1535+
1536+ // (c) "TLS handshake failed" → false (no PSK keyword at all)
1537+ LT_BEGIN_AUTO_TEST (ws_start_stop_suite, is_psk_unsupported_error_tls_handshake_failed)
1538+ LT_CHECK_EQ (is_psk_unsupported_error(fake_exception(" TLS handshake failed" )), false);
1539+ LT_END_AUTO_TEST (is_psk_unsupported_error_tls_handshake_failed)
1540+
1541+ // (d) "PSK credential error" → false (PSK present but no 'unsupported' token;
1542+ // must NOT be treated as a skip — real PSK config failures should surface)
1543+ LT_BEGIN_AUTO_TEST (ws_start_stop_suite, is_psk_unsupported_error_psk_credential_error)
1544+ LT_CHECK_EQ (is_psk_unsupported_error(fake_exception(" PSK credential error" )), false);
1545+ LT_END_AUTO_TEST (is_psk_unsupported_error_psk_credential_error)
1546+
15071547// Test PSK credential handler setup
15081548LT_BEGIN_AUTO_TEST (ws_start_stop_suite, psk_handler_setup)
15091549 int port = PORT + 28 ;
0 commit comments