this repo has no description
1(*---------------------------------------------------------------------------
2 Copyright (c) 2025 Anil Madhavapeddy. All rights reserved.
3 SPDX-License-Identifier: ISC
4 ---------------------------------------------------------------------------*)
5
6(** JMAP Protocol codec tests using sample JSON files *)
7
8let read_file path =
9 let ic = open_in path in
10 let n = in_channel_length ic in
11 let s = really_input_string ic n in
12 close_in ic;
13 s
14
15let decode jsont json_str =
16 Jsont_bytesrw.decode_string' jsont json_str
17
18let encode jsont value =
19 Jsont_bytesrw.encode_string' jsont value
20
21(* Test helpers *)
22
23let test_decode_success name jsont path () =
24 let json = read_file path in
25 match decode jsont json with
26 | Ok _ -> ()
27 | Error e ->
28 Alcotest.failf "%s: expected success but got error: %s" name (Jsont.Error.to_string e)
29
30let test_decode_failure name jsont path () =
31 let json = read_file path in
32 match decode jsont json with
33 | Ok _ -> Alcotest.failf "%s: expected failure but got success" name
34 | Error _ -> ()
35
36let test_roundtrip name jsont path () =
37 let json = read_file path in
38 match decode jsont json with
39 | Error e ->
40 Alcotest.failf "%s: decode failed: %s" name (Jsont.Error.to_string e)
41 | Ok value ->
42 match encode jsont value with
43 | Error e ->
44 Alcotest.failf "%s: encode failed: %s" name (Jsont.Error.to_string e)
45 | Ok encoded ->
46 match decode jsont encoded with
47 | Error e ->
48 Alcotest.failf "%s: re-decode failed: %s" name (Jsont.Error.to_string e)
49 | Ok _ -> ()
50
51(* ID tests *)
52module Id_tests = struct
53 open Jmap_proto
54
55 let test_valid_simple () =
56 let json = "\"abc123\"" in
57 match decode Id.jsont json with
58 | Ok id -> Alcotest.(check string) "id value" "abc123" (Id.to_string id)
59 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
60
61 let test_valid_single_char () =
62 let json = "\"a\"" in
63 match decode Id.jsont json with
64 | Ok id -> Alcotest.(check string) "id value" "a" (Id.to_string id)
65 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
66
67 let test_valid_with_hyphen () =
68 let json = "\"msg-2024-01\"" in
69 match decode Id.jsont json with
70 | Ok id -> Alcotest.(check string) "id value" "msg-2024-01" (Id.to_string id)
71 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
72
73 let test_valid_with_underscore () =
74 let json = "\"user_id_123\"" in
75 match decode Id.jsont json with
76 | Ok id -> Alcotest.(check string) "id value" "user_id_123" (Id.to_string id)
77 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
78
79 let test_invalid_empty () =
80 let json = "\"\"" in
81 match decode Id.jsont json with
82 | Ok _ -> Alcotest.fail "expected failure for empty id"
83 | Error _ -> ()
84
85 let test_invalid_with_space () =
86 let json = "\"hello world\"" in
87 match decode Id.jsont json with
88 | Ok _ -> Alcotest.fail "expected failure for id with space"
89 | Error _ -> ()
90
91 let test_invalid_with_special () =
92 let json = "\"abc@def\"" in
93 match decode Id.jsont json with
94 | Ok _ -> Alcotest.fail "expected failure for id with @"
95 | Error _ -> ()
96
97 let test_invalid_not_string () =
98 let json = "12345" in
99 match decode Id.jsont json with
100 | Ok _ -> Alcotest.fail "expected failure for non-string"
101 | Error _ -> ()
102
103 let test_edge_max_length () =
104 let id_255 = String.make 255 'a' in
105 let json = Printf.sprintf "\"%s\"" id_255 in
106 match decode Id.jsont json with
107 | Ok id -> Alcotest.(check int) "id length" 255 (String.length (Id.to_string id))
108 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
109
110 let test_edge_over_max_length () =
111 let id_256 = String.make 256 'a' in
112 let json = Printf.sprintf "\"%s\"" id_256 in
113 match decode Id.jsont json with
114 | Ok _ -> Alcotest.fail "expected failure for 256 char id"
115 | Error _ -> ()
116
117 let tests = [
118 "valid: simple", `Quick, test_valid_simple;
119 "valid: single char", `Quick, test_valid_single_char;
120 "valid: with hyphen", `Quick, test_valid_with_hyphen;
121 "valid: with underscore", `Quick, test_valid_with_underscore;
122 "invalid: empty", `Quick, test_invalid_empty;
123 "invalid: with space", `Quick, test_invalid_with_space;
124 "invalid: with special", `Quick, test_invalid_with_special;
125 "invalid: not string", `Quick, test_invalid_not_string;
126 "edge: max length 255", `Quick, test_edge_max_length;
127 "edge: over max length 256", `Quick, test_edge_over_max_length;
128 ]
129end
130
131(* Int53 tests *)
132module Int53_tests = struct
133 open Jmap_proto
134
135 let test_zero () =
136 match decode Int53.Signed.jsont "0" with
137 | Ok n -> Alcotest.(check int64) "value" 0L n
138 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
139
140 let test_positive () =
141 match decode Int53.Signed.jsont "12345" with
142 | Ok n -> Alcotest.(check int64) "value" 12345L n
143 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
144
145 let test_negative () =
146 match decode Int53.Signed.jsont "-12345" with
147 | Ok n -> Alcotest.(check int64) "value" (-12345L) n
148 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
149
150 let test_max_safe () =
151 match decode Int53.Signed.jsont "9007199254740991" with
152 | Ok n -> Alcotest.(check int64) "value" 9007199254740991L n
153 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
154
155 let test_min_safe () =
156 match decode Int53.Signed.jsont "-9007199254740991" with
157 | Ok n -> Alcotest.(check int64) "value" (-9007199254740991L) n
158 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
159
160 let test_over_max_safe () =
161 match decode Int53.Signed.jsont "9007199254740992" with
162 | Ok _ -> Alcotest.fail "expected failure for over max safe"
163 | Error _ -> ()
164
165 let test_under_min_safe () =
166 match decode Int53.Signed.jsont "-9007199254740992" with
167 | Ok _ -> Alcotest.fail "expected failure for under min safe"
168 | Error _ -> ()
169
170 let test_unsigned_zero () =
171 match decode Int53.Unsigned.jsont "0" with
172 | Ok n -> Alcotest.(check int64) "value" 0L n
173 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
174
175 let test_unsigned_max () =
176 match decode Int53.Unsigned.jsont "9007199254740991" with
177 | Ok n -> Alcotest.(check int64) "value" 9007199254740991L n
178 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
179
180 let test_unsigned_negative () =
181 match decode Int53.Unsigned.jsont "-1" with
182 | Ok _ -> Alcotest.fail "expected failure for negative unsigned"
183 | Error _ -> ()
184
185 let tests = [
186 "signed: zero", `Quick, test_zero;
187 "signed: positive", `Quick, test_positive;
188 "signed: negative", `Quick, test_negative;
189 "signed: max safe", `Quick, test_max_safe;
190 "signed: min safe", `Quick, test_min_safe;
191 "signed: over max safe", `Quick, test_over_max_safe;
192 "signed: under min safe", `Quick, test_under_min_safe;
193 "unsigned: zero", `Quick, test_unsigned_zero;
194 "unsigned: max", `Quick, test_unsigned_max;
195 "unsigned: negative fails", `Quick, test_unsigned_negative;
196 ]
197end
198
199(* Date tests *)
200module Date_tests = struct
201 open Jmap_proto
202
203 let test_utc_z () =
204 match decode Date.Utc.jsont "\"2024-01-15T10:30:00Z\"" with
205 | Ok _ -> ()
206 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
207
208 let test_rfc3339_with_offset () =
209 match decode Date.Rfc3339.jsont "\"2024-01-15T10:30:00+05:30\"" with
210 | Ok _ -> ()
211 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
212
213 let test_with_milliseconds () =
214 match decode Date.Rfc3339.jsont "\"2024-01-15T10:30:00.123Z\"" with
215 | Ok _ -> ()
216 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
217
218 let test_invalid_format () =
219 match decode Date.Rfc3339.jsont "\"January 15, 2024\"" with
220 | Ok _ -> Alcotest.fail "expected failure for invalid format"
221 | Error _ -> ()
222
223 let test_not_string () =
224 match decode Date.Rfc3339.jsont "1705315800" with
225 | Ok _ -> Alcotest.fail "expected failure for non-string"
226 | Error _ -> ()
227
228 let tests = [
229 "utc: Z suffix", `Quick, test_utc_z;
230 "rfc3339: with offset", `Quick, test_rfc3339_with_offset;
231 "rfc3339: with milliseconds", `Quick, test_with_milliseconds;
232 "invalid: bad format", `Quick, test_invalid_format;
233 "invalid: not string", `Quick, test_not_string;
234 ]
235end
236
237(* Session tests *)
238module Session_tests = struct
239 open Jmap_proto
240
241 let test_minimal () =
242 test_decode_success "minimal session" Session.jsont "session/valid/minimal.json" ()
243
244 let test_with_mail () =
245 test_decode_success "session with mail" Session.jsont "session/valid/with_mail.json" ()
246
247 let test_roundtrip_minimal () =
248 test_roundtrip "minimal session roundtrip" Session.jsont "session/valid/minimal.json" ()
249
250 let test_values () =
251 let json = read_file "session/valid/minimal.json" in
252 match decode Session.jsont json with
253 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
254 | Ok session ->
255 Alcotest.(check string) "username" "test@example.com" (Session.username session);
256 Alcotest.(check string) "apiUrl" "https://api.example.com/jmap/" (Session.api_url session);
257 Alcotest.(check string) "state" "abc123" (Session.state session);
258 Alcotest.(check bool) "has core capability" true
259 (Session.has_capability Capability.core session)
260
261 let test_with_accounts () =
262 test_decode_success "with accounts" Session.jsont "session/valid/with_accounts.json" ()
263
264 let test_empty_accounts () =
265 test_decode_success "empty accounts" Session.jsont "session/edge/empty_accounts.json" ()
266
267 let test_accounts_values () =
268 let json = read_file "session/valid/with_accounts.json" in
269 match decode Session.jsont json with
270 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
271 | Ok session ->
272 Alcotest.(check int) "accounts count" 2 (List.length (Session.accounts session));
273 Alcotest.(check int) "primary_accounts count" 2 (List.length (Session.primary_accounts session))
274
275 let tests = [
276 "valid: minimal", `Quick, test_minimal;
277 "valid: with mail", `Quick, test_with_mail;
278 "valid: with accounts", `Quick, test_with_accounts;
279 "edge: empty accounts", `Quick, test_empty_accounts;
280 "roundtrip: minimal", `Quick, test_roundtrip_minimal;
281 "values: minimal", `Quick, test_values;
282 "values: accounts", `Quick, test_accounts_values;
283 ]
284end
285
286(* Request tests *)
287module Request_tests = struct
288 open Jmap_proto
289
290 let test_single_method () =
291 test_decode_success "single method" Request.jsont "request/valid/single_method.json" ()
292
293 let test_multiple_methods () =
294 test_decode_success "multiple methods" Request.jsont "request/valid/multiple_methods.json" ()
295
296 let test_with_created_ids () =
297 test_decode_success "with created ids" Request.jsont "request/valid/with_created_ids.json" ()
298
299 let test_empty_methods () =
300 test_decode_success "empty methods" Request.jsont "request/valid/empty_methods.json" ()
301
302 let test_values () =
303 let json = read_file "request/valid/single_method.json" in
304 match decode Request.jsont json with
305 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
306 | Ok request ->
307 Alcotest.(check int) "using count" 2 (List.length (Request.using request));
308 Alcotest.(check int) "method calls count" 1 (List.length (Request.method_calls request))
309
310 let test_roundtrip () =
311 test_roundtrip "single method roundtrip" Request.jsont "request/valid/single_method.json" ()
312
313 let tests = [
314 "valid: single method", `Quick, test_single_method;
315 "valid: multiple methods", `Quick, test_multiple_methods;
316 "valid: with created ids", `Quick, test_with_created_ids;
317 "valid: empty methods", `Quick, test_empty_methods;
318 "values: single method", `Quick, test_values;
319 "roundtrip: single method", `Quick, test_roundtrip;
320 ]
321end
322
323(* Response tests *)
324module Response_tests = struct
325 open Jmap_proto
326
327 let test_success () =
328 test_decode_success "success" Response.jsont "response/valid/success.json" ()
329
330 let test_with_created_ids () =
331 test_decode_success "with created ids" Response.jsont "response/valid/with_created_ids.json" ()
332
333 let test_with_error () =
334 test_decode_success "with error" Response.jsont "response/valid/with_error.json" ()
335
336 let test_multiple_responses () =
337 test_decode_success "multiple responses" Response.jsont "response/valid/multiple_responses.json" ()
338
339 let test_values () =
340 let json = read_file "response/valid/success.json" in
341 match decode Response.jsont json with
342 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
343 | Ok response ->
344 Alcotest.(check string) "session state" "session123" (Response.session_state response);
345 Alcotest.(check int) "method responses count" 1 (List.length (Response.method_responses response))
346
347 let test_roundtrip () =
348 test_roundtrip "success roundtrip" Response.jsont "response/valid/success.json" ()
349
350 let tests = [
351 "valid: success", `Quick, test_success;
352 "valid: with created ids", `Quick, test_with_created_ids;
353 "valid: with error", `Quick, test_with_error;
354 "valid: multiple responses", `Quick, test_multiple_responses;
355 "values: success", `Quick, test_values;
356 "roundtrip: success", `Quick, test_roundtrip;
357 ]
358end
359
360(* Invocation tests *)
361module Invocation_tests = struct
362 open Jmap_proto
363
364 let test_get () =
365 test_decode_success "get" Invocation.jsont "invocation/valid/get.json" ()
366
367 let test_set () =
368 test_decode_success "set" Invocation.jsont "invocation/valid/set.json" ()
369
370 let test_query () =
371 test_decode_success "query" Invocation.jsont "invocation/valid/query.json" ()
372
373 let test_values () =
374 let json = read_file "invocation/valid/get.json" in
375 match decode Invocation.jsont json with
376 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
377 | Ok inv ->
378 Alcotest.(check string) "name" "Email/get" (Invocation.name inv);
379 Alcotest.(check string) "method call id" "call-001" (Invocation.method_call_id inv)
380
381 let test_invalid_not_array () =
382 test_decode_failure "not array" Invocation.jsont "invocation/invalid/not_array.json" ()
383
384 let test_invalid_wrong_length () =
385 test_decode_failure "wrong length" Invocation.jsont "invocation/invalid/wrong_length.json" ()
386
387 let tests = [
388 "valid: get", `Quick, test_get;
389 "valid: set", `Quick, test_set;
390 "valid: query", `Quick, test_query;
391 "values: get", `Quick, test_values;
392 "invalid: not array", `Quick, test_invalid_not_array;
393 "invalid: wrong length", `Quick, test_invalid_wrong_length;
394 ]
395end
396
397(* Capability tests *)
398module Capability_tests = struct
399 open Jmap_proto
400
401 let test_core () =
402 test_decode_success "core" Capability.Core.jsont "capability/valid/core.json" ()
403
404 let test_mail () =
405 test_decode_success "mail" Capability.Mail.jsont "capability/valid/mail.json" ()
406
407 let test_submission () =
408 test_decode_success "submission" Capability.Submission.jsont "capability/valid/submission.json" ()
409
410 let test_core_values () =
411 let json = read_file "capability/valid/core.json" in
412 match decode Capability.Core.jsont json with
413 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
414 | Ok cap ->
415 Alcotest.(check int64) "maxSizeUpload" 50000000L (Capability.Core.max_size_upload cap);
416 Alcotest.(check int) "maxConcurrentUpload" 4 (Capability.Core.max_concurrent_upload cap);
417 Alcotest.(check int) "maxCallsInRequest" 16 (Capability.Core.max_calls_in_request cap)
418
419 let test_mail_values () =
420 let json = read_file "capability/valid/mail.json" in
421 match decode Capability.Mail.jsont json with
422 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
423 | Ok cap ->
424 Alcotest.(check int64) "maxSizeMailboxName" 490L (Capability.Mail.max_size_mailbox_name cap);
425 Alcotest.(check bool) "mayCreateTopLevelMailbox" true (Capability.Mail.may_create_top_level_mailbox cap)
426
427 let tests = [
428 "valid: core", `Quick, test_core;
429 "valid: mail", `Quick, test_mail;
430 "valid: submission", `Quick, test_submission;
431 "values: core", `Quick, test_core_values;
432 "values: mail", `Quick, test_mail_values;
433 ]
434end
435
436(* Method args/response tests *)
437module Method_tests = struct
438 open Jmap_proto
439
440 let test_get_args () =
441 test_decode_success "get_args" Method.get_args_jsont "method/valid/get_args.json" ()
442
443 let test_get_args_minimal () =
444 test_decode_success "get_args_minimal" Method.get_args_jsont "method/valid/get_args_minimal.json" ()
445
446 let test_query_response () =
447 test_decode_success "query_response" Method.query_response_jsont "method/valid/query_response.json" ()
448
449 let test_changes_response () =
450 test_decode_success "changes_response" Method.changes_response_jsont "method/valid/changes_response.json" ()
451
452 let test_get_args_values () =
453 let json = read_file "method/valid/get_args.json" in
454 match decode Method.get_args_jsont json with
455 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
456 | Ok args ->
457 Alcotest.(check string) "accountId" "acc1" (Id.to_string args.account_id);
458 Alcotest.(check (option (list string))) "properties" (Some ["id"; "name"; "role"]) args.properties
459
460 let test_query_response_values () =
461 let json = read_file "method/valid/query_response.json" in
462 match decode Method.query_response_jsont json with
463 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
464 | Ok resp ->
465 Alcotest.(check int) "ids count" 5 (List.length resp.ids);
466 Alcotest.(check int64) "position" 0L resp.position;
467 Alcotest.(check bool) "canCalculateChanges" true resp.can_calculate_changes;
468 Alcotest.(check (option int64)) "total" (Some 250L) resp.total
469
470 let test_changes_response_values () =
471 let json = read_file "method/valid/changes_response.json" in
472 match decode Method.changes_response_jsont json with
473 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
474 | Ok resp ->
475 Alcotest.(check string) "oldState" "old123" resp.old_state;
476 Alcotest.(check string) "newState" "new456" resp.new_state;
477 Alcotest.(check bool) "hasMoreChanges" false resp.has_more_changes;
478 Alcotest.(check int) "created count" 2 (List.length resp.created);
479 Alcotest.(check int) "destroyed count" 2 (List.length resp.destroyed)
480
481 let tests = [
482 "valid: get_args", `Quick, test_get_args;
483 "valid: get_args_minimal", `Quick, test_get_args_minimal;
484 "valid: query_response", `Quick, test_query_response;
485 "valid: changes_response", `Quick, test_changes_response;
486 "values: get_args", `Quick, test_get_args_values;
487 "values: query_response", `Quick, test_query_response_values;
488 "values: changes_response", `Quick, test_changes_response_values;
489 ]
490end
491
492(* Error tests *)
493module Error_tests = struct
494 open Jmap_proto
495
496 let test_method_error () =
497 test_decode_success "method_error" Error.method_error_jsont "error/valid/method_error.json" ()
498
499 let test_set_error () =
500 test_decode_success "set_error" Error.set_error_jsont "error/valid/set_error.json" ()
501
502 let test_request_error () =
503 test_decode_success "request_error" Error.Request_error.jsont "error/valid/request_error.json" ()
504
505 let method_error_type_testable =
506 Alcotest.testable
507 (fun fmt t -> Format.pp_print_string fmt (Error.method_error_type_to_string t))
508 (=)
509
510 let test_method_error_values () =
511 let json = read_file "error/valid/method_error.json" in
512 match decode Error.method_error_jsont json with
513 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
514 | Ok err ->
515 Alcotest.(check method_error_type_testable) "type" Error.Unknown_method err.type_
516
517 (* Additional error type tests *)
518 let test_set_error_forbidden () =
519 test_decode_success "set_error_forbidden" Error.set_error_jsont "error/valid/set_error_forbidden.json" ()
520
521 let test_set_error_not_found () =
522 test_decode_success "set_error_not_found" Error.set_error_jsont "error/valid/set_error_not_found.json" ()
523
524 let test_set_error_invalid_properties () =
525 test_decode_success "set_error_invalid_properties" Error.set_error_jsont "error/valid/set_error_invalid_properties.json" ()
526
527 let test_set_error_singleton () =
528 test_decode_success "set_error_singleton" Error.set_error_jsont "error/valid/set_error_singleton.json" ()
529
530 let test_set_error_over_quota () =
531 test_decode_success "set_error_over_quota" Error.set_error_jsont "error/valid/set_error_over_quota.json" ()
532
533 let test_method_error_invalid_arguments () =
534 test_decode_success "method_error_invalid_arguments" Error.method_error_jsont "error/valid/method_error_invalid_arguments.json" ()
535
536 let test_method_error_server_fail () =
537 test_decode_success "method_error_server_fail" Error.method_error_jsont "error/valid/method_error_server_fail.json" ()
538
539 let test_method_error_account_not_found () =
540 test_decode_success "method_error_account_not_found" Error.method_error_jsont "error/valid/method_error_account_not_found.json" ()
541
542 let test_method_error_forbidden () =
543 test_decode_success "method_error_forbidden" Error.method_error_jsont "error/valid/method_error_forbidden.json" ()
544
545 let test_method_error_account_read_only () =
546 test_decode_success "method_error_account_read_only" Error.method_error_jsont "error/valid/method_error_account_read_only.json" ()
547
548 let test_request_error_not_json () =
549 test_decode_success "request_error_not_json" Error.Request_error.jsont "error/valid/request_error_not_json.json" ()
550
551 let test_request_error_limit () =
552 test_decode_success "request_error_limit" Error.Request_error.jsont "error/valid/request_error_limit.json" ()
553
554 let set_error_type_testable =
555 Alcotest.testable
556 (fun fmt t -> Format.pp_print_string fmt (Error.set_error_type_to_string t))
557 (=)
558
559 let test_set_error_types () =
560 let json = read_file "error/valid/set_error_invalid_properties.json" in
561 match decode Error.set_error_jsont json with
562 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
563 | Ok err ->
564 Alcotest.(check set_error_type_testable) "type" Error.Invalid_properties err.Error.type_;
565 match err.Error.properties with
566 | None -> Alcotest.fail "expected properties"
567 | Some props -> Alcotest.(check int) "properties count" 2 (List.length props)
568
569 let tests = [
570 "valid: method_error", `Quick, test_method_error;
571 "valid: set_error", `Quick, test_set_error;
572 "valid: request_error", `Quick, test_request_error;
573 "valid: set_error forbidden", `Quick, test_set_error_forbidden;
574 "valid: set_error notFound", `Quick, test_set_error_not_found;
575 "valid: set_error invalidProperties", `Quick, test_set_error_invalid_properties;
576 "valid: set_error singleton", `Quick, test_set_error_singleton;
577 "valid: set_error overQuota", `Quick, test_set_error_over_quota;
578 "valid: method_error invalidArguments", `Quick, test_method_error_invalid_arguments;
579 "valid: method_error serverFail", `Quick, test_method_error_server_fail;
580 "valid: method_error accountNotFound", `Quick, test_method_error_account_not_found;
581 "valid: method_error forbidden", `Quick, test_method_error_forbidden;
582 "valid: method_error accountReadOnly", `Quick, test_method_error_account_read_only;
583 "valid: request_error notJSON", `Quick, test_request_error_not_json;
584 "valid: request_error limit", `Quick, test_request_error_limit;
585 "values: method_error", `Quick, test_method_error_values;
586 "values: set_error types", `Quick, test_set_error_types;
587 ]
588end
589
590(* Mailbox tests *)
591module Mailbox_tests = struct
592 open Jmap_mail
593
594 let role_testable =
595 Alcotest.testable
596 (fun fmt t -> Format.pp_print_string fmt (Mailbox.role_to_string t))
597 (=)
598
599 let test_simple () =
600 test_decode_success "simple" Mailbox.jsont "mail/mailbox/valid/simple.json" ()
601
602 let test_nested () =
603 test_decode_success "nested" Mailbox.jsont "mail/mailbox/valid/nested.json" ()
604
605 let test_values () =
606 let json = read_file "mail/mailbox/valid/simple.json" in
607 match decode Mailbox.jsont json with
608 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
609 | Ok mb ->
610 Alcotest.(check string) "id" "mb1" (Jmap_proto.Id.to_string (Mailbox.id mb));
611 Alcotest.(check string) "name" "Inbox" (Mailbox.name mb);
612 Alcotest.(check (option role_testable)) "role" (Some Mailbox.Inbox) (Mailbox.role mb);
613 Alcotest.(check int64) "totalEmails" 150L (Mailbox.total_emails mb);
614 Alcotest.(check int64) "unreadEmails" 5L (Mailbox.unread_emails mb)
615
616 let test_roundtrip () =
617 test_roundtrip "simple roundtrip" Mailbox.jsont "mail/mailbox/valid/simple.json" ()
618
619 let test_with_all_roles () =
620 test_decode_success "with all roles" Mailbox.jsont "mail/mailbox/valid/with_all_roles.json" ()
621
622 let test_all_rights_false () =
623 test_decode_success "all rights false" Mailbox.jsont "mail/mailbox/edge/all_rights_false.json" ()
624
625 let test_roles_values () =
626 let json = read_file "mail/mailbox/valid/with_all_roles.json" in
627 match decode Mailbox.jsont json with
628 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
629 | Ok mb ->
630 Alcotest.(check (option role_testable)) "role" (Some Mailbox.Archive) (Mailbox.role mb);
631 Alcotest.(check int64) "totalEmails" 1000L (Mailbox.total_emails mb)
632
633 let tests = [
634 "valid: simple", `Quick, test_simple;
635 "valid: nested", `Quick, test_nested;
636 "valid: with all roles", `Quick, test_with_all_roles;
637 "edge: all rights false", `Quick, test_all_rights_false;
638 "values: simple", `Quick, test_values;
639 "values: roles", `Quick, test_roles_values;
640 "roundtrip: simple", `Quick, test_roundtrip;
641 ]
642end
643
644(* Email tests *)
645module Email_tests = struct
646 open Jmap_mail
647
648 let test_minimal () =
649 test_decode_success "minimal" Email.jsont "mail/email/valid/minimal.json" ()
650
651 let test_full () =
652 test_decode_success "full" Email.jsont "mail/email/valid/full.json" ()
653
654 let test_with_headers () =
655 test_decode_success "with_headers" Email.jsont "mail/email/valid/with_headers.json" ()
656
657 let test_minimal_values () =
658 let json = read_file "mail/email/valid/minimal.json" in
659 match decode Email.jsont json with
660 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
661 | Ok email ->
662 Alcotest.(check string) "id" "e1" (Jmap_proto.Id.to_string (Email.id email));
663 Alcotest.(check string) "blobId" "blob1" (Jmap_proto.Id.to_string (Email.blob_id email));
664 Alcotest.(check int64) "size" 1024L (Email.size email)
665
666 let test_full_values () =
667 let json = read_file "mail/email/valid/full.json" in
668 match decode Email.jsont json with
669 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
670 | Ok email ->
671 Alcotest.(check (option string)) "subject" (Some "Re: Important meeting") (Email.subject email);
672 Alcotest.(check bool) "hasAttachment" true (Email.has_attachment email);
673 (* Check from address *)
674 match Email.from email with
675 | None -> Alcotest.fail "expected from address"
676 | Some addrs ->
677 Alcotest.(check int) "from count" 1 (List.length addrs);
678 let addr = List.hd addrs in
679 Alcotest.(check (option string)) "from name" (Some "Alice Smith") (Email_address.name addr);
680 Alcotest.(check string) "from email" "alice@example.com" (Email_address.email addr)
681
682 let test_with_keywords () =
683 test_decode_success "with keywords" Email.jsont "mail/email/valid/with_keywords.json" ()
684
685 let test_multiple_mailboxes () =
686 test_decode_success "multiple mailboxes" Email.jsont "mail/email/valid/multiple_mailboxes.json" ()
687
688 let test_draft_email () =
689 test_decode_success "draft email" Email.jsont "mail/email/valid/draft_email.json" ()
690
691 let test_with_all_system_keywords () =
692 test_decode_success "all system keywords" Email.jsont "mail/email/valid/with_all_system_keywords.json" ()
693
694 let test_empty_keywords () =
695 test_decode_success "empty keywords" Email.jsont "mail/email/edge/empty_keywords.json" ()
696
697 let test_with_message_ids () =
698 test_decode_success "with message ids" Email.jsont "mail/email/valid/with_message_ids.json" ()
699
700 let test_keywords_values () =
701 let json = read_file "mail/email/valid/with_keywords.json" in
702 match decode Email.jsont json with
703 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
704 | Ok email ->
705 let keywords = Email.keywords email in
706 Alcotest.(check int) "keywords count" 3 (List.length keywords);
707 Alcotest.(check bool) "$seen present" true (List.mem_assoc "$seen" keywords);
708 Alcotest.(check bool) "$flagged present" true (List.mem_assoc "$flagged" keywords)
709
710 let test_mailbox_ids_values () =
711 let json = read_file "mail/email/valid/multiple_mailboxes.json" in
712 match decode Email.jsont json with
713 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
714 | Ok email ->
715 let mailbox_ids = Email.mailbox_ids email in
716 Alcotest.(check int) "mailboxIds count" 3 (List.length mailbox_ids)
717
718 let tests = [
719 "valid: minimal", `Quick, test_minimal;
720 "valid: full", `Quick, test_full;
721 "valid: with_headers", `Quick, test_with_headers;
722 "valid: with keywords", `Quick, test_with_keywords;
723 "valid: multiple mailboxes", `Quick, test_multiple_mailboxes;
724 "valid: draft email", `Quick, test_draft_email;
725 "valid: all system keywords", `Quick, test_with_all_system_keywords;
726 "valid: with message ids", `Quick, test_with_message_ids;
727 "edge: empty keywords", `Quick, test_empty_keywords;
728 "values: minimal", `Quick, test_minimal_values;
729 "values: full", `Quick, test_full_values;
730 "values: keywords", `Quick, test_keywords_values;
731 "values: mailboxIds", `Quick, test_mailbox_ids_values;
732 ]
733end
734
735(* Thread tests *)
736module Thread_tests = struct
737 open Jmap_mail
738
739 let test_simple () =
740 test_decode_success "simple" Thread.jsont "mail/thread/valid/simple.json" ()
741
742 let test_conversation () =
743 test_decode_success "conversation" Thread.jsont "mail/thread/valid/conversation.json" ()
744
745 let test_values () =
746 let json = read_file "mail/thread/valid/conversation.json" in
747 match decode Thread.jsont json with
748 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
749 | Ok thread ->
750 Alcotest.(check string) "id" "t2" (Jmap_proto.Id.to_string (Thread.id thread));
751 Alcotest.(check int) "emailIds count" 5 (List.length (Thread.email_ids thread))
752
753 let tests = [
754 "valid: simple", `Quick, test_simple;
755 "valid: conversation", `Quick, test_conversation;
756 "values: conversation", `Quick, test_values;
757 ]
758end
759
760(* Identity tests *)
761module Identity_tests = struct
762 open Jmap_mail
763
764 let test_simple () =
765 test_decode_success "simple" Identity.jsont "mail/identity/valid/simple.json" ()
766
767 let test_values () =
768 let json = read_file "mail/identity/valid/simple.json" in
769 match decode Identity.jsont json with
770 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
771 | Ok ident ->
772 Alcotest.(check string) "name" "Work Identity" (Identity.name ident);
773 Alcotest.(check string) "email" "john.doe@company.com" (Identity.email ident);
774 Alcotest.(check bool) "mayDelete" true (Identity.may_delete ident)
775
776 let tests = [
777 "valid: simple", `Quick, test_simple;
778 "values: simple", `Quick, test_values;
779 ]
780end
781
782(* Email address tests *)
783module Email_address_tests = struct
784 open Jmap_mail
785
786 let test_full () =
787 test_decode_success "full" Email_address.jsont "mail/email_address/valid/full.json" ()
788
789 let test_email_only () =
790 test_decode_success "email_only" Email_address.jsont "mail/email_address/valid/email_only.json" ()
791
792 let test_full_values () =
793 let json = read_file "mail/email_address/valid/full.json" in
794 match decode Email_address.jsont json with
795 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
796 | Ok addr ->
797 Alcotest.(check (option string)) "name" (Some "John Doe") (Email_address.name addr);
798 Alcotest.(check string) "email" "john.doe@example.com" (Email_address.email addr)
799
800 let test_email_only_values () =
801 let json = read_file "mail/email_address/valid/email_only.json" in
802 match decode Email_address.jsont json with
803 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
804 | Ok addr ->
805 Alcotest.(check (option string)) "name" None (Email_address.name addr);
806 Alcotest.(check string) "email" "anonymous@example.com" (Email_address.email addr)
807
808 let tests = [
809 "valid: full", `Quick, test_full;
810 "valid: email_only", `Quick, test_email_only;
811 "values: full", `Quick, test_full_values;
812 "values: email_only", `Quick, test_email_only_values;
813 ]
814end
815
816(* Vacation tests *)
817module Vacation_tests = struct
818 open Jmap_mail
819
820 let test_enabled () =
821 test_decode_success "enabled" Vacation.jsont "mail/vacation/valid/enabled.json" ()
822
823 let test_disabled () =
824 test_decode_success "disabled" Vacation.jsont "mail/vacation/valid/disabled.json" ()
825
826 let test_enabled_values () =
827 let json = read_file "mail/vacation/valid/enabled.json" in
828 match decode Vacation.jsont json with
829 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
830 | Ok vac ->
831 Alcotest.(check bool) "isEnabled" true (Vacation.is_enabled vac);
832 Alcotest.(check (option string)) "subject" (Some "Out of Office") (Vacation.subject vac)
833
834 let test_disabled_values () =
835 let json = read_file "mail/vacation/valid/disabled.json" in
836 match decode Vacation.jsont json with
837 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
838 | Ok vac ->
839 Alcotest.(check bool) "isEnabled" false (Vacation.is_enabled vac);
840 Alcotest.(check (option string)) "subject" None (Vacation.subject vac)
841
842 let tests = [
843 "valid: enabled", `Quick, test_enabled;
844 "valid: disabled", `Quick, test_disabled;
845 "values: enabled", `Quick, test_enabled_values;
846 "values: disabled", `Quick, test_disabled_values;
847 ]
848end
849
850(* Comparator tests *)
851module Comparator_tests = struct
852 open Jmap_proto
853
854 let test_minimal () =
855 test_decode_success "minimal" Filter.comparator_jsont "filter/valid/comparator_minimal.json" ()
856
857 let test_descending () =
858 test_decode_success "descending" Filter.comparator_jsont "filter/valid/comparator_descending.json" ()
859
860 let test_with_collation () =
861 test_decode_success "with collation" Filter.comparator_jsont "filter/valid/comparator_with_collation.json" ()
862
863 let test_minimal_values () =
864 let json = read_file "filter/valid/comparator_minimal.json" in
865 match decode Filter.comparator_jsont json with
866 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
867 | Ok comp ->
868 Alcotest.(check string) "property" "size" (Filter.comparator_property comp);
869 Alcotest.(check bool) "isAscending" true (Filter.comparator_is_ascending comp);
870 Alcotest.(check (option string)) "collation" None (Filter.comparator_collation comp)
871
872 let test_collation_values () =
873 let json = read_file "filter/valid/comparator_with_collation.json" in
874 match decode Filter.comparator_jsont json with
875 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
876 | Ok comp ->
877 Alcotest.(check string) "property" "subject" (Filter.comparator_property comp);
878 Alcotest.(check (option string)) "collation" (Some "i;unicode-casemap") (Filter.comparator_collation comp)
879
880 let tests = [
881 "valid: minimal", `Quick, test_minimal;
882 "valid: descending", `Quick, test_descending;
883 "valid: with collation", `Quick, test_with_collation;
884 "values: minimal", `Quick, test_minimal_values;
885 "values: with collation", `Quick, test_collation_values;
886 ]
887end
888
889(* EmailBody tests *)
890module EmailBody_tests = struct
891 open Jmap_mail
892
893 let test_text_part () =
894 test_decode_success "text part" Email_body.Part.jsont "mail/email_body/valid/text_part.json" ()
895
896 let test_multipart () =
897 test_decode_success "multipart" Email_body.Part.jsont "mail/email_body/valid/multipart.json" ()
898
899 let test_multipart_mixed () =
900 test_decode_success "multipart mixed" Email_body.Part.jsont "mail/email_body/valid/multipart_mixed.json" ()
901
902 let test_with_inline_image () =
903 test_decode_success "with inline image" Email_body.Part.jsont "mail/email_body/valid/with_inline_image.json" ()
904
905 let test_with_language () =
906 test_decode_success "with language" Email_body.Part.jsont "mail/email_body/valid/with_language.json" ()
907
908 let test_deep_nesting () =
909 test_decode_success "deep nesting" Email_body.Part.jsont "mail/email_body/edge/deep_nesting.json" ()
910
911 let test_multipart_values () =
912 let json = read_file "mail/email_body/valid/multipart.json" in
913 match decode Email_body.Part.jsont json with
914 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
915 | Ok part ->
916 Alcotest.(check (option string)) "partId" (Some "0") (Email_body.Part.part_id part);
917 Alcotest.(check string) "type" "multipart/alternative" (Email_body.Part.type_ part);
918 match Email_body.Part.sub_parts part with
919 | None -> Alcotest.fail "expected sub_parts"
920 | Some subs -> Alcotest.(check int) "sub_parts count" 2 (List.length subs)
921
922 let tests = [
923 "valid: text part", `Quick, test_text_part;
924 "valid: multipart", `Quick, test_multipart;
925 "valid: multipart mixed", `Quick, test_multipart_mixed;
926 "valid: with inline image", `Quick, test_with_inline_image;
927 "valid: with language", `Quick, test_with_language;
928 "edge: deep nesting", `Quick, test_deep_nesting;
929 "values: multipart", `Quick, test_multipart_values;
930 ]
931end
932
933(* EmailSubmission tests *)
934module EmailSubmission_tests = struct
935 open Jmap_mail
936
937 let test_simple () =
938 test_decode_success "simple" Submission.jsont "mail/submission/valid/simple.json" ()
939
940 let test_with_envelope () =
941 test_decode_success "with envelope" Submission.jsont "mail/submission/valid/with_envelope.json" ()
942
943 let test_final_status () =
944 test_decode_success "final status" Submission.jsont "mail/submission/valid/final_status.json" ()
945
946 let test_simple_values () =
947 let json = read_file "mail/submission/valid/simple.json" in
948 match decode Submission.jsont json with
949 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e)
950 | Ok sub ->
951 Alcotest.(check string) "id" "sub1" (Jmap_proto.Id.to_string (Submission.id sub));
952 (* Check undoStatus is Pending *)
953 match Submission.undo_status sub with
954 | Submission.Pending -> ()
955 | _ -> Alcotest.fail "expected undoStatus to be pending"
956
957 let tests = [
958 "valid: simple", `Quick, test_simple;
959 "valid: with envelope", `Quick, test_with_envelope;
960 "valid: final status", `Quick, test_final_status;
961 "values: simple", `Quick, test_simple_values;
962 ]
963end
964
965(* Run all tests *)
966let () =
967 Alcotest.run "JMAP Proto Codecs" [
968 "Id", Id_tests.tests;
969 "Int53", Int53_tests.tests;
970 "Date", Date_tests.tests;
971 "Session", Session_tests.tests;
972 "Request", Request_tests.tests;
973 "Response", Response_tests.tests;
974 "Invocation", Invocation_tests.tests;
975 "Capability", Capability_tests.tests;
976 "Method", Method_tests.tests;
977 "Error", Error_tests.tests;
978 "Comparator", Comparator_tests.tests;
979 "Mailbox", Mailbox_tests.tests;
980 "Email", Email_tests.tests;
981 "EmailBody", EmailBody_tests.tests;
982 "Thread", Thread_tests.tests;
983 "Identity", Identity_tests.tests;
984 "Email_address", Email_address_tests.tests;
985 "EmailSubmission", EmailSubmission_tests.tests;
986 "Vacation", Vacation_tests.tests;
987 ]