def test_emit_event_with_tuple_payload_fails_array_schema_validation( server_info: Info, faker: Faker): namespace = f"/{faker.pystr()}" event_name = faker.pystr() spec = AsyncApiSpec( asyncapi=faker.pystr(), info=server_info, channels={ namespace: Channel(subscribe=Operation(message=OneOfMessages(one_of=[ Message( name=event_name, payload={ "type": "array", "items": { "type": "string" } }, ) ]), )) }, ) server = AsynctionSocketIO(spec, True, True, [], None, None) payload = ("foo", "bar") # valid element types, but invalid container type with pytest.raises(PayloadValidationException): server.emit(event_name, payload, namespace=namespace)
def test_register_handlers_registers_noop_handler_for_message_with_no_ack( server_info: Info, faker: Faker, ): namespace = f"/{faker.pystr()}" event_name = faker.word() spec = AsyncApiSpec( asyncapi=faker.pystr(), info=server_info, channels={ namespace: Channel(publish=Operation(message=OneOfMessages(one_of=[ Message( name=event_name, payload={"type": "object"}, x_handler=faker.pystr(), ) ]), )) }, ) server = new_mock_asynction_socket_io(spec) server._register_handlers() assert len(server.handlers) == 2 # connect handler included as well registered_event, registered_handler, registered_namespace = server.handlers[ 0] assert registered_event == event_name assert registered_namespace == namespace handler = deep_unwrap(registered_handler) assert handler == _noop_handler
def test_register_handlers_skips_payload_validator_if_validation_is_disabled( server_info: Info, faker: Faker, ): namespace = f"/{faker.pystr()}" event_name = faker.word() spec = AsyncApiSpec( asyncapi=faker.pystr(), info=server_info, channels={ namespace: Channel(publish=Operation(message=OneOfMessages(one_of=[ Message( name=event_name, payload={"type": "string"}, x_handler="tests.fixtures.handlers.ping", ) ]), )) }, ) server = AsynctionSocketIO(spec, False, True, [], None, None) server._register_handlers() _, registered_handler, _ = server.handlers[0] handler_with_validation = deep_unwrap(registered_handler, depth=1) actual_handler = deep_unwrap(handler_with_validation) assert handler_with_validation == actual_handler args = (faker.pyint(), ) handler_with_validation(*args) # handler does not raise validation errors assert True
def test_register_handlers_adds_payload_validator_if_validation_is_enabled( server_info: Info, faker: Faker, ): namespace = f"/{faker.pystr()}" spec = AsyncApiSpec( asyncapi=faker.pystr(), info=server_info, channels={ namespace: Channel(publish=Operation(message=OneOfMessages(one_of=[ Message( name=faker.word(), payload={"type": "string"}, x_handler=faker.pystr(), ) ]), )) }, ) server = new_mock_asynction_socket_io(spec) server._register_handlers() _, registered_handler, _ = server.handlers[0] handler_with_validation = deep_unwrap(registered_handler, depth=1) actual_handler = deep_unwrap(handler_with_validation) args = (faker.pyint(), ) actual_handler(*args) # actual handler does not raise validation errors with pytest.raises(PayloadValidationException): handler_with_validation(*args)
def test_register_handlers_registers_callables_with_correct_event_name_and_namespace( server_info: Info, faker: Faker, ): namespace = f"/{faker.pystr()}" event_name = faker.word() spec = AsyncApiSpec( asyncapi=faker.pystr(), info=server_info, channels={ namespace: Channel(publish=Operation(message=OneOfMessages(one_of=[ Message( name=event_name, payload={"type": "object"}, x_handler="tests.fixtures.handlers.ping", ) ]), )) }, ) server = AsynctionSocketIO(spec, True, True, [], None, None) server._register_handlers() assert len(server.handlers) == 2 # connection handler is also registered ping_handler_entry, connect_handler_entry = server.handlers registered_event, registered_handler, registered_namespace = ping_handler_entry assert registered_event == event_name assert deep_unwrap(registered_handler) == ping assert registered_namespace == namespace connection_event, connection_handler, registered_namespace = connect_handler_entry assert connection_event == "connect" assert deep_unwrap(connection_handler) == _noop_handler assert registered_namespace == namespace
def test_make_subscription_task_with_message_payload_but_no_ack( server_info: Info, faker: Faker): namespace = f"/{faker.pystr()}" message = Message( name=faker.word(), payload={ "type": "string", "enum": [faker.pystr(), faker.pystr()], }, ) spec = AsyncApiSpec( asyncapi=faker.pystr(), info=server_info, channels={ namespace: Channel(subscribe=Operation(message=OneOfMessages( one_of=[message]), )) }, ) server = new_mock_asynction_socket_io(spec) task = server.make_subscription_task(message=message, namespace=namespace) with patch.object(server, "emit") as emit_mock: task() emit_mock.assert_called_once_with(message.name, ANY, namespace=namespace, callback=None) _, data = emit_mock.call_args[0] jsonschema.validate(data, message.payload) assert True
def test_emit_event_with_tuple_payload_is_treated_as_multiple_args( super_method_mock: mock.Mock, server_info: Info, faker: Faker): namespace = f"/{faker.pystr()}" event_name = faker.pystr() spec = AsyncApiSpec( asyncapi=faker.pystr(), info=server_info, channels={ namespace: Channel(subscribe=Operation(message=OneOfMessages(one_of=[ Message( name=event_name, payload={ "type": "array", "prefixItems": [ { "type": "number" }, { "type": "string" }, ], }, ) ]), )) }, ) server = AsynctionSocketIO(spec, True, True, [], None, None) payload = (faker.pyint(), faker.pystr()) server.emit(event_name, payload, namespace=namespace) super_method_mock.assert_called_once_with(event_name, payload, namespace=namespace)
def test_make_subscription_task_with_no_message_payload_but_ack( server_info: Info, faker: Faker): namespace = f"/{faker.pystr()}" message = Message( name=faker.word(), x_ack=MessageAck(args={ "type": "string", "enum": [faker.pystr(), faker.pystr()], }), ) spec = AsyncApiSpec( asyncapi=faker.pystr(), info=server_info, channels={ namespace: Channel(subscribe=Operation(message=OneOfMessages( one_of=[message]), )) }, ) server = new_mock_asynction_socket_io(spec) task = server.make_subscription_task(message=message, namespace=namespace) with patch.object(server, "emit") as emit_mock: task() emit_mock.assert_called_once_with(message.name, None, namespace=namespace, callback=_noop_handler)
def test_emit_valid_event_invokes_super_method(super_method_mock: mock.Mock, server_info: Info, faker: Faker): namespace = f"/{faker.pystr()}" event_name = faker.pystr() spec = AsyncApiSpec( asyncapi=faker.pystr(), info=server_info, channels={ namespace: Channel(subscribe=Operation(message=OneOfMessages(one_of=[ Message( name=event_name, payload={"type": "string"}, ) ]), )) }, ) server = AsynctionSocketIO(spec, True, True, [], None, None) event_args = [faker.pystr()] server.emit(event_name, *event_args, namespace=namespace) super_method_mock.assert_called_once_with(event_name, *event_args, namespace=namespace)
def test_emit_event_with_array_payload_is_treated_as_single_arg( super_method_mock: mock.Mock, server_info: Info, faker: Faker): namespace = f"/{faker.pystr()}" event_name = faker.pystr() spec = AsyncApiSpec( asyncapi=faker.pystr(), info=server_info, channels={ namespace: Channel(subscribe=Operation(message=OneOfMessages(one_of=[ Message( name=event_name, payload={ "type": "array", "items": { "type": "number" } }, ) ]), )) }, ) server = AsynctionSocketIO(spec, True, True, [], None, None) payload = faker.pylist(value_types=[int]) server.emit(event_name, payload, namespace=namespace) super_method_mock.assert_called_once_with(event_name, payload, namespace=namespace)
def test_publish_message_validator_factory_invalid_ack_fails_validation( faker: Faker, ): with_validation: Decorator = publish_message_validator_factory( message=Message( name=faker.word(), payload={ "type": "object", "properties": { "hello": { "type": "string" } }, }, x_ack=MessageAck( args={ "type": "object", "properties": { "acknowledged": { "type": "string" } }, "required": ["acknowledged"], }), )) @with_validation def handler(message: Mapping) -> Mapping[str, str]: assert "hello" in message return {"not-acknowledged": faker.pystr()} with pytest.raises(MessageAckValidationException): handler({"hello": faker.pystr()})
def test_emit_event_not_defined_under_given_valid_namespace_raises_validation_exc( server_info: Info, faker: Faker, ): namespace = f"/{faker.pystr()}" spec = AsyncApiSpec( asyncapi=faker.pystr(), info=server_info, channels={ namespace: Channel(subscribe=Operation(message=OneOfMessages(one_of=[ Message( name=faker.pystr(), payload={"type": "object"}, ) ]), )) }, ) server = AsynctionSocketIO(spec, True, True, [], None, None) with pytest.raises(ValidationException): # Correct namespace but undefined event: server.emit(faker.pystr(), faker.pydict(value_types=[str, int]), namespace=namespace)
def test_publish_message_validator_factory_skips_ack_validation_if_handler_returns_none( faker: Faker, ): with_validation: Decorator = publish_message_validator_factory( message=Message( name=faker.word(), payload={ "type": "object", "properties": { "hello": { "type": "string" } }, }, x_ack=MessageAck( args={ "type": "object", "properties": { "acknowledged": { "type": "string" } }, "required": ["acknowledged"], }), )) @with_validation def handler(message: Mapping) -> None: assert "hello" in message handler({"hello": faker.pystr()}) assert True
def test_publish_message_validator_factory_validates_valid_args_and_acks_successfully( faker: Faker, ): with_validation: Decorator = publish_message_validator_factory( message=Message( name=faker.word(), payload={ "type": "object", "properties": { "hello": { "type": "string" } }, }, x_ack=MessageAck( args={ "type": "object", "properties": { "acknowledged": { "type": "string" } }, "required": ["acknowledged"], }), )) @with_validation def handler(message: Mapping) -> Mapping[str, str]: assert "hello" in message return {"acknowledged": faker.pystr()} handler({"hello": faker.pystr()}) assert True
def test_emit_validiation_is_ignored_if_validation_flag_is_false( super_method_mock: mock.Mock, server_info: Info, faker: Faker): namespace = f"/{faker.pystr()}" event_name = faker.pystr() spec = AsyncApiSpec( asyncapi=faker.pystr(), info=server_info, channels={ namespace: Channel(subscribe=Operation(message=OneOfMessages(one_of=[ Message( name=event_name, payload={"type": "number"}, ) ]), )) }, ) server = AsynctionSocketIO(spec, False, True, [], None, None) event_args = [faker.pystr()] # invalid args server.emit(event_name, *event_args, namespace=namespace) # super method called because validation was skipped super_method_mock.assert_called_once_with(event_name, *event_args, namespace=namespace)
def test_channel_raises_value_error_if_publish_messages_miss_handler( faker: Faker): with pytest.raises(ValueError): Channel( subscribe=Operation(message=OneOfMessages(one_of=[ Message( name=faker.pystr(), payload=faker.pydict(value_types=[str, int]), ) ])), publish=Operation(message=OneOfMessages(one_of=[ *[ Message( name=faker.pystr(), payload=faker.pydict(value_types=[str, int]), x_handler=faker.pystr(), ) for _ in range(faker.pyint(min_value=2, max_value=10)) ], Message( name=faker.pystr(), payload=faker.pydict(value_types=[str, int]), ), ])), )
def test_register_handlers_registers_valid_handler_for_message_with_ack( server_info: Info, faker: Faker): namespace = f"/{faker.pystr()}" event_name = faker.word() ack_schema = { "type": "object", "properties": { "foo": { "type": "string", "enum": [faker.pystr(), faker.pystr()], }, "bar": { "type": "number", "minimum": 10, "maximum": 20, }, }, "required": ["foo", "bar"], } spec = AsyncApiSpec( asyncapi=faker.pystr(), info=server_info, channels={ namespace: Channel(publish=Operation(message=OneOfMessages(one_of=[ Message( name=event_name, payload={"type": "object"}, x_handler=faker.pystr(), x_ack=MessageAck(args=ack_schema), ) ]), )) }, ) server = new_mock_asynction_socket_io(spec) server._register_handlers() assert len(server.handlers) == 2 # connect handler included as well registered_event, registered_handler, registered_namespace = server.handlers[ 0] assert registered_event == event_name assert registered_namespace == namespace handler = deep_unwrap(registered_handler) ack = handler(faker.pydict()) jsonschema.validate(ack, ack_schema) assert True
def test_make_subscription_task_with_message_payload_and_ack( server_info: Info, faker: Faker): namespace = f"/{faker.pystr()}" message = Message( name=faker.word(), payload={ "type": "object", "properties": { "foo": { "type": "string", "enum": [faker.pystr(), faker.pystr()], }, "bar": { "type": "number", "minimum": 10, "maximum": 20, }, }, "required": ["foo", "bar"], }, x_ack=MessageAck(args={ "type": "string", "enum": [faker.pystr(), faker.pystr()], }), ) spec = AsyncApiSpec( asyncapi=faker.pystr(), info=server_info, channels={ namespace: Channel(subscribe=Operation(message=OneOfMessages( one_of=[message]), )) }, ) server = new_mock_asynction_socket_io(spec) task = server.make_subscription_task(message=message, namespace=namespace) with patch.object(server, "emit") as emit_mock: task() emit_mock.assert_called_once_with(message.name, ANY, namespace=namespace, callback=_noop_handler) _, data = emit_mock.call_args[0] jsonschema.validate(data, message.payload) assert True
def test_emit_event_wraps_callback_with_validator(super_method_mock: mock.Mock, server_info: Info, faker: Faker): namespace = f"/{faker.pystr()}" event_name = faker.pystr() spec = AsyncApiSpec( asyncapi=faker.pystr(), info=server_info, channels={ namespace: Channel(subscribe=Operation(message=OneOfMessages(one_of=[ Message( name=event_name, payload={"type": "number"}, x_ack=MessageAck(args={"type": "boolean"}), ) ]), )) }, ) server = AsynctionSocketIO(spec, True, True, [], None, None) def actual_callback(*args): # dummy callback pass server.emit(event_name, faker.pyint(), namespace=namespace, callback=actual_callback) super_method_mock.assert_called_once() *_, kwargs = super_method_mock.call_args callback_with_validation = kwargs["callback"] callback_args = [faker.pystr() ] # invalid callback args (should have been a boolean) # actual callback has no validation -- hence it does not fail actual_callback(*callback_args) with pytest.raises(MessageAckValidationException): callback_with_validation(*callback_args)
def test_register_handlers_adds_ack_validator_if_validation_is_enabled( server_info: Info, faker: Faker): namespace = f"/{faker.pystr()}" event_name = faker.word() spec = AsyncApiSpec( asyncapi=faker.pystr(), info=server_info, channels={ namespace: Channel(publish=Operation(message=OneOfMessages(one_of=[ Message( name=event_name, payload={"type": "string"}, x_handler="tests.fixtures.handlers.ping_with_ack", x_ack=MessageAck( args={ "type": "object", "properties": { "ack": { "type": "number" } }, "required": ["ack"], }), ) ]), )) }, ) server = AsynctionSocketIO(spec, True, True, [], None, None) server._register_handlers() _, registered_handler, _ = server.handlers[0] handler_with_validation = deep_unwrap(registered_handler, depth=1) actual_handler = deep_unwrap(handler_with_validation) args = (faker.pystr(), ) # valid handler args # actual handler does not raise validation errors, although it returns invalid data actual_handler(*args) with pytest.raises(MessageAckValidationException): handler_with_validation(*args)
def test_init_app_does_not_register_handlers_if_app_is_none( server_info: Info, faker: Faker): namespace = f"/{faker.pystr()}" event_name = faker.pystr() spec = AsyncApiSpec( asyncapi=faker.pystr(), info=server_info, channels={ namespace: Channel(publish=Operation(message=OneOfMessages(one_of=[ Message( name=event_name, payload={"type": "object"}, x_handler="tests.fixtures.handlers.ping", ) ]), )) }, ) server = AsynctionSocketIO(spec, False, False, [], None, None) server.init_app(app=None) assert not server.handlers
def test_publish_message_validato_factory_invalid_args_fail_payload_validation( faker: Faker, ): with_validation: Decorator = publish_message_validator_factory( message=Message( name=faker.word(), payload={ "type": "object", "properties": { "hello": { "type": "string" } }, }, ), ) @with_validation def handler(message: Mapping) -> None: assert "hello" in message with pytest.raises(PayloadValidationException): handler({"hello": faker.pyint()})
def test_callback_validator_factory_invalid_callback_args_fail_validation( faker: Faker, ): with_validation: Decorator = callback_validator_factory(message=Message( name=faker.word(), x_ack=MessageAck( args={ "type": "object", "properties": { "acknowledged": { "type": "string" } }, "required": ["acknowledged"], }), )) @with_validation def callback(message: Mapping) -> None: assert "not-acknowledged" in message with pytest.raises(MessageAckValidationException): callback({"not-acknowledged": faker.pystr()})
def test_callback_validator_factory_validates_valid_callback_args_successfully( faker: Faker, ): with_validation: Decorator = callback_validator_factory(message=Message( name=faker.word(), x_ack=MessageAck( args={ "type": "object", "properties": { "acknowledged": { "type": "string" } }, "required": ["acknowledged"], }), )) @with_validation def callback(message: Mapping) -> None: assert "acknowledged" in message callback({"acknowledged": faker.pystr()}) assert True
def test_emit_event_with_invalid_args_fails_validation(server_info: Info, faker: Faker): namespace = f"/{faker.pystr()}" event_name = faker.pystr() spec = AsyncApiSpec( asyncapi=faker.pystr(), info=server_info, channels={ namespace: Channel(subscribe=Operation(message=OneOfMessages(one_of=[ Message( name=event_name, payload={"type": "number"}, ) ]), )) }, ) server = AsynctionSocketIO(spec, True, True, [], None, None) with pytest.raises(PayloadValidationException): # Event args do not adhere to the schema server.emit(event_name, faker.pystr(), namespace=namespace)
def test_run_spawns_background_tasks_and_calls_super_run( server_info: Info, faker: Faker): namespace = f"/{faker.pystr()}" spec = AsyncApiSpec( asyncapi=faker.pystr(), info=server_info, channels={ namespace: Channel(subscribe=Operation(message=OneOfMessages(one_of=[ Message( name=faker.word(), payload={"type": "string"}, ) ]), )) }, ) flask_app = Flask(__name__) server = new_mock_asynction_socket_io(spec, flask_app) background_tasks: MutableSequence[MockThread] = [] def start_background_task_mock(target, *args, **kwargs): mt = MockThread(target=target, args=args, kwargs=kwargs) background_tasks.append(mt) return mt with patch.object(SocketIO, "run") as super_run_mock: with patch.object(server, "start_background_task", start_background_task_mock): server.run(flask_app) assert len(background_tasks) == 2 assert background_tasks[0].target == task_runner assert background_tasks[-1].target == task_scheduler super_run_mock.assert_called_once_with(flask_app, host=None, port=None)
def test_publish_message_validator_factory_skips_ack_validation_if_no_ack_schema( faker: Faker, ): with_validation: Decorator = publish_message_validator_factory( message=Message( name=faker.word(), payload={ "type": "object", "properties": { "hello": { "type": "string" } }, }, x_ack=None, )) @with_validation def handler(message: Mapping) -> Mapping[str, str]: assert "hello" in message return {"not-acknowledged": faker.pystr()} handler({"hello": faker.pystr()}) assert True
def test_emit_event_that_has_no_subscribe_operation_raises_validation_exc( server_info: Info, faker: Faker): namespace = f"/{faker.pystr()}" event_name = faker.pystr() spec = AsyncApiSpec( asyncapi=faker.pystr(), info=server_info, channels={ namespace: Channel(publish=Operation(message=OneOfMessages(one_of=[ Message( name=event_name, payload={"type": "object"}, x_handler="tests.fixtures.handlers.ping", ) ]), )) }, ) server = AsynctionSocketIO(spec, True, True, [], None, None) with pytest.raises(ValidationException): server.emit(event_name, faker.pydict(value_types=[str, int]), namespace=namespace)
def test_run_spawns_minimum_number_of_workers(server_info: Info, faker: Faker): max_worker_number = faker.pyint(min_value=8, max_value=15) sub_messages_number = max_worker_number - faker.pyint(min_value=3, max_value=5) namespace = f"/{faker.pystr()}" spec = AsyncApiSpec( asyncapi=faker.pystr(), info=server_info, channels={ namespace: Channel(subscribe=Operation(message=OneOfMessages(one_of=[ Message( name=faker.word(), payload={"type": "string"}, ) for _ in range(sub_messages_number) ]), )) }, ) background_tasks: MutableSequence[MockThread] = [] def start_background_task_mock(target, *args, **kwargs): mt = MockThread(target=target, args=args, kwargs=kwargs) background_tasks.append(mt) return mt flask_app = Flask(__name__) server = new_mock_asynction_socket_io(spec, flask_app) with patch.object(SocketIO, "run"): with patch.object(server, "start_background_task", start_background_task_mock): server.run(flask_app, max_worker_number=max_worker_number) assert len(background_tasks) == sub_messages_number + 1