diff --git a/spec-nylas/fixtures/delta-sync/sample-clustered.json b/spec-nylas/fixtures/delta-sync/sample-clustered.json new file mode 100644 index 000000000..40b3fc82c --- /dev/null +++ b/spec-nylas/fixtures/delta-sync/sample-clustered.json @@ -0,0 +1,447 @@ +{ + "create": { + "message": { + "9p571g0ie63rg0ekec699ol5e": { + "body": "<>", + "files": [], + "from": [ + { + "name": "Karen Rustad Tölva", + "email": "karen.rustad@gmail.com" + } + ], + "account_id": "9jx3zd30wqx04p26vh09ptox1", + "thread_id": "62ehtebypazlgokcjvo8o9yak", + "cc": [], + "object": "message", + "bcc": [], + "snippet": "<>", + "to": [ + { + "name": "", + "email": "ben@nylas.com" + } + ], + "folder": { + "display_name": "Inbox", + "id": "bn7z083ho0mqfhqd68tio5a70", + "name": "inbox" + }, + "date": 1440610192, + "reply_to": [], + "events": [], + "starred": false, + "unread": true, + "id": "9p571g0ie63rg0ekec699ol5e", + "subject": "This is an email following up on the Electron meetup." + }, + "4rv7upa3gzuf1hal48b15lxrx": { + "body": "<>", + "files": [], + "from": [ + { + "name": "evan (Evan Morikawa)", + "email": "noreply+phabricator@nilas.com" + } + ], + "account_id": "9jx3zd30wqx04p26vh09ptox1", + "thread_id": "cungn0trv89l19mf08a2blyuh", + "cc": [], + "object": "message", + "bcc": [], + "snippet": "<>", + "to": [ + { + "name": "", + "email": "ben@inboxapp.com" + } + ], + "folder": { + "display_name": "Inbox", + "id": "bn7z083ho0mqfhqd68tio5a70", + "name": "inbox" + }, + "date": 1440609916, + "reply_to": [], + "events": [], + "starred": false, + "unread": true, + "id": "4rv7upa3gzuf1hal48b15lxrx", + "subject": "[Differential] [Accepted] D1936: feat(work): Create the \"Work\" window, move TaskQueue, Nylas sync workers" + }, + "tt4i92q8rtpgrbxaq6viqlf4": { + "body": "<>", + "files": [], + "from": [ + { + "name": "Ismail Pelaseyed", + "email": "ismail@sendcase.com" + } + ], + "account_id": "9jx3zd30wqx04p26vh09ptox1", + "thread_id": "8qesqxoftrd3nqiig3cz6gu49", + "cc": [], + "object": "message", + "bcc": [], + "snippet": "<>", + "to": [ + { + "name": "", + "email": "support@nylas.com" + } + ], + "folder": { + "display_name": "Inbox", + "id": "bn7z083ho0mqfhqd68tio5a70", + "name": "inbox" + }, + "date": 1440609839, + "reply_to": [], + "events": [], + "starred": false, + "unread": true, + "id": "tt4i92q8rtpgrbxaq6viqlf4", + "subject": "Re: Incoming webhook POST body empty" + }, + "7q4wtqzj3nxb42y6ysbl5l73t": { + "body": "<>", + "files": [], + "from": [ + { + "name": "Ismail Pelaseyed", + "email": "ismail@sendcase.com" + } + ], + "account_id": "9jx3zd30wqx04p26vh09ptox1", + "thread_id": "auro0q0gn0eawfrmqgzbi4f53", + "cc": [], + "object": "message", + "bcc": [], + "snippet": "<>", + "to": [ + { + "name": "", + "email": "support@nylas.com" + } + ], + "folder": { + "display_name": "Deleted Items", + "id": "b5t18ldx25xibwq5ctuh10u8h", + "name": "trash" + }, + "date": 1440609839, + "reply_to": [], + "events": [], + "starred": false, + "unread": true, + "id": "7q4wtqzj3nxb42y6ysbl5l73t", + "subject": "Re: Incoming webhook POST body empty" + }, + "4oesh7fhsbd45t7dx30p03vz1": { + "body": "<>", + "files": [], + "from": [ + { + "name": "Ismail Pelaseyed", + "email": "ismail@sendcase.com" + } + ], + "account_id": "9jx3zd30wqx04p26vh09ptox1", + "thread_id": "auro0q0gn0eawfrmqgzbi4f53", + "cc": [], + "object": "message", + "bcc": [], + "snippet": "<>", + "to": [ + { + "name": "", + "email": "support@nylas.com" + } + ], + "folder": { + "display_name": "Deleted Items", + "id": "b5t18ldx25xibwq5ctuh10u8h", + "name": "trash" + }, + "date": 1440594160, + "reply_to": [], + "events": [], + "starred": false, + "unread": true, + "id": "4oesh7fhsbd45t7dx30p03vz1", + "subject": "Incoming webhook POST body empty" + } + }, + "thread": { + "62ehtebypazlgokcjvo8o9yak": { + "folders": [ + { + "display_name": "Inbox", + "id": "bn7z083ho0mqfhqd68tio5a70", + "name": "inbox" + } + ], + "object": "thread", + "account_id": "9jx3zd30wqx04p26vh09ptox1", + "tags": [ + { + "name": "Inbox", + "id": "inbox" + }, + { + "name": "unread", + "id": "unread" + } + ], + "last_message_timestamp": 1440610192, + "has_attachments": false, + "first_message_timestamp": 1440610192, + "id": "62ehtebypazlgokcjvo8o9yak", + "subject": "This is an email following up on the Electron meetup.", + "last_message_received_timestamp": 1440610192, + "message_ids": [ + "9p571g0ie63rg0ekec699ol5e" + ], + "snippet": "<>", + "participants": [ + { + "name": "", + "email": "ben@nylas.com" + }, + { + "name": "Karen Rustad Tölva", + "email": "karen.rustad@gmail.com" + } + ], + "version": 0, + "starred": false, + "unread": true, + "draft_ids": [] + }, + "auro0q0gn0eawfrmqgzbi4f53": { + "folders": [ + { + "display_name": "Deleted Items", + "id": "b5t18ldx25xibwq5ctuh10u8h", + "name": "trash" + } + ], + "object": "thread", + "account_id": "9jx3zd30wqx04p26vh09ptox1", + "tags": [ + { + "name": "Deleted Items", + "id": "trash" + }, + { + "name": "unread", + "id": "unread" + } + ], + "last_message_timestamp": 1440609839, + "has_attachments": false, + "first_message_timestamp": 1440594160, + "id": "auro0q0gn0eawfrmqgzbi4f53", + "subject": "Incoming webhook POST body empty", + "last_message_received_timestamp": 1440609839, + "message_ids": [ + "4oesh7fhsbd45t7dx30p03vz1", + "7q4wtqzj3nxb42y6ysbl5l73t" + ], + "snippet": "<>", + "participants": [ + { + "name": "", + "email": "support@nylas.com" + }, + { + "name": "Ismail Pelaseyed", + "email": "ismail@sendcase.com" + } + ], + "version": 1, + "starred": false, + "unread": true, + "draft_ids": [] + } + }, + "contact": { + "1faoqpnn2rhf6mstmyc0ve71u": { + "email": "karen.rustad@gmail.com", + "object": "contact", + "id": "1faoqpnn2rhf6mstmyc0ve71u", + "name": "Karen Rustad Tölva", + "account_id": "9jx3zd30wqx04p26vh09ptox1" + } + } + }, + "modify": { + "thread": { + "cungn0trv89l19mf08a2blyuh": { + "folders": [ + { + "display_name": "Inbox", + "id": "bn7z083ho0mqfhqd68tio5a70", + "name": "inbox" + } + ], + "object": "thread", + "account_id": "9jx3zd30wqx04p26vh09ptox1", + "tags": [ + { + "name": "Inbox", + "id": "inbox" + }, + { + "name": "unread", + "id": "unread" + } + ], + "last_message_timestamp": 1440609916, + "has_attachments": false, + "first_message_timestamp": 1440543552, + "id": "cungn0trv89l19mf08a2blyuh", + "subject": "[Differential] [Request, 1,621 lines] D1936: feat(work): Create the \"Work\" window, move TaskQueue, Nylas sync workers", + "last_message_received_timestamp": 1440609916, + "message_ids": [ + "9uznoal93shahahlkesxkmfkr", + "4rv7upa3gzuf1hal48b15lxrx" + ], + "snippet": "<>", + "participants": [ + { + "name": "", + "email": "ben@inboxapp.com" + }, + { + "name": "evan (Evan Morikawa)", + "email": "noreply+phabricator@nilas.com" + }, + { + "name": "bengotow (Ben Gotow)", + "email": "noreply+phabricator@nilas.com" + } + ], + "version": 1, + "starred": false, + "unread": true, + "draft_ids": [] + }, + "8qesqxoftrd3nqiig3cz6gu49": { + "folders": [ + { + "display_name": "Inbox", + "id": "bn7z083ho0mqfhqd68tio5a70", + "name": "inbox" + } + ], + "object": "thread", + "account_id": "9jx3zd30wqx04p26vh09ptox1", + "tags": [ + { + "name": "Inbox", + "id": "inbox" + }, + { + "name": "unread", + "id": "unread" + } + ], + "last_message_timestamp": 1440609839, + "has_attachments": false, + "first_message_timestamp": 1440594160, + "id": "8qesqxoftrd3nqiig3cz6gu49", + "subject": "Incoming webhook POST body empty", + "last_message_received_timestamp": 1440609839, + "message_ids": [ + "b2dq8u1kbambwor2vy5nz619n", + "tt4i92q8rtpgrbxaq6viqlf4" + ], + "snippet": "<>", + "participants": [ + { + "name": "", + "email": "support@nylas.com" + }, + { + "name": "Ismail Pelaseyed", + "email": "ismail@sendcase.com" + } + ], + "version": 1, + "starred": false, + "unread": true, + "draft_ids": [] + }, + "auro0q0gn0eawfrmqgzbi4f53": { + "folders": [ + { + "display_name": "Deleted Items", + "id": "b5t18ldx25xibwq5ctuh10u8h", + "name": "trash" + } + ], + "object": "thread", + "account_id": "9jx3zd30wqx04p26vh09ptox1", + "tags": [ + { + "name": "Deleted Items", + "id": "trash" + }, + { + "name": "unread", + "id": "unread" + } + ], + "last_message_timestamp": 1440609839, + "has_attachments": false, + "first_message_timestamp": 1440594160, + "id": "auro0q0gn0eawfrmqgzbi4f53", + "subject": "Incoming webhook POST body empty", + "last_message_received_timestamp": 1440609839, + "message_ids": [ + "4oesh7fhsbd45t7dx30p03vz1", + "7q4wtqzj3nxb42y6ysbl5l73t" + ], + "snippet": "<>", + "participants": [ + { + "name": "", + "email": "support@nylas.com" + }, + { + "name": "Ismail Pelaseyed", + "email": "ismail@sendcase.com" + } + ], + "version": 1, + "starred": false, + "unread": true, + "draft_ids": [] + } + } + }, + "destroy": [ + { + "cursor": "bb95ddzqtr2gpmvgrng73t6ih", + "object": "thread", + "event": "delete", + "id": "8qesqxoftrd3nqiig3cz6gu49", + "timestamp": "2015-08-26T17:36:45.297Z" + }, + { + "cursor": "f1pw0buzv336n5xbuod7579cv", + "object": "message", + "event": "delete", + "id": "tt4i92q8rtpgrbxaq6viqlf4", + "timestamp": "2015-08-26T17:36:45.297Z" + }, + { + "cursor": "du99szyqlrujornwr5fgrzxeg", + "object": "message", + "event": "delete", + "id": "b2dq8u1kbambwor2vy5nz619n", + "timestamp": "2015-08-26T17:36:45.297Z" + } + ] +} diff --git a/spec-nylas/fixtures/delta-sync/sample.json b/spec-nylas/fixtures/delta-sync/sample.json new file mode 100644 index 000000000..088e2cf97 --- /dev/null +++ b/spec-nylas/fixtures/delta-sync/sample.json @@ -0,0 +1,510 @@ +[ + { + "cursor": "9w3el67207f8vvbkv89os5ay6", + "attributes": { + "body": "<>", + "files": [], + "from": [ + { + "name": "Karen Rustad Tölva", + "email": "karen.rustad@gmail.com" + } + ], + "account_id": "9jx3zd30wqx04p26vh09ptox1", + "thread_id": "62ehtebypazlgokcjvo8o9yak", + "cc": [], + "object": "message", + "bcc": [], + "snippet": "<>", + "to": [ + { + "name": "", + "email": "ben@nylas.com" + } + ], + "folder": { + "display_name": "Inbox", + "id": "bn7z083ho0mqfhqd68tio5a70", + "name": "inbox" + }, + "date": 1440610192, + "reply_to": [], + "events": [], + "starred": false, + "unread": true, + "id": "9p571g0ie63rg0ekec699ol5e", + "subject": "This is an email following up on the Electron meetup." + }, + "object": "message", + "event": "create", + "id": "9p571g0ie63rg0ekec699ol5e", + "timestamp": "2015-08-26T17:35:16.506Z" + }, + { + "cursor": "bqw8x58pghty4fw828tezb0va", + "attributes": { + "folders": [ + { + "display_name": "Inbox", + "id": "bn7z083ho0mqfhqd68tio5a70", + "name": "inbox" + } + ], + "object": "thread", + "account_id": "9jx3zd30wqx04p26vh09ptox1", + "tags": [ + { + "name": "Inbox", + "id": "inbox" + }, + { + "name": "unread", + "id": "unread" + } + ], + "last_message_timestamp": 1440610192, + "has_attachments": false, + "first_message_timestamp": 1440610192, + "id": "62ehtebypazlgokcjvo8o9yak", + "subject": "This is an email following up on the Electron meetup.", + "last_message_received_timestamp": 1440610192, + "message_ids": [ + "9p571g0ie63rg0ekec699ol5e" + ], + "snippet": "<>", + "participants": [ + { + "name": "", + "email": "ben@nylas.com" + }, + { + "name": "Karen Rustad Tölva", + "email": "karen.rustad@gmail.com" + } + ], + "version": 0, + "starred": false, + "unread": true, + "draft_ids": [] + }, + "object": "thread", + "event": "create", + "id": "62ehtebypazlgokcjvo8o9yak", + "timestamp": "2015-08-26T17:35:16.506Z" + }, + { + "cursor": "ewa1rr5nczdvrwpu11d3454ee", + "attributes": { + "email": "karen.rustad@gmail.com", + "object": "contact", + "id": "1faoqpnn2rhf6mstmyc0ve71u", + "name": "Karen Rustad Tölva", + "account_id": "9jx3zd30wqx04p26vh09ptox1" + }, + "object": "contact", + "event": "create", + "id": "1faoqpnn2rhf6mstmyc0ve71u", + "timestamp": "2015-08-26T17:35:16.506Z" + }, + { + "cursor": "756iwtaiufb6vxbtp4lt57cx0", + "attributes": { + "folders": [ + { + "display_name": "Inbox", + "id": "bn7z083ho0mqfhqd68tio5a70", + "name": "inbox" + } + ], + "object": "thread", + "account_id": "9jx3zd30wqx04p26vh09ptox1", + "tags": [ + { + "name": "Inbox", + "id": "inbox" + }, + { + "name": "unread", + "id": "unread" + } + ], + "last_message_timestamp": 1440609916, + "has_attachments": false, + "first_message_timestamp": 1440543552, + "id": "cungn0trv89l19mf08a2blyuh", + "subject": "[Differential] [Request, 1,621 lines] D1936: feat(work): Create the \"Work\" window, move TaskQueue, Nylas sync workers", + "last_message_received_timestamp": 1440609916, + "message_ids": [ + "9uznoal93shahahlkesxkmfkr", + "4rv7upa3gzuf1hal48b15lxrx" + ], + "snippet": "<>", + "participants": [ + { + "name": "", + "email": "ben@inboxapp.com" + }, + { + "name": "evan (Evan Morikawa)", + "email": "noreply+phabricator@nilas.com" + }, + { + "name": "bengotow (Ben Gotow)", + "email": "noreply+phabricator@nilas.com" + } + ], + "version": 1, + "starred": false, + "unread": true, + "draft_ids": [] + }, + "object": "thread", + "event": "modify", + "id": "cungn0trv89l19mf08a2blyuh", + "timestamp": "2015-08-26T17:35:16.506Z" + }, + { + "cursor": "ev9ewky31ps7deaf0dm90f4qa", + "attributes": { + "body": "<>", + "files": [], + "from": [ + { + "name": "evan (Evan Morikawa)", + "email": "noreply+phabricator@nilas.com" + } + ], + "account_id": "9jx3zd30wqx04p26vh09ptox1", + "thread_id": "cungn0trv89l19mf08a2blyuh", + "cc": [], + "object": "message", + "bcc": [], + "snippet": "<>", + "to": [ + { + "name": "", + "email": "ben@inboxapp.com" + } + ], + "folder": { + "display_name": "Inbox", + "id": "bn7z083ho0mqfhqd68tio5a70", + "name": "inbox" + }, + "date": 1440609916, + "reply_to": [], + "events": [], + "starred": false, + "unread": true, + "id": "4rv7upa3gzuf1hal48b15lxrx", + "subject": "[Differential] [Accepted] D1936: feat(work): Create the \"Work\" window, move TaskQueue, Nylas sync workers" + }, + "object": "message", + "event": "create", + "id": "4rv7upa3gzuf1hal48b15lxrx", + "timestamp": "2015-08-26T17:35:16.506Z" + }, + { + "cursor": "3gynk12o31m2199ppja76nkve", + "attributes": { + "folders": [ + { + "display_name": "Inbox", + "id": "bn7z083ho0mqfhqd68tio5a70", + "name": "inbox" + } + ], + "object": "thread", + "account_id": "9jx3zd30wqx04p26vh09ptox1", + "tags": [ + { + "name": "Inbox", + "id": "inbox" + }, + { + "name": "unread", + "id": "unread" + } + ], + "last_message_timestamp": 1440609839, + "has_attachments": false, + "first_message_timestamp": 1440594160, + "id": "8qesqxoftrd3nqiig3cz6gu49", + "subject": "Incoming webhook POST body empty", + "last_message_received_timestamp": 1440609839, + "message_ids": [ + "b2dq8u1kbambwor2vy5nz619n", + "tt4i92q8rtpgrbxaq6viqlf4" + ], + "snippet": "<>", + "participants": [ + { + "name": "", + "email": "support@nylas.com" + }, + { + "name": "Ismail Pelaseyed", + "email": "ismail@sendcase.com" + } + ], + "version": 1, + "starred": false, + "unread": true, + "draft_ids": [] + }, + "object": "thread", + "event": "modify", + "id": "8qesqxoftrd3nqiig3cz6gu49", + "timestamp": "2015-08-26T17:35:16.506Z" + }, + { + "cursor": "e8wnx217l8v1qx205d4hxvlk1", + "attributes": { + "body": "<>", + "files": [], + "from": [ + { + "name": "Ismail Pelaseyed", + "email": "ismail@sendcase.com" + } + ], + "account_id": "9jx3zd30wqx04p26vh09ptox1", + "thread_id": "8qesqxoftrd3nqiig3cz6gu49", + "cc": [], + "object": "message", + "bcc": [], + "snippet": "<>", + "to": [ + { + "name": "", + "email": "support@nylas.com" + } + ], + "folder": { + "display_name": "Inbox", + "id": "bn7z083ho0mqfhqd68tio5a70", + "name": "inbox" + }, + "date": 1440609839, + "reply_to": [], + "events": [], + "starred": false, + "unread": true, + "id": "tt4i92q8rtpgrbxaq6viqlf4", + "subject": "Re: Incoming webhook POST body empty" + }, + "object": "message", + "event": "create", + "id": "tt4i92q8rtpgrbxaq6viqlf4", + "timestamp": "2015-08-26T17:35:16.506Z" + }, + { + "cursor": "asyt17hyf9shing0tbdc1s5n9", + "attributes": { + "folders": [ + { + "display_name": "Deleted Items", + "id": "b5t18ldx25xibwq5ctuh10u8h", + "name": "trash" + } + ], + "object": "thread", + "account_id": "9jx3zd30wqx04p26vh09ptox1", + "tags": [ + { + "name": "Deleted Items", + "id": "trash" + }, + { + "name": "unread", + "id": "unread" + } + ], + "last_message_timestamp": 1440609839, + "has_attachments": false, + "first_message_timestamp": 1440594160, + "id": "auro0q0gn0eawfrmqgzbi4f53", + "subject": "Incoming webhook POST body empty", + "last_message_received_timestamp": 1440609839, + "message_ids": [ + "4oesh7fhsbd45t7dx30p03vz1", + "7q4wtqzj3nxb42y6ysbl5l73t" + ], + "snippet": "<>", + "participants": [ + { + "name": "", + "email": "support@nylas.com" + }, + { + "name": "Ismail Pelaseyed", + "email": "ismail@sendcase.com" + } + ], + "version": 1, + "starred": false, + "unread": true, + "draft_ids": [] + }, + "object": "thread", + "event": "modify", + "id": "auro0q0gn0eawfrmqgzbi4f53", + "timestamp": "2015-08-26T17:36:45.297Z" + }, + { + "cursor": "2r2f2indqv6fc9zptw9y49h97", + "attributes": { + "body": "<>", + "files": [], + "from": [ + { + "name": "Ismail Pelaseyed", + "email": "ismail@sendcase.com" + } + ], + "account_id": "9jx3zd30wqx04p26vh09ptox1", + "thread_id": "auro0q0gn0eawfrmqgzbi4f53", + "cc": [], + "object": "message", + "bcc": [], + "snippet": "<>", + "to": [ + { + "name": "", + "email": "support@nylas.com" + } + ], + "folder": { + "display_name": "Deleted Items", + "id": "b5t18ldx25xibwq5ctuh10u8h", + "name": "trash" + }, + "date": 1440609839, + "reply_to": [], + "events": [], + "starred": false, + "unread": true, + "id": "7q4wtqzj3nxb42y6ysbl5l73t", + "subject": "Re: Incoming webhook POST body empty" + }, + "object": "message", + "event": "create", + "id": "7q4wtqzj3nxb42y6ysbl5l73t", + "timestamp": "2015-08-26T17:36:45.297Z" + }, + { + "cursor": "ahvjhhsorjc0qe0snhwqydvui", + "attributes": { + "folders": [ + { + "display_name": "Deleted Items", + "id": "b5t18ldx25xibwq5ctuh10u8h", + "name": "trash" + } + ], + "object": "thread", + "account_id": "9jx3zd30wqx04p26vh09ptox1", + "tags": [ + { + "name": "Deleted Items", + "id": "trash" + }, + { + "name": "unread", + "id": "unread" + } + ], + "last_message_timestamp": 1440609839, + "has_attachments": false, + "first_message_timestamp": 1440594160, + "id": "auro0q0gn0eawfrmqgzbi4f53", + "subject": "Incoming webhook POST body empty", + "last_message_received_timestamp": 1440609839, + "message_ids": [ + "4oesh7fhsbd45t7dx30p03vz1", + "7q4wtqzj3nxb42y6ysbl5l73t" + ], + "snippet": "<>", + "participants": [ + { + "name": "", + "email": "support@nylas.com" + }, + { + "name": "Ismail Pelaseyed", + "email": "ismail@sendcase.com" + } + ], + "version": 1, + "starred": false, + "unread": true, + "draft_ids": [] + }, + "object": "thread", + "event": "create", + "id": "auro0q0gn0eawfrmqgzbi4f53", + "timestamp": "2015-08-26T17:36:45.297Z" + }, + { + "cursor": "2e35c6lcfvsx165kuu5534zng", + "attributes": { + "body": "<>", + "files": [], + "from": [ + { + "name": "Ismail Pelaseyed", + "email": "ismail@sendcase.com" + } + ], + "account_id": "9jx3zd30wqx04p26vh09ptox1", + "thread_id": "auro0q0gn0eawfrmqgzbi4f53", + "cc": [], + "object": "message", + "bcc": [], + "snippet": "<>", + "to": [ + { + "name": "", + "email": "support@nylas.com" + } + ], + "folder": { + "display_name": "Deleted Items", + "id": "b5t18ldx25xibwq5ctuh10u8h", + "name": "trash" + }, + "date": 1440594160, + "reply_to": [], + "events": [], + "starred": false, + "unread": true, + "id": "4oesh7fhsbd45t7dx30p03vz1", + "subject": "Incoming webhook POST body empty" + }, + "object": "message", + "event": "create", + "id": "4oesh7fhsbd45t7dx30p03vz1", + "timestamp": "2015-08-26T17:36:45.297Z" + }, + { + "cursor": "bb95ddzqtr2gpmvgrng73t6ih", + "object": "thread", + "event": "delete", + "id": "8qesqxoftrd3nqiig3cz6gu49", + "timestamp": "2015-08-26T17:36:45.297Z" + }, + { + "cursor": "f1pw0buzv336n5xbuod7579cv", + "object": "message", + "event": "delete", + "id": "tt4i92q8rtpgrbxaq6viqlf4", + "timestamp": "2015-08-26T17:36:45.297Z" + }, + { + "cursor": "du99szyqlrujornwr5fgrzxeg", + "object": "message", + "event": "delete", + "id": "b2dq8u1kbambwor2vy5nz619n", + "timestamp": "2015-08-26T17:36:45.297Z" + } +] diff --git a/spec-nylas/nylas-api-spec.coffee b/spec-nylas/nylas-api-spec.coffee new file mode 100644 index 000000000..e141bd5a8 --- /dev/null +++ b/spec-nylas/nylas-api-spec.coffee @@ -0,0 +1,182 @@ +_ = require 'underscore' +fs = require 'fs' +Actions = require '../src/flux/actions' +NylasAPI = require '../src/flux/nylas-api' +Thread = require '../src/flux/models/thread' +DatabaseStore = require '../src/flux/stores/database-store' + +describe "NylasAPI", -> + describe "handleModel404", -> + it "should unpersist the model from the cache that was requested", -> + model = new Thread(id: 'threadidhere') + spyOn(DatabaseStore, 'unpersistModel') + spyOn(DatabaseStore, 'find').andCallFake (klass, id) => + return Promise.resolve(model) + NylasAPI._handleModel404("/threads/#{model.id}") + advanceClock() + expect(DatabaseStore.find).toHaveBeenCalledWith(Thread, model.id) + expect(DatabaseStore.unpersistModel).toHaveBeenCalledWith(model) + + it "should not do anything if the model is not in the cache", -> + spyOn(DatabaseStore, 'unpersistModel') + spyOn(DatabaseStore, 'find').andCallFake (klass, id) => + return Promise.resolve(null) + NylasAPI._handleModel404("/threads/1234") + advanceClock() + expect(DatabaseStore.find).toHaveBeenCalledWith(Thread, '1234') + expect(DatabaseStore.unpersistModel).not.toHaveBeenCalledWith() + + it "should not do anything bad if it doesn't recognize the class", -> + spyOn(DatabaseStore, 'find') + spyOn(DatabaseStore, 'unpersistModel') + waitsForPromise -> + NylasAPI._handleModel404("/asdasdasd/1234") + runs -> + expect(DatabaseStore.find).not.toHaveBeenCalled() + expect(DatabaseStore.unpersistModel).not.toHaveBeenCalled() + + it "should not do anything bad if the endpoint only has a single segment", -> + spyOn(DatabaseStore, 'find') + spyOn(DatabaseStore, 'unpersistModel') + waitsForPromise -> + NylasAPI._handleModel404("/account") + runs -> + expect(DatabaseStore.find).not.toHaveBeenCalled() + expect(DatabaseStore.unpersistModel).not.toHaveBeenCalled() + + describe "handle401", -> + it "should post a notification", -> + spyOn(Actions, 'postNotification') + NylasAPI._handle401('/threads/1234') + expect(Actions.postNotification).toHaveBeenCalled() + expect(Actions.postNotification.mostRecentCall.args[0].message).toEqual("Nylas can no longer authenticate with your mail provider. You will not be able to send or receive mail. Please log out and sign in again.") + + describe "handleDeltas", -> + beforeEach -> + @sampleDeltas = JSON.parse(fs.readFileSync('./spec-nylas/fixtures/delta-sync/sample.json')) + @sampleClustered = JSON.parse(fs.readFileSync('./spec-nylas/fixtures/delta-sync/sample-clustered.json')) + + it "should immediately fire the received raw deltas event", -> + spyOn(Actions, 'longPollReceivedRawDeltas') + spyOn(NylasAPI, '_clusterDeltas').andReturn({create: {}, modify: {}, destroy: []}) + NylasAPI._handleDeltas(@sampleDeltas) + expect(Actions.longPollReceivedRawDeltas).toHaveBeenCalled() + + it "should call helper methods for all creates first, then modifications, then destroys", -> + spyOn(Actions, 'longPollProcessedDeltas') + + handleDeltaDeletionPromises = [] + resolveDeltaDeletionPromises = -> + fn() for fn in handleDeltaDeletionPromises + handleDeltaDeletionPromises = [] + + spyOn(NylasAPI, '_handleDeltaDeletion').andCallFake -> + new Promise (resolve, reject) -> + handleDeltaDeletionPromises.push(resolve) + + handleModelResponsePromises = [] + resolveModelResponsePromises = -> + fn() for fn in handleModelResponsePromises + handleModelResponsePromises = [] + + spyOn(NylasAPI, '_handleModelResponse').andCallFake -> + new Promise (resolve, reject) -> + handleModelResponsePromises.push(resolve) + + NylasAPI._handleDeltas(@sampleDeltas) + + createTypes = Object.keys(@sampleClustered['create']) + expect(NylasAPI._handleModelResponse.calls.length).toEqual(createTypes.length) + expect(NylasAPI._handleModelResponse.calls[0].args[0]).toEqual(_.values(@sampleClustered['create'][createTypes[0]])) + expect(NylasAPI._handleDeltaDeletion.calls.length).toEqual(0) + + NylasAPI._handleModelResponse.reset() + resolveModelResponsePromises() + advanceClock() + + modifyTypes = Object.keys(@sampleClustered['modify']) + expect(NylasAPI._handleModelResponse.calls.length).toEqual(modifyTypes.length) + expect(NylasAPI._handleModelResponse.calls[0].args[0]).toEqual(_.values(@sampleClustered['modify'][modifyTypes[0]])) + expect(NylasAPI._handleDeltaDeletion.calls.length).toEqual(0) + + NylasAPI._handleModelResponse.reset() + resolveModelResponsePromises() + advanceClock() + + destroyCount = @sampleClustered['destroy'].length + expect(NylasAPI._handleDeltaDeletion.calls.length).toEqual(destroyCount) + expect(NylasAPI._handleDeltaDeletion.calls[0].args[0]).toEqual(@sampleClustered['destroy'][0]) + + expect(Actions.longPollProcessedDeltas).not.toHaveBeenCalled() + + resolveDeltaDeletionPromises() + advanceClock() + + expect(Actions.longPollProcessedDeltas).toHaveBeenCalled() + + describe "clusterDeltas", -> + beforeEach -> + @sampleDeltas = JSON.parse(fs.readFileSync('./spec-nylas/fixtures/delta-sync/sample.json')) + @expectedClustered = JSON.parse(fs.readFileSync('./spec-nylas/fixtures/delta-sync/sample-clustered.json')) + + it "should collect create/modify events into a hash by model type", -> + {create, modify} = NylasAPI._clusterDeltas(@sampleDeltas) + expect(create).toEqual(@expectedClustered.create) + expect(modify).toEqual(@expectedClustered.modify) + + it "should collect destroys into an array", -> + {destroy} = NylasAPI._clusterDeltas(@sampleDeltas) + expect(destroy).toEqual(@expectedClustered.destroy) + + describe "handleDeltaDeletion", -> + beforeEach -> + @thread = new Thread(id: 'idhere') + @delta = + "cursor": "bb95ddzqtr2gpmvgrng73t6ih", + "object": "thread", + "event": "delete", + "id": @thread.id, + "timestamp": "2015-08-26T17:36:45.297Z" + + it "should resolve if the object cannot be found", -> + spyOn(DatabaseStore, 'find').andCallFake (klass, id) => + return Promise.resolve(null) + spyOn(DatabaseStore, 'unpersistModel') + waitsForPromise => + NylasAPI._handleDeltaDeletion(@delta) + runs => + expect(DatabaseStore.find).toHaveBeenCalledWith(Thread, 'idhere') + expect(DatabaseStore.unpersistModel).not.toHaveBeenCalled() + + it "should call unpersistModel if the object exists", -> + spyOn(DatabaseStore, 'find').andCallFake (klass, id) => + return Promise.resolve(@thread) + spyOn(DatabaseStore, 'unpersistModel') + waitsForPromise => + NylasAPI._handleDeltaDeletion(@delta) + runs => + expect(DatabaseStore.find).toHaveBeenCalledWith(Thread, 'idhere') + expect(DatabaseStore.unpersistModel).toHaveBeenCalledWith(@thread) + + # These specs are on hold because this function is changing very soon + + xdescribe "handleModelResponse", -> + it "should reject if no JSON is provided", -> + it "should resolve if an empty JSON array is provided", -> + + describe "if JSON contains the same object more than once", -> + it "should warn", -> + it "should omit duplicates", -> + + describe "if JSON contains objects which are of unknown types", -> + it "should warn and resolve", -> + + describe "when the object type is `thread`", -> + it "should check that models are acceptable", -> + + describe "when the object type is `draft`", -> + it "should check that models are acceptable", -> + + it "should call persistModels to save all of the received objects", -> + + it "should resolve with the objects", -> diff --git a/src/flux/nylas-api.coffee b/src/flux/nylas-api.coffee index e908fd4f5..7854a82ba 100644 --- a/src/flux/nylas-api.coffee +++ b/src/flux/nylas-api.coffee @@ -6,7 +6,6 @@ PriorityUICoordinator = require '../priority-ui-coordinator' DatabaseStore = require './stores/database-store' NylasSyncWorker = require './nylas-sync-worker' NylasLongConnection = require './nylas-long-connection' -DatabaseObjectRegistry = require '../database-object-registry' async = require 'async' PermanentErrorCodes = [400, 404, 500] @@ -236,12 +235,13 @@ class NylasAPI {pathname, query} = url.parse(modelUrl, true) components = pathname.split('/') - if components.length is 5 - [root, ns, nsId, collection, klassId] = components - klass = DatabaseObjectRegistry.get(collection[0..-2]) # Warning: threads => thread + if components.length is 3 + [root, collection, klassId] = components + klass = @_apiObjectToClassMap[collection[0..-2]] # Warning: threads => thread if klass and klassId and klassId.length > 0 - console.warn("Deleting #{klass.name}:#{klassId} due to API 404") + unless atom.inSpecMode() + console.warn("Deleting #{klass.name}:#{klassId} due to API 404") DatabaseStore.find(klass, klassId).then (model) -> if model return DatabaseStore.unpersistModel(model) @@ -279,6 +279,29 @@ class NylasAPI if delta.attributes Object.defineProperty(delta.attributes, '_delta', { get: -> delta }) + {create, modify, destroy} = @_clusterDeltas(deltas) + + # Apply all the deltas to create objects. Gets promises for handling + # each type of model in the `create` hash, waits for them all to resolve. + create[type] = @_handleModelResponse(_.values(dict)) for type, dict of create + Promise.props(create).then (created) => + # Apply all the deltas to modify objects. Gets promises for handling + # each type of model in the `modify` hash, waits for them all to resolve. + modify[type] = @_handleModelResponse(_.values(dict)) for type, dict of modify + Promise.props(modify).then (modified) => + + # Now that we've persisted creates/updates, fire an action + # that allows other parts of the app to update based on new models + # (notifications) + if _.flatten(_.values(created)).length > 0 + Actions.didPassivelyReceiveNewModels(created) + + # Apply all of the deletions + destroyPromises = destroy.map(@_handleDeltaDeletion) + Promise.settle(destroyPromises).then => + Actions.longPollProcessedDeltas() + + _clusterDeltas: (deltas) -> # Group deltas by object type so we can mutate the cache efficiently. # NOTE: This code must not just accumulate creates, modifies and destroys # but also de-dupe them. We cannot call "persistModels(itemA, itemA, itemB)" @@ -297,32 +320,14 @@ class NylasAPI else if delta.event is 'delete' destroy.push(delta) - # Apply all the deltas to create objects. Gets promises for handling - # each type of model in the `create` hash, waits for them all to resolve. - create[type] = @_handleModelResponse(_.values(dict)) for type, dict of create - Promise.props(create).then (created) => - # Apply all the deltas to modify objects. Gets promises for handling - # each type of model in the `modify` hash, waits for them all to resolve. - modify[type] = @_handleModelResponse(_.values(dict)) for type, dict of modify - Promise.props(modify).then (modified) -> + {create, modify, destroy} - # Now that we've persisted creates/updates, fire an action - # that allows other parts of the app to update based on new models - # (notifications) - if _.flatten(_.values(created)).length > 0 - Actions.didPassivelyReceiveNewModels(created) - - # Apply all of the deletions - destroyPromises = destroy.map (delta) -> - console.log(" - 1 #{delta.object} (#{delta.id})") - klass = DatabaseObjectRegistry.get(delta.object) - return unless klass - DatabaseStore.find(klass, delta.id).then (model) -> - return Promise.resolve() unless model - return DatabaseStore.unpersistModel(model) - - Promise.settle(destroyPromises).then => - Actions.longPollProcessedDeltas() + _handleDeltaDeletion: (delta) -> + klass = @_apiObjectToClassMap[delta.object] + return unless klass + DatabaseStore.find(klass, delta.id).then (model) -> + return Promise.resolve() unless model + return DatabaseStore.unpersistModel(model) # Returns a Promsie that resolves when any parsed out models (if any) # have been created and persisted to the database. @@ -340,38 +345,33 @@ class NylasAPI console.warn("NylasAPI.handleModelResponse: called with non-unique object set. Maybe an API request returned the same object more than once?") type = jsons[0].object - name = @_apiObjectToClassnameMap[type] - if not name + klass = @_apiObjectToClassMap[type] + if not klass console.warn("NylasAPI::handleModelResponse: Received unknown API object type: #{type}") return Promise.resolve([]) accepted = Promise.resolve(uniquedJSONs) - if type is "thread" - Thread = require './models/thread' - accepted = @_acceptableModelsInResponse(Thread, uniquedJSONs) - else if type is "draft" - Message = require './models/message' - accepted = @_acceptableModelsInResponse(Message, uniquedJSONs) + if type is "thread" or type is "draft" + accepted = @_acceptableModelsInResponse(klass, uniquedJSONs) - mapper = (json) -> - return DatabaseObjectRegistry.deserialize(name, json) + mapper = (json) -> (new klass).fromJSON(json) accepted.map(mapper).then (objects) -> DatabaseStore.persistModels(objects).then -> return Promise.resolve(objects) - _apiObjectToClassnameMap: - "file": "File" - "event": "Event" - "label": "Label" - "folder": "Folder" - "thread": "Thread" - "draft": "Message" - "account": "Account" - "message": "Message" - "contact": "Contact" - "calendar": "Calendar" - "metadata": "Metadata" + _apiObjectToClassMap: + "file": require('./models/file') + "event": require('./models/event') + "label": require('./models/label') + "folder": require('./models/folder') + "thread": require('./models/thread') + "draft": require('./models/message') + "account": require('./models/account') + "message": require('./models/message') + "contact": require('./models/contact') + "calendar": require('./models/calendar') + "metadata": require('./models/metadata') _acceptableModelsInResponse: (klass, jsons) -> # Filter out models that are locked by pending optimistic changes