@@ -2503,6 +2503,199 @@ LT_BEGIN_AUTO_TEST(basic_suite, response_with_footers)
25032503 ws2.stop();
25042504LT_END_AUTO_TEST (response_with_footers)
25052505
2506+ // Resource that tests get_arg with non-existent key
2507+ class arg_not_found_resource : public http_resource {
2508+ public:
2509+ shared_ptr<http_response> render_GET (const http_request& req) {
2510+ // Get an arg that doesn't exist - should return empty http_arg_value
2511+ auto missing_arg = req.get_arg (" nonexistent_key" );
2512+ // http_arg_value.get_all_values() should return empty vector
2513+ std::string result = missing_arg.get_all_values ().empty () ? " EMPTY" : " HAS_VALUES" ;
2514+ return std::make_shared<string_response>(result, 200 , " text/plain" );
2515+ }
2516+ };
2517+
2518+ LT_BEGIN_AUTO_TEST (basic_suite, arg_not_found)
2519+ arg_not_found_resource resource;
2520+ LT_ASSERT_EQ (true , ws->register_resource (" arg_not_found" , &resource));
2521+ curl_global_init (CURL_GLOBAL_ALL);
2522+ string s;
2523+ CURL *curl = curl_easy_init();
2524+ CURLcode res;
2525+ curl_easy_setopt (curl, CURLOPT_URL, " localhost:" PORT_STRING " /arg_not_found?existing=value" );
2526+ curl_easy_setopt (curl, CURLOPT_HTTPGET, 1L );
2527+ curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, writefunc);
2528+ curl_easy_setopt (curl, CURLOPT_WRITEDATA, &s);
2529+ res = curl_easy_perform(curl);
2530+ LT_ASSERT_EQ (res, 0 );
2531+ LT_CHECK_EQ (s, " EMPTY" );
2532+ curl_easy_cleanup (curl);
2533+ LT_END_AUTO_TEST (arg_not_found)
2534+
2535+ // Resource that tests get_arg_flat fallback to connection value
2536+ class arg_flat_fallback_resource : public http_resource {
2537+ public:
2538+ shared_ptr<http_response> render_GET (const http_request& req) {
2539+ // Test get_arg_flat with a key that exists in GET args but not in unescaped_args
2540+ // This tests the fallback branch in get_arg_flat
2541+ std::string val = std::string (req.get_arg_flat (" qparam" ));
2542+ return std::make_shared<string_response>(val, 200 , " text/plain" );
2543+ }
2544+ };
2545+
2546+ LT_BEGIN_AUTO_TEST (basic_suite, arg_flat_fallback)
2547+ arg_flat_fallback_resource resource;
2548+ LT_ASSERT_EQ (true , ws->register_resource (" arg_flat_fb" , &resource));
2549+ curl_global_init (CURL_GLOBAL_ALL);
2550+ string s;
2551+ CURL *curl = curl_easy_init();
2552+ CURLcode res;
2553+ curl_easy_setopt (curl, CURLOPT_URL, " localhost:" PORT_STRING " /arg_flat_fb?qparam=myvalue" );
2554+ curl_easy_setopt (curl, CURLOPT_HTTPGET, 1L );
2555+ curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, writefunc);
2556+ curl_easy_setopt (curl, CURLOPT_WRITEDATA, &s);
2557+ res = curl_easy_perform(curl);
2558+ LT_ASSERT_EQ (res, 0 );
2559+ LT_CHECK_EQ (s, " myvalue" );
2560+ curl_easy_cleanup (curl);
2561+ LT_END_AUTO_TEST (arg_flat_fallback)
2562+
2563+ // Resource that tests get_path_piece with out of bounds index
2564+ class path_piece_oob_resource : public http_resource {
2565+ public:
2566+ shared_ptr<http_response> render_GET (const http_request& req) {
2567+ // Get path piece at an index that's out of bounds
2568+ std::string piece = req.get_path_piece (100 ); // Way beyond the path pieces
2569+ // Should return empty string
2570+ std::string result = piece.empty () ? " OOB_EMPTY" : piece;
2571+ return std::make_shared<string_response>(result, 200 , " text/plain" );
2572+ }
2573+ };
2574+
2575+ LT_BEGIN_AUTO_TEST (basic_suite, path_piece_out_of_bounds)
2576+ path_piece_oob_resource resource;
2577+ LT_ASSERT_EQ (true , ws->register_resource (" path/piece/test" , &resource));
2578+ curl_global_init (CURL_GLOBAL_ALL);
2579+ string s;
2580+ CURL *curl = curl_easy_init();
2581+ CURLcode res;
2582+ curl_easy_setopt (curl, CURLOPT_URL, " localhost:" PORT_STRING " /path/piece/test" );
2583+ curl_easy_setopt (curl, CURLOPT_HTTPGET, 1L );
2584+ curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, writefunc);
2585+ curl_easy_setopt (curl, CURLOPT_WRITEDATA, &s);
2586+ res = curl_easy_perform(curl);
2587+ LT_ASSERT_EQ (res, 0 );
2588+ LT_CHECK_EQ (s, " OOB_EMPTY" );
2589+ curl_easy_cleanup (curl);
2590+ LT_END_AUTO_TEST (path_piece_out_of_bounds)
2591+
2592+ // Resource that tests empty querystring
2593+ class empty_querystring_resource : public http_resource {
2594+ public:
2595+ shared_ptr<http_response> render_GET (const http_request& req) {
2596+ std::string qs = std::string (req.get_querystring ());
2597+ std::string result = qs.empty () ? " NO_QS" : qs;
2598+ return std::make_shared<string_response>(result, 200 , " text/plain" );
2599+ }
2600+ };
2601+
2602+ LT_BEGIN_AUTO_TEST (basic_suite, empty_querystring)
2603+ empty_querystring_resource resource;
2604+ LT_ASSERT_EQ (true , ws->register_resource (" empty_qs" , &resource));
2605+ curl_global_init (CURL_GLOBAL_ALL);
2606+ string s;
2607+ CURL *curl = curl_easy_init();
2608+ CURLcode res;
2609+ // URL without any query string
2610+ curl_easy_setopt (curl, CURLOPT_URL, " localhost:" PORT_STRING " /empty_qs" );
2611+ curl_easy_setopt (curl, CURLOPT_HTTPGET, 1L );
2612+ curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, writefunc);
2613+ curl_easy_setopt (curl, CURLOPT_WRITEDATA, &s);
2614+ res = curl_easy_perform(curl);
2615+ LT_ASSERT_EQ (res, 0 );
2616+ LT_CHECK_EQ (s, " NO_QS" );
2617+ curl_easy_cleanup (curl);
2618+ LT_END_AUTO_TEST (empty_querystring)
2619+
2620+ // Resource that tests query parameters with null/empty values
2621+ // Covers http_request.cpp lines 234 and 248 (arg_value == nullptr branches)
2622+ class null_value_query_resource : public http_resource {
2623+ public:
2624+ shared_ptr<http_response> render_GET (const http_request& req) {
2625+ // Test getting an argument that was passed without a value (e.g., ?keyonly)
2626+ auto keyonly_arg = req.get_arg (" keyonly" );
2627+ auto normal_arg = req.get_arg (" normal" );
2628+
2629+ // Also test querystring which exercises build_request_querystring
2630+ std::string qs = std::string (req.get_querystring ());
2631+
2632+ stringstream ss;
2633+ ss << " keyonly=" << (keyonly_arg.get_all_values ().empty () ? " MISSING" :
2634+ (keyonly_arg.get_all_values ()[0 ].empty () ? " EMPTY" : " VALUE" ));
2635+ ss << " ,normal=" << (normal_arg.get_all_values ().empty () ? " MISSING" :
2636+ std::string (normal_arg.get_all_values ()[0 ]));
2637+ ss << " ,qs=" << (qs.find (" keyonly" ) != string::npos ? " HAS_KEYONLY" : " NO_KEYONLY" );
2638+
2639+ return std::make_shared<string_response>(ss.str (), 200 , " text/plain" );
2640+ }
2641+ };
2642+
2643+ // Resource that tests auth caching (get_user/get_pass called multiple times)
2644+ class auth_cache_resource : public http_resource {
2645+ public:
2646+ shared_ptr<http_response> render_GET (const http_request& req) {
2647+ // Call get_user and get_pass multiple times to test caching
2648+ std::string user1 = std::string (req.get_user ());
2649+ std::string pass1 = std::string (req.get_pass ());
2650+ std::string user2 = std::string (req.get_user ()); // Should hit cache
2651+ std::string pass2 = std::string (req.get_pass ()); // Should hit cache
2652+
2653+ std::string result = user1.empty () ? " NO_AUTH" : (" USER:" + user1);
2654+ return std::make_shared<string_response>(result, 200 , " text/plain" );
2655+ }
2656+ };
2657+
2658+ LT_BEGIN_AUTO_TEST (basic_suite, auth_caching)
2659+ auth_cache_resource resource;
2660+ LT_ASSERT_EQ (true , ws->register_resource (" auth_cache" , &resource));
2661+ curl_global_init (CURL_GLOBAL_ALL);
2662+ string s;
2663+ CURL *curl = curl_easy_init();
2664+ CURLcode res;
2665+ curl_easy_setopt (curl, CURLOPT_URL, " localhost:" PORT_STRING " /auth_cache" );
2666+ curl_easy_setopt (curl, CURLOPT_HTTPGET, 1L );
2667+ curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, writefunc);
2668+ curl_easy_setopt (curl, CURLOPT_WRITEDATA, &s);
2669+ // No authentication provided
2670+ res = curl_easy_perform(curl);
2671+ LT_ASSERT_EQ (res, 0 );
2672+ LT_CHECK_EQ (s, " NO_AUTH" );
2673+ curl_easy_cleanup (curl);
2674+ LT_END_AUTO_TEST (auth_caching)
2675+
2676+ // Test query parameters with null/empty values (e.g., ?keyonly&normal=value)
2677+ // This covers http_request.cpp lines 234 and 248 (arg_value == nullptr branches)
2678+ LT_BEGIN_AUTO_TEST(basic_suite, null_value_query_param)
2679+ null_value_query_resource resource;
2680+ LT_ASSERT_EQ (true , ws->register_resource (" null_val_query" , &resource));
2681+ curl_global_init (CURL_GLOBAL_ALL);
2682+ string s;
2683+ CURL *curl = curl_easy_init();
2684+ CURLcode res;
2685+ // Query string with a key that has no value (keyonly) and one with value (normal=test)
2686+ curl_easy_setopt (curl, CURLOPT_URL, " localhost:" PORT_STRING " /null_val_query?keyonly&normal=test" );
2687+ curl_easy_setopt (curl, CURLOPT_HTTPGET, 1L );
2688+ curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, writefunc);
2689+ curl_easy_setopt (curl, CURLOPT_WRITEDATA, &s);
2690+ res = curl_easy_perform(curl);
2691+ LT_ASSERT_EQ (res, 0 );
2692+ // keyonly should have an empty value (not missing)
2693+ LT_CHECK_EQ (s.find(" keyonly=EMPTY" ) != string::npos, true);
2694+ LT_CHECK_EQ (s.find(" normal=test" ) != string::npos, true);
2695+ LT_CHECK_EQ (s.find(" qs=HAS_KEYONLY" ) != string::npos, true);
2696+ curl_easy_cleanup (curl);
2697+ LT_END_AUTO_TEST (null_value_query_param)
2698+
25062699LT_BEGIN_AUTO_TEST_ENV()
25072700 AUTORUN_TESTS()
25082701LT_END_AUTO_TEST_ENV()
0 commit comments