Exemplo n.º 1
0
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']
Exemplo n.º 2
0
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
Exemplo n.º 3
0
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
Exemplo n.º 4
0
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
Exemplo n.º 5
0
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
Exemplo n.º 6
0
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
Exemplo n.º 7
0
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
Exemplo n.º 8
0
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
Exemplo n.º 9
0
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
Exemplo n.º 10
0
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
Exemplo n.º 11
0
def test_namespace_limiting(db, api_client, default_namespaces):
    dt = datetime.datetime.utcnow()
    subject = dt.isoformat()
    namespaces = db.session.query(Namespace).all()
    assert len(namespaces) > 1
    for ns in namespaces:
        thread = Thread(namespace=ns,
                        subjectdate=dt,
                        recentdate=dt,
                        subject=subject)
        add_fake_message(db.session,
                         ns.id,
                         thread,
                         received_date=dt,
                         subject=subject)
        db.session.add(Block(namespace=ns, filename=subject))
    db.session.commit()

    for ns in namespaces:
        r = api_client.get_data('/threads?subject={}'.format(subject))
        assert len(r) == 1

        r = api_client.get_data('/messages?subject={}'.format(subject))
        assert len(r) == 1

        r = api_client.get_data('/files?filename={}'.format(subject))
        assert len(r) == 1
Exemplo n.º 12
0
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
Exemplo n.º 13
0
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
Exemplo n.º 14
0
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
Exemplo n.º 15
0
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'ἄνδρα μοι ἔννεπε'
    elif filename == 'long-non-ascii-filename.txt':
        filename = 100 * 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
Exemplo n.º 16
0
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'ἄνδρα μοι ἔννεπε'
    elif filename == 'long-non-ascii-filename.txt':
        filename = 100 * 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
Exemplo n.º 17
0
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)
Exemplo n.º 18
0
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)
Exemplo n.º 19
0
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
Exemplo n.º 20
0
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
Exemplo n.º 21
0
def test_api_create(db, api_client, calendar, default_account):
    e_data = {
        'title': 'Friday Office Party',
        'calendar_id': calendar.public_id,
        'when': {
            'time': 1407542195
        },
        'location': 'Nylas HQ',
    }

    e_resp = api_client.post_data('/events', e_data)
    e_resp_data = json.loads(e_resp.data)
    assert e_resp_data['object'] == 'event'
    assert e_resp_data['account_id'] == default_account.namespace.public_id
    assert e_resp_data['title'] == e_data['title']
    assert e_resp_data['location'] == e_data['location']
    assert e_resp_data['when']['time'] == e_data['when']['time']
    assert 'id' in e_resp_data
    e_id = e_resp_data['id']
    e_get_resp = api_client.get_data('/events/' + e_id)

    assert e_get_resp['object'] == 'event'
    assert e_get_resp['account_id'] == default_account.namespace.public_id
    assert e_get_resp['id'] == e_id
    assert e_get_resp['title'] == e_data['title']
    assert e_get_resp['when']['time'] == e_data['when']['time']
Exemplo n.º 22
0
def test_adding_a_custom_label_preserves_other_labels(db, api_client,
                                                      default_account,
                                                      folder_and_message_maps,
                                                      label):
    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']

    custom_label = add_fake_label(db.session, default_account, '<3', None)
    db.session.commit()

    # Adding only a custom label does not move a message to a different folder
    # i.e. does not change its 'all'/ 'trash'/ 'spam' labels.
    response = api_client.put_data(
        '/messages/{}'.format(message.public_id),
        {'label_ids': [custom_label.category.public_id, existing_label]})
    labels = json.loads(response.data)['labels']
    assert len(labels) == 2
    assert set([l['name'] for l in labels]) == set([label, None])
    assert '<3' in [l['display_name'] for l in labels]
Exemplo n.º 23
0
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
Exemplo n.º 24
0
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
Exemplo n.º 25
0
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
Exemplo n.º 26
0
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'])
Exemplo n.º 27
0
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]
Exemplo n.º 28
0
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
Exemplo n.º 29
0
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'
Exemplo n.º 30
0
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
Exemplo n.º 31
0
def test_api_pessimistic_delete(db, api_client, calendar, default_account):
    e_data = {
        'title': '',
        'calendar_id': calendar.public_id,
        'when': {
            'time': 1407542195
        },
    }

    e_resp = api_client.post_data('/events',
                                  e_data,
                                  headers={"Api-Version": API_VERSIONS[1]})
    e_resp_data = json.loads(e_resp.data)
    assert e_resp_data['object'] == 'event'
    assert e_resp_data['title'] == e_data['title']
    assert e_resp_data['when']['time'] == e_data['when']['time']
    assert 'id' in e_resp_data
    e_id = e_resp_data['id']

    e_delete_resp = api_client.delete('/events/' + e_id,
                                      headers={"Api-Version": API_VERSIONS[1]})
    assert e_delete_resp.status_code == 200

    e_resp = api_client.get_data('/events/' + e_id)
    assert e_resp['status'] == 'confirmed'
