···
+
let string_contains s sub =
+
let _ = Str.search_forward (Str.regexp_string sub) s 0 in
+
with Not_found -> false
+
module Test_server = struct
+
let make_server ~port handler env =
+
Eio.Net.listen env#net ~sw:env#sw ~backlog:128 ~reuse_addr:true
+
(`Tcp (Eio.Net.Ipaddr.V4.loopback, port))
+
let callback _conn req body =
+
let (resp, body_content) = handler ~request:req ~body in
+
Server.respond_string () ~status:(Http.Response.status resp)
+
~headers:(Http.Response.headers resp)
+
let server = Server.make ~callback () in
+
Server.run server_socket server ~on_error:(fun exn ->
+
Logs.err (fun m -> m "Server error: %s" (Printexc.to_string exn))
+
let echo_handler ~request ~body =
+
let uri = Http.Request.resource request in
+
let meth = Http.Request.meth request in
+
let headers = Http.Request.headers request in
+
let body_str = Eio.Flow.read_all body in
+
"method", `String (Cohttp.Code.string_of_method meth);
+
Cohttp.Header.to_lines headers
+
|> List.map (fun line ->
+
match String.split_on_char ':' line with
+
| [k; v] -> (String.trim k, `String (String.trim v))
+
| _ -> ("", `String line)
+
"body", `String body_str;
+
|> Yojson.Basic.to_string
+
let resp = Http.Response.make ~status:`OK () in
+
let resp_headers = Cohttp.Header.add_unless_exists
+
(Http.Response.headers resp) "content-type" "application/json"
+
({ resp with headers = resp_headers }, response_body)
+
let status_handler status_code ~request:_ ~body:_ =
+
let status = Cohttp.Code.status_of_code status_code in
+
let resp = Http.Response.make ~status () in
+
let redirect_handler target_path ~request:_ ~body:_ =
+
let resp = Http.Response.make ~status:`Moved_permanently () in
+
let headers = Cohttp.Header.add
+
(Http.Response.headers resp) "location" target_path
+
({ resp with headers }, "")
+
let cookie_handler ~request ~body:_ =
+
let headers = Http.Request.headers request in
+
match Cohttp.Header.get headers "cookie" with
+
| Some cookie_str -> cookie_str
+
let resp = Http.Response.make ~status:`OK () in
+
Http.Response.headers resp
+
|> (fun h -> Cohttp.Header.add h "set-cookie" "test_cookie=test_value; Path=/")
+
|> (fun h -> Cohttp.Header.add h "set-cookie" "session=abc123; Path=/; HttpOnly")
+
({ resp with headers = resp_headers },
+
let auth_handler ~request ~body:_ =
+
let headers = Http.Request.headers request in
+
match Cohttp.Header.get headers "authorization" with
+
if String.starts_with ~prefix:"Bearer " auth then
+
let token = String.sub auth 7 (String.length auth - 7) in
+
if token = "valid_token" then "authorized"
+
else if String.starts_with ~prefix:"Basic " auth then
+
if auth_result = "authorized" || auth_result = "basic auth received"
+
let resp = Http.Response.make ~status () in
+
let json_handler ~request:_ ~body =
+
let body_str = Eio.Flow.read_all body in
+
let parsed = Yojson.Basic.from_string body_str in
+
"error", `String "invalid json";
+
"received", `String body_str;
+
let resp = Http.Response.make ~status:`OK () in
+
let resp_headers = Cohttp.Header.add_unless_exists
+
(Http.Response.headers resp) "content-type" "application/json"
+
({ resp with headers = resp_headers },
+
Yojson.Basic.to_string json)
+
let timeout_handler clock delay ~request:_ ~body:_ =
+
Eio.Time.sleep clock delay;
+
let resp = Http.Response.make ~status:`OK () in
+
(resp,"delayed response")
+
let chunked_handler _clock chunks ~request:_ ~body:_ =
+
let resp = Http.Response.make ~status:`OK () in
+
let body_str = String.concat "" chunks in
+
let large_response_handler size ~request:_ ~body:_ =
+
let data = String.make size 'X' in
+
let resp = Http.Response.make ~status:`OK () in
+
let multipart_handler ~request ~body =
+
let headers = Http.Request.headers request in
+
let content_type = Cohttp.Header.get headers "content-type" in
+
let body_str = Eio.Flow.read_all body in
+
match content_type with
+
| Some ct when String.starts_with ~prefix:"multipart/form-data" ct ->
+
Printf.sprintf "Multipart received: %d bytes" (String.length body_str)
+
let resp = Http.Response.make ~status:`OK () in
+
let router clock ~request ~body =
+
let uri = Http.Request.resource request in
+
| "/" | "/echo" -> echo_handler ~request ~body
+
| "/status/200" -> status_handler 200 ~request ~body
+
| "/status/404" -> status_handler 404 ~request ~body
+
| "/status/500" -> status_handler 500 ~request ~body
+
| "/redirect" -> redirect_handler "/redirected" ~request ~body
+
let resp = Http.Response.make ~status:`OK () in
+
(resp,"redirect successful")
+
| "/cookies" -> cookie_handler ~request ~body
+
| "/auth" -> auth_handler ~request ~body
+
| "/json" -> json_handler ~request ~body
+
| "/timeout" -> timeout_handler clock 2.0 ~request ~body
+
chunked_handler clock ["chunk1"; "chunk2"; "chunk3"] ~request ~body
+
| "/large" -> large_response_handler 10000 ~request ~body
+
| "/multipart" -> multipart_handler ~request ~body
+
| _ -> status_handler 404 ~request ~body
+
let start_server ~port env =
+
Eio.Fiber.fork ~sw:env#sw (fun () ->
+
make_server ~port (router env#clock) env
+
Eio.Time.sleep env#clock 0.1
+
let test_get_request () =
+
Eio.Switch.run @@ fun sw ->
+
let port = get_free_port () in
+
let base_url = Printf.sprintf "http://127.0.0.1:%d" port in
+
method clock = env#clock
+
Test_server.start_server ~port test_env;
+
let req = Requests.create ~sw env in
+
let response = Requests.get req (base_url ^ "/echo") in
+
Alcotest.(check int) "GET status" 200 (Requests.Response.status_code response);
+
let body_str = Requests.Response.body response |> Eio.Flow.read_all in
+
let json = Yojson.Basic.from_string body_str in
+
json |> Yojson.Basic.Util.member "method" |> Yojson.Basic.Util.to_string
+
Alcotest.(check string) "GET method" "GET" method_str
+
let test_post_request () =
+
Eio.Switch.run @@ fun sw ->
+
let port = get_free_port () in
+
let base_url = Printf.sprintf "http://127.0.0.1:%d" port in
+
method clock = env#clock
+
Test_server.start_server ~port test_env;
+
let req = Requests.create ~sw env in
+
let body = Requests.Body.text "test post data" in
+
let response = Requests.post req ~body (base_url ^ "/echo") in
+
Alcotest.(check int) "POST status" 200 (Requests.Response.status_code response);
+
let body_str = Requests.Response.body response |> Eio.Flow.read_all in
+
let json = Yojson.Basic.from_string body_str in
+
json |> Yojson.Basic.Util.member "body" |> Yojson.Basic.Util.to_string
+
Alcotest.(check string) "POST body" "test post data" received_body
+
let test_put_request () =
+
Eio.Switch.run @@ fun sw ->
+
let port = get_free_port () in
+
let base_url = Printf.sprintf "http://127.0.0.1:%d" port in
+
method clock = env#clock
+
Test_server.start_server ~port test_env;
+
let req = Requests.create ~sw env in
+
let body = Requests.Body.text "put data" in
+
let response = Requests.put req ~body (base_url ^ "/echo") in
+
Alcotest.(check int) "PUT status" 200 (Requests.Response.status_code response);
+
let body_str = Requests.Response.body response |> Eio.Flow.read_all in
+
let json = Yojson.Basic.from_string body_str in
+
json |> Yojson.Basic.Util.member "method" |> Yojson.Basic.Util.to_string
+
Alcotest.(check string) "PUT method" "PUT" method_str
+
let test_delete_request () =
+
Eio.Switch.run @@ fun sw ->
+
let port = get_free_port () in
+
let base_url = Printf.sprintf "http://127.0.0.1:%d" port in
+
method clock = env#clock
+
Test_server.start_server ~port test_env;
+
let req = Requests.create ~sw env in
+
let response = Requests.delete req (base_url ^ "/echo") in
+
Alcotest.(check int) "DELETE status" 200 (Requests.Response.status_code response);
+
let body_str = Requests.Response.body response |> Eio.Flow.read_all in
+
let json = Yojson.Basic.from_string body_str in
+
json |> Yojson.Basic.Util.member "method" |> Yojson.Basic.Util.to_string
+
Alcotest.(check string) "DELETE method" "DELETE" method_str
+
let test_patch_request () =
+
Eio.Switch.run @@ fun sw ->
+
let port = get_free_port () in
+
let base_url = Printf.sprintf "http://127.0.0.1:%d" port in
+
method clock = env#clock
+
Test_server.start_server ~port test_env;
+
let req = Requests.create ~sw env in
+
let body = Requests.Body.of_string Requests.Mime.json {|{"patch": "data"}|} in
+
let response = Requests.patch req ~body (base_url ^ "/echo") in
+
Alcotest.(check int) "PATCH status" 200 (Requests.Response.status_code response);
+
let body_str = Requests.Response.body response |> Eio.Flow.read_all in
+
let json = Yojson.Basic.from_string body_str in
+
json |> Yojson.Basic.Util.member "method" |> Yojson.Basic.Util.to_string
+
Alcotest.(check string) "PATCH method" "PATCH" method_str
+
let test_head_request () =
+
Eio.Switch.run @@ fun sw ->
+
let port = get_free_port () in
+
let base_url = Printf.sprintf "http://127.0.0.1:%d" port in
+
method clock = env#clock
+
Test_server.start_server ~port test_env;
+
let req = Requests.create ~sw env in
+
let response = Requests.head req (base_url ^ "/echo") in
+
Alcotest.(check int) "HEAD status" 200 (Requests.Response.status_code response)
+
let test_options_request () =
+
Eio.Switch.run @@ fun sw ->
+
let port = get_free_port () in
+
let base_url = Printf.sprintf "http://127.0.0.1:%d" port in
+
method clock = env#clock
+
Test_server.start_server ~port test_env;
+
let req = Requests.create ~sw env in
+
let response = Requests.options req (base_url ^ "/echo") in
+
Alcotest.(check int) "OPTIONS status" 200 (Requests.Response.status_code response)
+
let test_custom_headers () =
+
Eio.Switch.run @@ fun sw ->
+
let port = get_free_port () in
+
let base_url = Printf.sprintf "http://127.0.0.1:%d" port in
+
method clock = env#clock
+
Test_server.start_server ~port test_env;
+
let req = Requests.create ~sw env in
+
|> Requests.Headers.set "X-Custom-Header" "custom-value"
+
|> Requests.Headers.set "User-Agent" "test-agent"
+
let response = Requests.get req ~headers (base_url ^ "/echo") in
+
Alcotest.(check int) "Headers status" 200 (Requests.Response.status_code response);
+
let body_str = Requests.Response.body response |> Eio.Flow.read_all in
+
let json = Yojson.Basic.from_string body_str in
+
let headers_obj = json |> Yojson.Basic.Util.member "headers" in
+
|> Yojson.Basic.Util.member "x-custom-header"
+
|> Yojson.Basic.Util.to_string_option
+
|> Option.value ~default:""
+
Alcotest.(check string) "Custom header" "custom-value" custom_header
+
let test_query_params () =
+
Eio.Switch.run @@ fun sw ->
+
let port = get_free_port () in
+
let base_url = Printf.sprintf "http://127.0.0.1:%d" port in
+
method clock = env#clock
+
Test_server.start_server ~port test_env;
+
let req = Requests.create ~sw env in
+
let params = [("key1", "value1"); ("key2", "value2")] in
+
let response = Requests.get req ~params (base_url ^ "/echo") in
+
Alcotest.(check int) "Query params status" 200 (Requests.Response.status_code response);
+
let body_str = Requests.Response.body response |> Eio.Flow.read_all in
+
let json = Yojson.Basic.from_string body_str in
+
let uri = json |> Yojson.Basic.Util.member "uri" |> Yojson.Basic.Util.to_string in
+
Alcotest.(check bool) "Query params present" true
+
(string_contains uri "key1=value1" && string_contains uri "key2=value2")
+
let test_json_body () =
+
Eio.Switch.run @@ fun sw ->
+
let port = get_free_port () in
+
let base_url = Printf.sprintf "http://127.0.0.1:%d" port in
+
method clock = env#clock
+
Test_server.start_server ~port test_env;
+
let req = Requests.create ~sw env in
+
let json_data = {|{"name": "test", "value": 42}|} in
+
let body = Requests.Body.of_string Requests.Mime.json json_data in
+
let response = Requests.post req ~body (base_url ^ "/json") in
+
Alcotest.(check int) "JSON body status" 200 (Requests.Response.status_code response);
+
let body_str = Requests.Response.body response |> Eio.Flow.read_all in
+
let json = Yojson.Basic.from_string body_str in
+
let received = json |> Yojson.Basic.Util.member "received" in
+
let name = received |> Yojson.Basic.Util.member "name" |> Yojson.Basic.Util.to_string in
+
Alcotest.(check string) "JSON field" "test" name
+
let test_form_data () =
+
Eio.Switch.run @@ fun sw ->
+
let port = get_free_port () in
+
let base_url = Printf.sprintf "http://127.0.0.1:%d" port in
+
method clock = env#clock
+
Test_server.start_server ~port test_env;
+
let req = Requests.create ~sw env in
+
let form_data = [("field1", "value1"); ("field2", "value2")] in
+
let body = Requests.Body.form form_data in
+
let response = Requests.post req ~body (base_url ^ "/echo") in
+
Alcotest.(check int) "Form data status" 200 (Requests.Response.status_code response);
+
let body_str = Requests.Response.body response |> Eio.Flow.read_all in
+
let json = Yojson.Basic.from_string body_str in
+
json |> Yojson.Basic.Util.member "body" |> Yojson.Basic.Util.to_string
+
Alcotest.(check bool) "Form data encoded" true
+
(string_contains received_body "field1=value1" &&
+
string_contains received_body "field2=value2")
+
let test_status_codes () =
+
Eio.Switch.run @@ fun sw ->
+
let port = get_free_port () in
+
let base_url = Printf.sprintf "http://127.0.0.1:%d" port in
+
method clock = env#clock
+
Test_server.start_server ~port test_env;
+
let req = Requests.create ~sw env in
+
let resp_200 = Requests.get req (base_url ^ "/status/200") in
+
Alcotest.(check int) "Status 200" 200 (Requests.Response.status_code resp_200);
+
let resp_404 = Requests.get req (base_url ^ "/status/404") in
+
Alcotest.(check int) "Status 404" 404 (Requests.Response.status_code resp_404);
+
let resp_500 = Requests.get req (base_url ^ "/status/500") in
+
Alcotest.(check int) "Status 500" 500 (Requests.Response.status_code resp_500)
+
let test_redirects () =
+
Eio.Switch.run @@ fun sw ->
+
let port = get_free_port () in
+
let base_url = Printf.sprintf "http://127.0.0.1:%d" port in
+
method clock = env#clock
+
Test_server.start_server ~port test_env;
+
let req = Requests.create ~sw ~follow_redirects:true env in
+
let response = Requests.get req (base_url ^ "/redirect") in
+
Alcotest.(check int) "Redirect followed" 200 (Requests.Response.status_code response);
+
let body_str = Requests.Response.body response |> Eio.Flow.read_all in
+
Alcotest.(check string) "Redirect result" "redirect successful" body_str
+
let test_no_redirect () =
+
Eio.Switch.run @@ fun sw ->
+
let port = get_free_port () in
+
let base_url = Printf.sprintf "http://127.0.0.1:%d" port in
+
method clock = env#clock
+
Test_server.start_server ~port test_env;
+
let req = Requests.create ~sw env in
+
let response = Requests.request req ~follow_redirects:false ~method_:`GET (base_url ^ "/redirect") in
+
Alcotest.(check int) "Redirect not followed" 301
+
(Requests.Response.status_code response)
+
Eio.Switch.run @@ fun sw ->
+
let port = get_free_port () in
+
let base_url = Printf.sprintf "http://127.0.0.1:%d" port in
+
method clock = env#clock
+
Test_server.start_server ~port test_env;
+
let req = Requests.create ~sw env in
+
let _first_response = Requests.get req (base_url ^ "/cookies") in
+
let second_response = Requests.get req (base_url ^ "/cookies") in
+
let body_str = Requests.Response.body second_response |> Eio.Flow.read_all in
+
Alcotest.(check bool) "Cookies sent back" true
+
(string_contains body_str "test_cookie=test_value")
+
let test_bearer_auth () =
+
Eio.Switch.run @@ fun sw ->
+
let port = get_free_port () in
+
let base_url = Printf.sprintf "http://127.0.0.1:%d" port in
+
method clock = env#clock
+
Test_server.start_server ~port test_env;
+
let req = Requests.create ~sw env in
+
let auth = Requests.Auth.bearer ~token:"valid_token" in
+
let response = Requests.get req ~auth (base_url ^ "/auth") in
+
Alcotest.(check int) "Bearer auth status" 200 (Requests.Response.status_code response);
+
let body_str = Requests.Response.body response |> Eio.Flow.read_all in
+
Alcotest.(check string) "Bearer auth result" "authorized" body_str
+
let test_basic_auth () =
+
Eio.Switch.run @@ fun sw ->
+
let port = get_free_port () in
+
let base_url = Printf.sprintf "http://127.0.0.1:%d" port in
+
method clock = env#clock
+
Test_server.start_server ~port test_env;
+
let req = Requests.create ~sw env in
+
let auth = Requests.Auth.basic ~username:"user" ~password:"pass" in
+
let response = Requests.get req ~auth (base_url ^ "/auth") in
+
Alcotest.(check int) "Basic auth status" 200 (Requests.Response.status_code response);
+
let body_str = Requests.Response.body response |> Eio.Flow.read_all in
+
Alcotest.(check string) "Basic auth result" "basic auth received" body_str
+
Eio.Switch.run @@ fun sw ->
+
let port = get_free_port () in
+
let base_url = Printf.sprintf "http://127.0.0.1:%d" port in
+
method clock = env#clock
+
Test_server.start_server ~port test_env;
+
let req = Requests.create ~sw env in
+
let timeout = Requests.Timeout.create ~total:0.5 () in
+
let _ = Requests.get req ~timeout (base_url ^ "/timeout") in
+
Alcotest.(check bool) "Timeout triggered" true exception_raised
+
let test_concurrent_requests () =
+
Eio.Switch.run @@ fun sw ->
+
let port = get_free_port () in
+
let base_url = Printf.sprintf "http://127.0.0.1:%d" port in
+
method clock = env#clock
+
Test_server.start_server ~port test_env;
+
let req = Requests.create ~sw env in
+
(fun () -> r1 := Some (Requests.get req (base_url ^ "/status/200")));
+
(fun () -> r2 := Some (Requests.get req (base_url ^ "/status/404")));
+
(fun () -> r3 := Some (Requests.get req (base_url ^ "/status/500")));
+
let r1 = Option.get !r1 in
+
let r2 = Option.get !r2 in
+
let r3 = Option.get !r3 in
+
Alcotest.(check int) "Concurrent 1" 200 (Requests.Response.status_code r1);
+
Alcotest.(check int) "Concurrent 2" 404 (Requests.Response.status_code r2);
+
Alcotest.(check int) "Concurrent 3" 500 (Requests.Response.status_code r3)
+
let test_large_response () =
+
Eio.Switch.run @@ fun sw ->
+
let port = get_free_port () in
+
let base_url = Printf.sprintf "http://127.0.0.1:%d" port in
+
method clock = env#clock
+
Test_server.start_server ~port test_env;
+
let req = Requests.create ~sw env in
+
let response = Requests.get req (base_url ^ "/large") in
+
Alcotest.(check int) "Large response status" 200 (Requests.Response.status_code response);
+
let body_str = Requests.Response.body response |> Eio.Flow.read_all in
+
Alcotest.(check int) "Large response size" 10000 (String.length body_str)
+
let test_one_module () =
+
Eio.Switch.run @@ fun sw ->
+
let port = get_free_port () in
+
let base_url = Printf.sprintf "http://127.0.0.1:%d" port in
+
method clock = env#clock
+
Test_server.start_server ~port test_env;
+
let client = Requests.One.create ~clock:env#clock ~net:env#net () in
+
let response = Requests.One.get ~sw ~client (base_url ^ "/echo") in
+
Alcotest.(check int) "One module status" 200 (Requests.Response.status_code response)
+
let test_multipart () =
+
Eio.Switch.run @@ fun sw ->
+
let port = get_free_port () in
+
let base_url = Printf.sprintf "http://127.0.0.1:%d" port in
+
method clock = env#clock
+
Test_server.start_server ~port test_env;
+
let req = Requests.create ~sw env in
+
{ Requests.Body.name = "field1";
+
content_type = Requests.Mime.text;
+
content = `String "value1" };
+
{ Requests.Body.name = "field2";
+
filename = Some "test.txt";
+
content_type = Requests.Mime.text;
+
content = `String "file content" };
+
let body = Requests.Body.multipart parts in
+
let response = Requests.post req ~body (base_url ^ "/multipart") in
+
Alcotest.(check int) "Multipart status" 200 (Requests.Response.status_code response);
+
let body_str = Requests.Response.body response |> Eio.Flow.read_all in
+
Alcotest.(check bool) "Multipart recognized" true
+
(String.starts_with ~prefix:"Multipart received:" body_str)
+
let test_response_headers () =
+
Eio.Switch.run @@ fun sw ->
+
let port = get_free_port () in
+
let base_url = Printf.sprintf "http://127.0.0.1:%d" port in
+
method clock = env#clock
+
Test_server.start_server ~port test_env;
+
let req = Requests.create ~sw env in
+
let response = Requests.get req (base_url ^ "/json") in
+
Requests.Response.headers response
+
|> Requests.Headers.get "content-type"
+
Alcotest.(check (option string)) "Response content-type"
+
(Some "application/json") content_type
+
let test_default_headers () =
+
Eio.Switch.run @@ fun sw ->
+
let port = get_free_port () in
+
let base_url = Printf.sprintf "http://127.0.0.1:%d" port in
+
method clock = env#clock
+
Test_server.start_server ~port test_env;
+
|> Requests.Headers.set "X-Default" "default-value"
+
let req = Requests.create ~sw ~default_headers env in
+
let response = Requests.get req (base_url ^ "/echo") in
+
let body_str = Requests.Response.body response |> Eio.Flow.read_all in
+
let json = Yojson.Basic.from_string body_str in
+
let headers_obj = json |> Yojson.Basic.Util.member "headers" in
+
|> Yojson.Basic.Util.member "x-default"
+
|> Yojson.Basic.Util.to_string_option
+
|> Option.value ~default:""
+
Alcotest.(check string) "Default header present" "default-value" default_header
+
let test_session_persistence () =
+
Eio.Switch.run @@ fun sw ->
+
let port = get_free_port () in
+
let base_url = Printf.sprintf "http://127.0.0.1:%d" port in
+
method clock = env#clock
+
Test_server.start_server ~port test_env;
+
let req = Requests.create ~sw env in
+
Requests.set_default_header req "X-Session" "session-123";
+
let auth = Requests.Auth.bearer ~token:"test_token" in
+
Requests.set_auth req auth;
+
let response1 = Requests.get req (base_url ^ "/echo") in
+
let body_str1 = Requests.Response.body response1 |> Eio.Flow.read_all in
+
let json1 = Yojson.Basic.from_string body_str1 in
+
let headers1 = json1 |> Yojson.Basic.Util.member "headers" in
+
|> Yojson.Basic.Util.member "x-session"
+
|> Yojson.Basic.Util.to_string_option
+
|> Option.value ~default:""
+
Alcotest.(check string) "Session header persisted" "session-123" session_header;
+
Requests.remove_default_header req "X-Session";
+
let response2 = Requests.get req (base_url ^ "/echo") in
+
let body_str2 = Requests.Response.body response2 |> Eio.Flow.read_all in
+
let json2 = Yojson.Basic.from_string body_str2 in
+
let headers2 = json2 |> Yojson.Basic.Util.member "headers" in
+
|> Yojson.Basic.Util.member "x-session"
+
|> Yojson.Basic.Util.to_string_option
+
Alcotest.(check (option string)) "Session header removed" None session_header2
+
Logs.set_reporter (Logs.format_reporter ());
+
Logs.set_level (Some Logs.Warning);
+
test_case "GET request" `Quick test_get_request;
+
test_case "POST request" `Quick test_post_request;
+
test_case "PUT request" `Quick test_put_request;
+
test_case "DELETE request" `Quick test_delete_request;
+
test_case "PATCH request" `Quick test_patch_request;
+
test_case "HEAD request" `Quick test_head_request;
+
test_case "OPTIONS request" `Quick test_options_request;
+
test_case "Custom headers" `Quick test_custom_headers;
+
test_case "Query parameters" `Quick test_query_params;
+
test_case "JSON body" `Quick test_json_body;
+
test_case "Form data" `Quick test_form_data;
+
test_case "Multipart upload" `Quick test_multipart;
+
test_case "Default headers" `Quick test_default_headers;
+
test_case "Status codes" `Quick test_status_codes;
+
test_case "Response headers" `Quick test_response_headers;
+
test_case "Large response" `Quick test_large_response;
+
test_case "Follow redirects" `Quick test_redirects;
+
test_case "No follow redirects" `Quick test_no_redirect;
+
test_case "Bearer auth" `Quick test_bearer_auth;
+
test_case "Basic auth" `Quick test_basic_auth;
+
test_case "Cookies" `Quick test_cookies;
+
test_case "Session persistence" `Quick test_session_persistence;
+
test_case "Timeout handling" `Quick test_timeout;
+
test_case "Concurrent requests" `Quick test_concurrent_requests;
+
test_case "One module" `Quick test_one_module;