def test_write_endpoints(db, setup_account, api_client, default_account): # Write operations (create, update, delete) succeed. r = api_client.post_data( "/drafts", data={"body": "<html><body><h2>Sea, birds and sand.</h2></body></html>"}, ) assert r.status_code == 200 draft_id = json.loads(r.data)["id"] endpoint = "/messages/{}".format(setup_account["message"]) r = api_client.put_data(endpoint, data={"starred": True}) assert r.status_code == 200 endpoint = "/events/{}".format(setup_account["event"]) r = api_client.delete(endpoint) assert r.status_code == 200 default_account.sync_state = "invalid" db.session.commit() # Write operations fail with an HTTP 403. r = api_client.post_data("/labels", data={"display_name": "Neu!"}) assert r.status_code == 403 endpoint = "/threads/{}".format(setup_account["thread"]) r = api_client.put_data(endpoint, data={"starred": True}) assert r.status_code == 403 endpoint = "/drafts/{}".format(draft_id) r = api_client.delete(endpoint) assert r.status_code == 403
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_write_endpoints(db, setup_account, api_client, default_account): # Write operations (create, update, delete) succeed. r = api_client.post_data( '/drafts', data={ 'body': '<html><body><h2>Sea, birds and sand.</h2></body></html>' }) assert r.status_code == 200 draft_id = json.loads(r.data)['id'] endpoint = '/messages/{}'.format(setup_account['message']) r = api_client.put_data(endpoint, data={"starred": True}) assert r.status_code == 200 endpoint = '/events/{}'.format(setup_account['event']) r = api_client.delete(endpoint) assert r.status_code == 200 default_account.sync_state = 'invalid' db.session.commit() # Write operations fail with an HTTP 403. r = api_client.post_data('/labels', data={"display_name": "Neu!"}) assert r.status_code == 403 endpoint = '/threads/{}'.format(setup_account['thread']) r = api_client.put_data(endpoint, data={"starred": True}) assert r.status_code == 403 endpoint = '/drafts/{}'.format(draft_id) r = api_client.delete(endpoint) assert r.status_code == 403
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_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_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_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_api_remove_participant(db, api_client, calendar): e_data = { 'title': 'Friday Office Party', 'when': {'time': 1407542195}, 'calendar_id': calendar.public_id, 'participants': [{'email': '*****@*****.**'}, {'email': '*****@*****.**'}, {'email': '*****@*****.**'}, {'email': '*****@*****.**'}, {'email': '*****@*****.**'}] } e_resp = api_client.post_data('/events', e_data) e_resp_data = json.loads(e_resp.data) assert len(e_resp_data['participants']) == 5 for i, p in enumerate(e_resp_data['participants']): res = [e for e in e_resp_data['participants'] if e['email'] == p['email']] assert len(res) == 1 assert res[0]['name'] is None event_id = e_resp_data['id'] e_data['participants'].pop() e_resp = api_client.put_data('/events/' + event_id, e_data) e_resp_data = json.loads(e_resp.data) assert len(e_resp_data['participants']) == 4 for i, p in enumerate(e_resp_data['participants']): res = [e for e in e_resp_data['participants'] if e['email'] == p['email']] assert len(res) == 1 assert p['name'] is None
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_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 # Check that contacts aren't created for garbage recipients. r = api_client.post_data("/drafts", {"to": [{"name": "who", "email": "nope"}]}) assert r.status_code == 200 r = api_client.get_data("/threads?to=nope") assert len(r) == 0 r = api_client.get_data("/contacts?filter=nope") assert len(r) == 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]
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_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_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_adding_trash_or_spam_removes_inbox( db, api_client, default_account, folder_and_message_maps, label ): # Verify a Gmail message in 'trash', 'spam' cannot have 'inbox'. # We specifically test that adding 'trash'/ 'spam' to a message with 'inbox' # removes it. folder_map, message_map = folder_and_message_maps message = message_map["all"] add_inbox_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 set([l["name"] for l in labels]) == set(["all", "inbox"]) # Adding 'trash'/ 'spam' removes 'inbox' (and 'all'), # irrespective of whether it's provided in the request or not. label_to_add = folder_map[label] 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) == 1 assert labels[0]["name"] == label
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_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_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_api_pessimistic_update(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["account_id"] == default_account.namespace.public_id 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_update_data = {"title": "new title"} e_put_resp = api_client.put_data("/events/" + e_id, e_update_data, headers={"Api-Version": API_VERSIONS[1]}) e_put_data = json.loads(e_put_resp.data) assert e_put_data["object"] == "event" assert e_put_data["account_id"] == default_account.namespace.public_id assert e_put_data["id"] == e_id assert e_put_data["title"] == "" assert e_put_data["when"]["object"] == "time" assert e_put_data["when"]["time"] == e_data["when"]["time"]
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_api_update_title(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) 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['when']['time'] == e_data['when']['time'] assert 'id' in e_resp_data e_id = e_resp_data['id'] e_update_data = {'title': 'new title'} e_put_resp = api_client.put_data('/events/' + e_id, e_update_data) e_put_data = json.loads(e_put_resp.data) assert e_put_data['object'] == 'event' assert e_put_data['account_id'] == default_account.namespace.public_id assert e_put_data['id'] == e_id assert e_put_data['title'] == 'new title' assert e_put_data['when']['object'] == 'time' assert e_put_data['when']['time'] == e_data['when']['time']
def test_update_draft(api_client): 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 # Sleep so that timestamp on updated draft is different. gevent.sleep(1) 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_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_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_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_folders_labels_delete(db, api_client, generic_account, gmail_account): 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 = api_client.post_data('/folders/', {"display_name": "Test_Folder"}) assert resp.status_code == 200 generic_folder = json.loads(resp.data) data = {"folder_id": generic_folder['id']} # Add message to folder api_client.put_data('/messages/{}'.format(generic_message.public_id), data) # try deleting folder that contains a message delete_data = api_client.delete('/folders/{}'.format(generic_folder['id'])) assert delete_data.status_code == 403 resp = api_client.post_data('/folders/', {"display_name": "Test_Folder2"}) empty_folder = json.loads(resp.data) # try deleting folder that contains a message delete_data = api_client.delete('/folders/{}'.format(empty_folder['id'])) assert delete_data.status_code == 200 # Because we're using the generic_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 = api_client.post_data('/labels/', {"display_name": "Test_Labels"}) assert resp.status_code == 200 gmail_label = json.loads(resp.data) data = {"folder_id": gmail_label['id']} # Add label to message api_client.put_data('/messages/{}'.format(gmail_message.public_id), data) # try deleting label delete_data = api_client.delete('/labels/{}'.format(gmail_label['id'])) assert delete_data.status_code == 200
def test_validation(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"] # Adding more than one mutually exclusive label is not allowed. # For example, adding 'trash' and 'spam'. # (Adding one is okay because it's simply replaced). labels_to_add = [] for key in message_map: if key == label: continue labels_to_add += [folder_map[key].category.public_id] response = api_client.put_data( "/messages/{}".format(message.public_id), {"label_ids": labels_to_add} ) resp_data = json.loads(response.data) assert response.status_code == 400 assert resp_data.get("type") == "invalid_request_error" response = api_client.put_data( "/messages/{}".format(message.public_id), {"label_ids": labels_to_add + [existing_label]}, ) resp_data = json.loads(response.data) assert response.status_code == 400 assert resp_data.get("type") == "invalid_request_error" # Removing all labels is not allowed, because this will remove # the required label (one of 'all'/ 'trash'/ 'spam') too. response = api_client.put_data( "/messages/{}".format(message.public_id), {"label_ids": []} ) resp_data = json.loads(response.data) assert response.status_code == 400 assert resp_data.get("type") == "invalid_request_error"
def test_validation(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'] # Adding more than one mutually exclusive label is not allowed. # For example, adding 'trash' and 'spam'. # (Adding one is okay because it's simply replaced). labels_to_add = [] for key in message_map: if key == label: continue labels_to_add += [folder_map[key].category.public_id] response = api_client.put_data( '/messages/{}'.format(message.public_id), {'label_ids': labels_to_add}) resp_data = json.loads(response.data) assert response.status_code == 400 assert resp_data.get('type') == 'invalid_request_error' response = api_client.put_data( '/messages/{}'.format(message.public_id), {'label_ids': labels_to_add + [existing_label]}) resp_data = json.loads(response.data) assert response.status_code == 400 assert resp_data.get('type') == 'invalid_request_error' # Removing all labels is not allowed, because this will remove # the required label (one of 'all'/ 'trash'/ 'spam') too. response = api_client.put_data( '/messages/{}'.format(message.public_id), {'label_ids': []}) resp_data = json.loads(response.data) assert response.status_code == 400 assert resp_data.get('type') == 'invalid_request_error'
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_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_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_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_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_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 # Check that contacts aren't created for garbage recipients. r = api_client.post_data('/drafts', {'to': [{ 'name': 'who', 'email': 'nope' }]}) assert r.status_code == 200 r = api_client.get_data('/threads?to=nope') assert len(r) == 0 r = api_client.get_data('/contacts?filter=nope') assert len(r) == 0
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 # Check that contacts aren't created for garbage recipients. r = api_client.post_data("/drafts", {"to": [{ "name": "who", "email": "nope" }]}) assert r.status_code == 200 r = api_client.get_data("/threads?to=nope") assert len(r) == 0 r = api_client.get_data("/contacts?filter=nope") assert len(r) == 0
def test_delete_draft(api_client, thread, message): original_draft = { 'subject': 'parent 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'] 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'] r = api_client.delete('/drafts/{}'.format(updated_public_id), {'version': updated_version}) # Check that drafts were deleted drafts = api_client.get_data('/drafts') assert not drafts # Check that no orphaned threads are around threads = api_client.get_data('/threads?subject=parent%20draft') assert not threads threads = api_client.get_data('/threads?subject=updated%20draft') assert not threads # And check that threads aren't deleted if they still have messages. thread_public_id = api_client.get_data('/threads')[0]['id'] reply_draft = { 'subject': 'test reply', 'body': 'test reply', 'thread_id': thread_public_id } r = api_client.post_data('/drafts', reply_draft) public_id = json.loads(r.data)['id'] version = json.loads(r.data)['version'] thread = api_client.get_data('/threads/{}'.format(thread_public_id)) assert len(thread['draft_ids']) > 0 api_client.delete('/drafts/{}'.format(public_id), {'version': version}) thread = api_client.get_data('/threads/{}'.format(thread_public_id)) assert thread assert len(thread['draft_ids']) == 0
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_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_delete_draft(api_client, thread, message): original_draft = {"subject": "parent 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"] 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"] r = api_client.delete("/drafts/{}".format(updated_public_id), {"version": updated_version}) # Check that drafts were deleted drafts = api_client.get_data("/drafts") assert not drafts # Check that no orphaned threads are around threads = api_client.get_data("/threads?subject=parent%20draft") assert not threads threads = api_client.get_data("/threads?subject=updated%20draft") assert not threads # And check that threads aren't deleted if they still have messages. thread_public_id = api_client.get_data("/threads")[0]["id"] reply_draft = { "subject": "test reply", "body": "test reply", "thread_id": thread_public_id, } r = api_client.post_data("/drafts", reply_draft) public_id = json.loads(r.data)["id"] version = json.loads(r.data)["version"] thread = api_client.get_data("/threads/{}".format(thread_public_id)) assert len(thread["draft_ids"]) > 0 api_client.delete("/drafts/{}".format(public_id), {"version": version}) thread = api_client.get_data("/threads/{}".format(thread_public_id)) assert thread assert len(thread["draft_ids"]) == 0
def test_api_update_participant_status(db, api_client, calendar): e_data = { 'title': 'Friday Office Party', 'when': {'time': 1407542195}, 'calendar_id': calendar.public_id, 'participants': [{'email': '*****@*****.**'}, {'email': '*****@*****.**'}, {'email': '*****@*****.**'}, {'email': '*****@*****.**'}, {'email': '*****@*****.**'}] } e_resp = api_client.post_data('/events', e_data) e_resp_data = json.loads(e_resp.data) assert len(e_resp_data['participants']) == 5 for i, p in enumerate(e_resp_data['participants']): res = [e for e in e_data['participants'] if e['email'] == p['email']] assert len(res) == 1 assert p['name'] is None event_id = e_resp_data['id'] update_data = { 'calendar_id': calendar.public_id, 'participants': [{'email': '*****@*****.**', 'status': 'yes'}, {'email': '*****@*****.**', 'status': 'no'}, {'email': '*****@*****.**', 'status': 'maybe'}, {'email': '*****@*****.**'}, {'email': '*****@*****.**'}] } e_resp = api_client.put_data('/events/' + event_id, update_data) e_resp_data = json.loads(e_resp.data) # Make sure that nothing changed that we didn't specify assert e_resp_data['title'] == 'Friday Office Party' assert e_resp_data['when']['time'] == 1407542195 assert len(e_resp_data['participants']) == 5 for i, p in enumerate(e_resp_data['participants']): res = [e for e in e_data['participants'] if e['email'] == p['email']] assert len(res) == 1 assert p['name'] is None
def test_delete_draft(api_client, thread, message): original_draft = {'subject': 'parent 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'] 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'] r = api_client.delete('/drafts/{}'.format(updated_public_id), {'version': updated_version}) # Check that drafts were deleted drafts = api_client.get_data('/drafts') assert not drafts # Check that no orphaned threads are around threads = api_client.get_data('/threads?subject=parent%20draft') assert not threads threads = api_client.get_data('/threads?subject=updated%20draft') assert not threads # And check that threads aren't deleted if they still have messages. thread_public_id = api_client.get_data('/threads')[0]['id'] reply_draft = { 'subject': 'test reply', 'body': 'test reply', 'thread_id': thread_public_id } r = api_client.post_data('/drafts', reply_draft) public_id = json.loads(r.data)['id'] version = json.loads(r.data)['version'] thread = api_client.get_data('/threads/{}'.format(thread_public_id)) assert len(thread['draft_ids']) > 0 api_client.delete('/drafts/{}'.format(public_id), {'version': version}) thread = api_client.get_data('/threads/{}'.format(thread_public_id)) assert thread assert len(thread['draft_ids']) == 0
def test_api_event_when_update(db, api_client, calendar, default_namespace): e_data = { 'title': 'Friday Office Party', 'location': 'home', 'calendar_id': calendar.public_id, } e_data['when'] = {'time': 0} e_resp_data = _verify_create(default_namespace.public_id, api_client, e_data) e_id = e_resp_data['id'] e_update_data = {'when': {'time': 1}} e_put_resp = api_client.put_data('/events/' + e_id, e_update_data) e_put_data = json.loads(e_put_resp.data) assert e_put_data['when']['object'] == 'time' assert e_put_data['when']['time'] == e_update_data['when']['time']
def test_api_event_when_update(db, api_client, calendar, default_namespace): e_data = { "title": "Friday Office Party", "location": "home", "calendar_id": calendar.public_id, } e_data["when"] = {"time": 0} e_resp_data = _verify_create(default_namespace.public_id, api_client, e_data) e_id = e_resp_data["id"] e_update_data = {"when": {"time": 1}} e_put_resp = api_client.put_data("/events/" + e_id, e_update_data) e_put_data = json.loads(e_put_resp.data) assert e_put_data["when"]["object"] == "time" assert e_put_data["when"]["time"] == e_update_data["when"]["time"]
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
def test_removing_a_mutually_exclusive_label_does_not_orphan_a_message( 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 custom_label = add_fake_label(db.session, default_account, '<3', None) db.session.commit() # Removing a message's ONLY folder "label" does not remove it. # Gmail messages MUST belong to one of 'all'/ 'trash'/ 'spam'. response = api_client.put_data( '/messages/{}'.format(message.public_id), {'label_ids': [custom_label.category.public_id]}) 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]
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]
def test_delete_draft(api_client, thread, message): original_draft = {"subject": "parent 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"] 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"] r = api_client.delete("/drafts/{}".format(updated_public_id), {"version": updated_version}) # Check that drafts were deleted drafts = api_client.get_data("/drafts") assert not drafts # Check that no orphaned threads are around threads = api_client.get_data("/threads?subject=parent%20draft") assert not threads threads = api_client.get_data("/threads?subject=updated%20draft") assert not threads # And check that threads aren't deleted if they still have messages. thread_public_id = api_client.get_data("/threads")[0]["id"] reply_draft = {"subject": "test reply", "body": "test reply", "thread_id": thread_public_id} r = api_client.post_data("/drafts", reply_draft) public_id = json.loads(r.data)["id"] version = json.loads(r.data)["version"] thread = api_client.get_data("/threads/{}".format(thread_public_id)) assert len(thread["draft_ids"]) > 0 api_client.delete("/drafts/{}".format(public_id), {"version": version}) thread = api_client.get_data("/threads/{}".format(thread_public_id)) assert thread assert len(thread["draft_ids"]) == 0
def test_adding_trash_or_spam_removes_inbox( db, api_client, default_account, folder_and_message_maps, label): # Verify a Gmail message in 'trash', 'spam' cannot have 'inbox'. # We specifically test that adding 'trash'/ 'spam' to a message with 'inbox' # removes it. folder_map, message_map = folder_and_message_maps message = message_map['all'] add_inbox_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 set([l['name'] for l in labels]) == set(['all', 'inbox']) # Adding 'trash'/ 'spam' removes 'inbox' (and 'all'), # irrespective of whether it's provided in the request or not. label_to_add = folder_map[label] 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) == 1 assert labels[0]['name'] == label
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_api_update_invalid(db, api_client, calendar): e_update_data = {'title': 'new title'} e_id = generate_public_id() e_put_resp = api_client.put_data('/events/' + e_id, e_update_data) assert e_put_resp.status_code != 200