def test_handle_circuit_breaker_on_command(self): """ given that we have a circuit breaker policy for a command, when we raise an exception, then we should break the circuit after n retries""" self._handler = MyHandlerBreakingCircuitAfterThreeFailures() self._request = MyCommand() self._subscriber_registry.register(MyCommand, lambda: self._handler) exception_raised = False try: self._commandProcessor.send(self._request) except RuntimeError: exception_raised = True # Now see if the circuit is broken following the failed call circuit_broken = False try: self._commandProcessor.send(self._request) except CircuitBrokenError: circuit_broken = True self.assertTrue( exception_raised, "Exepcted an exception to be raised, when we run out of retries") self.assertTrue(circuit_broken, "Expected the circuit to be broken to further calls") self.assertFalse( self._handler.called, "Did not expect the handle method on the handler to be called with the message" ) self.assertTrue(self._handler.call_count == 3, "Expected two retries of the pipeline")
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_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 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_handle_command(self): """ given that we have a handler registered for a command, when we send a command, it should call the handler""" self._handler = MyCommandHandler() self._request = MyCommand() self._subscriber_registry.register(MyCommand, lambda: self._handler) self._commandProcessor.send(self._request) self.assertTrue(self._handler.called, "Expected the handle method on the handler to be called with the message")
def test_handle_command(self): """ given that we have a message mapper and producer registered for a commnd, when we post a command, it should send via the producer""" self._request = MyCommand() self._commandProcessor.post(self._request) self.assertTrue(self._message_store.message_was_added, "Expected a message to be added") self.assertTrue(self._message_store.get_message(self._request.id), "Expected the command to be converted into a message") self.assertTrue(self._producer.was_sent_message, "Expected a message to be sent via the producer")
def test_handle_retry_on_command(self): """ given that we have a retry plocy for a command, when we raise an exception, then we should retry n times or until succeeds """ self._handler = MyHandlerSupportingRetry() self._request = MyCommand() self._subscriber_registry.register(MyCommand, lambda: self._handler) self._commandProcessor.send(self._request) self.assertTrue(self._handler.called, "Expected the handle method on the handler to be called with the message") self.assertTrue(self._handler.call_count == 3, "Expected two retries of the pipeline")
def test_missing_command_handler_registration(self): """Given that we are missing a handler for a command, when we send a command, it should throw an exception""" self._handler = MyCommandHandler() self._request = MyCommand() exception_thrown = False try: self._commandProcessor.send(self._request) except: exception_thrown = True self.assertTrue(exception_thrown, "Expected an exception to be thrown when no handler is registered for a command")
def test_exceed_retry_on_command(self): """ given that we have a retry policy for a command, when we raise an exception, then we should bubble the exception out after n retries""" self._handler = MyHandlerBreakingAfterRetry() self._request = MyCommand() self._subscriber_registry.register(MyCommand, lambda: self._handler) exception_raised = False try: self._commandProcessor.send(self._request) except RuntimeError: exception_raised = True self.assertTrue(exception_raised, "Exepcted the exception to bubble out, when we run out of retries") self.assertFalse(self._handler.called, "Did not expect the handle method on the handler to be called with the message") self.assertTrue(self._handler.call_count == 3, "Expected two retries of the pipeline")
def test_missing_message_producer(self): """given that we have no me message producer configured for the commandprocessor when we post a command it should raise a confiugration error """ self._commandProcessor = CommandProcessor( message_mapper_registry=self._messageMapperRegistry, message_store=self._message_store, producer=None) was_exception_thrown = False try: self._request = MyCommand() self._commandProcessor.post(self._request) except ConfigurationException: was_exception_thrown = True self.assertTrue(was_exception_thrown)
def test_logging_a_handler(self): """s Given that I have a handler decorated for logging When I call that handler Then I should receive logs indicating the call and return of the handler * N.b. This is an example of using decorators to extend the Brightside pipeline """ handler = MyCommandHandler() request = MyCommand() self._subscriber_registry.register(MyCommand, lambda: handler) logger = logging.getLogger("tests.handlers_testdoubles") with patch.object(logger, 'log') as mock_log: self._commandProcessor.send(request) mock_log.assert_has_calls([ call(logging.DEBUG, "Entering handle " + str(request)), call(logging.DEBUG, "Exiting handle " + str(request)) ])
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)
def test_stop_performer(self): """ Given that I have started a performer When I stop the performer Then it should terminate the pump :return: """ request = MyCommand() pipeline = Queue() connection = Connection(config.broker_uri, "examples.perfomer.exchange") configuration = BrightsideConsumerConfiguration( pipeline, "performer.test.queue", "examples.tests.mycommand") performer = Performer("test_channel", connection, configuration, mock_consumer_factory, mock_command_processor_factory, 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) pipeline.put(message) started_event = Event() p = performer.run(started_event) started_event.wait() time.sleep(1) performer.stop() p.join() self.assertTrue(True)
def test_stop_consumer(self): """Given that I have a dispatcher When I stop a consumer Then the performer should terminate """ request = MyCommand() pipeline = Queue() connection = Connection(config.broker_uri, "examples.perfomer.exchange") configuration = BrightsideConsumerConfiguration( pipeline, "dispatcher.test.queue", "examples.tests.mycommand") consumer = ConsumerConfiguration(connection, configuration, mock_consumer_factory, mock_command_processor_factory, map_my_command_to_request) dispatcher = Dispatcher({"MyCommand": consumer}) header = BrightsideMessageHeader(uuid4(), request.__class__.__name__, BrightsideMessageType.MT_COMMAND) body = BrightsideMessageBody( JsonRequestSerializer(request=request).serialize_to_json(), BrightsideMessageBodyType.application_json) message = BrightsideMessage(header, body) pipeline.put(message) self.assertEqual(dispatcher.state, DispatcherState.ds_awaiting) dispatcher.receive() time.sleep(1) dispatcher.end() self.assertEqual(dispatcher.state, DispatcherState.ds_stopped) self.assertTrue(pipeline.empty())
def test_restart_consumer(self): """Given that I have a dispatcher with all consumers stopped When I restart a consumer Then the dispatcher should have one running consumer """ connection = Connection(config.broker_uri, "examples.perfomer.exchange") # First consumer request = MyCommand() pipeline_one = Queue() configuration_one = BrightsideConsumerConfiguration( pipeline_one, "restart_command.test.queue", "examples.tests.mycommand") consumer_one = ConsumerConfiguration(connection, configuration_one, mock_consumer_factory, mock_command_processor_factory, map_my_command_to_request) header_one = BrightsideMessageHeader(uuid4(), request.__class__.__name__, BrightsideMessageType.MT_COMMAND) body_one = BrightsideMessageBody( JsonRequestSerializer(request=request).serialize_to_json(), BrightsideMessageBodyType.application_json) message_one = BrightsideMessage(header_one, body_one) pipeline_one.put(message_one) # Second consumer event = MyEvent() pipeline_two = Queue() configuration_two = BrightsideConsumerConfiguration( pipeline_two, "restart_event.test.queue", "examples.tests.myevent") consumer_two = ConsumerConfiguration(connection, configuration_two, mock_consumer_factory, mock_command_processor_factory, map_my_event_to_request) header_two = BrightsideMessageHeader(uuid4(), event.__class__.__name__, BrightsideMessageType.MT_EVENT) body_two = BrightsideMessageBody( JsonRequestSerializer(request=event).serialize_to_json(), BrightsideMessageBodyType.application_json) message_two = BrightsideMessage(header_two, body_two) pipeline_two.put_nowait(message_two) # Dispatcher dispatcher = Dispatcher({ "consumer_one": consumer_one, "consumer_two": consumer_two }) # Consume the messages and stop self.assertEqual(dispatcher.state, DispatcherState.ds_awaiting) dispatcher.receive() time.sleep(1) dispatcher.end() self.assertEqual(dispatcher.state, DispatcherState.ds_stopped) self.assertTrue(pipeline_one.empty()) #Now add a new message, restart a consumer, and eat event_three = MyEvent() header_three = BrightsideMessageHeader(uuid4(), event.__class__.__name__, BrightsideMessageType.MT_EVENT) body_three = BrightsideMessageBody( JsonRequestSerializer(request=event_three).serialize_to_json(), BrightsideMessageBodyType.application_json) message_three = BrightsideMessage(header_three, body_three) pipeline_two.put_nowait(message_three) dispatcher.open("consumer_two") time.sleep(1) dispatcher.end() self.assertEqual(dispatcher.state, DispatcherState.ds_stopped) self.assertTrue(pipeline_two.empty())