A batteries included HTTP/1.1 client in OCaml
at main 164 lines 5.2 kB view raw
1(*--------------------------------------------------------------------------- 2 Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved. 3 SPDX-License-Identifier: ISC 4 ---------------------------------------------------------------------------*) 5 6(** Tests for Method module - HTTP methods per RFC 9110 Section 9 *) 7 8module Method = Requests.Method 9 10(** {1 Conversion Tests} *) 11 12let standard_methods = 13 [ 14 (`GET, "GET"); 15 (`POST, "POST"); 16 (`PUT, "PUT"); 17 (`DELETE, "DELETE"); 18 (`HEAD, "HEAD"); 19 (`OPTIONS, "OPTIONS"); 20 (`PATCH, "PATCH"); 21 (`CONNECT, "CONNECT"); 22 (`TRACE, "TRACE"); 23 ] 24 25let test_to_string_roundtrip () = 26 List.iter 27 (fun (method_, expected_str) -> 28 let str = Method.to_string method_ in 29 Alcotest.(check string) 30 (Fmt.str "to_string %s" expected_str) 31 expected_str str; 32 let method_' = Method.of_string str in 33 Alcotest.(check bool) 34 (Fmt.str "roundtrip %s" expected_str) 35 true 36 (Method.equal method_ method_')) 37 standard_methods 38 39let test_of_string_case_insensitive () = 40 let got = Method.of_string "get" in 41 Alcotest.(check bool) "get -> GET" true (Method.equal `GET got); 42 let got2 = Method.of_string "Post" in 43 Alcotest.(check bool) "Post -> POST" true (Method.equal `POST got2); 44 let got3 = Method.of_string "dElEtE" in 45 Alcotest.(check bool) "dElEtE -> DELETE" true (Method.equal `DELETE got3) 46 47let test_custom_method () = 48 let custom = `Other "CUSTOM" in 49 let str = Method.to_string custom in 50 Alcotest.(check string) "custom to_string" "CUSTOM" str; 51 let parsed = Method.of_string "CUSTOM" in 52 Alcotest.(check bool) "custom roundtrip" true (Method.equal custom parsed) 53 54(** {1 Safe Methods (RFC 9110 Section 9.2.1)} *) 55 56let test_is_safe () = 57 let safe_methods = [ `GET; `HEAD; `OPTIONS; `TRACE ] in 58 let unsafe_methods = [ `POST; `PUT; `DELETE; `PATCH; `CONNECT ] in 59 List.iter 60 (fun m -> 61 Alcotest.(check bool) 62 (Fmt.str "%s is safe" (Method.to_string m)) 63 true (Method.is_safe m)) 64 safe_methods; 65 List.iter 66 (fun m -> 67 Alcotest.(check bool) 68 (Fmt.str "%s is not safe" (Method.to_string m)) 69 false (Method.is_safe m)) 70 unsafe_methods 71 72(** {1 Idempotent Methods (RFC 9110 Section 9.2.2)} *) 73 74let test_is_idempotent () = 75 let idempotent = [ `GET; `HEAD; `PUT; `DELETE; `OPTIONS; `TRACE ] in 76 let not_idempotent = [ `POST; `PATCH ] in 77 List.iter 78 (fun m -> 79 Alcotest.(check bool) 80 (Fmt.str "%s is idempotent" (Method.to_string m)) 81 true (Method.is_idempotent m)) 82 idempotent; 83 List.iter 84 (fun m -> 85 Alcotest.(check bool) 86 (Fmt.str "%s is not idempotent" (Method.to_string m)) 87 false (Method.is_idempotent m)) 88 not_idempotent 89 90(** {1 Cacheable Methods (RFC 9110 Section 9.2.3)} *) 91 92let test_is_cacheable () = 93 let cacheable = [ `GET; `HEAD; `POST ] in 94 let not_cacheable = [ `PUT; `DELETE; `PATCH; `OPTIONS; `TRACE; `CONNECT ] in 95 List.iter 96 (fun m -> 97 Alcotest.(check bool) 98 (Fmt.str "%s is cacheable" (Method.to_string m)) 99 true (Method.is_cacheable m)) 100 cacheable; 101 List.iter 102 (fun m -> 103 Alcotest.(check bool) 104 (Fmt.str "%s is not cacheable" (Method.to_string m)) 105 false (Method.is_cacheable m)) 106 not_cacheable 107 108(** {1 Request Body Semantics (RFC 9110 Section 9.3)} *) 109 110let test_request_body_semantics () = 111 (* Body required *) 112 List.iter 113 (fun m -> 114 Alcotest.(check bool) 115 (Fmt.str "%s body required" (Method.to_string m)) 116 true 117 (Method.request_body_semantics m = Method.Body_required)) 118 [ `POST; `PUT; `PATCH ]; 119 (* Body forbidden *) 120 List.iter 121 (fun m -> 122 Alcotest.(check bool) 123 (Fmt.str "%s body forbidden" (Method.to_string m)) 124 true 125 (Method.request_body_semantics m = Method.Body_forbidden)) 126 [ `HEAD; `TRACE; `CONNECT ]; 127 (* Body optional *) 128 List.iter 129 (fun m -> 130 Alcotest.(check bool) 131 (Fmt.str "%s body optional" (Method.to_string m)) 132 true 133 (Method.request_body_semantics m = Method.Body_optional)) 134 [ `GET; `DELETE; `OPTIONS ] 135 136(** {1 Equality} *) 137 138let test_equal () = 139 Alcotest.(check bool) "GET = GET" true (Method.equal `GET `GET); 140 Alcotest.(check bool) "GET <> POST" false (Method.equal `GET `POST); 141 Alcotest.(check bool) 142 "Other = Other" true 143 (Method.equal (`Other "X") (`Other "X")); 144 Alcotest.(check bool) 145 "Other <> Other" false 146 (Method.equal (`Other "X") (`Other "Y")) 147 148(** {1 Test Suite} *) 149 150let suite = 151 ( "method", 152 [ 153 Alcotest.test_case "to_string/of_string roundtrip" `Quick 154 test_to_string_roundtrip; 155 Alcotest.test_case "of_string case insensitive" `Quick 156 test_of_string_case_insensitive; 157 Alcotest.test_case "custom method" `Quick test_custom_method; 158 Alcotest.test_case "safe methods (9.2.1)" `Quick test_is_safe; 159 Alcotest.test_case "idempotent methods (9.2.2)" `Quick test_is_idempotent; 160 Alcotest.test_case "cacheable methods (9.2.3)" `Quick test_is_cacheable; 161 Alcotest.test_case "request body semantics (9.3)" `Quick 162 test_request_body_semantics; 163 Alcotest.test_case "equal" `Quick test_equal; 164 ] )