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