My agentic slop goes here. Not intended for anyone else!
1open Cookeio
2
3let cookie_testable : Cookeio.t Alcotest.testable =
4 Alcotest.testable
5 (fun ppf c ->
6 Format.fprintf ppf
7 "{ name=%S; value=%S; domain=%S; path=%S; secure=%b; http_only=%b; \
8 expires=%a; same_site=%a }"
9 (Cookeio.name c) (Cookeio.value c) (Cookeio.domain c) (Cookeio.path c) (Cookeio.secure c) (Cookeio.http_only c)
10 (Format.pp_print_option Ptime.pp)
11 (Cookeio.expires c)
12 (Format.pp_print_option (fun ppf -> function
13 | `Strict -> Format.pp_print_string ppf "Strict"
14 | `Lax -> Format.pp_print_string ppf "Lax"
15 | `None -> Format.pp_print_string ppf "None"))
16 (Cookeio.same_site c))
17 (fun c1 c2 ->
18 Cookeio.name c1 = Cookeio.name c2 && Cookeio.value c1 = Cookeio.value c2 && Cookeio.domain c1 = Cookeio.domain c2
19 && Cookeio.path c1 = Cookeio.path c2 && Cookeio.secure c1 = Cookeio.secure c2
20 && Cookeio.http_only c1 = Cookeio.http_only c2
21 && Option.equal Ptime.equal (Cookeio.expires c1) (Cookeio.expires c2)
22 && Option.equal ( = ) (Cookeio.same_site c1) (Cookeio.same_site c2))
23
24let test_load_mozilla_cookies () =
25 let content =
26 {|# Netscape HTTP Cookie File
27# http://curl.haxx.se/rfc/cookie_spec.html
28# This is a generated file! Do not edit.
29
30example.com FALSE /foo/ FALSE 0 cookie-1 v$1
31.example.com TRUE /foo/ FALSE 0 cookie-2 v$2
32example.com FALSE /foo/ FALSE 1257894000 cookie-3 v$3
33example.com FALSE /foo/ FALSE 1257894000 cookie-4 v$4
34example.com FALSE /foo/ TRUE 1257894000 cookie-5 v$5
35#HttpOnly_example.com FALSE /foo/ FALSE 1257894000 cookie-6 v$6
36#HttpOnly_.example.com TRUE /foo/ FALSE 1257894000 cookie-7 v$7
37|}
38 in
39 let jar = from_mozilla_format content in
40 let cookies = get_all_cookies jar in
41
42 (* Check total number of cookies (should skip commented lines) *)
43 Alcotest.(check int) "cookie count" 5 (List.length cookies);
44 Alcotest.(check int) "count function" 5 (count jar);
45 Alcotest.(check bool) "not empty" false (is_empty jar);
46
47 let find_cookie name = List.find (fun c -> Cookeio.name c = name) cookies in
48
49 (* Test cookie-1: session cookie on exact domain *)
50 let cookie1 = find_cookie "cookie-1" in
51 Alcotest.(check string) "cookie-1 domain" "example.com" (Cookeio.domain cookie1);
52 Alcotest.(check string) "cookie-1 path" "/foo/" (Cookeio.path cookie1);
53 Alcotest.(check string) "cookie-1 name" "cookie-1" (Cookeio.name cookie1);
54 Alcotest.(check string) "cookie-1 value" "v$1" (Cookeio.value cookie1);
55 Alcotest.(check bool) "cookie-1 secure" false (Cookeio.secure cookie1);
56 Alcotest.(check bool) "cookie-1 http_only" false (Cookeio.http_only cookie1);
57 Alcotest.(check (option (Alcotest.testable Ptime.pp Ptime.equal)))
58 "cookie-1 expires" None (Cookeio.expires cookie1);
59 Alcotest.(
60 check
61 (option
62 (Alcotest.testable
63 (fun ppf -> function
64 | `Strict -> Format.pp_print_string ppf "Strict"
65 | `Lax -> Format.pp_print_string ppf "Lax"
66 | `None -> Format.pp_print_string ppf "None")
67 ( = ))))
68 "cookie-1 same_site" None (Cookeio.same_site cookie1);
69
70 (* Test cookie-2: session cookie on subdomain pattern *)
71 let cookie2 = find_cookie "cookie-2" in
72 Alcotest.(check string) "cookie-2 domain" ".example.com" (Cookeio.domain cookie2);
73 Alcotest.(check string) "cookie-2 path" "/foo/" (Cookeio.path cookie2);
74 Alcotest.(check string) "cookie-2 name" "cookie-2" (Cookeio.name cookie2);
75 Alcotest.(check string) "cookie-2 value" "v$2" (Cookeio.value cookie2);
76 Alcotest.(check bool) "cookie-2 secure" false (Cookeio.secure cookie2);
77 Alcotest.(check bool) "cookie-2 http_only" false (Cookeio.http_only cookie2);
78 Alcotest.(check (option (Alcotest.testable Ptime.pp Ptime.equal)))
79 "cookie-2 expires" None (Cookeio.expires cookie2);
80
81 (* Test cookie-3: non-session cookie with expiry *)
82 let cookie3 = find_cookie "cookie-3" in
83 let expected_expiry = Ptime.of_float_s 1257894000.0 in
84 Alcotest.(check string) "cookie-3 domain" "example.com" (Cookeio.domain cookie3);
85 Alcotest.(check string) "cookie-3 path" "/foo/" (Cookeio.path cookie3);
86 Alcotest.(check string) "cookie-3 name" "cookie-3" (Cookeio.name cookie3);
87 Alcotest.(check string) "cookie-3 value" "v$3" (Cookeio.value cookie3);
88 Alcotest.(check bool) "cookie-3 secure" false (Cookeio.secure cookie3);
89 Alcotest.(check bool) "cookie-3 http_only" false (Cookeio.http_only cookie3);
90 Alcotest.(check (option (Alcotest.testable Ptime.pp Ptime.equal)))
91 "cookie-3 expires" expected_expiry (Cookeio.expires cookie3);
92
93 (* Test cookie-4: another non-session cookie *)
94 let cookie4 = find_cookie "cookie-4" in
95 Alcotest.(check string) "cookie-4 domain" "example.com" (Cookeio.domain cookie4);
96 Alcotest.(check string) "cookie-4 path" "/foo/" (Cookeio.path cookie4);
97 Alcotest.(check string) "cookie-4 name" "cookie-4" (Cookeio.name cookie4);
98 Alcotest.(check string) "cookie-4 value" "v$4" (Cookeio.value cookie4);
99 Alcotest.(check bool) "cookie-4 secure" false (Cookeio.secure cookie4);
100 Alcotest.(check bool) "cookie-4 http_only" false (Cookeio.http_only cookie4);
101 Alcotest.(check (option (Alcotest.testable Ptime.pp Ptime.equal)))
102 "cookie-4 expires" expected_expiry (Cookeio.expires cookie4);
103
104 (* Test cookie-5: secure cookie *)
105 let cookie5 = find_cookie "cookie-5" in
106 Alcotest.(check string) "cookie-5 domain" "example.com" (Cookeio.domain cookie5);
107 Alcotest.(check string) "cookie-5 path" "/foo/" (Cookeio.path cookie5);
108 Alcotest.(check string) "cookie-5 name" "cookie-5" (Cookeio.name cookie5);
109 Alcotest.(check string) "cookie-5 value" "v$5" (Cookeio.value cookie5);
110 Alcotest.(check bool) "cookie-5 secure" true (Cookeio.secure cookie5);
111 Alcotest.(check bool) "cookie-5 http_only" false (Cookeio.http_only cookie5);
112 Alcotest.(check (option (Alcotest.testable Ptime.pp Ptime.equal)))
113 "cookie-5 expires" expected_expiry (Cookeio.expires cookie5)
114
115let test_load_from_file env =
116 (* This test loads from the actual test/cookies.txt file using the load function *)
117 let cwd = Eio.Stdenv.cwd env in
118 let cookie_path = Eio.Path.(cwd / "cookies.txt") in
119 let jar = load cookie_path in
120 let cookies = get_all_cookies jar in
121
122 (* Should have the same 5 cookies as the string test *)
123 Alcotest.(check int) "file load cookie count" 5 (List.length cookies);
124
125 let find_cookie name = List.find (fun c -> Cookeio.name c = name) cookies in
126
127 (* Verify a few key cookies are loaded correctly *)
128 let cookie1 = find_cookie "cookie-1" in
129 Alcotest.(check string) "file cookie-1 value" "v$1" (Cookeio.value cookie1);
130 Alcotest.(check string) "file cookie-1 domain" "example.com" (Cookeio.domain cookie1);
131 Alcotest.(check bool) "file cookie-1 secure" false (Cookeio.secure cookie1);
132 Alcotest.(check (option (Alcotest.testable Ptime.pp Ptime.equal)))
133 "file cookie-1 expires" None (Cookeio.expires cookie1);
134
135 let cookie5 = find_cookie "cookie-5" in
136 Alcotest.(check string) "file cookie-5 value" "v$5" (Cookeio.value cookie5);
137 Alcotest.(check bool) "file cookie-5 secure" true (Cookeio.secure cookie5);
138 let expected_expiry = Ptime.of_float_s 1257894000.0 in
139 Alcotest.(check (option (Alcotest.testable Ptime.pp Ptime.equal)))
140 "file cookie-5 expires" expected_expiry (Cookeio.expires cookie5);
141
142 (* Verify subdomain cookie *)
143 let cookie2 = find_cookie "cookie-2" in
144 Alcotest.(check string) "file cookie-2 domain" ".example.com" (Cookeio.domain cookie2);
145 Alcotest.(check (option (Alcotest.testable Ptime.pp Ptime.equal)))
146 "file cookie-2 expires" None (Cookeio.expires cookie2)
147
148let test_cookie_matching () =
149 let jar = create () in
150
151 (* Add test cookies with different domain patterns *)
152 let exact_cookie =
153 Cookeio.make ~domain:"example.com" ~path:"/" ~name:"exact" ~value:"test1"
154 ~secure:false ~http_only:false ?expires:None ?same_site:None
155 ~creation_time:Ptime.epoch ~last_access:Ptime.epoch ()
156 in
157 let subdomain_cookie =
158 Cookeio.make ~domain:".example.com" ~path:"/" ~name:"subdomain" ~value:"test2"
159 ~secure:false ~http_only:false ?expires:None ?same_site:None
160 ~creation_time:Ptime.epoch ~last_access:Ptime.epoch ()
161 in
162 let secure_cookie =
163 Cookeio.make ~domain:"example.com" ~path:"/" ~name:"secure" ~value:"test3"
164 ~secure:true ~http_only:false ?expires:None ?same_site:None
165 ~creation_time:Ptime.epoch ~last_access:Ptime.epoch ()
166 in
167
168 add_cookie jar exact_cookie;
169 add_cookie jar subdomain_cookie;
170 add_cookie jar secure_cookie;
171
172 (* Test exact domain matching *)
173 let cookies_http =
174 get_cookies jar ~domain:"example.com" ~path:"/" ~is_secure:false
175 in
176 Alcotest.(check int) "http cookies count" 2 (List.length cookies_http);
177
178 let cookies_https =
179 get_cookies jar ~domain:"example.com" ~path:"/" ~is_secure:true
180 in
181 Alcotest.(check int) "https cookies count" 3 (List.length cookies_https);
182
183 (* Test subdomain matching *)
184 let cookies_sub =
185 get_cookies jar ~domain:"sub.example.com" ~path:"/" ~is_secure:false
186 in
187 Alcotest.(check int) "subdomain cookies count" 1 (List.length cookies_sub);
188 let sub_cookie = List.hd cookies_sub in
189 Alcotest.(check string) "subdomain cookie name" "subdomain" (Cookeio.name sub_cookie)
190
191let test_empty_jar () =
192 let jar = create () in
193 Alcotest.(check bool) "empty jar" true (is_empty jar);
194 Alcotest.(check int) "empty count" 0 (count jar);
195 Alcotest.(check (list cookie_testable))
196 "empty cookies" [] (get_all_cookies jar);
197
198 let cookies =
199 get_cookies jar ~domain:"example.com" ~path:"/" ~is_secure:false
200 in
201 Alcotest.(check int) "no matching cookies" 0 (List.length cookies)
202
203let test_round_trip_mozilla_format () =
204 let jar = create () in
205
206 let test_cookie =
207 Cookeio.make ~domain:"example.com" ~path:"/test/" ~name:"test" ~value:"value"
208 ~secure:true ~http_only:false ?expires:(Ptime.of_float_s 1257894000.0)
209 ~same_site:`Strict ~creation_time:Ptime.epoch ~last_access:Ptime.epoch ()
210 in
211
212 add_cookie jar test_cookie;
213
214 (* Convert to Mozilla format and back *)
215 let mozilla_format = to_mozilla_format jar in
216 let jar2 = from_mozilla_format mozilla_format in
217 let cookies2 = get_all_cookies jar2 in
218
219 Alcotest.(check int) "round trip count" 1 (List.length cookies2);
220 let cookie2 = List.hd cookies2 in
221 Alcotest.(check string) "round trip name" "test" (Cookeio.name cookie2);
222 Alcotest.(check string) "round trip value" "value" (Cookeio.value cookie2);
223 Alcotest.(check string) "round trip domain" "example.com" (Cookeio.domain cookie2);
224 Alcotest.(check string) "round trip path" "/test/" (Cookeio.path cookie2);
225 Alcotest.(check bool) "round trip secure" true (Cookeio.secure cookie2);
226 (* Note: http_only and same_site are lost in Mozilla format *)
227 Alcotest.(check (option (Alcotest.testable Ptime.pp Ptime.equal)))
228 "round trip expires"
229 (Ptime.of_float_s 1257894000.0)
230 (Cookeio.expires cookie2)
231
232let () =
233 Eio_main.run @@ fun env ->
234 let open Alcotest in
235 run "Cookeio Tests"
236 [
237 ( "mozilla_format",
238 [
239 test_case "Load Mozilla format from string" `Quick
240 test_load_mozilla_cookies;
241 test_case "Load Mozilla format from file" `Quick (fun () ->
242 test_load_from_file env);
243 test_case "Round trip Mozilla format" `Quick
244 test_round_trip_mozilla_format;
245 ] );
246 ( "cookie_matching",
247 [ test_case "Domain and security matching" `Quick test_cookie_matching ]
248 );
249 ( "basic_operations",
250 [ test_case "Empty jar operations" `Quick test_empty_jar ] );
251 ]