this repo has no description
at main 40 kB view raw
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 ]