int main() { using namespace scripting; http::http_request_params_t params{}; array options; http::parse_request_options(params, options); assert(params.limits.connect_timeout_ms == 5000 && params.limits.timeout_ms == 15000); options["connect_timeout_ms"] = 120; options["timeout_ms"] = 450; options["method"] = "POST"; options["body"] = "snapshot"; array headers; headers["Authorization"] = "Bearer test-only"; options["headers"] = headers; http::parse_request_options(params, options); assert(params.limits.connect_timeout_ms == 120 && params.limits.timeout_ms == 450); assert(params.method == "POST" && params.fields == "snapshot" && params.headers.at("Authorization") == "Bearer test-only"); for (const auto* key : {"connect_timeout_ms", "timeout_ms"}) { for (script_value value : {script_value(0), script_value(-1), script_value(1.5), script_value("150")}) { array bad; bad[key] = value; bool rejected = false; try { http::parse_request_options(params, bad); } catch (const std::runtime_error&) { rejected = true; } assert(rejected); } } http::parse_request_options(params, options); auto send = [&]() { notifications.clear(); http::create_request(params); http::check_requests(); assert(notifications.empty()); // Incomplete work never notifies early. scheduler::thread_pool.run(); http::check_requests(); assert(notifications.size() == 1 && notifications.front().size() == 4); assert(utils::http::observed_limits.connect_timeout_ms == 120 && utils::http::observed_limits.timeout_ms == 450); auto diagnostics = notifications.front()[3].as(); assert(diagnostics["worker_queue_ms"].as() >= 0 && diagnostics["notify_ms"].as() >= 0); assert(diagnostics["connect_timeout_ms"].as() == 120 && diagnostics["timeout_ms"].as() == 450); assert(diagnostics.data->size() == 11); http::check_requests(); assert(notifications.size() == 1); // Exactly one completion. return diagnostics; }; utils::http::result successful; successful.code = CURLE_OK; successful.status = 200; successful.buffer = "accepted"; successful.dns_ms = 1; successful.connect_ms = 2; successful.tls_ms = 3; successful.first_byte_ms = 4; successful.total_ms = 5; utils::http::next_result = successful; auto diagnostics = send(); assert(notifications.front()[0].as() == "accepted" && notifications.front()[1].as()); assert(notifications.front()[2].get_raw().type == game::SCRIPT_NONE); assert(diagnostics["http_status"].as() == 200 && diagnostics["curl_code"].as() == 0); assert(diagnostics["dns_ms"].as() == 1 && diagnostics["connect_ms"].as() == 2); assert(diagnostics["tls_ms"].as() == 3 && diagnostics["first_byte_ms"].as() == 4 && diagnostics["total_ms"].as() == 5); utils::http::next_result->status = 500; assert(send()["http_status"].as() == 500 && notifications.front()[1].as()); utils::http::next_result->code = CURLE_OPERATION_TIMEDOUT; assert(send()["curl_code"].as() == 28 && !notifications.front()[1].as()); assert(notifications.front()[0].get_raw().type == game::SCRIPT_NONE && notifications.front()[2].is()); utils::http::next_result = std::nullopt; send(); assert(!notifications.front()[1].as()); utils::http::throw_from_transfer = true; send(); assert(!notifications.front()[1].as()); utils::http::throw_from_transfer = false; utils::http::next_result = successful; utils::http::next_result->buffer = std::string(0x5001, 'x'); send(); assert(notifications.front()[0].as().size() == 0x5000); // A resumed waiter can enqueue enough work to reallocate the pending vector. utils::http::next_result = successful; notifications.clear(); http::create_request(params); scheduler::thread_pool.run(); after_notify = [&]() { for (int i = 0; i < 100; ++i) http::create_request(params); }; http::check_requests(); after_notify = {}; assert(http::requests.size() == 100 && notifications.size() == 1); scheduler::thread_pool.run(); http::check_requests(); assert(http::requests.empty() && notifications.size() == 101); puts("\nPASS component: option validation, metadata, compatible argument order, single completion on failures/exceptions, reentrant queue"); }