My agentic slop goes here. Not intended for anyone else!
at jsont 41 kB view raw
1(** JMAP Test Suite using Alcotest 2 3 This test suite validates JMAP parsing using the comprehensive 4 JSON test files in data/. 5 6 To run: dune test 7*) 8 9open Alcotest 10 11(** Helper to load JSON file *) 12let load_json path = 13 (* When running from _build/default/jmap/test, we need to go up to workspace root *) 14 let try_paths = [ 15 path; (* Try direct path *) 16 "data/" ^ (Filename.basename path); (* Try data/ subdirectory *) 17 "../../../../jmap/test/" ^ path; (* From _build/default/jmap/test to jmap/test *) 18 ] in 19 let rec find_file = function 20 | [] -> path (* Return original path, will fail with proper error *) 21 | p :: rest -> if Sys.file_exists p then p else find_file rest 22 in 23 let full_path = find_file try_paths in 24 let ic = open_in full_path in 25 Fun.protect 26 ~finally:(fun () -> close_in ic) 27 (fun () -> Ezjsonm.from_channel ic) 28 29(** Test Core Protocol *) 30 31let test_echo_request () = 32 let _json = load_json "data/core/request_echo.json" in 33 (* TODO: Parse and validate *) 34 check bool "Echo request loaded" true true 35 36let test_echo_response () = 37 let _json = load_json "data/core/response_echo.json" in 38 (* TODO: Parse and validate *) 39 check bool "Echo response loaded" true true 40 41let test_get_request () = 42 let _json = load_json "data/core/request_get.json" in 43 (* TODO: Parse and validate *) 44 check bool "Get request loaded" true true 45 46let test_get_response () = 47 let _json = load_json "data/core/response_get.json" in 48 (* TODO: Parse and validate *) 49 check bool "Get response loaded" true true 50 51let test_session () = 52 let _json = load_json "data/core/session.json" in 53 (* TODO: Parse Session object *) 54 check bool "Session loaded" true true 55 56(** Test Mail Protocol - Mailbox *) 57 58let test_mailbox_get_request () = 59 let json = load_json "data/mail/mailbox_get_request.json" in 60 let req = Jmap_mail.Mailbox.Get.request_of_json json in 61 62 (* Verify account_id *) 63 let account_id = Jmap_core.Standard_methods.Get.account_id req in 64 check string "Account ID" "u123456" (Jmap_core.Id.to_string account_id); 65 66 (* Verify ids is null (None) *) 67 let ids = Jmap_core.Standard_methods.Get.ids req in 68 check bool "IDs should be None" true (ids = None); 69 70 (* Verify properties list *) 71 let props = Jmap_core.Standard_methods.Get.properties req in 72 match props with 73 | Some p -> 74 check int "Properties count" 11 (List.length p); 75 check bool "Has id property" true (List.mem "id" p); 76 check bool "Has name property" true (List.mem "name" p); 77 check bool "Has role property" true (List.mem "role" p); 78 check bool "Has myRights property" true (List.mem "myRights" p) 79 | None -> 80 fail "Properties should not be None" 81 82let test_mailbox_get_response () = 83 let json = load_json "data/mail/mailbox_get_response.json" in 84 let resp = Jmap_mail.Mailbox.Get.response_of_json json in 85 86 (* Verify account_id *) 87 let account_id = Jmap_core.Standard_methods.Get.response_account_id resp in 88 check string "Account ID" "u123456" (Jmap_core.Id.to_string account_id); 89 90 (* Verify state *) 91 let state = Jmap_core.Standard_methods.Get.state resp in 92 check string "State" "m42:100" state; 93 94 (* Verify mailbox list *) 95 let mailboxes = Jmap_core.Standard_methods.Get.list resp in 96 check int "Mailbox count" 5 (List.length mailboxes); 97 98 (* Verify not_found is empty *) 99 let not_found = Jmap_core.Standard_methods.Get.not_found resp in 100 check int "Not found count" 0 (List.length not_found); 101 102 (* Test first mailbox (INBOX) *) 103 let inbox = List.hd mailboxes in 104 check string "INBOX id" "mb001" (Jmap_core.Id.to_string (Jmap_mail.Mailbox.id inbox)); 105 check string "INBOX name" "INBOX" (Jmap_mail.Mailbox.name inbox); 106 check bool "INBOX parentId is None" true (Jmap_mail.Mailbox.parent_id inbox = None); 107 check string "INBOX role" "inbox" (match Jmap_mail.Mailbox.role inbox with Some r -> r | None -> ""); 108 check int "INBOX sortOrder" 10 (Jmap_core.Primitives.UnsignedInt.to_int (Jmap_mail.Mailbox.sort_order inbox)); 109 check int "INBOX totalEmails" 1523 (Jmap_core.Primitives.UnsignedInt.to_int (Jmap_mail.Mailbox.total_emails inbox)); 110 check int "INBOX unreadEmails" 42 (Jmap_core.Primitives.UnsignedInt.to_int (Jmap_mail.Mailbox.unread_emails inbox)); 111 check int "INBOX totalThreads" 987 (Jmap_core.Primitives.UnsignedInt.to_int (Jmap_mail.Mailbox.total_threads inbox)); 112 check int "INBOX unreadThreads" 35 (Jmap_core.Primitives.UnsignedInt.to_int (Jmap_mail.Mailbox.unread_threads inbox)); 113 check bool "INBOX isSubscribed" true (Jmap_mail.Mailbox.is_subscribed inbox); 114 115 (* Test INBOX rights *) 116 let inbox_rights = Jmap_mail.Mailbox.my_rights inbox in 117 check bool "INBOX mayReadItems" true (Jmap_mail.Mailbox.Rights.may_read_items inbox_rights); 118 check bool "INBOX mayAddItems" true (Jmap_mail.Mailbox.Rights.may_add_items inbox_rights); 119 check bool "INBOX mayRemoveItems" true (Jmap_mail.Mailbox.Rights.may_remove_items inbox_rights); 120 check bool "INBOX maySetSeen" true (Jmap_mail.Mailbox.Rights.may_set_seen inbox_rights); 121 check bool "INBOX maySetKeywords" true (Jmap_mail.Mailbox.Rights.may_set_keywords inbox_rights); 122 check bool "INBOX mayCreateChild" true (Jmap_mail.Mailbox.Rights.may_create_child inbox_rights); 123 check bool "INBOX mayRename" false (Jmap_mail.Mailbox.Rights.may_rename inbox_rights); 124 check bool "INBOX mayDelete" false (Jmap_mail.Mailbox.Rights.may_delete inbox_rights); 125 check bool "INBOX maySubmit" true (Jmap_mail.Mailbox.Rights.may_submit inbox_rights); 126 127 (* Test second mailbox (Sent) *) 128 let sent = List.nth mailboxes 1 in 129 check string "Sent id" "mb002" (Jmap_core.Id.to_string (Jmap_mail.Mailbox.id sent)); 130 check string "Sent name" "Sent" (Jmap_mail.Mailbox.name sent); 131 check string "Sent role" "sent" (match Jmap_mail.Mailbox.role sent with Some r -> r | None -> ""); 132 check int "Sent sortOrder" 20 (Jmap_core.Primitives.UnsignedInt.to_int (Jmap_mail.Mailbox.sort_order sent)); 133 134 (* Test Work mailbox (no role) *) 135 let work = List.nth mailboxes 4 in 136 check string "Work id" "mb005" (Jmap_core.Id.to_string (Jmap_mail.Mailbox.id work)); 137 check string "Work name" "Work" (Jmap_mail.Mailbox.name work); 138 check bool "Work role is None" true (Jmap_mail.Mailbox.role work = None); 139 check int "Work totalEmails" 342 (Jmap_core.Primitives.UnsignedInt.to_int (Jmap_mail.Mailbox.total_emails work)); 140 141 (* Test Work rights (user-created mailbox has full permissions) *) 142 let work_rights = Jmap_mail.Mailbox.my_rights work in 143 check bool "Work mayRename" true (Jmap_mail.Mailbox.Rights.may_rename work_rights); 144 check bool "Work mayDelete" true (Jmap_mail.Mailbox.Rights.may_delete work_rights) 145 146let test_mailbox_query_request () = 147 let json = load_json "data/mail/mailbox_query_request.json" in 148 let req = Jmap_mail.Mailbox.Query.request_of_json json in 149 150 (* Verify account_id *) 151 let account_id = Jmap_mail.Mailbox.Query.account_id req in 152 check string "Account ID" "u123456" (Jmap_core.Id.to_string account_id); 153 154 (* Verify filter is present *) 155 let filter = Jmap_mail.Mailbox.Query.filter req in 156 check bool "Filter should be Some" true (filter <> None); 157 158 (* Verify sort *) 159 let sort = Jmap_mail.Mailbox.Query.sort req in 160 match sort with 161 | Some s -> 162 check int "Sort criteria count" 2 (List.length s); 163 (* First sort by sortOrder ascending *) 164 let sort1 = List.hd s in 165 check string "First sort property" "sortOrder" (Jmap_core.Comparator.property sort1); 166 check bool "First sort ascending" true (Jmap_core.Comparator.is_ascending sort1); 167 (* Second sort by name ascending *) 168 let sort2 = List.nth s 1 in 169 check string "Second sort property" "name" (Jmap_core.Comparator.property sort2); 170 check bool "Second sort ascending" true (Jmap_core.Comparator.is_ascending sort2) 171 | None -> 172 fail "Sort should not be None"; 173 174 (* Verify calculateTotal *) 175 let calculate_total = Jmap_mail.Mailbox.Query.calculate_total req in 176 check bool "Calculate total should be Some true" true (calculate_total = Some true) 177 178let test_mailbox_query_response () = 179 let json = load_json "data/mail/mailbox_query_response.json" in 180 let resp = Jmap_mail.Mailbox.Query.response_of_json json in 181 182 (* Verify account_id *) 183 let account_id = Jmap_core.Standard_methods.Query.response_account_id resp in 184 check string "Account ID" "u123456" (Jmap_core.Id.to_string account_id); 185 186 (* Verify query_state *) 187 let query_state = Jmap_core.Standard_methods.Query.query_state resp in 188 check string "Query state" "mq42:100" query_state; 189 190 (* Verify can_calculate_changes *) 191 let can_calc = Jmap_core.Standard_methods.Query.can_calculate_changes resp in 192 check bool "Can calculate changes" true can_calc; 193 194 (* Verify position *) 195 let position = Jmap_core.Standard_methods.Query.response_position resp in 196 check int "Position" 0 (Jmap_core.Primitives.UnsignedInt.to_int position); 197 198 (* Verify ids *) 199 let ids = Jmap_core.Standard_methods.Query.ids resp in 200 check int "IDs count" 3 (List.length ids); 201 check string "First ID" "mb005" (Jmap_core.Id.to_string (List.hd ids)); 202 check string "Second ID" "mb008" (Jmap_core.Id.to_string (List.nth ids 1)); 203 check string "Third ID" "mb012" (Jmap_core.Id.to_string (List.nth ids 2)); 204 205 (* Verify total *) 206 let total = Jmap_core.Standard_methods.Query.total resp in 207 match total with 208 | Some t -> check int "Total" 3 (Jmap_core.Primitives.UnsignedInt.to_int t) 209 | None -> fail "Total should not be None" 210 211let test_mailbox_set_request () = 212 let json = load_json "data/mail/mailbox_set_request.json" in 213 let req = Jmap_mail.Mailbox.Set.request_of_json json in 214 215 (* Verify account_id *) 216 let account_id = Jmap_core.Standard_methods.Set.account_id req in 217 check string "Account ID" "u123456" (Jmap_core.Id.to_string account_id); 218 219 (* Verify if_in_state *) 220 let if_in_state = Jmap_core.Standard_methods.Set.if_in_state req in 221 check bool "If in state should be Some" true (if_in_state = Some "m42:100"); 222 223 (* Verify create *) 224 let create = Jmap_core.Standard_methods.Set.create req in 225 (match create with 226 | Some c -> 227 check int "Create count" 1 (List.length c); 228 let (temp_id, mailbox) = List.hd c in 229 check string "Temp ID" "temp-mb-1" (Jmap_core.Id.to_string temp_id); 230 check string "Created mailbox name" "Projects" (Jmap_mail.Mailbox.name mailbox); 231 check bool "Created mailbox parentId is None" true (Jmap_mail.Mailbox.parent_id mailbox = None); 232 check bool "Created mailbox role is None" true (Jmap_mail.Mailbox.role mailbox = None); 233 check int "Created mailbox sortOrder" 60 (Jmap_core.Primitives.UnsignedInt.to_int (Jmap_mail.Mailbox.sort_order mailbox)); 234 check bool "Created mailbox isSubscribed" true (Jmap_mail.Mailbox.is_subscribed mailbox) 235 | None -> 236 fail "Create should not be None"); 237 238 (* Verify update *) 239 let update = Jmap_core.Standard_methods.Set.update req in 240 (match update with 241 | Some u -> 242 check int "Update count" 1 (List.length u); 243 let (update_id, _patches) = List.hd u in 244 check string "Update ID" "mb005" (Jmap_core.Id.to_string update_id) 245 | None -> 246 fail "Update should not be None"); 247 248 (* Verify destroy *) 249 let destroy = Jmap_core.Standard_methods.Set.destroy req in 250 (match destroy with 251 | Some d -> 252 check int "Destroy count" 1 (List.length d); 253 check string "Destroy ID" "mb012" (Jmap_core.Id.to_string (List.hd d)) 254 | None -> 255 fail "Destroy should not be None") 256 257let test_mailbox_set_response () = 258 let json = load_json "data/mail/mailbox_set_response.json" in 259 let resp = Jmap_mail.Mailbox.Set.response_of_json json in 260 261 (* Verify account_id *) 262 let account_id = Jmap_core.Standard_methods.Set.response_account_id resp in 263 check string "Account ID" "u123456" (Jmap_core.Id.to_string account_id); 264 265 (* Verify old_state *) 266 let old_state = Jmap_core.Standard_methods.Set.old_state resp in 267 check bool "Old state should be Some" true (old_state = Some "m42:100"); 268 269 (* Verify new_state *) 270 let new_state = Jmap_core.Standard_methods.Set.new_state resp in 271 check string "New state" "m42:103" new_state; 272 273 (* Verify created *) 274 let created = Jmap_core.Standard_methods.Set.created resp in 275 (match created with 276 | Some c -> 277 check int "Created count" 1 (List.length c); 278 let (temp_id, mailbox) = List.hd c in 279 check string "Created temp ID" "temp-mb-1" (Jmap_core.Id.to_string temp_id); 280 check string "Created mailbox ID" "mb020" (Jmap_core.Id.to_string (Jmap_mail.Mailbox.id mailbox)); 281 check int "Created mailbox totalEmails" 0 (Jmap_core.Primitives.UnsignedInt.to_int (Jmap_mail.Mailbox.total_emails mailbox)); 282 check int "Created mailbox unreadEmails" 0 (Jmap_core.Primitives.UnsignedInt.to_int (Jmap_mail.Mailbox.unread_emails mailbox)); 283 (* Verify rights of created mailbox *) 284 let rights = Jmap_mail.Mailbox.my_rights mailbox in 285 check bool "Created mailbox mayRename" true (Jmap_mail.Mailbox.Rights.may_rename rights); 286 check bool "Created mailbox mayDelete" true (Jmap_mail.Mailbox.Rights.may_delete rights) 287 | None -> 288 fail "Created should not be None"); 289 290 (* Verify updated *) 291 let updated = Jmap_core.Standard_methods.Set.updated resp in 292 (match updated with 293 | Some u -> 294 check int "Updated count" 1 (List.length u); 295 let (update_id, update_val) = List.hd u in 296 check string "Updated ID" "mb005" (Jmap_core.Id.to_string update_id); 297 check bool "Updated value is None" true (update_val = None) 298 | None -> 299 fail "Updated should not be None"); 300 301 (* Verify destroyed *) 302 let destroyed = Jmap_core.Standard_methods.Set.destroyed resp in 303 (match destroyed with 304 | Some d -> 305 check int "Destroyed count" 1 (List.length d); 306 check string "Destroyed ID" "mb012" (Jmap_core.Id.to_string (List.hd d)) 307 | None -> 308 fail "Destroyed should not be None"); 309 310 (* Verify not_created, not_updated, not_destroyed are None *) 311 check bool "Not created is None" true (Jmap_core.Standard_methods.Set.not_created resp = None); 312 check bool "Not updated is None" true (Jmap_core.Standard_methods.Set.not_updated resp = None); 313 check bool "Not destroyed is None" true (Jmap_core.Standard_methods.Set.not_destroyed resp = None) 314 315(** Test Mail Protocol - Email *) 316 317let test_email_get_request () = 318 let json = load_json "data/mail/email_get_request.json" in 319 let request = Jmap_mail.Email.Get.request_of_json json in 320 321 (* Validate account_id *) 322 let account_id = Jmap_mail.Email.Get.account_id request in 323 check string "Account ID" "u123456" (Jmap_core.Id.to_string account_id); 324 325 (* Validate ids *) 326 let ids = Jmap_mail.Email.Get.ids request in 327 check bool "IDs present" true (Option.is_some ids); 328 let ids_list = Option.get ids in 329 check int "Two IDs requested" 2 (List.length ids_list); 330 check string "First ID" "e001" (Jmap_core.Id.to_string (List.nth ids_list 0)); 331 check string "Second ID" "e002" (Jmap_core.Id.to_string (List.nth ids_list 1)); 332 333 (* Validate properties *) 334 let properties = Jmap_mail.Email.Get.properties request in 335 check bool "Properties present" true (Option.is_some properties); 336 let props_list = Option.get properties in 337 check bool "Properties include 'subject'" true (List.mem "subject" props_list); 338 check bool "Properties include 'from'" true (List.mem "from" props_list); 339 check bool "Properties include 'to'" true (List.mem "to" props_list) 340 341let test_email_get_full_request () = 342 let json = load_json "data/mail/email_get_full_request.json" in 343 let request = Jmap_mail.Email.Get.request_of_json json in 344 345 (* Validate body fetch options *) 346 let fetch_text = Jmap_mail.Email.Get.fetch_text_body_values request in 347 check bool "Fetch text body values" true (Option.value ~default:false fetch_text); 348 349 let fetch_html = Jmap_mail.Email.Get.fetch_html_body_values request in 350 check bool "Fetch HTML body values" true (Option.value ~default:false fetch_html); 351 352 let fetch_all = Jmap_mail.Email.Get.fetch_all_body_values request in 353 check bool "Fetch all body values is false" false (Option.value ~default:true fetch_all); 354 355 let max_bytes = Jmap_mail.Email.Get.max_body_value_bytes request in 356 check bool "Max body value bytes present" true (Option.is_some max_bytes); 357 check int "Max bytes is 32768" 32768 358 (Jmap_core.Primitives.UnsignedInt.to_int (Option.get max_bytes)) 359 360let test_email_get_response () = 361 let json = load_json "data/mail/email_get_response.json" in 362 let response = Jmap_mail.Email.Get.response_of_json json in 363 364 (* Validate response metadata *) 365 let account_id = Jmap_core.Standard_methods.Get.response_account_id response in 366 check string "Response account ID" "u123456" (Jmap_core.Id.to_string account_id); 367 368 let state = Jmap_core.Standard_methods.Get.state response in 369 check string "Response state" "e42:100" state; 370 371 (* Validate emails list *) 372 let emails = Jmap_core.Standard_methods.Get.list response in 373 check int "Two emails returned" 2 (List.length emails); 374 375 (* Test first email (e001) *) 376 let email1 = List.nth emails 0 in 377 check string "Email 1 ID" "e001" (Jmap_core.Id.to_string (Jmap_mail.Email.id email1)); 378 check string "Email 1 thread ID" "t001" (Jmap_core.Id.to_string (Jmap_mail.Email.thread_id email1)); 379 check string "Email 1 subject" "Project Update Q4 2025" 380 (Option.get (Jmap_mail.Email.subject email1)); 381 check int "Email 1 size" 15234 382 (Jmap_core.Primitives.UnsignedInt.to_int (Jmap_mail.Email.size email1)); 383 check bool "Email 1 has no attachment" false (Jmap_mail.Email.has_attachment email1); 384 385 (* Test email 1 from address *) 386 let from1 = Option.get (Jmap_mail.Email.from email1) in 387 check int "Email 1 has one from address" 1 (List.length from1); 388 let from_addr = List.nth from1 0 in 389 check string "Email 1 from name" "Bob Smith" 390 (Option.get (Jmap_mail.Email.EmailAddress.name from_addr)); 391 check string "Email 1 from email" "bob@example.com" 392 (Jmap_mail.Email.EmailAddress.email from_addr); 393 394 (* Test email 1 to addresses *) 395 let to1 = Option.get (Jmap_mail.Email.to_ email1) in 396 check int "Email 1 has one to address" 1 (List.length to1); 397 let to_addr = List.nth to1 0 in 398 check string "Email 1 to name" "Alice Jones" 399 (Option.get (Jmap_mail.Email.EmailAddress.name to_addr)); 400 check string "Email 1 to email" "alice@example.com" 401 (Jmap_mail.Email.EmailAddress.email to_addr); 402 403 (* Test email 1 keywords *) 404 let keywords1 = Jmap_mail.Email.keywords email1 in 405 check bool "Email 1 has $seen keyword" true 406 (List.mem_assoc "$seen" keywords1); 407 check bool "Email 1 $seen is true" true 408 (List.assoc "$seen" keywords1); 409 410 (* Test second email (e002) *) 411 let email2 = List.nth emails 1 in 412 check string "Email 2 ID" "e002" (Jmap_core.Id.to_string (Jmap_mail.Email.id email2)); 413 check string "Email 2 subject" "Re: Technical Requirements Review" 414 (Option.get (Jmap_mail.Email.subject email2)); 415 416 (* Test email 2 to addresses (multiple recipients) *) 417 let to2 = Option.get (Jmap_mail.Email.to_ email2) in 418 check int "Email 2 has two to addresses" 2 (List.length to2); 419 420 (* Test email 2 keywords *) 421 let keywords2 = Jmap_mail.Email.keywords email2 in 422 check bool "Email 2 has $seen keyword" true 423 (List.mem_assoc "$seen" keywords2); 424 check bool "Email 2 has $flagged keyword" true 425 (List.mem_assoc "$flagged" keywords2); 426 427 (* Test email 2 replyTo *) 428 let reply_to2 = Jmap_mail.Email.reply_to email2 in 429 check bool "Email 2 has replyTo" true (Option.is_some reply_to2); 430 let reply_to_list = Option.get reply_to2 in 431 check int "Email 2 has one replyTo address" 1 (List.length reply_to_list); 432 let reply_addr = List.nth reply_to_list 0 in 433 check string "Email 2 replyTo email" "support@company.com" 434 (Jmap_mail.Email.EmailAddress.email reply_addr); 435 436 (* Validate notFound is empty *) 437 let not_found = Jmap_core.Standard_methods.Get.not_found response in 438 check int "No emails not found" 0 (List.length not_found) 439 440let test_email_get_full_response () = 441 let json = load_json "data/mail/email_get_full_response.json" in 442 let response = Jmap_mail.Email.Get.response_of_json json in 443 444 let emails = Jmap_core.Standard_methods.Get.list response in 445 check int "One email returned" 1 (List.length emails); 446 447 let email = List.nth emails 0 in 448 449 (* Validate basic fields *) 450 check string "Email ID" "e001" (Jmap_core.Id.to_string (Jmap_mail.Email.id email)); 451 check bool "Has attachment" true (Jmap_mail.Email.has_attachment email); 452 453 (* Validate bodyStructure (multipart/mixed with nested multipart/alternative) *) 454 let body_structure = Jmap_mail.Email.body_structure email in 455 check bool "Has bodyStructure" true (Option.is_some body_structure); 456 457 let root_part = Option.get body_structure in 458 check string "Root type is multipart/mixed" "multipart/mixed" 459 (Jmap_mail.Email.BodyPart.type_ root_part); 460 461 let sub_parts = Jmap_mail.Email.BodyPart.sub_parts root_part in 462 check bool "Root has subParts" true (Option.is_some sub_parts); 463 let parts_list = Option.get sub_parts in 464 check int "Root has 2 subParts" 2 (List.length parts_list); 465 466 (* First subpart: multipart/alternative *) 467 let alt_part = List.nth parts_list 0 in 468 check string "First subpart is multipart/alternative" "multipart/alternative" 469 (Jmap_mail.Email.BodyPart.type_ alt_part); 470 471 let alt_sub_parts = Option.get (Jmap_mail.Email.BodyPart.sub_parts alt_part) in 472 check int "Alternative has 2 subParts" 2 (List.length alt_sub_parts); 473 474 (* Text/plain part *) 475 let text_part = List.nth alt_sub_parts 0 in 476 check string "Text part type" "text/plain" (Jmap_mail.Email.BodyPart.type_ text_part); 477 check string "Text part charset" "utf-8" 478 (Option.get (Jmap_mail.Email.BodyPart.charset text_part)); 479 check string "Text part ID" "1" (Option.get (Jmap_mail.Email.BodyPart.part_id text_part)); 480 check int "Text part size" 2134 481 (Jmap_core.Primitives.UnsignedInt.to_int (Jmap_mail.Email.BodyPart.size text_part)); 482 483 (* Text/html part *) 484 let html_part = List.nth alt_sub_parts 1 in 485 check string "HTML part type" "text/html" (Jmap_mail.Email.BodyPart.type_ html_part); 486 check string "HTML part ID" "2" (Option.get (Jmap_mail.Email.BodyPart.part_id html_part)); 487 check int "HTML part size" 4567 488 (Jmap_core.Primitives.UnsignedInt.to_int (Jmap_mail.Email.BodyPart.size html_part)); 489 490 (* Attachment part *) 491 let attach_part = List.nth parts_list 1 in 492 check string "Attachment type" "application/pdf" 493 (Jmap_mail.Email.BodyPart.type_ attach_part); 494 check string "Attachment name" "Q4_Report.pdf" 495 (Option.get (Jmap_mail.Email.BodyPart.name attach_part)); 496 check string "Attachment disposition" "attachment" 497 (Option.get (Jmap_mail.Email.BodyPart.disposition attach_part)); 498 check string "Attachment part ID" "3" 499 (Option.get (Jmap_mail.Email.BodyPart.part_id attach_part)); 500 check int "Attachment size" 8533 501 (Jmap_core.Primitives.UnsignedInt.to_int (Jmap_mail.Email.BodyPart.size attach_part)); 502 503 (* Validate textBody *) 504 let text_body = Jmap_mail.Email.text_body email in 505 check bool "Has textBody" true (Option.is_some text_body); 506 let text_body_list = Option.get text_body in 507 check int "One textBody part" 1 (List.length text_body_list); 508 let text_body_part = List.nth text_body_list 0 in 509 check string "textBody part ID" "1" 510 (Option.get (Jmap_mail.Email.BodyPart.part_id text_body_part)); 511 check string "textBody type" "text/plain" 512 (Jmap_mail.Email.BodyPart.type_ text_body_part); 513 514 (* Validate htmlBody *) 515 let html_body = Jmap_mail.Email.html_body email in 516 check bool "Has htmlBody" true (Option.is_some html_body); 517 let html_body_list = Option.get html_body in 518 check int "One htmlBody part" 1 (List.length html_body_list); 519 let html_body_part = List.nth html_body_list 0 in 520 check string "htmlBody part ID" "2" 521 (Option.get (Jmap_mail.Email.BodyPart.part_id html_body_part)); 522 check string "htmlBody type" "text/html" 523 (Jmap_mail.Email.BodyPart.type_ html_body_part); 524 525 (* Validate attachments *) 526 let attachments = Jmap_mail.Email.attachments email in 527 check bool "Has attachments" true (Option.is_some attachments); 528 let attachments_list = Option.get attachments in 529 check int "One attachment" 1 (List.length attachments_list); 530 let attachment = List.nth attachments_list 0 in 531 check string "Attachment name" "Q4_Report.pdf" 532 (Option.get (Jmap_mail.Email.BodyPart.name attachment)); 533 534 (* Validate bodyValues *) 535 let body_values = Jmap_mail.Email.body_values email in 536 check bool "Has bodyValues" true (Option.is_some body_values); 537 let values_list = Option.get body_values in 538 check int "Two bodyValues" 2 (List.length values_list); 539 540 (* Text body value *) 541 check bool "Has bodyValue for part 1" true (List.mem_assoc "1" values_list); 542 let text_value = List.assoc "1" values_list in 543 let text_content = Jmap_mail.Email.BodyValue.value text_value in 544 check bool "Text content starts with 'Hi Alice'" true 545 (String.starts_with ~prefix:"Hi Alice" text_content); 546 check bool "Text not truncated" false 547 (Jmap_mail.Email.BodyValue.is_truncated text_value); 548 check bool "Text no encoding problem" false 549 (Jmap_mail.Email.BodyValue.is_encoding_problem text_value); 550 551 (* HTML body value *) 552 check bool "Has bodyValue for part 2" true (List.mem_assoc "2" values_list); 553 let html_value = List.assoc "2" values_list in 554 let html_content = Jmap_mail.Email.BodyValue.value html_value in 555 check bool "HTML content starts with '<html>'" true 556 (String.starts_with ~prefix:"<html>" html_content); 557 check bool "HTML not truncated" false 558 (Jmap_mail.Email.BodyValue.is_truncated html_value) 559 560let test_email_query_request () = 561 let json = load_json "data/mail/email_query_request.json" in 562 let request = Jmap_mail.Email.Query.request_of_json json in 563 564 (* Validate account_id *) 565 let account_id = Jmap_mail.Email.Query.account_id request in 566 check string "Account ID" "u123456" (Jmap_core.Id.to_string account_id); 567 568 (* Validate limit *) 569 let limit = Jmap_mail.Email.Query.limit request in 570 check bool "Has limit" true (Option.is_some limit); 571 check int "Limit is 50" 50 572 (Jmap_core.Primitives.UnsignedInt.to_int (Option.get limit)); 573 574 (* Validate calculateTotal *) 575 let calc_total = Jmap_mail.Email.Query.calculate_total request in 576 check bool "Calculate total is true" true (Option.value ~default:false calc_total); 577 578 (* Validate collapseThreads *) 579 let collapse = Jmap_mail.Email.Query.collapse_threads request in 580 check bool "Collapse threads is false" false (Option.value ~default:true collapse); 581 582 (* Validate position *) 583 let position = Jmap_mail.Email.Query.position request in 584 check bool "Has position" true (Option.is_some position); 585 check int "Position is 0" 0 586 (Jmap_core.Primitives.Int53.to_int (Option.get position)) 587 588let test_email_query_response () = 589 let json = load_json "data/mail/email_query_response.json" in 590 let response = Jmap_mail.Email.Query.response_of_json json in 591 592 (* Validate account_id *) 593 let account_id = Jmap_core.Standard_methods.Query.response_account_id response in 594 check string "Account ID" "u123456" (Jmap_core.Id.to_string account_id); 595 596 (* Validate query state *) 597 let query_state = Jmap_core.Standard_methods.Query.query_state response in 598 check string "Query state" "eq42:100" query_state; 599 600 (* Validate can calculate changes *) 601 let can_calc = Jmap_core.Standard_methods.Query.can_calculate_changes response in 602 check bool "Can calculate changes" true can_calc; 603 604 (* Validate position *) 605 let position = Jmap_core.Standard_methods.Query.response_position response in 606 check int "Position is 0" 0 (Jmap_core.Primitives.UnsignedInt.to_int position); 607 608 (* Validate IDs *) 609 let ids = Jmap_core.Standard_methods.Query.ids response in 610 check int "Five IDs returned" 5 (List.length ids); 611 check string "First ID" "e015" (Jmap_core.Id.to_string (List.nth ids 0)); 612 check string "Last ID" "e005" (Jmap_core.Id.to_string (List.nth ids 4)); 613 614 (* Validate total *) 615 let total = Jmap_core.Standard_methods.Query.total response in 616 check bool "Has total" true (Option.is_some total); 617 check int "Total is 5" 5 618 (Jmap_core.Primitives.UnsignedInt.to_int (Option.get total)) 619 620let test_email_set_request () = 621 let json = load_json "data/mail/email_set_request.json" in 622 let request = Jmap_mail.Email.Set.request_of_json json in 623 624 (* Validate account_id *) 625 let account_id = Jmap_core.Standard_methods.Set.account_id request in 626 check string "Account ID" "u123456" (Jmap_core.Id.to_string account_id); 627 628 (* Validate ifInState *) 629 let if_in_state = Jmap_core.Standard_methods.Set.if_in_state request in 630 check bool "Has ifInState" true (Option.is_some if_in_state); 631 check string "ifInState value" "e42:100" (Option.get if_in_state); 632 633 (* Validate create *) 634 let create = Jmap_core.Standard_methods.Set.create request in 635 check bool "Has create" true (Option.is_some create); 636 let create_list = Option.get create in 637 check int "One email to create" 1 (List.length create_list); 638 let (create_id, _email) = List.nth create_list 0 in 639 check string "Create ID" "temp-email-1" (Jmap_core.Id.to_string create_id); 640 641 (* Validate update *) 642 let update = Jmap_core.Standard_methods.Set.update request in 643 check bool "Has update" true (Option.is_some update); 644 let update_list = Option.get update in 645 check int "Two emails to update" 2 (List.length update_list); 646 647 (* Validate destroy *) 648 let destroy = Jmap_core.Standard_methods.Set.destroy request in 649 check bool "Has destroy" true (Option.is_some destroy); 650 let destroy_list = Option.get destroy in 651 check int "One email to destroy" 1 (List.length destroy_list); 652 check string "Destroy ID" "e099" (Jmap_core.Id.to_string (List.nth destroy_list 0)) 653 654let test_email_set_response () = 655 let json = load_json "data/mail/email_set_response.json" in 656 let response = Jmap_mail.Email.Set.response_of_json json in 657 658 (* Validate account_id *) 659 let account_id = Jmap_core.Standard_methods.Set.response_account_id response in 660 check string "Account ID" "u123456" (Jmap_core.Id.to_string account_id); 661 662 (* Validate states *) 663 let old_state = Jmap_core.Standard_methods.Set.old_state response in 664 check bool "Has old state" true (Option.is_some old_state); 665 check string "Old state" "e42:100" (Option.get old_state); 666 667 let new_state = Jmap_core.Standard_methods.Set.new_state response in 668 check string "New state" "e42:103" new_state; 669 670 (* Validate created *) 671 let created = Jmap_core.Standard_methods.Set.created response in 672 check bool "Has created" true (Option.is_some created); 673 let created_list = Option.get created in 674 check int "One email created" 1 (List.length created_list); 675 let (temp_id, email) = List.nth created_list 0 in 676 check string "Created temp ID" "temp-email-1" (Jmap_core.Id.to_string temp_id); 677 check string "Created email ID" "e101" (Jmap_core.Id.to_string (Jmap_mail.Email.id email)); 678 check string "Created thread ID" "t050" 679 (Jmap_core.Id.to_string (Jmap_mail.Email.thread_id email)); 680 681 (* Validate updated *) 682 let updated = Jmap_core.Standard_methods.Set.updated response in 683 check bool "Has updated" true (Option.is_some updated); 684 let updated_map = Option.get updated in 685 check int "Two emails updated" 2 (List.length updated_map); 686 687 (* Validate destroyed *) 688 let destroyed = Jmap_core.Standard_methods.Set.destroyed response in 689 check bool "Has destroyed" true (Option.is_some destroyed); 690 let destroyed_list = Option.get destroyed in 691 check int "One email destroyed" 1 (List.length destroyed_list); 692 check string "Destroyed ID" "e099" (Jmap_core.Id.to_string (List.nth destroyed_list 0)) 693 694let test_email_import_request () = 695 let json = load_json "data/mail/email_import_request.json" in 696 let request = Jmap_mail.Email.Import.request_of_json json in 697 698 (* Validate account_id *) 699 let account_id = Jmap_mail.Email.Import.account_id request in 700 check string "Account ID" "u123456" (Jmap_core.Id.to_string account_id); 701 702 (* Validate ifInState *) 703 let if_in_state = Jmap_mail.Email.Import.if_in_state request in 704 check bool "Has ifInState" true (Option.is_some if_in_state); 705 check string "ifInState value" "e42:103" (Option.get if_in_state); 706 707 (* Validate emails *) 708 let emails = Jmap_mail.Email.Import.emails request in 709 check int "Two emails to import" 2 (List.length emails); 710 711 let (import_id1, import_email1) = List.nth emails 0 in 712 check string "First import ID" "temp-import-1" (Jmap_core.Id.to_string import_id1); 713 let blob_id1 = Jmap_mail.Email.Import.import_blob_id import_email1 in 714 check string "First blob ID starts correctly" "Gb5f55i6" 715 (String.sub (Jmap_core.Id.to_string blob_id1) 0 8); 716 717 let keywords1 = Jmap_mail.Email.Import.import_keywords import_email1 in 718 check bool "First email has $seen keyword" true 719 (List.mem_assoc "$seen" keywords1) 720 721let test_email_import_response () = 722 let json = load_json "data/mail/email_import_response.json" in 723 let response = Jmap_mail.Email.Import.response_of_json json in 724 725 (* Validate account_id *) 726 let account_id = Jmap_mail.Email.Import.response_account_id response in 727 check string "Account ID" "u123456" (Jmap_core.Id.to_string account_id); 728 729 (* Validate states *) 730 let old_state = Jmap_mail.Email.Import.old_state response in 731 check bool "Has old state" true (Option.is_some old_state); 732 check string "Old state" "e42:103" (Option.get old_state); 733 734 let new_state = Jmap_mail.Email.Import.new_state response in 735 check string "New state" "e42:105" new_state; 736 737 (* Validate created *) 738 let created = Jmap_mail.Email.Import.created response in 739 check bool "Has created" true (Option.is_some created); 740 let created_list = Option.get created in 741 check int "Two emails imported" 2 (List.length created_list); 742 743 let (temp_id1, email1) = List.nth created_list 0 in 744 check string "First temp ID" "temp-import-1" (Jmap_core.Id.to_string temp_id1); 745 check string "First email ID" "e102" (Jmap_core.Id.to_string (Jmap_mail.Email.id email1)); 746 check string "First thread ID" "t051" 747 (Jmap_core.Id.to_string (Jmap_mail.Email.thread_id email1)); 748 749 let (temp_id2, email2) = List.nth created_list 1 in 750 check string "Second temp ID" "temp-import-2" (Jmap_core.Id.to_string temp_id2); 751 check string "Second email ID" "e103" (Jmap_core.Id.to_string (Jmap_mail.Email.id email2)) 752 753let test_email_parse_request () = 754 let json = load_json "data/mail/email_parse_request.json" in 755 let request = Jmap_mail.Email.Parse.request_of_json json in 756 757 (* Validate account_id *) 758 let account_id = Jmap_mail.Email.Parse.account_id request in 759 check string "Account ID" "u123456" (Jmap_core.Id.to_string account_id); 760 761 (* Validate blob_ids *) 762 let blob_ids = Jmap_mail.Email.Parse.blob_ids request in 763 check int "One blob ID" 1 (List.length blob_ids); 764 let blob_id = List.nth blob_ids 0 in 765 check string "Blob ID starts correctly" "Gb5f77k8" 766 (String.sub (Jmap_core.Id.to_string blob_id) 0 8); 767 768 (* Validate fetch options *) 769 let fetch_text = Jmap_mail.Email.Parse.fetch_text_body_values request in 770 check bool "Fetch text body values" true (Option.value ~default:false fetch_text); 771 772 let fetch_html = Jmap_mail.Email.Parse.fetch_html_body_values request in 773 check bool "Fetch HTML body values" true (Option.value ~default:false fetch_html); 774 775 let max_bytes = Jmap_mail.Email.Parse.max_body_value_bytes request in 776 check bool "Has max bytes" true (Option.is_some max_bytes); 777 check int "Max bytes is 16384" 16384 778 (Jmap_core.Primitives.UnsignedInt.to_int (Option.get max_bytes)) 779 780let test_email_parse_response () = 781 let json = load_json "data/mail/email_parse_response.json" in 782 let response = Jmap_mail.Email.Parse.response_of_json json in 783 784 (* Validate account_id *) 785 let account_id = Jmap_mail.Email.Parse.response_account_id response in 786 check string "Account ID" "u123456" (Jmap_core.Id.to_string account_id); 787 788 (* Validate parsed *) 789 let parsed = Jmap_mail.Email.Parse.parsed response in 790 check bool "Has parsed emails" true (Option.is_some parsed); 791 let parsed_list = Option.get parsed in 792 check int "One email parsed" 1 (List.length parsed_list); 793 794 let (blob_id, email) = List.nth parsed_list 0 in 795 check string "Blob ID starts correctly" "Gb5f77k8" 796 (String.sub (Jmap_core.Id.to_string blob_id) 0 8); 797 798 (* Validate parsed email *) 799 check string "Subject" "Important Announcement" 800 (Option.get (Jmap_mail.Email.subject email)); 801 check bool "Has no attachment" false (Jmap_mail.Email.has_attachment email); 802 803 (* Validate from *) 804 let from = Option.get (Jmap_mail.Email.from email) in 805 check int "One from address" 1 (List.length from); 806 let from_addr = List.nth from 0 in 807 check string "From name" "Charlie Green" 808 (Option.get (Jmap_mail.Email.EmailAddress.name from_addr)); 809 check string "From email" "charlie@company.com" 810 (Jmap_mail.Email.EmailAddress.email from_addr); 811 812 (* Validate bodyStructure (simple text/plain) *) 813 let body_structure = Jmap_mail.Email.body_structure email in 814 check bool "Has bodyStructure" true (Option.is_some body_structure); 815 let body_part = Option.get body_structure in 816 check string "Body type" "text/plain" (Jmap_mail.Email.BodyPart.type_ body_part); 817 check string "Body part ID" "1" 818 (Option.get (Jmap_mail.Email.BodyPart.part_id body_part)); 819 check int "Body size" 1523 820 (Jmap_core.Primitives.UnsignedInt.to_int (Jmap_mail.Email.BodyPart.size body_part)); 821 822 (* Validate textBody *) 823 let text_body = Jmap_mail.Email.text_body email in 824 check bool "Has textBody" true (Option.is_some text_body); 825 let text_body_list = Option.get text_body in 826 check int "One textBody part" 1 (List.length text_body_list); 827 828 (* Validate htmlBody is empty *) 829 let html_body = Jmap_mail.Email.html_body email in 830 check bool "Has htmlBody" true (Option.is_some html_body); 831 let html_body_list = Option.get html_body in 832 check int "No htmlBody parts" 0 (List.length html_body_list); 833 834 (* Validate attachments is empty *) 835 let attachments = Jmap_mail.Email.attachments email in 836 check bool "Has attachments" true (Option.is_some attachments); 837 let attachments_list = Option.get attachments in 838 check int "No attachments" 0 (List.length attachments_list); 839 840 (* Validate bodyValues *) 841 let body_values = Jmap_mail.Email.body_values email in 842 check bool "Has bodyValues" true (Option.is_some body_values); 843 let values_list = Option.get body_values in 844 check int "One bodyValue" 1 (List.length values_list); 845 check bool "Has bodyValue for part 1" true (List.mem_assoc "1" values_list); 846 let body_value = List.assoc "1" values_list in 847 let content = Jmap_mail.Email.BodyValue.value body_value in 848 check bool "Content starts with 'Team'" true 849 (String.starts_with ~prefix:"Team" content); 850 851 (* Validate notParsable and notFound are empty *) 852 let not_parsable = Jmap_mail.Email.Parse.not_parsable response in 853 check bool "Has notParsable" true (Option.is_some not_parsable); 854 check int "No unparsable blobs" 0 (List.length (Option.get not_parsable)); 855 856 let not_found = Jmap_mail.Email.Parse.not_found response in 857 check bool "Has notFound" true (Option.is_some not_found); 858 check int "No blobs not found" 0 (List.length (Option.get not_found)) 859 860(** Test suite definition *) 861let () = 862 run "JMAP" [ 863 "Core Protocol", [ 864 test_case "Echo request" `Quick test_echo_request; 865 test_case "Echo response" `Quick test_echo_response; 866 test_case "Get request" `Quick test_get_request; 867 test_case "Get response" `Quick test_get_response; 868 test_case "Session object" `Quick test_session; 869 ]; 870 "Mail Protocol - Mailbox", [ 871 test_case "Mailbox/get request" `Quick test_mailbox_get_request; 872 test_case "Mailbox/get response" `Quick test_mailbox_get_response; 873 test_case "Mailbox/query request" `Quick test_mailbox_query_request; 874 test_case "Mailbox/query response" `Quick test_mailbox_query_response; 875 test_case "Mailbox/set request" `Quick test_mailbox_set_request; 876 test_case "Mailbox/set response" `Quick test_mailbox_set_response; 877 ]; 878 "Mail Protocol - Email", [ 879 test_case "Email/get request" `Quick test_email_get_request; 880 test_case "Email/get full request" `Quick test_email_get_full_request; 881 test_case "Email/get response" `Quick test_email_get_response; 882 test_case "Email/get full response" `Quick test_email_get_full_response; 883 test_case "Email/query request" `Quick test_email_query_request; 884 test_case "Email/query response" `Quick test_email_query_response; 885 test_case "Email/set request" `Quick test_email_set_request; 886 test_case "Email/set response" `Quick test_email_set_response; 887 test_case "Email/import request" `Quick test_email_import_request; 888 test_case "Email/import response" `Quick test_email_import_response; 889 test_case "Email/parse request" `Quick test_email_parse_request; 890 test_case "Email/parse response" `Quick test_email_parse_response; 891 ]; 892 ]