def test_should_consumer_react_to_chaos_inputs( max_retries_allowed, expected_number_event_consumed, chaos ): spy = SpyEvents() def assert_consumer(event: Event) -> Result[bool, Error]: spy.append(event) return isSuccess event = EventUserCreatedMother.random() subscribers = [ EventSubscriber( event_name=event.event_name, event_version=event.event_version, handlers=[assert_consumer], ) ] configurer = RabbitMqEventConfigurerMother.with_retry_ttl_10ms() configurer.configure_subscribers(subscribers) bus = RabbitMqEventBusMother.default() bus.publish(event) consumer = RabbitMqEventConsumerMother.with_chaos(chaos, max_retries_allowed) consumer.add_subscribers(subscribers) consumer.start() sleep(1.0) consumer.stop() configurer.clear() spy.assert_count_by_event_id(event.event_id, expected_number_event_consumed)
def add_subscriber_on_dead_letter(self, subscriber: EventSubscriber, handler: Callable): queue_name = RabbitMqEventSubscriberQueueNameFormatter.format_dead_letter( subscriber, exchange_name=self.exchange_name) for handler_name in subscriber.get_handlers_names(): self.add_handler_on_queue( queue_name=f"{queue_name}.{handler_name}", handler=handler)
def configure_event(self, event: Event): self.configure_subscribers([ EventSubscriber( event_name=event.event_name, event_version=event.event_version, handlers=[], ) ])
def test_should_publish_consume_and_retry_event_with_two_handlers_from_rabbitmq_when_fail_consumer( max_retries_allowed, expected_number_event_consumed, simulated_results): spy_consumer_1 = SpyEvents() spy_consumer_2 = SpyEvents() simulated_results_1 = copy.deepcopy(simulated_results) simulated_results_2 = copy.deepcopy(simulated_results) def assert_consumer_1(event: Event) -> Result[bool, Error]: spy_consumer_1.append(event) result = simulated_results_1.pop(0) return result def assert_consumer_2(event: Event) -> Result[bool, Error]: spy_consumer_2.append(event) result = simulated_results_2.pop(0) return result event = EventUserCreatedMother.random() subscribers = [ EventSubscriber( event_name=event.event_name, event_version=event.event_version, handlers=[assert_consumer_1, assert_consumer_2], ) ] configurer = RabbitMqEventConfigurerMother.with_retry_ttl_10ms() configurer.configure_subscribers(subscribers) bus = RabbitMqEventBusMother.default() bus.publish(event) consumer = RabbitMqEventConsumerMother.with_max_retries( max_retries_allowed) consumer.add_subscribers(subscribers) consumer.start() sleep(1.5) consumer.stop() configurer.clear() print(f"num events: {len(spy_consumer_1.events)} - {spy_consumer_1}") print(f"num events: {len(spy_consumer_2.events)} - {spy_consumer_2}") spy_consumer_1.assert_number_unique_events(1) spy_consumer_1.assert_first_event(event) spy_consumer_1.assert_count_by_event_id(event.event_id, expected_number_event_consumed) spy_consumer_2.assert_number_unique_events(1) spy_consumer_2.assert_first_event(event) spy_consumer_2.assert_count_by_event_id(event.event_id, expected_number_event_consumed)
def test_should_consumer_react_to_chaos_with_failure_simulation_and_check_logger_with_subscribers(): spy = SpyEvents() spy_dead_letter = SpyEvents() logger = FakeLogger() def assert_consumer(event: Event) -> Result[bool, Error]: spy.append(event) return isSuccess def assert_dead_letter_consumer(event: Event) -> Result[bool, Error]: spy_dead_letter.append(event) return isSuccess event = EventUserCreatedMother.random() subscribers = [ EventSubscriber( event_name=event.event_name, event_version=event.event_version, handlers=[assert_consumer], ) ] configurer = RabbitMqEventConfigurerMother.with_main_and_retry_ttl_100ms() configurer.configure_subscribers(subscribers) bus = RabbitMqEventBusMother.default() bus.publish(event) max_retries_allowed = 5 chaos = RabbitMqEventChaos( percentage_simulate_failures=1.0, protected_routing_keys=[ "dead_letter.alice.petisco.1.event.user_created.assert_consumer" ], ) consumer = RabbitMqEventConsumerMother.with_chaos( chaos, max_retries_allowed, logger ) consumer.add_subscribers(subscribers) consumer.add_handler_on_queue( "dead_letter.alice.petisco.1.event.user_created.assert_consumer", assert_dead_letter_consumer, ) consumer.start() sleep(1.5) consumer.stop() configurer.clear() spy.assert_count_by_event_id(event.event_id, 0) spy_dead_letter.assert_count_by_event_id(event.event_id, 1) assert_logger_represents_simulated_failure_scenario(logger, max_retries_allowed)
def test_should_publish_consume_with_event_handler_notify_when_fail_consumer_with_critical_error( given_any_petisco ): spy = SpyEvents() logger = FakeLogger() notifier = FakeNotifier() class MyCriticalError(CriticalError): pass @event_handler(logger=logger, notifier=notifier) def assert_consumer(event: Event) -> Result[bool, Error]: spy.append(event) return Failure(MyCriticalError(Exception())) event = EventUserCreatedMother.random() subscribers = [ EventSubscriber( event_name=event.event_name, event_version=event.event_version, handlers=[assert_consumer], ) ] configurer = RabbitMqEventConfigurerMother.default() configurer.configure_subscribers(subscribers) bus = RabbitMqEventBusMother.default() bus.publish(event) consumer = RabbitMqEventConsumerMother.without_retry() consumer.add_subscribers(subscribers) consumer.start() sleep(1.0) consumer.stop() configurer.clear() spy.assert_number_unique_events(1) spy.assert_first_event(event) spy.assert_count_by_event_id(event.event_id, 1) first_logging_message = logger.get_logging_messages()[0] assert first_logging_message == ( DEBUG, LogMessageMother.get_event_handler( operation="assert_consumer", message={"event": event.event_name, "body": event.to_json()}, ).to_dict(), ) assert notifier.publish_called assert notifier.publish_times_called == 1
def test_should_publish_consume_and_retry_event_from_rabbitmq_when_a_queue_is_configured_with_specific_parameters( max_retries_allowed, expected_number_event_consumed, simulated_results ): spy = SpyEvents() spy_specific = SpyEvents() def assert_consumer(event: Event) -> Result[bool, Error]: spy.append(event) result = simulated_results.pop(0) return result def assert_specific_consumer(event: Event) -> Result[bool, Error]: spy_specific.append(event) return isSuccess event = EventUserCreatedMother.random() subscribers = [ EventSubscriber( event_name=event.event_name, event_version=event.event_version, handlers=[assert_consumer, assert_specific_consumer], ) ] specific_queue_config = SpecificQueueConfig( wildcard="*assert_specific_consumer", specific_retry_ttl=50, specific_main_ttl=75, ) queue_config = QueueConfigMother.with_specific_queue_config( specific_queue_config, default_retry_ttl=100, default_main_ttl=100 ) configurer = RabbitMqEventConfigurerMother.with_queue_config(queue_config) configurer.configure_subscribers(subscribers) bus = RabbitMqEventBusMother.default() bus.publish(event) consumer = RabbitMqEventConsumerMother.with_max_retries(max_retries_allowed) consumer.add_subscribers(subscribers) consumer.start() sleep(1.0) consumer.stop() configurer.clear() spy.assert_number_unique_events(1) spy.assert_first_event(event) spy.assert_last_event(event) spy.assert_count_by_event_id(event.event_id, expected_number_event_consumed) spy_specific.assert_number_unique_events(1)
def test_should_publish_consume_retry_and_send_to_dead_letter_event_from_rabbitmq_when_fail_consumer( ): max_retries_allowed = 2 expected_number_event_consumed = 3 spy = SpyEvents() spy_dead_letter = SpyEvents() def assert_consumer(event: Event) -> Result[bool, Error]: spy.append(event) return isFailure event = EventUserCreatedMother.random() subscriber = EventSubscriber( event_name=event.event_name, event_version=event.event_version, handlers=[assert_consumer], ) subscribers = [subscriber] configurer = RabbitMqEventConfigurerMother.with_retry_ttl_10ms() configurer.configure_subscribers(subscribers) bus = RabbitMqEventBusMother.default() bus.publish(event) consumer = RabbitMqEventConsumerMother.with_max_retries( max_retries_allowed) consumer.add_subscribers(subscribers) def dead_letter_consumer(event: Event) -> Result[bool, Error]: spy_dead_letter.append(event) return isSuccess consumer.add_subscriber_on_dead_letter(subscriber, dead_letter_consumer) consumer.start() sleep(1.5) consumer.stop() configurer.clear() spy.assert_number_unique_events(1) spy.assert_first_event(event) spy.assert_count_by_event_id(event.event_id, expected_number_event_consumed) spy_dead_letter.assert_number_unique_events(1) spy_dead_letter.assert_first_event(event) spy_dead_letter.assert_count_by_event_id(event.event_id, 1)
def test_should_publish_consume_and_retry_event_not_affecting_store_queue_from_rabbitmq_when_fail_handler_consumer( max_retries_allowed, expected_number_event_consumed, simulated_results): spy_consumer_event_store = SpyEvents() spy_consumer_handler = SpyEvents() def assert_consumer_event_store(event: Event) -> Result[bool, Error]: spy_consumer_event_store.append(event) return isSuccess def assert_consumer_handler(event: Event) -> Result[bool, Error]: spy_consumer_handler.append(event) result = simulated_results.pop(0) return result event = EventUserCreatedMother.random() subscribers = [ EventSubscriber( event_name=event.event_name, event_version=event.event_version, handlers=[assert_consumer_handler], ) ] configurer = RabbitMqEventConfigurerMother.with_retry_ttl_10ms() configurer.configure_subscribers(subscribers) bus = RabbitMqEventBusMother.default() bus.publish(event) consumer = RabbitMqEventConsumerMother.with_max_retries( max_retries_allowed) consumer.add_subscribers(subscribers) consumer.add_handler_on_queue("store", assert_consumer_event_store) consumer.start() sleep(1.0) consumer.stop() configurer.clear() spy_consumer_event_store.assert_number_unique_events(1) spy_consumer_event_store.assert_first_event(event) spy_consumer_event_store.assert_count_by_event_id(event.event_id, 1) spy_consumer_handler.assert_number_unique_events(1) spy_consumer_handler.assert_first_event(event) spy_consumer_handler.assert_count_by_event_id( event.event_id, expected_number_event_consumed)
def test_should_consumer_react_to_chaos_with_nck_simulation_and_check_logger(): spy = SpyEvents() logger = FakeLogger() def assert_consumer(event: Event) -> Result[bool, Error]: spy.append(event) return isSuccess event = EventUserCreatedMother.random() subscribers = [ EventSubscriber( event_name=event.event_name, event_version=event.event_version, handlers=[assert_consumer], ) ] configurer = RabbitMqEventConfigurerMother.with_retry_ttl_10ms() configurer.configure_subscribers(subscribers) bus = RabbitMqEventBusMother.default() bus.publish(event) max_retries_allowed = 5 chaos = RabbitMqEventChaos(percentage_simulate_nack=1.0) consumer = RabbitMqEventConsumerMother.with_chaos( chaos, max_retries_allowed, logger ) consumer.add_subscribers(subscribers) consumer.start() sleep(1.0) consumer.stop() configurer.clear() assert len(logger.get_logging_messages()) >= 200 first_logging_message = logger.get_logging_messages()[0] assert first_logging_message[0] == DEBUG assert first_logging_message[1]["meta"] == { "layer": "rabbitmq_event_consumer", "operation": "assert_consumer", } assert ( first_logging_message[1]["data"]["message"]["chaos_action"] == "nack simulated" )
def test_should_consumer_react_to_chaos_with_zero_probability(): spy = SpyEvents() logger = FakeLogger() def assert_consumer(event: Event) -> Result[bool, Error]: spy.append(event) return isSuccess event = EventUserCreatedMother.random() subscribers = [ EventSubscriber( event_name=event.event_name, event_version=event.event_version, handlers=[assert_consumer], ) ] configurer = RabbitMqEventConfigurerMother.with_retry_ttl_10ms() configurer.configure_subscribers(subscribers) bus = RabbitMqEventBusMother.default() bus.publish(event) max_retries_allowed = 5 chaos = RabbitMqEventChaos( percentage_simulate_nack=0.0, percentage_simulate_failures=0.0 ) consumer = RabbitMqEventConsumerMother.with_chaos( chaos, max_retries_allowed, logger ) consumer.add_subscribers(subscribers) consumer.start() sleep(1.0) consumer.stop() configurer.clear() spy.assert_number_unique_events(1) spy.assert_first_event(event) spy.assert_count_by_event_id(event.event_id, 1) assert len(logger.get_logging_messages()) == 2 logging_message = logger.get_logging_messages()[1] assert logging_message[0] == DEBUG assert logging_message[1]["data"]["message"]["derived_action"] == {}
def test_should_publish_consume_with_event_handler_when_fail_consumer(): spy = SpyEvents() logger = FakeLogger() @event_handler(logger=logger) def assert_consumer(event: Event) -> Result[bool, Error]: spy.append(event) return isFailure event = EventUserCreatedMother.random() subscribers = [ EventSubscriber( event_name=event.event_name, event_version=event.event_version, handlers=[assert_consumer], ) ] configurer = RabbitMqEventConfigurerMother.default() configurer.configure_subscribers(subscribers) bus = RabbitMqEventBusMother.default() bus.publish(event) consumer = RabbitMqEventConsumerMother.with_max_retries(1) consumer.add_subscribers(subscribers) consumer.start() sleep(1.0) consumer.stop() configurer.clear() spy.assert_number_unique_events(1) spy.assert_first_event(event) spy.assert_count_by_event_id(event.event_id, 1) first_logging_message = logger.get_logging_messages()[0] assert first_logging_message == ( DEBUG, LogMessageMother.get_event_handler( operation="assert_consumer", message={"event": event.event_name, "body": event.to_json()}, ).to_dict(), )
def test_should_publish_consume_and_retry_event_with_two_handlers_from_rabbitmq( ): spy_consumer_1 = SpyEvents() spy_consumer_2 = SpyEvents() def assert_consumer_1(event: Event) -> Result[bool, Error]: spy_consumer_1.append(event) return isSuccess def assert_consumer_2(event: Event) -> Result[bool, Error]: spy_consumer_2.append(event) return isSuccess event = EventUserCreatedMother.random() subscribers = [ EventSubscriber( event_name=event.event_name, event_version=event.event_version, handlers=[assert_consumer_1, assert_consumer_2], ) ] configurer = RabbitMqEventConfigurerMother.default() configurer.configure_subscribers(subscribers) bus = RabbitMqEventBusMother.default() bus.publish(event) consumer = RabbitMqEventConsumerMother.default() consumer.add_subscribers(subscribers) consumer.start() sleep(1.0) consumer.stop() configurer.clear() spy_consumer_1.assert_number_unique_events(1) spy_consumer_1.assert_first_event(event) spy_consumer_1.assert_count_by_event_id(event.event_id, 1) spy_consumer_2.assert_number_unique_events(1) spy_consumer_2.assert_first_event(event) spy_consumer_2.assert_count_by_event_id(event.event_id, 1)
def test_should_publish_consume_with_event_handler_with_default_parameters_when_success_consumer( given_any_petisco ): spy = SpyEvents() @event_handler() def assert_consumer(event: Event) -> Result[bool, Error]: spy.append(event) return isSuccess event = EventUserCreatedMother.random() subscribers = [ EventSubscriber( event_name=event.event_name, event_version=event.event_version, handlers=[assert_consumer], ) ] configurer = RabbitMqEventConfigurerMother.default() configurer.configure_subscribers(subscribers) bus = RabbitMqEventBusMother.default() bus.publish(event) consumer = RabbitMqEventConsumerMother.without_retry() consumer.add_subscribers(subscribers) consumer.start() sleep(1.0) consumer.stop() configurer.clear() spy.assert_number_unique_events(1) spy.assert_first_event(event) spy.assert_count_by_event_id(event.event_id, 1)
def test_should_publish_event_on_consumer_with_event_handler(): spy = SpyEvents() @event_handler(logger=FakeLogger()) def assert_consumer(event: Event, event_bus: IEventBus) -> BoolResult: spy.append(event) event = EventUserUpdatedMother.random() event_bus.publish(event) return isSuccess event = EventUserCreatedMother.random() subscribers = [ EventSubscriber( event_name=event.event_name, event_version=event.event_version, handlers=[assert_consumer], ) ] configurer = RabbitMqEventConfigurerMother.with_retry_ttl_10ms() configurer.configure_subscribers(subscribers) consumer = RabbitMqEventConsumerMother.with_max_retries(1) consumer.add_subscribers(subscribers) consumer.start() bus = RabbitMqEventBusMother.default() for _ in range(1): event = EventUserCreatedMother.random() bus.publish(event) sleep(1.0) consumer.stop() configurer.clear() spy.assert_number_unique_events(1)
def from_dict(kdict): event_subscribers = None queues_subscribers = None store_queue_subscriber = None events = kdict.get("events") queues = kdict.get("queues") retry_ttl = DEFAULT_QUEUE_RETRY_TTL main_ttl = DEFAULT_QUEUE_RETRY_TTL queue_config = QueueConfig.default(retry_ttl, main_ttl) if events: retry_ttl = events.get("retry_ttl", DEFAULT_QUEUE_RETRY_TTL) main_ttl = events.get("retry_ttl", DEFAULT_QUEUE_RETRY_TTL) queue_config = QueueConfig.default(retry_ttl, main_ttl) dict_events_subscribers = events.get("subscribers") if dict_events_subscribers: event_subscribers = [] for src_event_name, event_info in dict_events_subscribers.items( ): handlers_names = check_list_or_str_item( event_info, "handlers", typename="EventHandler") event_name = get_event_name(src_event_name) event_version = event_info.get("version", 1) handlers = get_handlers(handlers_names, kdict) event_subscribers.append( EventSubscriber( event_name=event_name, event_version=event_version, handlers=handlers, )) if queues: str_store_queue_subscriber = queues.get("store") if str_store_queue_subscriber: store_queue_subscriber = get_handlers( [str_store_queue_subscriber], kdict)[0] dict_queues_subscribers = queues.get("subscribers") if dict_queues_subscribers: queues_subscribers = {} for queue_name in dict_queues_subscribers.keys(): handlers_names = check_list_or_str_item( dict_queues_subscribers, queue_name, typename="QueueHandler") handlers = get_handlers(handlers_names, kdict) queues_subscribers[queue_name] = handlers specific_queues_config = queues.get("specific_config") if specific_queues_config: queue_config = QueueConfig.from_dict(specific_queues_config, retry_ttl, main_ttl) return ConfigEvents( organization=events.get("organization"), service=events.get("service"), publish_deploy_event=events.get("publish_deploy_event"), consumer_verbose=events.get("consumer_verbose", False), use_store_queues=events.get("use_store_queues", True), retry_ttl=retry_ttl, main_ttl=main_ttl, max_retries=events.get("max_retries", 5), message_broker=events.get("message_broker", "not_implemented"), event_subscribers=event_subscribers, store_queue_subscriber=store_queue_subscriber, queues_subscribers=queues_subscribers, chaos=RabbitMqEventChaos(), queue_config=queue_config, )