async def test_stream_paging(): output = TeeQueue() convo = IterStreamEvents("my-stream") response = proto.ReadStreamEventsCompleted() response.result = msg.ReadEventResult.Success response.next_event_number = 10 response.last_event_number = 9 response.is_end_of_stream = False response.last_commit_position = 8 await convo.respond_to( msg.InboundMessage( uuid4(), msg.TcpCommand.ReadStreamEventsForwardCompleted, response.SerializeToString(), ), output, ) reply = await output.get() body = proto.ReadStreamEvents() body.ParseFromString(reply.payload) assert body.from_event_number == 10
async def test_stream_not_found(): output = TeeQueue() convo = IterStreamEvents("my-stream") response = proto.ReadStreamEventsCompleted() response.result = msg.ReadStreamResult.NoStream response.is_end_of_stream = False response.next_event_number = 0 response.last_event_number = 0 response.last_commit_position = 0 await convo.respond_to( msg.InboundMessage( uuid4(), msg.TcpCommand.ReadStreamEventsForwardCompleted, response.SerializeToString(), ), output, ) with pytest.raises(exceptions.StreamNotFound) as exn: await convo.result assert exn.stream == "my-stream" assert exn.conversation_id == convo.conversation_id assert not output.items
async def test_end_of_stream(): output = TeeQueue() event_1_id = uuid4() event_2_id = uuid4() convo = IterStreamEvents("my-stream") response = proto.ReadStreamEventsCompleted() response.result = msg.ReadEventResult.Success response.next_event_number = 10 response.last_event_number = 9 response.is_end_of_stream = True response.last_commit_position = 8 event_1 = proto.ResolvedIndexedEvent() event_1.event.event_stream_id = "stream-123" event_1.event.event_number = 32 event_1.event.event_id = event_1_id.bytes_le event_1.event.event_type = "event-type" event_1.event.data_content_type = msg.ContentType.Json event_1.event.metadata_content_type = msg.ContentType.Binary event_1.event.data = """ { 'color': 'red', 'winner': true } """.encode( "UTF-8" ) event_2 = proto.ResolvedIndexedEvent() event_2.CopyFrom(event_1) event_2.event.event_type = "event-2-type" event_2.event.event_id = event_2_id.bytes_le event_2.event.event_number = 33 response.events.extend([event_1, event_2]) await convo.respond_to( msg.InboundMessage( uuid4(), msg.TcpCommand.ReadEventCompleted, response.SerializeToString() ), output, ) # Todo: Use a slice here so that we can give information # to the iterator about its position in the stream? # assert isinstance(reply.result, msg.StreamSlice) result = await convo.result [event_1, event_2] = [e async for e in result] assert event_1.event.stream == "stream-123" assert event_1.event.id == event_1_id assert event_1.event.type == "event-type" assert event_1.event.event_number == 32 assert event_2.event.stream == "stream-123" assert event_2.event.id == event_2_id assert event_2.event.type == "event-2-type" assert event_2.event.event_number == 33
async def test_when_a_stream_iterator_fails_midway(): """ Sometimes bad things happen. What happens if we're iterating a stream and some anarchist decides to delete it from under our feet? Well, I guess we should return an error to the caller and stop the iterator. """ dispatcher = MessageDispatcher() output_queue = TeeQueue() conversation = IterStreamEvents("my-stream") first_msg = read_stream_events_completed( conversation.conversation_id, "my-stream", [NewEvent("event", data={"x": 1})]) second_msg = read_stream_events_failure(conversation.conversation_id, ReadStreamResult.StreamDeleted) future = await dispatcher.start_conversation(conversation) await dispatcher.dispatch(first_msg, output_queue) await dispatcher.dispatch(second_msg, output_queue) iterator = await asyncio.wait_for(future, 1) event = await anext(iterator) assert event.event.json()["x"] == 1 with pytest.raises(StreamDeleted): await anext(iterator) assert not dispatcher.has_conversation(conversation.conversation_id)
async def test_reconnect_at_last_event_number(): output = TeeQueue() event_1_id = uuid4() event_2_id = uuid4() convo = IterStreamEvents("my-stream", from_event=32) await convo.start(output) response = proto.ReadStreamEventsCompleted() response.result = msg.ReadEventResult.Success response.next_event_number = 32 response.last_event_number = 31 response.is_end_of_stream = False response.last_commit_position = 31 event_1 = proto.ResolvedIndexedEvent() event_1.event.event_stream_id = "stream-123" event_1.event.event_number = 32 event_1.event.event_id = event_1_id.bytes_le event_1.event.event_type = "event-type" event_1.event.data_content_type = msg.ContentType.Json event_1.event.metadata_content_type = msg.ContentType.Binary event_1.event.data = """ { 'color': 'red', 'winner': true } """.encode("UTF-8") event_2 = proto.ResolvedIndexedEvent() event_2.CopyFrom(event_1) event_2.event.event_type = "event-2-type" event_2.event.event_id = event_2_id.bytes_le event_2.event.event_number = 33 response.events.extend([event_1, event_2]) await convo.respond_to( msg.InboundMessage( uuid4(), msg.TcpCommand.ReadStreamEventsForwardCompleted, response.SerializeToString(), ), output, ) await convo.start(output) request = output.items[-1] body = proto.ReadStreamEvents() body.ParseFromString(request.payload) assert request.command is msg.TcpCommand.ReadStreamEventsForward assert body.event_stream_id == "my-stream" assert body.from_event_number == 33 assert body.resolve_link_tos is True assert body.require_master is False assert body.max_count == 100
async def test_when_dispatching_stream_iterators(): """ When we're dealing with iterators, the first message should result in a StreamingIterator being returned to the caller; subsequent messages should push new events to the caller, and the final message should terminate iteration. """ conversation = IterStreamEvents("my-stream") first_msg = read_stream_events_completed( conversation.conversation_id, "my-stream", [NewEvent("event", data={"x": 1})]) second_msg = read_stream_events_completed( conversation.conversation_id, "my-stream", [NewEvent("event", data={"x": 2}), NewEvent("event", data={"x": 3})], ) final_msg = read_stream_events_completed( conversation.conversation_id, "my-stream", [NewEvent("event", data={"x": 4})], end_of_stream=True, ) dispatcher = MessageDispatcher() output_queue = TeeQueue() future = await dispatcher.start_conversation(conversation) # The first message should result in an iterator being returned to the caller # with one event. The conversation should still be active. await dispatcher.dispatch(first_msg, output_queue) iterator = await asyncio.wait_for(future, 1) e = await anext(iterator) assert e.event.json()["x"] == 1 assert dispatcher.has_conversation(conversation.conversation_id) # The second message should result in two events on the iterator. # The conversation should still be active. await dispatcher.dispatch(second_msg, output_queue) e = await anext(iterator) assert e.event.json()["x"] == 2 e = await anext(iterator) assert e.event.json()["x"] == 3 assert dispatcher.has_conversation(conversation.conversation_id) # The final message should result in one event and the iterator terminating await dispatcher.dispatch(final_msg, output_queue) [e] = [e async for e in iterator] assert e.event.json()["x"] == 4 assert not dispatcher.has_conversation(conversation.conversation_id)
async def test_read_stream_request(): output = TeeQueue() convo = IterStreamEvents("my-stream") await convo.start(output) request = await output.get() body = proto.ReadStreamEvents() body.ParseFromString(request.payload) assert request.command is msg.TcpCommand.ReadStreamEventsForward assert body.event_stream_id == "my-stream" assert body.from_event_number == 0 assert body.resolve_link_tos is True assert body.require_master is False assert body.max_count == 100
async def test_when_a_stream_iterator_fails_at_the_first_hurdle(): """ If we receive an error in reply to the first message in an iterator conversation, then we ought to raise the error instead of returning an iterator. """ dispatcher = MessageDispatcher() conversation = IterStreamEvents("my-stream") first_msg = read_stream_events_failure(conversation.conversation_id, ReadStreamResult.StreamDeleted) future = await dispatcher.start_conversation(conversation) await dispatcher.dispatch(first_msg, None) with pytest.raises(StreamDeleted): await asyncio.wait_for(future, 1) assert not dispatcher.has_conversation(conversation.conversation_id)
async def test_error_mid_stream(): output = TeeQueue() convo = IterStreamEvents("my-stream") response = proto.ReadStreamEventsCompleted() response.result = msg.ReadStreamResult.Success response.is_end_of_stream = False response.next_event_number = 0 response.last_event_number = 0 response.last_commit_position = 0 await convo.respond_to( msg.InboundMessage( uuid4(), msg.TcpCommand.ReadStreamEventsForwardCompleted, response.SerializeToString(), ), output, ) response.result = msg.ReadStreamResult.AccessDenied await convo.respond_to( msg.InboundMessage( uuid4(), msg.TcpCommand.ReadStreamEventsForwardCompleted, response.SerializeToString(), ), output, ) iterator = await convo.result with pytest.raises(exceptions.AccessDenied) as exn: await iterator.anext() assert exn.conversation_id == convo.conversation_id assert exn.conversation_type == "IterStreamEvents" assert len(output.items) == 1