def test_mock_result_event_names_fired(mock_result: testing.MockResult, property_name): mock_result.event.events = [ (EventMessage(api_name="api", event_name="event"), {}), (EventMessage(api_name="api2", event_name="event2"), {}), ] assert getattr(mock_result, property_name) == ["api.event", "api2.event2"]
def test_mock_result_assert_events_fired_times(mock_result: testing.MockResult, method_name): assert_events_fired = getattr(mock_result, method_name) mock_result.event.events = [ (EventMessage(api_name="api", event_name="event"), {}), (EventMessage(api_name="api", event_name="event"), {}), ] # No error try: assert_events_fired("api.event") except AssertionError as e: assert False, f"{method_name} incorrectly raised an assertion error: {e}" # No error try: assert_events_fired("api.event", times=2) except AssertionError as e: assert False, f"{method_name} incorrectly raised an assertion error: {e}" # Error with pytest.raises(AssertionError): assert_events_fired("api.event", times=0) # Error with pytest.raises(AssertionError): assert_events_fired("api.event", times=1) # Error with pytest.raises(AssertionError): assert_events_fired("api.event", times=3)
def test_mock_result_get_event_messages_filtered( mock_result: testing.MockResult, method_name): get_event_messages = getattr(mock_result, method_name) event1 = EventMessage(api_name="api", event_name="event1") event2 = EventMessage(api_name="api", event_name="event2") mock_result.event.events = [(event1, {}), (event2, {})] assert get_event_messages("api.event2") == [event2]
async def test_transaction_rollback_continue(dbapi_database: DbApiConnection, get_processed_events): # Check we can still use the connection following a rollback await dbapi_database.migrate() await dbapi_database.start_transaction() await dbapi_database.store_processed_event( EventMessage(api_name="api", event_name="event", id="123")) await dbapi_database.rollback_transaction() # Rollback await dbapi_database.store_processed_event( EventMessage(api_name="api", event_name="event", id="123")) assert len(await get_processed_events()) == 1
def test_mock_result_event_names_fired(mock_result: testing.MockResult, property_name): event1 = EventMessage(api_name="api1", event_name="event1") event2 = EventMessage(api_name="api2", event_name="event2") # fmt: off mock_result.mocker_context.event.to_transport.put_items = [ ( SendEventCommand(message=event1, options={}), None, ), ( SendEventCommand(message=event2, options={}), None, ), ] # fmt: on assert getattr(mock_result, property_name) == ["api1.event1", "api2.event2"]
def test_event_validate(create_bus_client_with_unhappy_schema): client: BusClient = create_bus_client_with_unhappy_schema() message = EventMessage(api_name="api", event_name="proc", kwargs={"p": 1}) with pytest.raises(ValidationError): validate_outgoing(config=client.config, schema=client.schema, message=message) jsonschema.validate.assert_called_with({"p": 1}, {"p": {}})
async def test_fetch_ok(transaction_transport_with_consumer, aiopg_cursor, loop): message1 = EventMessage(api_name="api", event_name="event", id="1") message2 = EventMessage(api_name="api", event_name="event", id="2") message3 = EventMessage(api_name="api", event_name="event", id="3") transport = await transaction_transport_with_consumer( event_messages=[message1, message2, message3]) consumer = transport.consume(listen_for="api.event") produced_events = await consumer_to_messages(consumer) assert produced_events == [message1, message2, message3] await aiopg_cursor.execute("SELECT COUNT(*) FROM lightbus_processed_events" ) total_processed_events = (await aiopg_cursor.fetchone())[0] assert total_processed_events == 3
async def test_transaction_commit(dbapi_database: DbApiConnection, get_processed_events): await dbapi_database.migrate() await dbapi_database.store_processed_event( EventMessage(api_name="api", event_name="event", id="123")) await dbapi_database.commit_transaction() assert len(await get_processed_events()) == 1
def test_event_validate(create_bus_client_with_unhappy_schema): client: BusClient = create_bus_client_with_unhappy_schema() message = EventMessage(api_name="api", event_name="proc", kwargs={"p": 1}) with pytest.raises(ValidationError): client._validate(message, direction="outgoing", api_name="api", procedure_name="proc") jsonschema.validate.assert_called_with({"p": 1}, {"p": {}})
async def test_fetch_duplicate(transaction_transport_with_consumer, aiopg_cursor, loop): # Same IDs = duplicate messages message1 = EventMessage(api_name="api", event_name="event", id="1") message2 = EventMessage(api_name="api", event_name="event", id="1") transport = await transaction_transport_with_consumer( event_messages=[message1, message2]) consumer = transport.consume(listen_for="api.event") produced_events = await consumer_to_messages(consumer) assert produced_events == [message1 ] # The second message should be ignored await aiopg_cursor.execute("SELECT COUNT(*) FROM lightbus_processed_events" ) total_processed_events = (await aiopg_cursor.fetchone())[0] assert total_processed_events == 1
def test_mock_result_get_event_messages_filtered(mock_result: testing.MockResult, method_name): get_event_messages = getattr(mock_result, method_name) event1 = EventMessage(api_name="api", event_name="event1") event2 = EventMessage(api_name="api", event_name="event2") # fmt: off mock_result.mocker_context.event.to_transport.put_items = [ ( SendEventCommand(message=event1, options={}), None, ), ( SendEventCommand(message=event2, options={}), None, ), ] # fmt: on assert get_event_messages("api.event2") == [event2]
async def test_send_event_bad_option_value(dbapi_database: DbApiConnection): await dbapi_database.migrate() message = EventMessage(api_name="api", event_name="event", kwargs={"field": "abc"}, id="123") options = {"key": range(1, 100)} # not json-serializable with pytest.raises(UnsupportedOptionValue): await dbapi_database.send_event(message, options)
async def test_transaction_rollback(dbapi_database: DbApiConnection, get_processed_events): await dbapi_database.migrate() # We're about to rollback, so make sure we commit our migration first await dbapi_database.start_transaction() await dbapi_database.store_processed_event( EventMessage(api_name="api", event_name="event", id="123")) await dbapi_database.rollback_transaction() assert len(await get_processed_events()) == 0
async def _check_in_out(): transport = await redis_pool.checkout() # Ensure we do something here in order to slow down the execution # time, thereby ensuring our pool starts to fill up. We also need to # use the connection to ensure the connection is lazy loaded await transport.send_event(EventMessage(api_name="api", event_name="event"), options={}) await asyncio.sleep(0.02) await redis_pool.checkin(transport)
def test_mock_result_assert_events_fired_times(mock_result: testing.MockResult, method_name): assert_events_fired = getattr(mock_result, method_name) mock_result.mocker_context.event.to_transport.put_items = [ ( SendEventCommand(message=EventMessage(api_name="api", event_name="event"), options={}), None, ), ( SendEventCommand(message=EventMessage(api_name="api", event_name="event"), options={}), None, ), ] # No error try: assert_events_fired("api.event") except AssertionError as e: assert False, f"{method_name} incorrectly raised an assertion error: {e}" # No error try: assert_events_fired("api.event", times=2) except AssertionError as e: assert False, f"{method_name} incorrectly raised an assertion error: {e}" # Error with pytest.raises(AssertionError): assert_events_fired("api.event", times=0) # Error with pytest.raises(AssertionError): assert_events_fired("api.event", times=1) # Error with pytest.raises(AssertionError): assert_events_fired("api.event", times=3)
async def test_remove_pending_event(dbapi_database: DbApiConnection, get_outbox): await dbapi_database.migrate() message = EventMessage(api_name="api", event_name="event", kwargs={"field": "abc"}, id="123") await dbapi_database.send_event(message, options={}) assert len(await get_outbox()) == 1 await dbapi_database.remove_pending_event(message_id="123") assert len(await get_outbox()) == 0
async def test_send_event(mocker, transaction_transport): f = asyncio.Future() f.set_result(None) m = mocker.patch.object(transaction_transport.database, "send_event", return_value=f) message = EventMessage(api_name="api", event_name="event", id="123") await transaction_transport.send_event(event_message=message, options={"a": 1}) assert m.called args, kwargs = m.call_args assert args == (message, {"a": 1})
def test_mock_result_assert_event_not_fired(mock_result: testing.MockResult, method_name): assert_event_not_fired = getattr(mock_result, method_name) mock_result.event.events = [(EventMessage(api_name="api", event_name="event"), {})] # No exception try: assert_event_not_fired("api.bad_event") except AssertionError as e: assert False, f"{method_name} incorrectly raised an assertion error: {e}" with pytest.raises(AssertionError): assert_event_not_fired("api.event")
async def test_publish_pending(transaction_transport, mocker): f = asyncio.Future() f.set_result(None) m = mocker.patch.object(transaction_transport.child_transport, "send_event", return_value=f) message1 = EventMessage(api_name="api", event_name="event", id="1") message2 = EventMessage(api_name="api", event_name="event", id="2") message3 = EventMessage(api_name="api", event_name="event", id="3") await transaction_transport.database.send_event(message1, options={"a": "1"}) await transaction_transport.database.send_event(message2, options={"a": "2"}) await transaction_transport.database.send_event(message3, options={"a": "3"}) await transaction_transport.publish_pending() assert m.call_count == 3 messages = [c[0][0] for c in m.call_args_list] message_ids = [message.id for message in messages] assert message_ids == ["1", "2", "3"]
async def test_checking_to_closed_transport(mocker, redis_pool: TransportPool): transport = await redis_pool.checkout() mocker.spy(transport, "close") # Close the pool await redis_pool.close() # Should work even though pool is closed await transport.send_event(EventMessage(api_name="api", event_name="event"), options={}) # Check the transport into the closed pool await redis_pool.checkin(transport) # The transport has been closed by the pool assert transport.close.called
async def test_exception_in_listener_shutdown( new_bus, worker: Worker, queue_mocker: Type[BusQueueMockerContext], caplog): caplog.set_level(logging.ERROR) class TestException(Exception): pass def listener(*args, **kwargs): raise TestException() bus = new_bus() bus.client.proxied_client.stop_loop = MagicMock() # Start the listener bus.client.listen_for_events( events=[("my_api", "my_event")], listener=listener, listener_name="test", on_error=OnError.SHUTDOWN, ) with queue_mocker(bus.client) as q: async with worker(bus): consume_command = q.event.to_transport.commands.get( ConsumeEventsCommand) consume_command.destination_queue.put_nowait( EventMessage(api_name="my_api", event_name="my_event")) await asyncio.sleep(0.1) assert len(q.errors.put_items ) == 1, f"Expected one error, got: {q.errors.put_items}" error: Error = q.errors.put_items[0] assert isinstance(error.value, TestException) assert bus.client.stop_loop.called # Note that this hasn't actually shut the bus down, we'll test that in test_server_shutdown assert len(caplog.records) == 2 exception_record: logging.LogRecord = caplog.records[0] assert "TestException" in exception_record.msg help_record: logging.LogRecord = caplog.records[1] assert "Lightbus will now shutdown" in help_record.message
async def test_exception_in_listener_log( new_bus, worker: Worker, queue_mocker: Type[BusQueueMockerContext], caplog): """When on_error=OnError.ACKNOWLEDGE_AND_LOG, we should see an exception logged and the messaged acked""" caplog.set_level(logging.ERROR) class TestException(Exception): pass def listener(*args, **kwargs): raise TestException() bus: BusPath = new_bus() bus.client.proxied_client.stop_loop = MagicMock() # Start the listener bus.client.listen_for_events( events=[("my_api", "my_event")], listener=listener, listener_name="test", on_error=OnError.ACKNOWLEDGE_AND_LOG, ) with queue_mocker(bus.client) as q: # Don't send event acks, we just want to ensure an attempt was made to ack q.event.to_transport.blackhole(AcknowledgeEventCommand) async with worker(bus): consume_command = q.event.to_transport.commands.get( ConsumeEventsCommand) consume_command.destination_queue.put_nowait( EventMessage(api_name="my_api", event_name="my_event")) await asyncio.sleep(0.1) # Ack happened assert not q.errors.put_items assert not bus.client.stop_loop.called assert q.event.to_transport.commands.get(AcknowledgeEventCommand) # Logging happened assert len(caplog.records) == 1 log_record: logging.LogRecord = caplog.records[0] assert log_record.levelname == "ERROR" assert type(log_record.msg) == TestException
async def test_send_event_ok(dbapi_database: DbApiConnection, get_outbox): await dbapi_database.migrate() message = EventMessage(api_name="api", event_name="event", kwargs={"field": "abc"}, id="123") options = {"key": "value"} await dbapi_database.send_event(message, options) assert len(await get_outbox()) == 1 retrieved_message, options = await dbapi_database.consume_pending_events( message_id="123").__anext__() assert retrieved_message.id == "123" assert retrieved_message.get_kwargs() == {"field": "abc"} assert retrieved_message.get_metadata() == { "api_name": "api", "event_name": "event", "id": "123", "version": 1, } assert options == {"key": "value"}
async def test_is_event_duplicate_false(dbapi_database: DbApiConnection): await dbapi_database.migrate() message = EventMessage(api_name="api", event_name="event", id="123") assert await dbapi_database.is_event_duplicate(message) == False
async def test_is_event_duplicate_true(dbapi_database: DbApiConnection): await dbapi_database.migrate() message = EventMessage(api_name="api", event_name="event", id="123") await dbapi_database.store_processed_event(message) assert await dbapi_database.is_event_duplicate(message) == True