Exemplo n.º 32
0
def test_api_list(db, api_client, calendar):
    e_data = {'title': 'subj', 'description': 'body1',
              'calendar_id': calendar.public_id,
              'when': {'time': 1}, 'location': 'NylasHQ'}
    e_data2 = {'title': 'subj2', 'description': 'body2',
               'calendar_id': calendar.public_id,
               'when': {'time': 1}, 'location': 'NylasHQ'}
    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()
Exemplo n.º 33
0
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]
Exemplo n.º 34
0
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'] == []
Exemplo n.º 35
0
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
Exemplo n.º 36
0
def test_send_draft(db, api_client, example_draft, default_account):

    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']

    r = api_client.post_data('/send', {'draft_id': public_id,
                                       'version': version})
    assert r.status_code == 200

    draft = api_client.get_data('/drafts/{}'.format(public_id))
    assert draft is not None

    assert draft['object'] != 'draft'

    with crispin_client(default_account.id, default_account.provider) as c:
        criteria = ['NOT DELETED', 'SUBJECT "{0}"'.format(
            example_draft['subject'])]

        c.conn.select_folder(default_account.drafts_folder.name,
                             readonly=False)

        draft_uids = c.conn.search(criteria)
        assert not draft_uids, 'Message still in Drafts folder'

        c.conn.select_folder(default_account.sent_folder.name, readonly=False)

        sent_uids = c.conn.search(criteria)
        assert sent_uids, 'Message missing from Sent folder'

        c.conn.delete_messages(sent_uids)
        c.conn.expunge()
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'
Exemplo n.º 38
0
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'] == []
Exemplo n.º 39
0
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'])
Exemplo n.º 40
0
def test_delete_remote_draft(db, api_client, message):
    message.is_draft = True
    db.session.commit()

    drafts = api_client.get_data('/drafts')
    assert len(drafts) == 1

    public_id = drafts[0]['id']
    version = drafts[0]['version']

    assert public_id == message.public_id and version == message.version

    api_client.delete('/drafts/{}'.format(public_id), {'version': version})

    # Check that drafts were deleted
    drafts = api_client.get_data('/drafts')
    assert not drafts
Exemplo n.º 41
0
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
Exemplo n.º 42
0
def test_delete_remote_draft(db, api_client, message):
    message.is_draft = True
    db.session.commit()

    drafts = api_client.get_data('/drafts')
    assert len(drafts) == 1

    public_id = drafts[0]['id']
    version = drafts[0]['version']

    assert public_id == message.public_id and version == message.version

    api_client.delete('/drafts/{}'.format(public_id),
                      {'version': version})

    # Check that drafts were deleted
    drafts = api_client.get_data('/drafts')
    assert not drafts
Exemplo n.º 43
0
def test_ordering(api_client, db, default_namespace):
    for i in range(3):
        thr = add_fake_thread(db.session, default_namespace.id)
        received_date = (datetime.datetime.utcnow() +
                         datetime.timedelta(seconds=22 * (i + 1)))
        add_fake_message(db.session, default_namespace.id,
                         thr, received_date=received_date)
    ordered_results = api_client.get_data('/messages')
    ordered_dates = [result['date'] for result in ordered_results]
    assert ordered_dates == sorted(ordered_dates, reverse=True)

    ordered_results = api_client.get_data('/messages?limit=3')
    expected_public_ids = [
        public_id for public_id, in
        db.session.query(Message.public_id).
        filter(Message.namespace_id == default_namespace.id).
        order_by(desc(Message.received_date)).limit(3)]
    assert expected_public_ids == [r['id'] for r in ordered_results]
Exemplo n.º 44
0
def test_resource_views(resource_name, db, api_client, generic_account,
                        message, thread, event, label, contact, folder):
    """Exercises various tests for views, mostly related to
    filtering. Note: this only tests views, it assumes the
    resources are working as expected."""
    # Folders don't work with GMail accounts, need generic IMAP
    if resource_name == 'folders':
        api_client = new_api_client(db, generic_account.namespace)
    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"
