(*--------------------------------------------------------------------------- Copyright (c) 2025 Anil Madhavapeddy . All rights reserved. SPDX-License-Identifier: ISC ---------------------------------------------------------------------------*) (** Tests for Headers module *) module Headers = Requests.Headers module Header_name = Requests.Header_name (** {1 empty Tests} *) let test_empty_has_no_headers () = let h = Headers.empty in let l = Headers.to_list h in Alcotest.(check int) "empty has no headers" 0 (List.length l) (** {1 of_list / to_list Tests} *) let test_of_list_roundtrip () = let pairs = [ ("Content-Type", "text/html"); ("Host", "example.com") ] in let h = Headers.of_list pairs in let l = Headers.to_list h in Alcotest.(check int) "two headers" 2 (List.length l) let test_of_list_preserves_values () = let h = Headers.of_list [ ("Content-Type", "text/html") ] in let v = Headers.find `Content_type h in Alcotest.(check (option string)) "Content-Type value" (Some "text/html") v (** {1 add / get / remove Tests} *) let test_add_and_get () = let h = Headers.empty |> Headers.add `Content_type "application/json" in let v = Headers.find `Content_type h in Alcotest.(check (option string)) "get after add" (Some "application/json") v let test_add_multiple () = let h = Headers.empty |> Headers.add `Set_cookie "a=1" |> Headers.add `Set_cookie "b=2" in let values = Headers.all `Set_cookie h in Alcotest.(check int) "multiple values" 2 (List.length values) let test_get_missing () = let h = Headers.empty in let v = Headers.find `Content_type h in Alcotest.(check (option string)) "missing header" None v let test_remove () = let h = Headers.empty |> Headers.add `Content_type "text/html" |> Headers.remove `Content_type in let v = Headers.find `Content_type h in Alcotest.(check (option string)) "removed header" None v (** {1 set Tests} *) let test_set_replaces () = let h = Headers.empty |> Headers.add `Content_type "text/html" |> Headers.set `Content_type "application/json" in let v = Headers.find `Content_type h in Alcotest.(check (option string)) "set replaces" (Some "application/json") v let test_set_replaces_all () = let h = Headers.empty |> Headers.add `Set_cookie "a=1" |> Headers.add `Set_cookie "b=2" |> Headers.set `Set_cookie "c=3" in let values = Headers.all `Set_cookie h in Alcotest.(check int) "set replaces all" 1 (List.length values); Alcotest.(check string) "set value" "c=3" (List.hd values) (** {1 all Tests} *) let test_get_all_empty () = let h = Headers.empty in let values = Headers.all `Content_type h in Alcotest.(check int) "all empty" 0 (List.length values) let test_get_all_multiple () = let h = Headers.empty |> Headers.add `Set_cookie "session=abc" |> Headers.add `Set_cookie "lang=en" in let values = Headers.all `Set_cookie h in Alcotest.(check int) "all multiple" 2 (List.length values) (** {1 merge Tests} *) let test_merge_combines () = let a = Headers.empty |> Headers.set `Content_type "text/html" in let b = Headers.empty |> Headers.set `Host "example.com" in let merged = Headers.merge a b in Alcotest.(check (option string)) "merged content-type" (Some "text/html") (Headers.find `Content_type merged); Alcotest.(check (option string)) "merged host" (Some "example.com") (Headers.find `Host merged) let test_merge_override () = let a = Headers.empty |> Headers.set `Content_type "text/html" in let b = Headers.empty |> Headers.set `Content_type "application/json" in let merged = Headers.merge a b in Alcotest.(check (option string)) "override replaces" (Some "application/json") (Headers.find `Content_type merged) (** {1 Convenience Constructor Tests} *) let test_content_type () = let h = Headers.empty |> Headers.content_type (Requests.Mime.of_string "text/html") in let v = Headers.find `Content_type h in Alcotest.(check (option string)) "content_type" (Some "text/html") v let test_content_length () = let h = Headers.empty |> Headers.content_length 42L in let v = Headers.find `Content_length h in Alcotest.(check (option string)) "content_length" (Some "42") v let test_host () = let h = Headers.empty |> Headers.host "example.com" in let v = Headers.find `Host h in Alcotest.(check (option string)) "host" (Some "example.com") v (** {1 Authentication Tests} *) let test_bearer () = let h = Headers.empty |> Headers.bearer "token123" in let v = Headers.find `Authorization h in Alcotest.(check (option string)) "bearer" (Some "Bearer token123") v let test_basic () = let h = Headers.empty |> Headers.basic ~username:"user" ~password:"pass" in let v = Headers.find `Authorization h in match v with | Some auth -> Alcotest.(check bool) "starts with Basic" true (String.length auth > 6 && String.sub auth 0 6 = "Basic ") | None -> Alcotest.fail "Expected Authorization header" (** {1 Connection Header Tests} *) let test_connection_close () = let h = Headers.of_list [ ("Connection", "close") ] in Alcotest.(check bool) "connection_close" true (Headers.connection_close h) let test_connection_close_false () = let h = Headers.of_list [ ("Connection", "keep-alive") ] in Alcotest.(check bool) "not connection_close" false (Headers.connection_close h) let test_connection_keep_alive () = let h = Headers.of_list [ ("Connection", "keep-alive") ] in Alcotest.(check bool) "connection_keep_alive" true (Headers.connection_keep_alive h) let test_parse_connection_header () = let h = Headers.of_list [ ("Connection", "close, X-Custom") ] in let names = Headers.parse_connection_header h in Alcotest.(check bool) "has entries" true (List.length names > 0) (** {1 HTTP/2 Pseudo-Header Tests} *) let test_is_pseudo_header () = Alcotest.(check bool) ":method is pseudo" true (Headers.is_pseudo_header ":method"); Alcotest.(check bool) "content-type not pseudo" false (Headers.is_pseudo_header "content-type") let test_set_get_pseudo () = let h = Headers.empty |> Headers.set_pseudo "method" "GET" in let v = Headers.pseudo "method" h in Alcotest.(check (option string)) "get pseudo" (Some "GET") v let test_pseudo_roundtrip () = let h = Headers.empty |> Headers.set_pseudo "method" "GET" |> Headers.set_pseudo "scheme" "https" |> Headers.set_pseudo "path" "/" in Alcotest.(check bool) "has pseudo headers" true (Headers.has_pseudo_headers h); let pseudos = Headers.pseudo_headers h in Alcotest.(check int) "3 pseudo headers" 3 (List.length pseudos) let test_remove_pseudo () = let h = Headers.empty |> Headers.set_pseudo "method" "GET" |> Headers.remove_pseudo "method" in Alcotest.(check bool) "removed pseudo" false (Headers.mem_pseudo "method" h) let test_regular_headers_exclude_pseudo () = let h = Headers.empty |> Headers.set_pseudo "method" "GET" |> Headers.set `Content_type "text/html" in let regular = Headers.regular_headers h in let has_pseudo = List.exists (fun (name, _) -> String.length name > 0 && name.[0] = ':') regular in Alcotest.(check bool) "no pseudo in regular" false has_pseudo (** {1 mem Tests} *) let test_mem_present () = let h = Headers.empty |> Headers.set `Content_type "text/html" in Alcotest.(check bool) "mem present" true (Headers.mem `Content_type h) let test_mem_absent () = let h = Headers.empty in Alcotest.(check bool) "mem absent" false (Headers.mem `Content_type h) (** {1 Test Suite} *) let suite = ( "headers", [ Alcotest.test_case "no headers" `Quick test_empty_has_no_headers; Alcotest.test_case "roundtrip" `Quick test_of_list_roundtrip; Alcotest.test_case "preserves values" `Quick test_of_list_preserves_values; Alcotest.test_case "add and get" `Quick test_add_and_get; Alcotest.test_case "add multiple" `Quick test_add_multiple; Alcotest.test_case "get missing" `Quick test_get_missing; Alcotest.test_case "remove" `Quick test_remove; Alcotest.test_case "replaces existing" `Quick test_set_replaces; Alcotest.test_case "replaces all" `Quick test_set_replaces_all; Alcotest.test_case "empty" `Quick test_get_all_empty; Alcotest.test_case "multiple values" `Quick test_get_all_multiple; Alcotest.test_case "combines" `Quick test_merge_combines; Alcotest.test_case "override" `Quick test_merge_override; Alcotest.test_case "content_type" `Quick test_content_type; Alcotest.test_case "content_length" `Quick test_content_length; Alcotest.test_case "host" `Quick test_host; Alcotest.test_case "bearer" `Quick test_bearer; Alcotest.test_case "basic" `Quick test_basic; Alcotest.test_case "connection_close" `Quick test_connection_close; Alcotest.test_case "connection_close false" `Quick test_connection_close_false; Alcotest.test_case "connection_keep_alive" `Quick test_connection_keep_alive; Alcotest.test_case "parse_connection_header" `Quick test_parse_connection_header; Alcotest.test_case "is_pseudo_header" `Quick test_is_pseudo_header; Alcotest.test_case "set and get" `Quick test_set_get_pseudo; Alcotest.test_case "roundtrip" `Quick test_pseudo_roundtrip; Alcotest.test_case "remove" `Quick test_remove_pseudo; Alcotest.test_case "regular excludes pseudo" `Quick test_regular_headers_exclude_pseudo; Alcotest.test_case "present" `Quick test_mem_present; Alcotest.test_case "absent" `Quick test_mem_absent; ] )