def test_the_pump_should_fail_on_a_missing_message_mapper(self): """ Given that I have a message pump for a channel When there is no message mapper for that channel Then we shhould throw an exception to indicate a configuration error """ handler = MyCommandHandler() request = MyCommand() channel = Mock(spec=Channel) command_processor = Mock(spec=CommandProcessor) message_pump = MessagePump(command_processor, channel, None) header = BrightsideMessageHeader(uuid4(), request.__class__.__name__, BrightsideMessageType.MT_COMMAND) body = BrightsideMessageBody(JsonRequestSerializer(request=request).serialize_to_json(), BrightsideMessageBodyType.application_json) message = BrightsideMessage(header, body) quit_message = create_quit_message() # add messages to that when channel is called it returns first message then qui tmessage response_queue = [message, quit_message] channel_spec = {"receive.side_effect": response_queue} channel.configure_mock(**channel_spec) excepton_caught = False try: message_pump.run() except ConfigurationException: excepton_caught = True self.assertTrue(excepton_caught)
def test_the_pump_should_dispatch_a_command_processor(self): """ Given that I have a message pump for a channel When I read a message from that channel Then the message should be dispatched to a handler """ handler = MyCommandHandler() request = MyCommand() channel = Mock(spec=Channel) command_processor = Mock(spec=CommandProcessor) message_pump = MessagePump(command_processor, channel, map_my_command_to_request) header = BrightsideMessageHeader(uuid4(), request.__class__.__name__, BrightsideMessageType.MT_COMMAND) body = BrightsideMessageBody(JsonRequestSerializer(request=request).serialize_to_json(), BrightsideMessageBodyType.application_json) message = BrightsideMessage(header, body) quit_message = create_quit_message() # add messages to that when channel is called it returns first message then quit message response_queue = [message, quit_message] channel_spec = {"receive.side_effect": response_queue} channel.configure_mock(**channel_spec) message_pump.run() channel.receive.assert_called_with(0.5) self.assertEqual(channel.receive.call_count, 2) self.assertTrue(command_processor.send.call_count, 1) self.assertEqual(channel.acknowledge.call_count, 1)
def test_the_pump_should_ack_failed_messages(self): """ Given that I have a message pump for a channel When the handler raises an application exception for that message Then ack the message to prevent 'poison pill' message """ handler = MyCommandHandler() request = MyCommand() channel = Mock(spec=Channel) command_processor = Mock(spec=CommandProcessor) message_pump = MessagePump(command_processor, channel, map_my_command_to_request) header = BrightsideMessageHeader(uuid4(), request.__class__.__name__, BrightsideMessageType.MT_COMMAND) body = BrightsideMessageBody(JsonRequestSerializer(request=request).serialize_to_json(), BrightsideMessageBodyType.application_json) message = BrightsideMessage(header, body) quit_message = create_quit_message() # add messages to that when channel is called it returns first message then qui tmessage response_queue = [message, quit_message] channel_spec = {"receive.side_effect": response_queue} channel.configure_mock(**channel_spec) app_error_spec = {"send.side_effect": ZeroDivisionError()} command_processor.configure_mock(**app_error_spec) message_pump.run() channel.receive.assert_called_with(0.5) self.assertEqual(channel.receive.call_count, 2) self.assertTrue(command_processor.send.call_count, 1) self.assertEqual(channel.acknowledge.call_count, 1)
def test_the_pump_should_acknowledge_and_discard_an_unacceptable_message(self): """ Given that I have a message pump for a channel When I cannot read the message received from that channel Then I should acknowledge the message to dicard it """ handler = MyCommandHandler() request = MyCommand() channel = Mock(spec=Channel) command_processor = Mock(spec=CommandProcessor) message_pump = MessagePump(command_processor, channel, map_my_command_to_request) header = BrightsideMessageHeader(uuid4(), request.__class__.__name__, BrightsideMessageType.MT_UNACCEPTABLE) body = BrightsideMessageBody(JsonRequestSerializer(request=request).serialize_to_json(), BrightsideMessageBodyType.application_json) message = BrightsideMessage(header, body) quit_message = create_quit_message() # add messages to that when channel is called it returns first message then qui tmessage response_queue = [message, quit_message] channel_spec = {"receive.side_effect": response_queue} channel.configure_mock(**channel_spec) message_pump.run() channel.receive.assert_called_with(0.5) self.assertEqual(channel.receive.call_count, 2) # We acknowledge so that a 'poison pill' message cannot block our queue self.assertEqual(channel.acknowledge.call_count, 1) # Does not send the message, just discards it self.assertEqual(command_processor.send.call_count, 0)
def _sub_process_main( started_event: Event, channel_name: str, connection: Connection, consumer_configuration: BrightsideConsumerConfiguration, consumer_factory: Callable[ [Connection, BrightsideConsumerConfiguration, logging.Logger], BrightsideConsumer], command_processor_factory: Callable[[str], CommandProcessor], mapper_func: Callable[[BrightsideMessage], Request]) -> None: """ This is the main method for the sub=process, everything we need to create the message pump and channel it needs to be passed in as parameters that can be pickled as when we run they will be serialized into this process. The data should be value types, not reference types as we will receive a copy of the original. Inter-process communication is signalled by the event - to indicate startup - and the pipeline to facilitate a sentinel or stop message :param started_event: Used by the sub-process to signal that it is ready :param channel_name: The name we want to give the channel to the broker for identification :param connection: The 'broker' connection :param consumer_configuration: How to configure our consumer of messages from the channel :param consumer_factory: Callback to create the consumer. User code as we don't know what consumer library they want to use. Arame? Something else? :param command_processor_factory: Callback to register subscribers, policies, and task queues then build command processor. User code that provides us with their requests and handlers :param mapper_func: We need to map between messages on the wire and our handlers :return: """ logger = logging.getLogger(__name__) consumer = consumer_factory(connection, consumer_configuration, logger) channel = Channel(name=channel_name, consumer=consumer, pipeline=consumer_configuration.pipeline) # TODO: Fix defaults that need passed in config values command_processor = command_processor_factory(channel_name) message_pump = MessagePump(command_processor=command_processor, channel=channel, mapper_func=mapper_func, timeout=500, unacceptable_message_limit=None, requeue_count=None) logger.debug("Starting the message pump for %s", channel_name) message_pump.run(started_event)
def test_handle_requeue_has_upper_bound(self): """ Given that I have a channel When I receive a requeue on that channel I should ask the consumer to requeue, up to a retry limit So that poison messages do not fill our queues """ handler = MyCommandHandler() request = MyCommand() channel = FakeChannel(name="MyCommand") command_processor = Mock(spec=CommandProcessor) message_pump = MessagePump(command_processor, channel, map_my_command_to_request, requeue_count=3) header = BrightsideMessageHeader(uuid4(), request.__class__.__name__, BrightsideMessageType.MT_COMMAND) body = BrightsideMessageBody(JsonRequestSerializer(request=request).serialize_to_json(), BrightsideMessageBodyType.application_json) message = BrightsideMessage(header, body) channel.add(message) requeue_spec = {"send.side_effect": DeferMessageException()} command_processor.configure_mock(**requeue_spec) started_event = Event() t = Thread(target=message_pump.run, args=(started_event,)) t.start() started_event.wait() time.sleep(1) channel.stop() t.join() self.assertTrue(command_processor.send.call_count, 3)