Exemplo n.º 45
0
def test_multisend_init_new_draft(patch_smtp, api_client, example_draft):
    r = api_client.post_data('/send-multiple', example_draft)
    assert r.status_code == 200
    draft_public_id = json.loads(r.data)['id']

    # Test that the sent draft can't be sent normally now
    r = api_client.post_data('/send', {
        'draft_id': draft_public_id,
        'version': 0
    })
    assert r.status_code == 400

    # It's not a draft anymore
    drafts = api_client.get_data('/drafts')
    assert not drafts

    # We can retrieve it as a message, but it's not "sent" yet
    message = api_client.get_data('/messages/{}'.format(draft_public_id))
    assert message['object'] == 'message'
Exemplo n.º 46
0
def test_multisend_init_new_draft(patch_smtp, api_client, example_draft):
    r = api_client.post_data('/send-multiple',
                             example_draft)
    assert r.status_code == 200
    draft_public_id = json.loads(r.data)['id']

    # Test that the sent draft can't be sent normally now
    r = api_client.post_data('/send',
                             {'draft_id': draft_public_id,
                              'version': 0})
    assert r.status_code == 400

    # It's not a draft anymore
    drafts = api_client.get_data('/drafts')
    assert not drafts

    # We can retrieve it as a message, but it's not "sent" yet
    message = api_client.get_data('/messages/{}'.format(draft_public_id))
    assert message['object'] == 'message'
Exemplo n.º 47
0
def test_resource_views(resource_name, db, api_client, generic_account,
                        message, thread, event, label, contact, folder):
    """Exercises various tests for views, mostly related to
    filtering. Note: this only tests views, it assumes the
    resources are working as expected."""
    # Folders don't work with GMail accounts, need generic IMAP
    if resource_name == 'folders':
        api_client = new_api_client(db, generic_account.namespace)
    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"
Exemplo n.º 48
0
def test_delete(api_client, uploaded_file_ids, draft):
    non_attachment_id = uploaded_file_ids.pop()
    attachment_id = uploaded_file_ids.pop()
    draft['file_ids'] = [attachment_id]
    r = api_client.post_data('/drafts', draft)
    assert r.status_code == 200

    # Test that we can delete a non-attachment
    r = api_client.delete('/files/{}'.format(non_attachment_id))
    assert r.status_code == 200

    data = api_client.get_data('/files/{}'.format(non_attachment_id))
    assert data['message'].startswith("Couldn't find file")

    # Make sure that we cannot delete attachments
    r = api_client.delete('/files/{}'.format(attachment_id))
    assert r.status_code == 400

    data = api_client.get_data('/files/{}'.format(attachment_id))
    assert data['id'] == attachment_id
Exemplo n.º 49
0
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
Exemplo n.º 50
0
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
Exemplo n.º 51
0
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']
Exemplo n.º 52
0
def test_ordering(api_client, db, default_namespace):
    for i in range(3):
        thr = add_fake_thread(db.session, default_namespace.id)
        received_date = (datetime.datetime.utcnow() +
                         datetime.timedelta(seconds=22 * (i + 1)))
        add_fake_message(db.session,
                         default_namespace.id,
                         thr,
                         received_date=received_date)
    ordered_results = api_client.get_data('/messages')
    ordered_dates = [result['date'] for result in ordered_results]
    assert ordered_dates == sorted(ordered_dates, reverse=True)

    ordered_results = api_client.get_data('/messages?limit=3')
    expected_public_ids = [
        public_id for public_id, in db.session.query(Message.public_id).filter(
            Message.namespace_id == default_namespace.id).order_by(
                desc(Message.received_date)).limit(3)
    ]
    assert expected_public_ids == [r['id'] for r in ordered_results]
Exemplo n.º 53
0
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
Exemplo n.º 54
0
def test_delete(api_client, uploaded_file_ids, draft):
    non_attachment_id = uploaded_file_ids.pop()
    attachment_id = uploaded_file_ids.pop()
    draft['file_ids'] = [attachment_id]
    r = api_client.post_data('/drafts', draft)
    assert r.status_code == 200

    # Test that we can delete a non-attachment
    r = api_client.delete('/files/{}'.format(non_attachment_id))
    assert r.status_code == 200

    data = api_client.get_data('/files/{}'.format(non_attachment_id))
    assert data['message'].startswith("Couldn't find file")

    # Make sure that we cannot delete attachments
    r = api_client.delete('/files/{}'.format(attachment_id))
    assert r.status_code == 400

    data = api_client.get_data('/files/{}'.format(attachment_id))
    assert data['id'] == attachment_id
Exemplo n.º 55
0
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']