forked from
anil.recoil.org/ocaml-requests
A batteries included HTTP/1.1 client in OCaml
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 ] )