async def test_get_last_event(get_ds, datastore_cls, timestamp): ds = await get_ds(datastore_cls) async with ds.connection() as conn: cds = await create_conv(conn, ds) pid = await cds.add_component( Components.PARTICIPANTS, address='*****@*****.**', permissions=perms.FULL, ) action = Action('*****@*****.**', '123', Verbs.ADD, Components.MESSAGES, item='msg_id', timestamp=timestamp) action.participant_id, action.perm = pid, perms.FULL await cds.save_event('event_1', action) event_id, event_timestamp = await cds.get_item_last_event(Components.MESSAGES, 'msg_id') assert event_id == 'event_1' assert event_timestamp == timestamp # go 1, 3, 2 to make sure we're ordering on the commit sequence not event_id await cds.save_event('event_3', action) event_id, event_timestamp = await cds.get_item_last_event(Components.MESSAGES, 'msg_id') assert event_id == 'event_3' assert event_timestamp == timestamp await cds.save_event('event_2', action) event_id, event_timestamp = await cds.get_item_last_event(Components.MESSAGES, 'msg_id') assert event_id == 'event_2' assert event_timestamp == timestamp with pytest.raises(EventNotFound): await cds.get_item_last_event(Components.PARTICIPANTS, 'foobar')
async def test_publish_invalid_args(controller): a = Action('*****@*****.**', conv_id, Verbs.ADD, timestamp=timestamp) a.event_id = a.calc_event_id() with pytest.raises(BadDataException) as excinfo: await controller.act(a, foo='bar') assert excinfo.value.args[0] == "Conversations.add_remote: missing a required argument: 'data'" assert len(controller.ds.data) == 0
async def test_publish_conversation_two_messages_wrong_id(controller): a = Action('*****@*****.**', conv_id, Verbs.ADD, timestamp=timestamp) a.event_id = a.calc_event_id() ds = controller.ds assert len(ds.data) == 0 data = deepcopy(correct_data) data['messages'][0]['id'] = 'id1' data['messages'].extend([ { 'author': '*****@*****.**', 'body': 'the second body', 'id': 'id2', 'parent': 'id1', 'timestamp': datetime.datetime(2016, 1, 2), }, { 'author': '*****@*****.**', 'body': 'the third body', 'id': 'id3', 'parent': 'foobar', 'timestamp': datetime.datetime(2016, 1, 3), } ]) with pytest.raises(ComponentNotFound) as excinfo: await controller.act(a, data=data) assert excinfo.value.args[0] == 'message foobar not found'
async def test_publish_conversation_two_messages(controller): a = Action('*****@*****.**', conv_id, Verbs.ADD, timestamp=timestamp) a.event_id = a.calc_event_id() ds = controller.ds assert len(ds.data) == 0 data = deepcopy(correct_data) data['messages'].append( { 'author': '*****@*****.**', 'body': 'the second body', 'id': 'different_id', 'parent': 'd21625d617b1e8eb8989aa3d57a5aae691f9ed2a', 'timestamp': datetime.datetime(2016, 1, 2), } ) await controller.act(a, data=data) assert len(ds.data) == 1 assert ds.data[0]['conv_id'] == conv_id assert ds.data[0]['subject'] == 'the subject' assert len(ds.data[0]['messages']) == 2 messages = list(ds.data[0]['messages'].values()) assert messages[0]['body'] == 'the body' assert messages[1]['body'] == 'the second body' assert ds.data[0]['timestamp'] == datetime.datetime(2016, 1, 2) assert len(ds.data[0]['participants']) == 2
async def test_publish_misshaped_data(controller, data, exc): a = Action('*****@*****.**', conv_id, Verbs.ADD, timestamp=timestamp) a.event_id = a.calc_event_id() with pytest.raises(BadDataException) as excinfo: await controller.act(a, data=data) assert exc == excinfo.value.args[0] assert len(controller.ds.data) == 0
async def test_publish_no_timestamp(controller): a = Action('*****@*****.**', conv_id, Verbs.ADD, timestamp=None) a.event_id = a.calc_event_id() with pytest.raises(BadDataException) as excinfo: await controller.act(a, data=deepcopy(correct_data)) assert excinfo.value.args[0] == 'remote actions should always have a timestamp' assert len(controller.ds.data) == 0
async def test_wrong_hash(controller): w_conv_id = conv_id + '+wrong' a = Action('*****@*****.**', w_conv_id, Verbs.ADD, timestamp=timestamp) a.event_id = a.calc_event_id() with pytest.raises(BadHash) as excinfo: await controller.act(a, data=deepcopy(correct_data)) assert excinfo.value.args[0] == 'provided hash {} does not match computed hash {}'.format(w_conv_id, conv_id) assert len(controller.ds.data) == 0
async def test_publish_conversation_wrong_message_id(controller): a = Action('*****@*****.**', conv_id, Verbs.ADD, timestamp=timestamp) a.event_id = a.calc_event_id() ds = controller.ds assert len(ds.data) == 0 data = deepcopy(correct_data) data['messages'][0]['parent'] = 'not None' with pytest.raises(MisshapedDataException) as excinfo: await controller.act(a, data=data) assert excinfo.value.args[0] == 'first message parent should be None'
async def test_lock_edit(conversation): ds, ctrl, conv_id = await conversation() msg1_id = list(ds.data[0]['messages'])[0] assert len(ds.data[0]['events']) == 1 peid = ds.data[0]['events'][0]['id'] a = Action('*****@*****.**', conv_id, Verbs.LOCK, Components.MESSAGES, item=msg1_id, parent_event_id=peid) await ctrl.act(a) peid = a.calc_event_id() a = Action('*****@*****.**', conv_id, Verbs.EDIT, Components.MESSAGES, item=msg1_id, parent_event_id=peid) with pytest.raises(ComponentLocked) as excinfo: await ctrl.act(a, body='hi, how are you again?') assert 'ComponentLocked: messages with id = {} locked'.format(msg1_id) in str(excinfo)
async def test_publish_simple_conversation(controller): a = Action('*****@*****.**', conv_id, Verbs.ADD, Components.CONVERSATIONS, timestamp=timestamp) a.event_id = a.calc_event_id() ds = controller.ds assert len(ds.data) == 0 await controller.act(a, data=deepcopy(correct_data)) assert len(ds.data) == 1 assert ds.data[0]['conv_id'] == conv_id assert ds.data[0]['subject'] == 'the subject' assert len(ds.data[0]['messages']) == 1 assert list(ds.data[0]['messages'].values())[0]['body'] == 'the body' assert ds.data[0]['timestamp'] == datetime.datetime(2016, 1, 2) assert len(ds.data[0]['participants']) == 2
async def test_publish_reply_bad_ts(two_controllers): ctrl1, ctrl2, conv_id = await two_controllers() assert (len(ctrl1.ds.data), len(ctrl2.ds.data)) == (1, 0) a = Action('*****@*****.**', conv_id, Verbs.PUBLISH, Components.CONVERSATIONS) await ctrl1.act(a) assert (len(ctrl1.ds.data), len(ctrl2.ds.data)) == (1, 1) conv_id = ctrl1.ds.data[0]['conv_id'] msg1_id = list(ctrl2.ds.data[0]['messages'])[0] a = Action('*****@*****.**', conv_id, Verbs.ADD, Components.MESSAGES, timestamp=datetime_tz(year=2000)) a.event_id = a.calc_event_id() with pytest.raises(BadDataException) as excinfo: await ctrl1.act(a, parent_id=msg1_id, body='this is a reply') assert excinfo.value.args[0] == 'timestamp not after parent timestamp: 2000-01-01 00:00:00+00:00'
async def test_missing_conversation(client): ts = datetime.now() action2 = Action('*****@*****.**', '123', Verbs.ADD, Components.MESSAGES, timestamp=ts) data = { 'address': '*****@*****.**', 'timestamp': ts, 'event_id': action2.calc_event_id(), 'kwargs': { 'parent_id': '123', 'body': 'reply', } } r = await client.post('/123/messages/add/', data=encoding.encode(data), headers=AUTH_HEADER) assert r.status == 400 content = await r.read() assert content == b'ConversationNotFound: conversation 123 not found\n'
async def test_lock_unlock_message(conversation): ds, ctrl, conv_id = await conversation() conv = ds.data[0] assert len(conv['messages']) == 1 msg1_id = list(conv['messages'])[0] peid = ds.data[0]['events'][0]['id'] a = Action('*****@*****.**', conv_id, Verbs.LOCK, Components.MESSAGES, item=msg1_id, parent_event_id=peid) await ctrl.act(a) peid = a.calc_event_id() assert len(conv['locked']) == 1 locked_v = list(conv['locked'])[0] assert locked_v == 'messages:{}'.format(msg1_id) a = Action('*****@*****.**', conv_id, Verbs.UNLOCK, Components.MESSAGES, item=msg1_id, parent_event_id=peid) await ctrl.act(a) assert len(conv['locked']) == 0
async def test_publish_conversation_two_messages_repeat_id(controller): a = Action('*****@*****.**', conv_id, Verbs.ADD, timestamp=timestamp) a.event_id = a.calc_event_id() ds = controller.ds assert len(ds.data) == 0 data = deepcopy(correct_data) data['messages'].append( { 'author': '*****@*****.**', 'body': 'the second body', 'id': 'd21625d617b1e8eb8989aa3d57a5aae691f9ed2a', 'parent': 'd21625d617b1e8eb8989aa3d57a5aae691f9ed2a', 'timestamp': datetime.datetime(2016, 1, 2), } ) with pytest.raises(BadDataException) as excinfo: await controller.act(a, data=data) assert excinfo.value.args[0] == 'message id d21625d617b1e8eb8989aa3d57a5aae691f9ed2a already exists'
async def test_publish_conversation_two_messages_wrong_timestamp(controller): a = Action('*****@*****.**', conv_id, Verbs.ADD, timestamp=timestamp) a.event_id = a.calc_event_id() ds = controller.ds assert len(ds.data) == 0 data = deepcopy(correct_data) data['messages'].extend([ { 'author': '*****@*****.**', 'body': 'the second body', 'id': 'id2', 'parent': 'd21625d617b1e8eb8989aa3d57a5aae691f9ed2a', 'timestamp': datetime.datetime(2015, 1, 1), }, ]) with pytest.raises(BadDataException) as excinfo: await controller.act(a, data=data) assert excinfo.value.args[0] == 'timestamp 2015-01-01 00:00:00 not after parent'
async def test_add_message(client): action = Action('*****@*****.**', None, Verbs.ADD) conv_id = await client.em2_ctrl.act(action, subject='foo bar', body='hi, how are you?') conv_ds = client.em2_ctrl.ds.new_conv_ds(conv_id, None) msg1_id = list(conv_ds.conv_obj['messages'])[0] ts = action.timestamp + timedelta(seconds=1) action2 = Action('*****@*****.**', conv_id, Verbs.ADD, Components.MESSAGES, timestamp=ts) data = { 'address': '*****@*****.**', 'timestamp': ts, 'event_id': action2.calc_event_id(), 'kwargs': { 'parent_id': msg1_id, 'body': 'reply', } } r = await client.post('/{}/messages/add/'.format(conv_id), data=encoding.encode(data), headers=AUTH_HEADER) assert r.status == 201 assert await r.text() == '\n'
async def test_edit_message_successful(conversation): ds, ctrl, conv_id = await conversation() a = Action('*****@*****.**', conv_id, Verbs.ADD, Components.PARTICIPANTS) await ctrl.act(a, address='*****@*****.**', permissions=perms.WRITE) conv = ds.data[0] msg1_id = list(conv['messages'])[0] a = Action('*****@*****.**', conv_id, Verbs.ADD, Components.MESSAGES) await ctrl.act(a, parent_id=msg1_id, body='reply') # have to do after act so timestamp is set parent_event_id = a.calc_event_id() msg2_id = None for msg2_id, info in conv['messages'].items(): if info['parent'] == msg1_id: break assert conv['messages'][msg2_id]['body'] == 'reply' a = Action('*****@*****.**', conv_id, Verbs.EDIT, Components.MESSAGES, item=msg2_id, parent_event_id=parent_event_id) await ctrl.act(a, body='changed message') assert conv['messages'][msg2_id]['body'] == 'changed message'
async def test_save_event(get_ds, datastore_cls, timestamp): ds = await get_ds(datastore_cls) async with ds.connection() as conn: cds = await create_conv(conn, ds) pid = await cds.add_component( Components.PARTICIPANTS, address='*****@*****.**', permissions=perms.FULL, ) action = Action('*****@*****.**', '123', Verbs.ADD, Components.PARTICIPANTS, item=pid, timestamp=timestamp) action.participant_id, action.perm = pid, perms.FULL await cds.save_event('event_1', action) await cds.save_event('event_2', action, value='foobar') # FIXME currently there are no api methods for returning updates and it's therefore not possible to check # these actions are saved correctly # NOTE: action action.conv_id is ignored in save_event and the cds conv_id is used instead cds2 = ds.new_conv_ds('bad', conn) action.participant_id, action.perm = pid, perms.FULL with pytest.raises(ConversationNotFound): await cds2.save_event('event_3', action)
async def test_publish_wrong_event_id(controller): a = Action('*****@*****.**', conv_id, Verbs.ADD, timestamp=timestamp) a.event_id = 'foobar' with pytest.raises(BadHash) as excinfo: await controller.act(a, data=deepcopy(correct_data)) assert excinfo.value.args[0] == 'event_id "foobar" incorrect'