def test_contacts_updated(api_client): """Tests that draft-contact associations are properly created and updated.""" draft = { 'to': [{'email': '*****@*****.**'}, {'email': '*****@*****.**'}] } r = api_client.post_data('/drafts', draft) assert r.status_code == 200 draft_id = json.loads(r.data)['id'] draft_version = json.loads(r.data)['version'] r = api_client.get_data('/[email protected]') assert len(r) == 1 updated_draft = { 'to': [{'email': '*****@*****.**'}, {'email': '*****@*****.**'}], 'version': draft_version } r = api_client.put_data('/drafts/{}'.format(draft_id), updated_draft) assert r.status_code == 200 r = api_client.get_data('/[email protected]') assert len(r) == 1 r = api_client.get_data('/[email protected]') assert len(r) == 0 r = api_client.get_data('/[email protected]') assert len(r) == 1
def test_api_expand_recurring_message(db, api_client, message, recurring_event): # This is a regression test for https://phab.nylas.com/T3556 # ("InflatedEvent should not be committed" exception in API"). event = recurring_event event.message = message db.session.commit() events = api_client.get_data('/events?expand_recurring=false') assert len(events) == 1 # Make sure the recurrence info is on the recurring event for e in events: if e['title'] == 'recurring-weekly': assert e.get('recurrence') is not None assert e.get('message_id') is not None r = api_client.get_raw('/events?expand_recurring=true') assert r.status_code == 200 all_events = api_client.get_data('/events?expand_recurring=true') assert len(all_events) != 0 for event in all_events: assert event['master_event_id'] is not None assert 'message_id' not in event
def test_account(db, api_client, generic_account, gmail_account): # Because we're using the generic_account namespace api_client = new_api_client(db, generic_account.namespace) resp_data = api_client.get_data('/account') assert resp_data['id'] == generic_account.namespace.public_id assert resp_data['object'] == 'account' assert resp_data['account_id'] == generic_account.namespace.public_id assert resp_data['email_address'] == generic_account.email_address assert resp_data['name'] == generic_account.name assert resp_data['organization_unit'] == 'folder' assert 'sync_state' in resp_data assert 'server_settings' not in resp_data # Because we're using the gmail account namespace api_client = new_api_client(db, gmail_account.namespace) resp_data = api_client.get_data('/account') assert resp_data['id'] == gmail_account.namespace.public_id assert resp_data['provider'] == 'gmail' assert resp_data['organization_unit'] == 'label' assert 'sync_state' in resp_data assert 'server_settings' not in resp_data
def test_api_get(contacts_provider, contact_sync, db, api_client, default_namespace): contacts_provider.supply_contact('Contact One', '*****@*****.**') contacts_provider.supply_contact('Contact Two', '*****@*****.**') contact_sync.provider = contacts_provider contact_sync.sync() contact_list = api_client.get_data('/contacts') contact_ids = [contact['id'] for contact in contact_list] c1found = False c2found = False for c_id in contact_ids: contact = api_client.get_data('/contacts/' + c_id) if contact['name'] == 'Contact One': c1found = True if contact['name'] == 'Contact Two': c2found = True assert c1found assert c2found
def test_events_are_condensed(api_client, message): """ Test that multiple revisions of the same object are rolled up in the delta response. """ ts = int(time.time() + 22) cursor = get_cursor(api_client, ts) # Modify a message, then modify it again message_id = api_client.get_data('/messages/')[0]['id'] message_path = '/messages/{}'.format(message_id) api_client.put_data(message_path, {'unread': True}) api_client.put_data(message_path, {'unread': False}) api_client.put_data(message_path, {'unread': True}) # Check that successive modifies are condensed. sync_data = api_client.get_data('/delta?cursor={}'.format(cursor)) deltas = sync_data['deltas'] # A message modify propagates to its thread message_deltas = [d for d in deltas if d['object'] == 'message'] assert len(message_deltas) == 1 delta = message_deltas[0] assert delta['object'] == 'message' and delta['event'] == 'modify' assert delta['attributes']['unread'] is True
def test_account_expanded(db, api_client, generic_account, gmail_account): # Generic accounts expose a `server_settings` attribute # Custom IMAP api_client = new_api_client(db, generic_account.namespace) resp_data = api_client.get_data('/account/?view=expanded') assert resp_data['provider'] == 'custom' assert 'server_settings' in resp_data assert set(resp_data['server_settings']) == set({ 'imap_host': 'imap.custom.com', 'smtp_host': 'smtp.custom.com', 'imap_port': 993, 'smtp_port': 587, 'ssl_required': True}) # Yahoo yahoo_account = add_fake_yahoo_account(db.session) api_client = new_api_client(db, yahoo_account.namespace) resp_data = api_client.get_data('/account/?view=expanded') assert resp_data['provider'] == 'yahoo' assert 'server_settings' in resp_data assert set(resp_data['server_settings']) == set({ 'imap_host': 'imap.mail.yahoo.com', 'smtp_host': 'smtp.mail.yahoo.com', 'imap_port': 993, 'smtp_port': 587, 'ssl_required': True}) # Gmail accounts don't expose a `server_settings` attribute api_client = new_api_client(db, gmail_account.namespace) resp_data = api_client.get_data('/account/?view=expanded') assert resp_data['provider'] == 'gmail' assert 'server_settings' not in resp_data
def test_api_get(db, api_client, calendar): e_data = {'title': 'subj', 'when': {'time': 1}, 'calendar_id': calendar.public_id, 'location': 'InboxHQ'} e_data2 = {'title': 'subj2', 'when': {'time': 1}, 'calendar_id': calendar.public_id, 'location': 'InboxHQ'} api_client.post_data('/events', e_data) api_client.post_data('/events', e_data2) event_list = api_client.get_data('/events') event_ids = [event['id'] for event in event_list] c1found = False c2found = False for c_id in event_ids: event = api_client.get_data('/events/' + c_id) if event['title'] == 'subj': c1found = True if event['title'] == 'subj2': c2found = True assert c1found assert c2found
def test_delete_from_readonly_calendar(db, default_namespace, api_client): add_fake_event(db.session, default_namespace.id, calendar=db.session.query(Calendar).filter( Calendar.namespace_id == default_namespace.id, Calendar.read_only == True).first(), # noqa read_only=True) calendar_list = api_client.get_data('/calendars') read_only_calendar = None for c in calendar_list: if c['read_only']: read_only_calendar = c break events = api_client.get_data('/events?calendar_id={}'.format( read_only_calendar['id'])) for event in events: if event['read_only']: read_only_event = event break assert read_only_calendar assert read_only_event e_id = read_only_event['id'] resp = api_client.delete('/events/{}'.format(e_id)) assert resp.status_code == 400
def test_update_draft(api_client): with freeze_time(datetime.now()) as freezer: original_draft = {"subject": "original draft", "body": "parent draft"} r = api_client.post_data("/drafts", original_draft) draft_public_id = json.loads(r.data)["id"] version = json.loads(r.data)["version"] assert version == 0 freezer.tick() updated_draft = {"subject": "updated draft", "body": "updated draft", "version": version} r = api_client.put_data("/drafts/{}".format(draft_public_id), updated_draft) updated_public_id = json.loads(r.data)["id"] updated_version = json.loads(r.data)["version"] assert updated_public_id == draft_public_id assert updated_version > 0 drafts = api_client.get_data("/drafts") assert len(drafts) == 1 assert drafts[0]["id"] == updated_public_id # Check that the thread is updated too. thread = api_client.get_data("/threads/{}".format(drafts[0]["thread_id"])) assert thread["subject"] == "updated draft" assert thread["first_message_timestamp"] == drafts[0]["date"] assert thread["last_message_timestamp"] == drafts[0]["date"]
def test_create_draft_with_attachments(api_client, attachments, example_draft): attachment_ids = [] upload_path = '/files' for filename, path in attachments: data = {'file': (open(path, 'rb'), filename)} r = api_client.post_raw(upload_path, data=data) assert r.status_code == 200 attachment_id = json.loads(r.data)[0]['id'] attachment_ids.append(attachment_id) first_attachment = attachment_ids.pop() example_draft['file_ids'] = [first_attachment] r = api_client.post_data('/drafts', example_draft) assert r.status_code == 200 returned_draft = json.loads(r.data) draft_public_id = returned_draft['id'] assert returned_draft['version'] == 0 example_draft['version'] = returned_draft['version'] assert len(returned_draft['files']) == 1 attachment_ids.append(first_attachment) example_draft['file_ids'] = attachment_ids r = api_client.put_data('/drafts/{}'.format(draft_public_id), example_draft) assert r.status_code == 200 returned_draft = json.loads(r.data) assert len(returned_draft['files']) == 3 assert returned_draft['version'] == 1 example_draft['version'] = returned_draft['version'] # Make sure we can't delete the files now for file_id in attachment_ids: r = api_client.delete('/files/{}'.format(file_id)) assert r.status_code == 400 # Now remove the attachment example_draft['file_ids'] = [first_attachment] r = api_client.put_data('/drafts/{}'.format(draft_public_id), example_draft) draft_data = api_client.get_data('/drafts/{}'.format(draft_public_id)) assert len(draft_data['files']) == 1 assert draft_data['version'] == 2 example_draft['version'] = draft_data['version'] example_draft['file_ids'] = [] r = api_client.put_data('/drafts/{}'.format(draft_public_id), example_draft) draft_data = api_client.get_data('/drafts/{}'.format(draft_public_id)) assert r.status_code == 200 assert len(draft_data['files']) == 0 assert draft_data['version'] == 3 # now that they're not attached, we should be able to delete them for file_id in attachment_ids: r = api_client.delete('/files/{}'.format(file_id)) assert r.status_code == 200
def test_get_with_id(api_client, uploaded_file_ids, filename): # See comment in uploaded_file_ids() if filename == 'piece-jointe.jpg': filename = u'pièce-jointe.jpg' elif filename == 'andra-moi-ennepe.txt': filename = u'ἄνδρα μοι ἔννεπε' in_file = api_client.get_data(u'/files?filename={}'.format(filename))[0] data = api_client.get_data('/files/{}'.format(in_file['id'])) assert data['filename'] == filename
def test_gmail_pagination(db, default_account, patch_crispin_client, patch_handler_from_provider, folder): for i in range(10): thread = add_fake_thread(db.session, default_account.namespace.id) message = add_fake_message(db.session, default_account.namespace.id, thread=thread, from_addr=[{'name': '', 'email': '{}@test.com'.format(str(i))}], subject='hi', g_msgid=i, received_date=datetime. datetime(2000 + i, 1, 1, 1, 0, 0)) add_fake_imapuid(db.session, default_account.id, message, folder, i) first_ten_messages_db = db.session.query(Message)\ .filter(Message.namespace_id == default_account.namespace.id). \ order_by(desc(Message.received_date)). \ limit(10).all() api_client = new_api_client(db, default_account.namespace) first_ten_messages_api = api_client.get_data('/messages/search?q=hi' '&limit=10') assert len(first_ten_messages_api) == len(first_ten_messages_db) for db_message, api_message in zip(first_ten_messages_db, first_ten_messages_api): assert db_message.public_id == api_message['id'] imap_uids = db.session.query(ImapUid).join(Message) \ .filter( ImapUid.message_id == Message.id, Message.g_msgid != None).all() uids = [uid.msg_uid for uid in imap_uids] first_ten_threads_db = db.session.query(Thread) \ .join(Message) \ .join(ImapUid) \ .filter(ImapUid.account_id == default_account.id, ImapUid.msg_uid.in_(uids), Thread.id == Message.thread_id)\ .order_by(desc(Message.received_date)) \ .limit(10).all() first_ten_threads_api = api_client.get_data('/threads/search?q=hi' '&limit=10') assert len(first_ten_threads_api) == len(first_ten_threads_db) for db_thread, api_thread in zip(first_ten_threads_db, first_ten_threads_api): assert db_thread.public_id == api_thread['id']
def test_reject_incompatible_reply_thread_and_message(db, api_client, message, thread, default_namespace): alt_thread = add_fake_thread(db.session, default_namespace.id) add_fake_message(db.session, default_namespace.id, alt_thread) thread = api_client.get_data("/threads")[0] alt_message_id = api_client.get_data("/threads")[1]["message_ids"][0] alt_message = api_client.get_data("/messages/{}".format(alt_message_id)) assert thread["id"] != alt_message["thread_id"] reply_draft = {"subject": "test reply", "reply_to_message_id": alt_message["id"], "thread_id": thread["id"]} r = api_client.post_data("/drafts", reply_draft) assert r.status_code == 400
def test_gmail_message_search(api_client, default_account, patch_token_manager, patch_gmail_search_response, sorted_gmail_messages, is_streaming): search_client = get_search_client(default_account) assert isinstance(search_client, GmailSearchClient) if is_streaming: messages = api_client.get_data('/messages/search/streaming?q=blah%20blah%20blah') else: messages = api_client.get_data('/messages/search?q=blah%20blah%20blah') assert_search_result(sorted_gmail_messages, messages)
def test_get_invalid(api_client, uploaded_file_ids): data = api_client.get_data('/files/0000000000000000000000000') assert data['message'].startswith("Couldn't find file") data = api_client.get_data('/files/!') assert data['message'].startswith("Invalid id") data = api_client.get_data('/files/0000000000000000000000000/download') assert data['message'].startswith("Couldn't find file") data = api_client.get_data('/files/!/download') assert data['message'].startswith("Invalid id") r = api_client.delete('/files/0000000000000000000000000') assert r.status_code == 404 r = api_client.delete('/files/!') assert r.status_code == 400
def test_gmail_search_unicode(db, api_client, test_gmail_thread, patch_token_manager, patch_gmail_search_response, default_account, sorted_gmail_messages, sorted_gmail_threads, is_streaming): search_client = get_search_client(default_account) assert isinstance(search_client, GmailSearchClient) if is_streaming: threads = api_client.get_data('/threads/search/streaming?q=存档') else: threads = api_client.get_data('/threads/search?q=存档') assert_search_result(sorted_gmail_threads, threads)
def test_message_label_updates(db, api_client, default_account, api_version, custom_label): """Check that you can update a message (optimistically or not), and that the update is queued in the ActionLog.""" headers = dict() headers['Api-Version'] = api_version # Gmail threads, messages have a 'labels' field gmail_thread = add_fake_thread(db.session, default_account.namespace.id) gmail_message = add_fake_message(db.session, default_account.namespace.id, gmail_thread) resp_data = api_client.get_data( '/messages/{}'.format(gmail_message.public_id), headers=headers) assert resp_data['labels'] == [] category = custom_label.category update = dict(labels=[category.public_id]) resp = api_client.put_data( '/messages/{}'.format(gmail_message.public_id), update, headers=headers) resp_data = json.loads(resp.data) if api_version == API_VERSIONS[0]: assert len(resp_data['labels']) == 1 assert resp_data['labels'][0]['id'] == category.public_id else: assert resp_data['labels'] == []
def test_resource_views(resource_name, db, api_client, message, thread, event, folder, label, contact): """Exercises various tests for views, mostly related to filtering. Note: this only tests views, it assumes the resources are working as expected.""" elements = api_client.get_data('/{}'.format(resource_name)) count = api_client.get_data('/{}?view=count'.format(resource_name)) assert count["count"] == len(elements) ids = api_client.get_data('/{}?view=ids'.format(resource_name)) for i, elem in enumerate(elements): assert isinstance(ids[i], basestring), \ "&views=ids should return string" assert elem["id"] == ids[i], "view=ids should preserve order"
def test_api_create(db, api_client, calendar): e_data = { 'title': 'Friday Office Party', 'when': {'time': 1407542195}, 'calendar_id': calendar.public_id, 'participants': [{ 'name': 'alyssa p. hacker', 'email': '*****@*****.**' }] } e_resp = api_client.post_data('/events', e_data) e_resp_data = json.loads(e_resp.data) assert len(e_resp_data['participants']) == 1 participant = e_resp_data['participants'][0] assert participant['name'] == e_data['participants'][0]['name'] assert participant['email'] == e_data['participants'][0]['email'] assert participant['status'] == 'noreply' e_resp_data = api_client.get_data('/events/' + e_resp_data['id']) assert len(e_resp_data['participants']) == 1 participant = e_resp_data['participants'][0] assert participant['name'] == e_data['participants'][0]['name'] assert participant['email'] == e_data['participants'][0]['email'] assert participant['status'] == 'noreply'
def test_api_override_serialization(db, api_client, default_namespace, recurring_event): event = recurring_event override = Event(original_start_time=event.start, master_event_uid=event.uid, namespace_id=default_namespace.id, calendar_id=event.calendar_id) override.update(event) override.uid = event.uid + "_" + event.start.strftime("%Y%m%dT%H%M%SZ") override.master = event override.master_event_uid = event.uid override.cancelled = True db.session.add(override) db.session.commit() filter = 'starts_after={}&ends_before={}'.format( urlsafe(event.start.replace(hours=-1)), urlsafe(event.start.replace(weeks=+1))) events = api_client.get_data('/events?' + filter) # We should have the base event and the override back, but no extras; # this allows clients to do their own expansion, should they ever desire # to experience the joy that is RFC 2445 section 4.8.5.4. assert len(events) == 2 assert events[0].get('object') == 'event' assert events[0].get('recurrence') is not None assert events[1].get('object') == 'event' assert events[1].get('status') == 'cancelled'
def test_create_draft_replying_to_message(api_client, message): message = api_client.get_data("/messages")[0] reply_draft = {"subject": "test reply", "body": "test reply", "reply_to_message_id": message["id"]} r = api_client.post_data("/drafts", reply_draft) data = json.loads(r.data) assert data["reply_to_message_id"] == message["id"] assert data["thread_id"] == message["thread_id"]
def test_update_to_nonexistent_draft(api_client): updated_draft = {"subject": "updated draft", "body": "updated draft", "version": 22} r = api_client.put_data("/drafts/{}".format("notarealid"), updated_draft) assert r.status_code == 404 drafts = api_client.get_data("/drafts") assert len(drafts) == 0
def test_drafts_filter(api_client, example_draft): r = api_client.post_data("/drafts", example_draft) thread_id = json.loads(r.data)["thread_id"] reply_draft = {"subject": "test reply", "body": "test reply", "thread_id": thread_id} r = api_client.post_data("/drafts", reply_draft) _filter = "?thread_id=0000000000000000000000000" results = api_client.get_data("/drafts" + _filter) assert len(results) == 0 results = api_client.get_data("/drafts?thread_id={}".format(thread_id)) assert len(results) == 2 results = api_client.get_data("/drafts?offset={}&thread_id={}".format(1, thread_id)) assert len(results) == 1
def test_query_target(db, api_client, thread, default_namespace): cat = Category(namespace_id=default_namespace.id, name='inbox', display_name='Inbox', type_='label') for _ in range(3): message = add_fake_message(db.session, default_namespace.id, thread, to_addr=[('Bob', '*****@*****.**')], from_addr=[('Alice', '*****@*****.**')], subject='some subject') message.categories.add(cat) db.session.commit() results = api_client.get_data('/messages?in=inbox') assert len(results) == 3 count = api_client.get_data('/messages?in=inbox&view=count') assert count['count'] == 3
def test_reject_incompatible_reply_thread_and_message( db, api_client, message, thread, default_namespace): alt_thread = add_fake_thread(db.session, default_namespace.id) add_fake_message(db.session, default_namespace.id, alt_thread) thread = api_client.get_data('/threads')[0] alt_message_id = api_client.get_data('/threads')[1]['message_ids'][0] alt_message = api_client.get_data('/messages/{}'.format(alt_message_id)) assert thread['id'] != alt_message['thread_id'] reply_draft = { 'subject': 'test reply', 'reply_to_message_id': alt_message['id'], 'thread_id': thread['id'] } r = api_client.post_data('/drafts', reply_draft) assert r.status_code == 400
def test_api_list(db, api_client, calendar): e_data = {'title': 'subj', 'description': 'body1', 'calendar_id': calendar.public_id, 'when': {'time': 1}, 'location': 'InboxHQ'} e_data2 = {'title': 'subj2', 'description': 'body2', 'calendar_id': calendar.public_id, 'when': {'time': 1}, 'location': 'InboxHQ'} api_client.post_data('/events', e_data) api_client.post_data('/events', e_data2) event_list = api_client.get_data('/events') event_titles = [event['title'] for event in event_list] assert 'subj' in event_titles assert 'subj2' in event_titles event_descriptions = [event['description'] for event in event_list] assert 'body1' in event_descriptions assert 'body2' in event_descriptions event_ids = [event['id'] for event in event_list] for e_id in event_ids: ev = db.session.query(Event).filter_by(public_id=e_id).one() db.session.delete(ev) db.session.commit()
def test_adding_a_mutually_exclusive_label_replaces_the_other( db, api_client, default_account, folder_and_message_maps, label): # Verify a Gmail message can only have ONE of the 'all', 'trash', 'spam' # labels at a time. We specifically test that adding 'all'/ 'trash'/ 'spam' # to a message in one of the other two folders *replaces* # the existing label with the label being added. folder_map, message_map = folder_and_message_maps label_to_add = folder_map[label] for key in message_map: if key == label: continue message = message_map[key] resp_data = api_client.get_data('/messages/{}'.format(message.public_id)) labels = resp_data['labels'] assert len(labels) == 1 assert labels[0]['name'] == key existing_label = labels[0]['id'] # Adding 'all'/ 'trash'/ 'spam' removes the existing one, # irrespective of whether it's provided in the request or not. response = api_client.put_data( '/messages/{}'.format(message.public_id), {'label_ids': [label_to_add.category.public_id, existing_label]}) labels = json.loads(response.data)['labels'] assert len(labels) == 1 assert labels[0]['name'] == label
def test_conflicting_updates(api_client): original_draft = { 'subject': 'parent draft', 'body': 'parent draft' } r = api_client.post_data('/drafts', original_draft) original_public_id = json.loads(r.data)['id'] version = json.loads(r.data)['version'] updated_draft = { 'subject': 'updated draft', 'body': 'updated draft', 'version': version } r = api_client.put_data('/drafts/{}'.format(original_public_id), updated_draft) assert r.status_code == 200 updated_public_id = json.loads(r.data)['id'] updated_version = json.loads(r.data)['version'] assert updated_version != version conflicting_draft = { 'subject': 'conflicting draft', 'body': 'conflicting draft', 'version': version } r = api_client.put_data('/drafts/{}'.format(original_public_id), conflicting_draft) assert r.status_code == 409 drafts = api_client.get_data('/drafts') assert len(drafts) == 1 assert drafts[0]['id'] == updated_public_id
def test_adding_a_mutually_exclusive_label_does_not_affect_custom_labels( db, api_client, default_account, folder_and_message_maps, label): folder_map, message_map = folder_and_message_maps label_to_add = folder_map[label] for key in message_map: if key == label: continue message = message_map[key] add_custom_label(db, default_account, message) resp_data = api_client.get_data('/messages/{}'.format(message.public_id)) labels = resp_data['labels'] assert len(labels) == 2 assert key in [l['name'] for l in labels] assert '<3' in [l['display_name'] for l in labels] # Adding only 'all'/ 'trash'/ 'spam' does not change custom labels. response = api_client.put_data( '/messages/{}'.format(message.public_id), {'label_ids': [label_to_add.category.public_id] + [l['id'] for l in labels]}) labels = json.loads(response.data)['labels'] assert len(labels) == 2 assert label in [l['name'] for l in labels] assert '<3' in [l['display_name'] for l in labels]
def test_adding_inbox_adds_all_and_removes_trash_spam( db, api_client, default_account, folder_and_message_maps, label): # Verify a Gmail message in 'trash', 'spam' cannot have 'inbox'. # This time we test that adding 'inbox' to a message in the 'trash'/ 'spam' # moves it to 'all' in addition to adding 'inbox'. folder_map, message_map = folder_and_message_maps message = message_map[label] resp_data = api_client.get_data('/messages/{}'.format(message.public_id)) labels = resp_data['labels'] assert len(labels) == 1 assert labels[0]['name'] == label existing_label = labels[0]['id'] inbox_label = add_fake_label(db.session, default_account, 'Inbox', 'inbox') db.session.commit() # Adding 'inbox' adds 'all', replacing 'trash'/ 'spam' if needed. response = api_client.put_data( '/messages/{}'.format(message.public_id), {'label_ids': [inbox_label.category.public_id, existing_label]}) db.session.commit() labels = json.loads(response.data)['labels'] assert len(labels) == 2 assert set([l['name'] for l in labels]) == set(['all', 'inbox'])
def test_account(db, api_client, generic_account, gmail_account): # Because we're using the generic_account namespace api_client = new_api_client(db, generic_account.namespace) resp_data = api_client.get_data('/account') assert resp_data['id'] == generic_account.namespace.public_id assert resp_data['object'] == 'account' assert resp_data['account_id'] == generic_account.namespace.public_id assert resp_data['email_address'] == generic_account.email_address assert resp_data['name'] == generic_account.name assert resp_data['organization_unit'] == 'folder' # Because we're using the gmail account namespace api_client = new_api_client(db, gmail_account.namespace) resp_data = api_client.get_data('/account') assert resp_data['id'] == gmail_account.namespace.public_id assert resp_data['provider'] == 'gmail' assert resp_data['organization_unit'] == 'label'
def test_message_labels(db, gmail_account): # Because we're using the gmail_account namespace api_client = new_api_client(db, gmail_account.namespace) # Gmail threads, messages have a 'labels' field gmail_thread = add_fake_thread(db.session, gmail_account.namespace.id) gmail_message = add_fake_message(db.session, gmail_account.namespace.id, gmail_thread) resp_data = api_client.get_data('/threads/{}'.format( gmail_thread.public_id)) assert resp_data['id'] == gmail_thread.public_id assert resp_data['object'] == 'thread' assert 'labels' in resp_data and 'folders' not in resp_data resp_data = api_client.get_data('/messages/{}'.format( gmail_message.public_id)) assert resp_data['id'] == gmail_message.public_id assert resp_data['object'] == 'message' assert 'labels' in resp_data and 'folders' not in resp_data
def test_api_list(contacts_provider, contact_sync, db, api_client, default_namespace): contacts_provider.supply_contact('Contact One', '*****@*****.**') contacts_provider.supply_contact('Contact Two', '*****@*****.**') contact_sync.provider = contacts_provider contact_sync.sync() contact_list = api_client.get_data('/contacts') contact_names = [contact['name'] for contact in contact_list] assert 'Contact One' in contact_names assert 'Contact Two' in contact_names contact_emails = [contact['email'] for contact in contact_list] assert '*****@*****.**' in contact_emails assert '*****@*****.**' in contact_emails contact_count = api_client.get_data('/contacts?view=count') assert contact_count['count'] == db.session.query(Contact). \ filter(Contact.namespace_id == default_namespace.id).count()
def test_exclude_account(api_client, db, default_namespace, thread): ts = int(time.time() + 22) cursor = get_cursor(api_client, ts) # Create `account`, `message`, `thread` deltas default_namespace.account.sync_state = 'invalid' db.session.commit() add_fake_message(db.session, default_namespace.id, thread) # Verify the default value of `exclude_account`=True and # the account delta is *not* included sync_data = api_client.get_data('/delta?cursor={}'.format(cursor)) assert len(sync_data['deltas']) == 2 assert set([d['object'] for d in sync_data['deltas']]) == \ set(['message', 'thread']) # Verify setting `exclude_account`=True returns the account delta as well. sync_data = api_client.get_data('/delta?cursor={}&exclude_account=false'. format(cursor)) assert len(sync_data['deltas']) == 3 assert set([d['object'] for d in sync_data['deltas']]) == \ set(['message', 'thread', 'account'])
def test_account_delta(api_client, db, default_namespace): ts = int(time.time() + 22) cursor = get_cursor(api_client, ts) account = default_namespace.account # Create an `account` delta default_namespace.account.sync_state = 'invalid' db.session.commit() sync_data = api_client.get_data('/delta?cursor={}&exclude_account=false'. format(cursor)) assert len(sync_data['deltas']) == 1 delta = sync_data['deltas'][0] assert delta['object'] == 'account' assert delta['event'] == 'modify' assert delta['attributes']['id'] == default_namespace.public_id assert delta['attributes']['account_id'] == default_namespace.public_id assert delta['attributes']['email_address'] == account.email_address assert delta['attributes']['name'] == account.name assert delta['attributes']['provider'] == account.provider assert delta['attributes']['organization_unit'] == account.category_type assert delta['attributes']['sync_state'] == 'invalid' cursor = sync_data['cursor_end'] # Create an new `account` delta default_namespace.account.sync_state = 'running' db.session.commit() sync_data = api_client.get_data('/delta?cursor={}&exclude_account=false'. format(cursor)) assert len(sync_data['deltas']) == 1 delta = sync_data['deltas'][0] assert delta['object'] == 'account' assert delta['event'] == 'modify' assert delta['attributes']['id'] == default_namespace.public_id assert delta['attributes']['sync_state'] == 'running'
def test_account_delta(api_client, db, default_namespace): ts = int(time.time() + 22) cursor = get_cursor(api_client, ts) account = default_namespace.account # Create an `account` delta default_namespace.account.sync_state = "invalid" db.session.commit() sync_data = api_client.get_data( "/delta?cursor={}&exclude_account=false".format(cursor)) assert len(sync_data["deltas"]) == 1 delta = sync_data["deltas"][0] assert delta["object"] == "account" assert delta["event"] == "modify" assert delta["attributes"]["id"] == default_namespace.public_id assert delta["attributes"]["account_id"] == default_namespace.public_id assert delta["attributes"]["email_address"] == account.email_address assert delta["attributes"]["name"] == account.name assert delta["attributes"]["provider"] == account.provider assert delta["attributes"]["organization_unit"] == account.category_type assert delta["attributes"]["sync_state"] == "invalid" cursor = sync_data["cursor_end"] # Create an new `account` delta default_namespace.account.sync_state = "running" db.session.commit() sync_data = api_client.get_data( "/delta?cursor={}&exclude_account=false".format(cursor)) assert len(sync_data["deltas"]) == 1 delta = sync_data["deltas"][0] assert delta["object"] == "account" assert delta["event"] == "modify" assert delta["attributes"]["id"] == default_namespace.public_id assert delta["attributes"]["sync_state"] == "running"
def test_gmail_search_unicode(db, api_client, test_gmail_thread, default_account, patch_crispin_client, patch_handler_from_provider, sorted_gmail_threads): Folder.find_or_create(db.session, default_account, '存档', '存档') search_client = get_search_client(default_account) assert search_client.__class__.__name__ == 'GmailSearchClient' threads = api_client.get_data('/threads/search?q=存档') for sorted_thread, result_thread in zip(sorted_gmail_threads, threads): assert sorted_thread.public_id == result_thread['id']
def test_send_existing_draft(patch_smtp, api_client, example_draft): r = api_client.post_data("/drafts", example_draft) draft_public_id = json.loads(r.data)["id"] version = json.loads(r.data)["version"] r = api_client.post_data("/send", { "draft_id": draft_public_id, "version": version }) assert r.status_code == 200 # Test that the sent draft can't be sent again. r = api_client.post_data("/send", { "draft_id": draft_public_id, "version": version }) assert r.status_code == 400 drafts = api_client.get_data("/drafts") assert not drafts message = api_client.get_data("/messages/{}".format(draft_public_id)) assert message["object"] == "message"
def test_create_and_get_draft(api_client, example_draft): r = api_client.post_data('/drafts', example_draft) assert r.status_code == 200 public_id = json.loads(r.data)['id'] version = json.loads(r.data)['version'] assert version == 0 r = api_client.get_data('/drafts') matching_saved_drafts = [draft for draft in r if draft['id'] == public_id] assert len(matching_saved_drafts) == 1 saved_draft = matching_saved_drafts[0] assert all(saved_draft[k] == v for k, v in example_draft.iteritems())
def test_get_all_drafts(api_client, example_draft): r = api_client.post_data("/drafts", example_draft) first_public_id = json.loads(r.data)["id"] r = api_client.post_data("/drafts", example_draft) second_public_id = json.loads(r.data)["id"] drafts = api_client.get_data("/drafts") assert len(drafts) == 2 assert first_public_id != second_public_id assert {first_public_id, second_public_id} == {draft["id"] for draft in drafts} assert all(item["object"] == "draft" for item in drafts)
def test_message_folders(db, generic_account): # Because we're using the generic_account namespace api_client = new_api_client(db, generic_account.namespace) # Generic IMAP threads, messages have a 'folders' field generic_thread = add_fake_thread(db.session, generic_account.namespace.id) generic_message = add_fake_message(db.session, generic_account.namespace.id, generic_thread) resp_data = api_client.get_data("/threads/{}".format( generic_thread.public_id)) assert resp_data["id"] == generic_thread.public_id assert resp_data["object"] == "thread" assert "folders" in resp_data and "labels" not in resp_data resp_data = api_client.get_data("/messages/{}".format( generic_message.public_id)) assert resp_data["id"] == generic_message.public_id assert resp_data["object"] == "message" assert "folder" in resp_data and "labels" not in resp_data
def test_get_all_drafts(api_client, example_draft): r = api_client.post_data('/drafts', example_draft) first_public_id = json.loads(r.data)['id'] r = api_client.post_data('/drafts', example_draft) second_public_id = json.loads(r.data)['id'] drafts = api_client.get_data('/drafts') assert len(drafts) == 2 assert first_public_id != second_public_id assert {first_public_id, second_public_id} == {draft['id'] for draft in drafts} assert all(item['object'] == 'draft' for item in drafts)
def test_send_existing_draft(patch_smtp, api_client, example_draft): r = api_client.post_data('/drafts', example_draft) draft_public_id = json.loads(r.data)['id'] version = json.loads(r.data)['version'] r = api_client.post_data('/send', { 'draft_id': draft_public_id, 'version': version }) assert r.status_code == 200 # Test that the sent draft can't be sent again. r = api_client.post_data('/send', { 'draft_id': draft_public_id, 'version': version }) assert r.status_code == 400 drafts = api_client.get_data('/drafts') assert not drafts message = api_client.get_data('/messages/{}'.format(draft_public_id)) assert message['object'] == 'message'
def test_message_events_are_propagated_to_thread(api_client, message): """ Test that a revision to a message's `propagated_attributes` returns a delta for the message and for its thread. """ ts = int(time.time() + 22) cursor = get_cursor(api_client, ts) message = api_client.get_data("/messages/")[0] message_id = message["id"] assert message["unread"] is True thread = api_client.get_data("/threads/{}".format(message["thread_id"])) assert thread["unread"] is True # Modify a `propagated_attribute` of the message message_path = "/messages/{}".format(message_id) api_client.put_data(message_path, {"unread": False}) # Verify that a `message` and a `thread` modify delta is returned sync_data = api_client.get_data("/delta?cursor={}".format(cursor)) deltas = sync_data["deltas"] assert len(deltas) == 2 message_deltas = [d for d in deltas if d["object"] == "message"] assert len(message_deltas) == 1 delta = message_deltas[0] assert delta["object"] == "message" and delta["event"] == "modify" assert delta["attributes"]["unread"] is False thread_deltas = [d for d in deltas if d["object"] == "thread"] assert len(thread_deltas) == 1 delta = thread_deltas[0] assert delta["object"] == "thread" and delta["event"] == "modify" assert delta["attributes"]["unread"] is False assert delta["attributes"]["version"] == thread["version"] + 1
def test_api_get(db, api_client, calendar): e_data = { 'title': 'subj', 'when': { 'time': 1 }, 'calendar_id': calendar.public_id, 'location': 'InboxHQ' } e_data2 = { 'title': 'subj2', 'when': { 'time': 1 }, 'calendar_id': calendar.public_id, 'location': 'InboxHQ' } api_client.post_data('/events', e_data) api_client.post_data('/events', e_data2) event_list = api_client.get_data('/events') event_ids = [event['id'] for event in event_list] c1found = False c2found = False for c_id in event_ids: event = api_client.get_data('/events/' + c_id) if event['title'] == 'subj': c1found = True if event['title'] == 'subj2': c2found = True assert c1found assert c2found
def test_api_get(db, api_client, calendar): e_data = { "title": "subj", "when": { "time": 1 }, "calendar_id": calendar.public_id, "location": "NylasHQ", } e_data2 = { "title": "subj2", "when": { "time": 1 }, "calendar_id": calendar.public_id, "location": "NylasHQ", } api_client.post_data("/events", e_data) api_client.post_data("/events", e_data2) event_list = api_client.get_data("/events") event_ids = [event["id"] for event in event_list] c1found = False c2found = False for c_id in event_ids: event = api_client.get_data("/events/" + c_id) if event["title"] == "subj": c1found = True if event["title"] == "subj2": c2found = True assert c1found assert c2found
def test_message_events_are_propagated_to_thread(api_client, message): """ Test that a revision to a message's `propagated_attributes` returns a delta for the message and for its thread. """ ts = int(time.time() + 22) cursor = get_cursor(api_client, ts) message = api_client.get_data('/messages/')[0] message_id = message['id'] assert message['unread'] is True thread = api_client.get_data('/threads/{}'.format(message['thread_id'])) assert thread['unread'] is True # Modify a `propagated_attribute` of the message message_path = '/messages/{}'.format(message_id) api_client.put_data(message_path, {'unread': False}) # Verify that a `message` and a `thread` modify delta is returned sync_data = api_client.get_data('/delta?cursor={}'.format(cursor)) deltas = sync_data['deltas'] assert len(deltas) == 2 message_deltas = [d for d in deltas if d['object'] == 'message'] assert len(message_deltas) == 1 delta = message_deltas[0] assert delta['object'] == 'message' and delta['event'] == 'modify' assert delta['attributes']['unread'] is False thread_deltas = [d for d in deltas if d['object'] == 'thread'] assert len(thread_deltas) == 1 delta = thread_deltas[0] assert delta['object'] == 'thread' and delta['event'] == 'modify' assert delta['attributes']['unread'] is False assert delta['attributes']['version'] == thread['version'] + 1
def test_api_expand_recurring_before_after(db, api_client, recurring_event): event = recurring_event starts_after = event.start.replace(weeks=+15) ends_before = starts_after.replace(days=+1) recur = 'expand_recurring=true&starts_after={}&ends_before={}'.format( urlsafe(starts_after), urlsafe(ends_before)) all_events = api_client.get_data('/events?' + recur) assert len(all_events) == 1 recur = 'expand_recurring=true&starts_after={}&starts_before={}'.format( urlsafe(starts_after), urlsafe(ends_before)) all_events = api_client.get_data('/events?' + recur) assert len(all_events) == 1 recur = 'expand_recurring=true&ends_after={}&starts_before={}'.format( urlsafe(starts_after), urlsafe(ends_before)) all_events = api_client.get_data('/events?' + recur) assert len(all_events) == 1 recur = 'expand_recurring=true&ends_after={}&ends_before={}'.format( urlsafe(starts_after), urlsafe(ends_before)) all_events = api_client.get_data('/events?' + recur) assert len(all_events) == 1
def test_query_target(db, api_client, thread, default_namespace): cat = Category( namespace_id=default_namespace.id, name="inbox", display_name="Inbox", type_="label", ) for _ in range(3): message = add_fake_message( db.session, default_namespace.id, thread, to_addr=[("Bob", "*****@*****.**")], from_addr=[("Alice", "*****@*****.**")], subject="some subject", ) message.categories.add(cat) db.session.commit() results = api_client.get_data("/messages?in=inbox") assert len(results) == 3 count = api_client.get_data("/messages?in=inbox&view=count") assert count["count"] == 3
def test_handle_missing_objects(api_client, db, thread, default_namespace): ts = int(time.time() + 22) cursor = get_cursor(api_client, ts) messages = [] for _ in range(100): messages.append( add_fake_message(db.session, default_namespace.id, thread)) for message in messages: db.session.delete(message) db.session.commit() sync_data = api_client.get_data( '/delta?cursor={}&exclude_types=thread'.format(cursor)) assert len(sync_data['deltas']) == 100 assert all(delta['event'] == 'delete' for delta in sync_data['deltas'])
def test_get_calendar(db, default_namespace, api_client): cal = Calendar(namespace_id=default_namespace.id, uid='uid', provider_name='WTF', name='Holidays') db.session.add(cal) db.session.commit() cal_id = cal.public_id calendar_item = api_client.get_data('/calendars/{}'.format(cal_id)) assert calendar_item['account_id'] == default_namespace.public_id assert calendar_item['name'] == 'Holidays' assert calendar_item['description'] is None assert calendar_item['read_only'] is False assert calendar_item['object'] == 'calendar'
def test_reply_headers_set(db, patch_smtp, api_client, example_draft, thread, message): message.message_id_header = '<*****@*****.**>' db.session.commit() thread_id = api_client.get_data('/threads')[0]['id'] api_client.post_data('/send', { 'to': [{ 'email': '*****@*****.**' }], 'thread_id': thread_id }) _, msg = patch_smtp[-1] parsed = mime.from_string(msg) assert 'In-Reply-To' in parsed.headers assert 'References' in parsed.headers
def test_reply_headers_set(db, patch_smtp, api_client, example_draft, thread, message): message.message_id_header = "<*****@*****.**>" db.session.commit() thread_id = api_client.get_data("/threads")[0]["id"] api_client.post_data("/send", { "to": [{ "email": "*****@*****.**" }], "thread_id": thread_id }) _, msg = patch_smtp[-1] parsed = mime.from_string(msg) assert "In-Reply-To" in parsed.headers assert "References" in parsed.headers
def test_file_filtering(api_client, uploaded_file_ids, draft): # Attach the files to a draft and search there draft["file_ids"] = uploaded_file_ids r = api_client.post_data("/drafts", draft) assert r.status_code == 200 draft_resp = json.loads(r.data) assert len(draft_resp["files"]) == len(uploaded_file_ids) d_id = draft_resp["id"] results = api_client.get_data("/files") assert len(results) == len(uploaded_file_ids) results = api_client.get_data("/files?message_id={}".format(d_id)) assert all([d_id in f["message_ids"] for f in results]) assert len(results) == len(uploaded_file_ids) results = api_client.get_data("/files?message_id={}&limit=1".format(d_id)) assert len(results) == 1 results = api_client.get_data("/files?message_id={}&offset=2".format(d_id)) assert len(results) == 3 results = api_client.get_data("/files?filename=LetMeSendYouEmail.wav") assert len(results) == 1 results = api_client.get_data("/files?content_type=audio%2Fx-wav") assert len(results) == 1 results = api_client.get_data("/files?content_type=image%2Fjpeg") assert len(results) == 2 results = api_client.get_data( "/files?content_type=image%2Fjpeg&view=count") assert results["count"] == 2 results = api_client.get_data("/files?content_type=image%2Fjpeg&view=ids") assert len(results) == 2
def test_get_calendar(db, default_namespace, api_client): cal = Calendar( namespace_id=default_namespace.id, uid="uid", provider_name="WTF", name="Holidays", ) db.session.add(cal) db.session.commit() cal_id = cal.public_id calendar_item = api_client.get_data("/calendars/{}".format(cal_id)) assert calendar_item["account_id"] == default_namespace.public_id assert calendar_item["name"] == "Holidays" assert calendar_item["description"] is None assert calendar_item["read_only"] is False assert calendar_item["object"] == "calendar"
def test_download(api_client, uploaded_file_ids, filename): # See comment in uploaded_file_ids() original_filename = filename if filename == 'piece-jointe.jpg': filename = u'pièce-jointe.jpg' elif filename == 'andra-moi-ennepe.txt': filename = u'ἄνδρα μοι ἔννεπε' in_file = api_client.get_data(u'/files?filename={}'.format(filename))[0] data = api_client.get_raw('/files/{}/download'.format(in_file['id'])).data path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'data', original_filename.encode('utf-8')) local_data = open(path, 'rb').read() local_md5 = md5.new(local_data).digest() dl_md5 = md5.new(data).digest() assert local_md5 == dl_md5
def test_api_expand_recurring(db, api_client, recurring_event): event = recurring_event events = api_client.get_data('/events?expand_recurring=false') assert len(events) == 1 # Make sure the recurrence info is on the recurring event for e in events: if e['title'] == 'recurring-weekly': assert e.get('recurrence') is not None thirty_weeks = event.start.replace(weeks=+30).isoformat() starts_after = event.start.replace(days=-1).isoformat() recur = 'expand_recurring=true&starts_after={}&ends_before={}'.format( urllib.quote_plus(starts_after), urllib.quote_plus(thirty_weeks)) all_events = api_client.get_data('/events?' + recur) if not event.all_day: assert len(all_events) == 28 # the ordering should be correct prev = all_events[0]['when']['start_time'] for e in all_events[1:]: assert e['when']['start_time'] > prev prev = e['when']['start_time'] # Check that the parent event recurring id is included # too. assert e['calendar_id'] == recurring_event.calendar.public_id events = api_client.get_data('/events?' + recur + '&view=count') assert events.get('count') == 28 else: # Since an all-day event starts at 00:00 we're returning one # more event. assert len(all_events) == 29 # the ordering should be correct prev = all_events[0]['when']['date'] for e in all_events[1:]: assert e['when']['date'] > prev prev = e['when']['date'] # Check that the parent event recurring id is included # too. assert e['calendar_id'] == recurring_event.calendar.public_id events = api_client.get_data('/events?' + recur + '&view=count') assert events.get('count') == 29 events = api_client.get_data('/events?' + recur + '&limit=5') assert len(events) == 5 events = api_client.get_data('/events?' + recur + '&offset=5') assert events[0]['id'] == all_events[5]['id']
def test_api_expand_recurring(db, api_client, recurring_event): event = recurring_event events = api_client.get_data("/events?expand_recurring=false") assert len(events) == 1 # Make sure the recurrence info is on the recurring event for e in events: if e["title"] == "recurring-weekly": assert e.get("recurrence") is not None thirty_weeks = event.start.replace(weeks=+30).isoformat() starts_after = event.start.replace(days=-1).isoformat() recur = "expand_recurring=true&starts_after={}&ends_before={}".format( urllib.quote_plus(starts_after), urllib.quote_plus(thirty_weeks)) all_events = api_client.get_data("/events?" + recur) if not event.all_day: assert len(all_events) == 28 # the ordering should be correct prev = all_events[0]["when"]["start_time"] for e in all_events[1:]: assert e["when"]["start_time"] > prev prev = e["when"]["start_time"] # Check that the parent event recurring id is included # too. assert e["calendar_id"] == recurring_event.calendar.public_id events = api_client.get_data("/events?" + recur + "&view=count") assert events.get("count") == 28 else: # Since an all-day event starts at 00:00 we're returning one # more event. assert len(all_events) == 29 # the ordering should be correct prev = all_events[0]["when"]["date"] for e in all_events[1:]: assert e["when"]["date"] > prev prev = e["when"]["date"] # Check that the parent event recurring id is included # too. assert e["calendar_id"] == recurring_event.calendar.public_id events = api_client.get_data("/events?" + recur + "&view=count") assert events.get("count") == 29 events = api_client.get_data("/events?" + recur + "&limit=5") assert len(events) == 5 events = api_client.get_data("/events?" + recur + "&offset=5") assert events[0]["id"] == all_events[5]["id"]
def test_add_to_read_only_calendar(db, api_client): cal_list = api_client.get_data("/calendars") ro_cal = None for c in cal_list: if c["read_only"]: ro_cal = c assert ro_cal e_data = { "calendar_id": ro_cal["id"], "title": "subj", "description": "body1", "when": {"time": 1}, "location": "NylasHQ", } resp = api_client.post_data("/events", e_data) assert resp.status_code == 400
def test_api_update_read_only(db, api_client, calendar, default_namespace): add_fake_event(db.session, default_namespace.id, calendar=calendar, read_only=True) event_list = api_client.get_data('/events') read_only_event = None for e in event_list: if e['read_only']: read_only_event = e break assert read_only_event e_id = read_only_event['id'] e_update_data = {'title': 'new title'} e_put_resp = api_client.put_data('/events/' + e_id, e_update_data) assert e_put_resp.status_code != 200