diff --git a/azure/functions/decorators/function_app.py b/azure/functions/decorators/function_app.py index 401b89b7..14af303f 100644 --- a/azure/functions/decorators/function_app.py +++ b/azure/functions/decorators/function_app.py @@ -204,6 +204,8 @@ def __str__(self): class FunctionBuilder(object): + function_bindings: dict = {} + def __init__(self, func, function_script_file): self._function = Function(func, function_script_file) @@ -232,6 +234,12 @@ def _validate_function(self, """ Validates the function information before building the function. + Functions with the same function name are not supported and should + fail indexing. If a function name is not defined, the default is the + method name. This also means that two functions with the same + method name will also fail indexing. + https://github.com/Azure/azure-functions-python-worker/issues/1489 + :param auth_level: Http auth level that will be set if http trigger function auth level is None. """ @@ -262,6 +270,16 @@ def _validate_function(self, parse_singular_param_to_enum(auth_level, AuthLevel)) self._function._is_http_function = True + # This dict contains the function name and its bindings for all + # functions in an app. If a previous function has the same name, + # indexing will fail here. + if self.function_bindings.get(function_name, None): + raise ValueError( + f"Function {function_name} does not have a unique" + f" function name. Please change @app.function_name() or" + f" the function method name to be unique.") + self.function_bindings[function_name] = bindings + def build(self, auth_level: Optional[AuthLevel] = None) -> Function: """ Validates and builds the function object. @@ -3333,11 +3351,13 @@ class ExternalHttpFunctionApp( @abc.abstractmethod def _add_http_app(self, http_middleware: Union[ - AsgiMiddleware, WsgiMiddleware]) -> None: + AsgiMiddleware, WsgiMiddleware], + function_name: str = 'http_app_func') -> None: """Add a Wsgi or Asgi app integrated http function. :param http_middleware: :class:`WsgiMiddleware` or class:`AsgiMiddleware` instance. + :param function_name: name for the function :return: None """ @@ -3346,17 +3366,18 @@ def _add_http_app(self, class AsgiFunctionApp(ExternalHttpFunctionApp): def __init__(self, app, - http_auth_level: Union[AuthLevel, str] = AuthLevel.FUNCTION): + http_auth_level: Union[AuthLevel, str] = AuthLevel.FUNCTION, + function_name: str = 'http_app_func'): """Constructor of :class:`AsgiFunctionApp` object. :param app: asgi app object. :param http_auth_level: Determines what keys, if any, need to be - present - on the request in order to invoke the function. + present on the request in order to invoke the function. + :param function_name: function name """ super().__init__(auth_level=http_auth_level) self.middleware = AsgiMiddleware(app) - self._add_http_app(self.middleware) + self._add_http_app(self.middleware, function_name) self.startup_task_done = False def __del__(self): @@ -3365,7 +3386,8 @@ def __del__(self): def _add_http_app(self, http_middleware: Union[ - AsgiMiddleware, WsgiMiddleware]) -> None: + AsgiMiddleware, WsgiMiddleware], + function_name: str = 'http_app_func') -> None: """Add an Asgi app integrated http function. :param http_middleware: :class:`WsgiMiddleware` @@ -3379,6 +3401,7 @@ def _add_http_app(self, asgi_middleware: AsgiMiddleware = http_middleware + @self.function_name(name=function_name) @self.http_type(http_type='asgi') @self.route(methods=(method for method in HttpMethod), auth_level=self.auth_level, @@ -3395,21 +3418,25 @@ async def http_app_func(req: HttpRequest, context: Context): class WsgiFunctionApp(ExternalHttpFunctionApp): def __init__(self, app, - http_auth_level: Union[AuthLevel, str] = AuthLevel.FUNCTION): + http_auth_level: Union[AuthLevel, str] = AuthLevel.FUNCTION, + function_name: str = 'http_app_func'): """Constructor of :class:`WsgiFunctionApp` object. :param app: wsgi app object. + :param function_name: function name """ super().__init__(auth_level=http_auth_level) - self._add_http_app(WsgiMiddleware(app)) + self._add_http_app(WsgiMiddleware(app), function_name) def _add_http_app(self, http_middleware: Union[ - AsgiMiddleware, WsgiMiddleware]) -> None: + AsgiMiddleware, WsgiMiddleware], + function_name: str = 'http_app_func') -> None: """Add a Wsgi app integrated http function. :param http_middleware: :class:`WsgiMiddleware` or class:`AsgiMiddleware` instance. + :param function_name: name for the function :return: None """ @@ -3419,6 +3446,7 @@ def _add_http_app(self, wsgi_middleware: WsgiMiddleware = http_middleware + @self.function_name(function_name) @self.http_type(http_type='wsgi') @self.route(methods=(method for method in HttpMethod), auth_level=self.auth_level, diff --git a/tests/decorators/test_dapr.py b/tests/decorators/test_dapr.py index 522f1b54..c87fa398 100644 --- a/tests/decorators/test_dapr.py +++ b/tests/decorators/test_dapr.py @@ -24,7 +24,7 @@ def test_dapr_service_invocation_trigger_default_args(self): @app.dapr_service_invocation_trigger(arg_name="req", method_name="dummy_method_name") - def dummy(): + def test_dapr_service_invocation_trigger_default_args(): pass func = self._get_user_function(app) @@ -50,7 +50,7 @@ def test_dapr_binding_trigger_default_args(self): @app.dapr_binding_trigger(arg_name="req", binding_name="dummy_binding_name") - def dummy(): + def test_dapr_binding_trigger_default_args(): pass func = self._get_user_function(app) @@ -73,7 +73,7 @@ def test_dapr_topic_trigger_default_args(self): pub_sub_name="dummy_pub_sub_name", topic="dummy_topic", route="/dummy_route") - def dummy(): + def test_dapr_topic_trigger_default_args(): pass func = self._get_user_function(app) @@ -99,7 +99,7 @@ def test_dapr_state_input_binding(self): @app.dapr_state_input(arg_name="in", state_store="dummy_state_store", key="dummy_key") - def dummy(): + def test_dapr_state_input_binding(): pass func = self._get_user_function(app) @@ -125,7 +125,7 @@ def test_dapr_secret_input_binding(self): secret_store_name="dummy_secret_store_name", key="dummy_key", metadata="dummy_metadata") - def dummy(): + def test_dapr_secret_input_binding(): pass func = self._get_user_function(app) @@ -151,7 +151,7 @@ def test_dapr_state_output_binding(self): @app.dapr_state_output(arg_name="out", state_store="dummy_state_store", key="dummy_key") - def dummy(): + def test_dapr_state_output_binding(): pass func = self._get_user_function(app) @@ -177,7 +177,7 @@ def test_dapr_invoke_output_binding(self): app_id="dummy_app_id", method_name="dummy_method_name", http_verb="dummy_http_verb") - def dummy(): + def test_dapr_invoke_output_binding(): pass func = self._get_user_function(app) @@ -203,7 +203,7 @@ def test_dapr_publish_output_binding(self): @app.dapr_publish_output(arg_name="out", pub_sub_name="dummy_pub_sub_name", topic="dummy_topic") - def dummy(): + def test_dapr_publish_output_binding(): pass func = self._get_user_function(app) @@ -228,7 +228,7 @@ def test_dapr_binding_output_binding(self): @app.dapr_binding_output(arg_name="out", binding_name="dummy_binding_name", operation="dummy_operation") - def dummy(): + def test_dapr_binding_output_binding(): pass func = self._get_user_function(app) diff --git a/tests/decorators/test_decorators.py b/tests/decorators/test_decorators.py index 1bd16de3..82973ba3 100644 --- a/tests/decorators/test_decorators.py +++ b/tests/decorators/test_decorators.py @@ -27,11 +27,11 @@ def _get_user_function(self, app): def test_route_is_function_name(self): app = self.func_app - test_func_name = "dummy_function" + test_func_name = "test_route_is_function_name" @app.function_name(test_func_name) @app.route() - def dummy_func(): + def test_route_is_function_name(): pass func = self._get_user_function(app) @@ -44,26 +44,28 @@ def test_route_is_python_function_name(self): app = self.func_app @app.route() - def dummy_func(): + def test_route_is_python_function_name(): pass func = self._get_user_function(app) - self.assertEqual(func.get_function_name(), "dummy_func") + self.assertEqual(func.get_function_name(), + "test_route_is_python_function_name") self.assertTrue(isinstance(func.get_trigger(), HttpTrigger)) - self.assertTrue(func.get_trigger().route, "dummy_func") + self.assertTrue(func.get_trigger().route, + "test_route_is_python_function_name") def test_route_is_custom(self): app = self.func_app - @app.function_name("dummy_function") + @app.function_name("test_route_is_custom") @app.route("dummy") def dummy_func(): pass func = self._get_user_function(app) - self.assertEqual("dummy_function", func.get_function_name()) + self.assertEqual("test_route_is_custom", func.get_function_name()) self.assertTrue(isinstance(func.get_trigger(), HttpTrigger)) self.assertTrue(func.get_trigger().route, "dummy") @@ -71,11 +73,12 @@ def test_schedule_trigger_default_args(self): app = self.func_app @app.schedule(arg_name="req", schedule="dummy_schedule") - def dummy_func(): + def test_schedule_trigger_default_args(): pass func = self._get_user_function(app) - self.assertEqual(func.get_function_name(), "dummy_func") + self.assertEqual(func.get_function_name(), + "test_schedule_trigger_default_args") assert_json(self, func, { "scriptFile": "function_app.py", "bindings": [ @@ -94,7 +97,7 @@ def test_schedule_trigger_full_args(self): @app.schedule(arg_name="req", schedule="dummy_schedule", run_on_startup=False, use_monitor=False, data_type=DataType.STRING, dummy_field='dummy') - def dummy(): + def test_schedule_trigger_full_args(): pass func = self._get_user_function(app) @@ -118,11 +121,12 @@ def test_timer_trigger_default_args(self): app = self.func_app @app.timer_trigger(arg_name="req", schedule="dummy_schedule") - def dummy_func(): + def test_timer_trigger_default_args(): pass func = self._get_user_function(app) - self.assertEqual(func.get_function_name(), "dummy_func") + self.assertEqual(func.get_function_name(), + "test_timer_trigger_default_args") assert_json(self, func, { "scriptFile": "function_app.py", "bindings": [ @@ -141,7 +145,7 @@ def test_timer_trigger_full_args(self): @app.timer_trigger(arg_name="req", schedule="dummy_schedule", run_on_startup=False, use_monitor=False, data_type=DataType.STRING, dummy_field='dummy') - def dummy(): + def test_timer_trigger_full_args(): pass func = self._get_user_function(app) @@ -165,7 +169,7 @@ def test_orchestration_trigger(self): app = self.func_app @app.orchestration_trigger("context") - def dummy1(context): + def test_orchestration_trigger(context): pass func = self._get_user_function(app) @@ -184,7 +188,7 @@ def test_activity_trigger(self): app = self.func_app @app.activity_trigger("arg") - def dummy2(arg): + def test_activity_trigger(arg): pass func = self._get_user_function(app) @@ -203,7 +207,7 @@ def test_entity_trigger(self): app = self.func_app @app.entity_trigger("context") - def dummy3(context): + def test_entity_trigger(context): pass func = self._get_user_function(app) @@ -223,7 +227,7 @@ def test_durable_client(self): @app.generic_trigger(arg_name="req", type=HTTP_TRIGGER) @app.durable_client_input(client_name="client") - def dummy(client): + def test_durable_client(client): pass func = self._get_user_function(app) @@ -243,7 +247,7 @@ def test_route_default_args(self): app = self.func_app @app.route() - def dummy(): + def test_route_default_args(): pass func = self._get_user_function(app) @@ -255,7 +259,7 @@ def dummy(): "direction": BindingDirection.IN, "type": HTTP_TRIGGER, "name": "req", - "route": "dummy" + "route": "test_route_default_args" }, { "direction": BindingDirection.OUT, @@ -273,7 +277,7 @@ def test_route_with_all_args(self): auth_level=AuthLevel.FUNCTION, route='dummy_route', trigger_extra_fields={"dummy_field": "dummy"}, binding_extra_fields={"dummy_field": "dummy"}) - def dummy(): + def test_route_with_all_args(): pass func = self._get_user_function(app) @@ -304,11 +308,12 @@ def test_warmup_trigger_default_args(self): app = self.func_app @app.warm_up_trigger(arg_name="req") - def dummy_func(): + def test_warmup_trigger_default_args(): pass func = self._get_user_function(app) - self.assertEqual(func.get_function_name(), "dummy_func") + self.assertEqual(func.get_function_name(), + "test_warmup_trigger_default_args") assert_json(self, func, { "scriptFile": "function_app.py", "bindings": [ @@ -325,7 +330,7 @@ def test_warmup_trigger_full_args(self): @app.warm_up_trigger(arg_name="req", data_type=DataType.STRING, dummy_field='dummy') - def dummy(): + def test_warmup_trigger_full_args(): pass func = self._get_user_function(app) @@ -349,7 +354,7 @@ def test_queue_default_args(self): connection="dummy_conn") @app.queue_output(arg_name="out", queue_name="dummy_out_queue", connection="dummy_out_conn") - def dummy(): + def test_queue_default_args(): pass func = self._get_user_function(app) @@ -376,7 +381,7 @@ def test_queue_trigger(self): @app.queue_trigger(arg_name="req", queue_name="dummy_queue", connection="dummy_conn") - def dummy(): + def test_queue_trigger(): pass func = self._get_user_function(app) @@ -399,7 +404,7 @@ def test_queue_output_binding(self): connection="dummy_conn") @app.queue_output(arg_name="out", queue_name="dummy_out_queue", connection="dummy_out_conn") - def dummy(): + def test_queue_output_binding(): pass func = self._get_user_function(app) @@ -424,7 +429,7 @@ def test_queue_full_args(self): @app.queue_output(arg_name="out", queue_name="dummy_out_queue", connection="dummy_out_conn", data_type=DataType.STRING, dummy_field="dummy") - def dummy(): + def test_queue_full_args(): pass func = self._get_user_function(app) @@ -459,7 +464,7 @@ def test_service_bus_queue_default_args(self): @app.service_bus_queue_output(arg_name='res', connection='dummy_out_conn', queue_name='dummy_out_queue') - def dummy(): + def test_service_bus_queue_default_args(): pass func = self._get_user_function(app) @@ -489,7 +494,7 @@ def test_service_bus_queue_trigger(self): @app.service_bus_queue_trigger(arg_name="req", connection="dummy_conn", queue_name="dummy_queue") - def dummy(): + def test_service_bus_queue_trigger(): pass func = self._get_user_function(app) @@ -514,7 +519,7 @@ def test_service_bus_queue_output_binding(self): @app.service_bus_queue_output(arg_name='res', connection='dummy_out_conn', queue_name='dummy_out_queue') - def dummy(): + def test_service_bus_queue_output_binding(): pass func = self._get_user_function(app) @@ -547,7 +552,7 @@ def test_service_bus_queue_full_args(self): data_type=DataType.STREAM, access_rights=AccessRights.MANAGE, dummy_field="dummy") - def dummy(): + def test_service_bus_queue_full_args(): pass func = self._get_user_function(app) @@ -589,7 +594,7 @@ def test_service_bus_topic_default_args(self): @app.service_bus_topic_output(arg_name='res', connection='dummy_conn', topic_name='dummy_topic', subscription_name='dummy_sub') - def dummy(): + def test_service_bus_topic_default_args(): pass func = self._get_user_function(app) @@ -622,7 +627,7 @@ def test_service_bus_topic_trigger(self): connection='dummy_conn', topic_name='dummy_topic', subscription_name='dummy_sub') - def dummy(): + def test_service_bus_topic_trigger(): pass func = self._get_user_function(app) @@ -649,7 +654,7 @@ def test_service_bus_topic_output_binding(self): @app.service_bus_topic_output(arg_name='res', connection='dummy_conn', topic_name='dummy_topic', subscription_name='dummy_sub') - def dummy(): + def test_service_bus_topic_output_binding(): pass func = self._get_user_function(app) @@ -684,7 +689,7 @@ def test_service_bus_topic_full_args(self): data_type=DataType.STRING, access_rights=AccessRights.LISTEN, dummy_field="dummy") - def dummy(): + def test_service_bus_topic_full_args(): pass func = self._get_user_function(app) @@ -727,7 +732,7 @@ def test_event_hub_default_args(self): @app.event_hub_output(arg_name="res", event_hub_name="dummy_event_hub", connection="dummy_connection") - def dummy(): + def test_event_hub_default_args(): pass func = self._get_user_function(app) @@ -757,7 +762,7 @@ def test_event_hub_trigger(self): @app.event_hub_message_trigger(arg_name="req", connection="dummy_connection", event_hub_name="dummy_event_hub") - def dummy(): + def test_event_hub_trigger(): pass func = self._get_user_function(app) @@ -782,7 +787,7 @@ def test_event_hub_output_binding(self): @app.event_hub_output(arg_name="res", event_hub_name="dummy_event_hub", connection="dummy_connection") - def dummy(): + def test_event_hub_output_binding(): pass func = self._get_user_function(app) @@ -813,7 +818,7 @@ def test_event_hub_full_args(self): connection="dummy_connection", data_type=DataType.UNDEFINED, dummy_field="dummy") - def dummy(): + def test_event_hub_full_args(): pass func = self._get_user_function(app) @@ -889,7 +894,7 @@ def test_cosmosdb_v3_full_args(self): preferred_locations="dummy_location", data_type=DataType.STRING, dummy_field="dummy") - def dummy(): + def test_cosmosdb_v3_full_args(): pass func = self._get_user_function(app) @@ -1006,7 +1011,7 @@ def test_cosmosdb_full_args(self): preferred_locations="dummy_location", data_type=DataType.STRING, dummy_field="dummy") - def dummy(): + def test_cosmosdb_full_args(): pass func = self._get_user_function(app) @@ -1090,7 +1095,7 @@ def test_cosmosdb_v3_default_args(self): database_name="dummy_out_db", collection_name="dummy_out_collection", connection_string_setting="dummy_str") - def dummy(): + def test_cosmosdb_v3_default_args(): pass func = self._get_user_function(app) @@ -1140,7 +1145,7 @@ def test_cosmosdb_default_args(self): database_name="dummy_out_db", container_name="dummy_out_container", connection="dummy_str") - def dummy(): + def test_cosmosdb_default_args(): pass func = self._get_user_function(app) @@ -1183,7 +1188,7 @@ def test_cosmosdb_v3_trigger(self): database_name="dummy_db", collection_name="dummy_collection", connection_string_setting="dummy_str") - def dummy(): + def test_cosmosdb_v3_trigger(): pass func = self._get_user_function(app) @@ -1207,7 +1212,7 @@ def test_cosmosdb_trigger(self): database_name="dummy_db", container_name="dummy_container", connection="dummy_str") - def dummy(): + def test_cosmosdb_trigger(): pass func = self._get_user_function(app) @@ -1231,7 +1236,7 @@ def test_not_http_function(self): database_name="dummy_db", container_name="dummy_container", connection="dummy_str") - def dummy(): + def test_not_http_function(): pass funcs = app.get_functions() @@ -1250,7 +1255,7 @@ def test_cosmosdb_v3_input_binding(self): database_name="dummy_in_db", collection_name="dummy_in_collection", connection_string_setting="dummy_str") - def dummy(): + def test_cosmosdb_v3_input_binding(): pass func = self._get_user_function(app) @@ -1279,7 +1284,7 @@ def test_cosmosdb_input_binding(self): database_name="dummy_in_db", container_name="dummy_in_container", connection="dummy_str") - def dummy(): + def test_cosmosdb_input_binding(): pass func = self._get_user_function(app) @@ -1308,7 +1313,7 @@ def test_cosmosdb_v3_output_binding(self): database_name="dummy_out_db", collection_name="dummy_out_collection", connection_string_setting="dummy_str") - def dummy(): + def test_cosmosdb_v3_output_binding(): pass func = self._get_user_function(app) @@ -1337,7 +1342,7 @@ def test_cosmosdb_output_binding(self): database_name="dummy_out_db", container_name="dummy_out_container", connection="dummy_str") - def dummy(): + def test_cosmosdb_output_binding(): pass func = self._get_user_function(app) @@ -1363,7 +1368,7 @@ def test_multiple_triggers(self): @app.schedule(arg_name="req1", schedule="dummy_schedule") @app.schedule(arg_name="req2", schedule="dummy_schedule") - def dummy(): + def test_multiple_triggers(): pass self.assertEqual(err.exception.args[0], "A trigger was already registered to this " @@ -1378,15 +1383,15 @@ def test_no_trigger(self): with self.assertRaises(ValueError) as err: @app.queue_output(arg_name="out", queue_name="dummy_out_queue", connection="dummy_out_conn") - def dummy(): + def test_no_trigger(): pass app.get_functions() self.assertEqual(err.exception.args[0], - "Function dummy does not have a trigger. A valid " - "function must have one and only one trigger " - "registered.") + "Function test_no_trigger does not have a trigger." + " A valid function must have one and only one" + " trigger registered.") def test_multiple_input_bindings(self): app = self.func_app @@ -1416,7 +1421,7 @@ def test_multiple_input_bindings(self): arg_name="res", event_hub_name="dummy_event_hub", connection="dummy_connection") - def dummy(): + def test_multiple_input_bindings(): pass func = self._get_user_function(app) @@ -1543,7 +1548,7 @@ def test_blob_default_args(self): connection="dummy_conn") @app.blob_output(arg_name="out", path="dummy_out_path", connection="dummy_out_conn") - def dummy(): + def test_blob_default_args(): pass func = self._get_user_function(app) @@ -1578,7 +1583,7 @@ def test_blob_trigger(self): @app.blob_trigger(arg_name="req", path="dummy_path", data_type=DataType.STRING, connection="dummy_conn") - def dummy(): + def test_blob_trigger(): pass func = self._get_user_function(app) @@ -1606,7 +1611,7 @@ def test_blob_input_binding(self): @app.blob_input(arg_name="file", path="dummy_in_path", connection="dummy_in_conn", data_type=DataType.STRING) - def dummy(): + def test_blob_input_binding(): pass func = self._get_user_function(app) @@ -1645,7 +1650,7 @@ def test_blob_output_binding(self): @app.blob_output(arg_name="out", path="dummy_out_path", connection="dummy_out_conn", data_type=DataType.STRING) - def dummy(): + def test_blob_output_binding(): pass func = self._get_user_function(app) @@ -1681,7 +1686,7 @@ def test_custom_trigger(self): data_type=DataType.BINARY, connection="dummy_conn", path="dummy_path") - def dummy(): + def test_custom_trigger(): pass func = self._get_user_function(app) @@ -1710,7 +1715,7 @@ def test_custom_input_binding(self): path="dummy_in_path", connection="dummy_in_conn", data_type=DataType.STRING) - def dummy(): + def test_custom_input_binding(): pass func = self._get_user_function(app) @@ -1748,7 +1753,7 @@ def test_custom_output_binding(self): path="dummy_out_path", connection="dummy_out_conn", data_type=DataType.STRING) - def dummy(): + def test_custom_output_binding(): pass func = self._get_user_function(app) @@ -1777,7 +1782,7 @@ def test_custom_http_trigger(self): app = self.func_app @app.generic_trigger(arg_name="req", type=HTTP_TRIGGER) - def dummy(): + def test_custom_http_trigger(): pass func = self._get_user_function(app) @@ -1791,7 +1796,7 @@ def dummy(): "direction": BindingDirection.IN, "type": HTTP_TRIGGER, "name": "req", - "route": "dummy", + "route": "test_custom_http_trigger", "authLevel": AuthLevel.FUNCTION }) @@ -1800,7 +1805,7 @@ def test_custom_binding_with_excluded_params(self): @app.generic_trigger(arg_name="req", type=QUEUE_TRIGGER, direction=BindingDirection.INOUT) - def dummy(): + def test_custom_binding_with_excluded_params(): pass func = self._get_user_function(app) @@ -1822,7 +1827,7 @@ def test_mixed_custom_and_supported_binding(self): path="dummy_out_path", connection="dummy_out_conn", data_type=DataType.STRING) - def dummy(): + def test_mixed_custom_and_supported_binding(): pass func = self._get_user_function(app) @@ -1855,7 +1860,7 @@ def test_event_grid_default_args(self): arg_name="res", topic_endpoint_uri="dummy_topic_endpoint_uri", topic_key_setting="dummy_topic_key_setting") - def dummy(): + def test_event_grid_default_args(): pass func = self._get_user_function(app) @@ -1891,7 +1896,7 @@ def test_event_grid_full_args(self): data_type=DataType.UNDEFINED, dummy_field="dummy" ) - def dummy(): + def test_event_grid_full_args(): pass func = self._get_user_function(app) @@ -1922,7 +1927,7 @@ def test_event_grid_trigger(self): app = self.func_app @app.event_grid_trigger(arg_name="req") - def dummy(): + def test_event_grid_trigger(): pass func = self._get_user_function(app) @@ -1944,7 +1949,7 @@ def test_event_grid_output_binding(self): arg_name="res", topic_endpoint_uri="dummy_topic_endpoint_uri", topic_key_setting="dummy_topic_key_setting") - def dummy(): + def test_event_grid_output_binding(): pass func = self._get_user_function(app) @@ -1970,7 +1975,7 @@ def test_table_default_args(self): connection="dummy_out_conn", row_key="dummy_key", partition_key="dummy_partition_key") - def dummy(): + def test_table_default_args(): pass func = self._get_user_function(app) @@ -1999,7 +2004,7 @@ def dummy(): "type": HTTP_TRIGGER, "name": "req", "authLevel": AuthLevel.FUNCTION, - "route": "dummy" + "route": "test_table_default_args" }, { "direction": BindingDirection.OUT, @@ -2027,7 +2032,7 @@ def test_table_with_all_args(self): connection="dummy_out_conn", row_key="dummy_key", partition_key="dummy_partition_key") - def dummy(): + def test_table_with_all_args(): pass func = self._get_user_function(app) @@ -2085,7 +2090,7 @@ def test_table_input_binding(self): take=1, filter="dummy_filter", data_type=DataType.STRING) - def dummy(): + def test_table_input_binding(): pass func = self._get_user_function(app) @@ -2116,7 +2121,7 @@ def test_table_output_binding(self): row_key="dummy_key", partition_key="dummy_partition_key", data_type=DataType.STRING) - def dummy(): + def test_table_output_binding(): pass func = self._get_user_function(app) @@ -2148,7 +2153,7 @@ def test_sql_default_args(self): @app.sql_output(arg_name="out", command_text="dummy_table", connection_string_setting="dummy_setting") - def dummy(): + def test_sql_default_args(): pass func = self._get_user_function(app) @@ -2201,7 +2206,7 @@ def test_sql_full_args(self): connection_string_setting="dummy_setting", data_type=DataType.STRING, dummy_field="dummy") - def dummy(): + def test_sql_full_args(): pass func = self._get_user_function(app) @@ -2247,7 +2252,7 @@ def test_sql_trigger(self): @app.sql_trigger(arg_name="trigger", table_name="dummy_table", connection_string_setting="dummy_setting") - def dummy(): + def test_sql_trigger(): pass func = self._get_user_function(app) @@ -2272,7 +2277,7 @@ def test_sql_input_binding(self): @app.sql_input(arg_name="in", command_text="dummy_query", connection_string_setting="dummy_setting") - def dummy(): + def test_sql_input_binding(): pass func = self._get_user_function(app) @@ -2298,7 +2303,7 @@ def test_sql_output_binding(self): @app.sql_output(arg_name="out", command_text="dummy_table", connection_string_setting="dummy_setting") - def dummy(): + def test_sql_output_binding(): pass func = self._get_user_function(app) @@ -2332,7 +2337,7 @@ def test_function_app_full_bindings_metadata_key_order(self): connection="dummy_out_conn", row_key="dummy_key", partition_key="dummy_partition_key") - def dummy(): + def test_function_app_full_bindings_metadata_key_order(): pass self._test_function_metadata_order(app) @@ -2341,7 +2346,7 @@ def test_function_app_generic_http_trigger_metadata_key_order(self): app = self.func_app @app.generic_trigger(arg_name="req", type=HTTP_TRIGGER) - def dummy(): + def test_function_app_generic_http_trigger_metadata_key_order(): pass self._test_function_metadata_order(app) @@ -2359,11 +2364,12 @@ def test_function_app_retry_default_args(self): @app.schedule(arg_name="req", schedule="dummy_schedule") @app.retry(strategy="fixed", max_retry_count="2", delay_interval="4") - def dummy_func(): + def test_function_app_retry_default_args(): pass func = self._get_user_function(app) - self.assertEqual(func.get_function_name(), "dummy_func") + self.assertEqual(func.get_function_name(), + "test_function_app_retry_default_args") self.assertEqual(func.get_setting("retry_policy").get_dict_repr(), { 'setting_name': 'retry_policy', 'strategy': 'fixed', diff --git a/tests/decorators/test_function_app.py b/tests/decorators/test_function_app.py index c85b5dfd..b2cd0d96 100644 --- a/tests/decorators/test_function_app.py +++ b/tests/decorators/test_function_app.py @@ -20,6 +20,7 @@ ) from azure.functions.decorators.http import HttpTrigger, HttpOutput, \ HttpMethod +from azure.functions.decorators.timer import TimerTrigger from azure.functions.decorators.retry_policy import RetryPolicy from test_core import DummyTrigger from tests.utils.testutils import assert_json @@ -114,30 +115,42 @@ def test_function_creation_with_binding_and_trigger(self): class TestFunctionBuilder(unittest.TestCase): - def setUp(self): - def dummy(): + + def test_function_builder_creation(self): + def test_function_builder_creation(): return "dummy" - self.dummy = dummy + self.dummy = test_function_builder_creation self.fb = FunctionBuilder(self.dummy, "dummy.py") - def test_function_builder_creation(self): self.assertTrue(callable(self.fb)) func = getattr(self.fb, "_function") self.assertEqual(self.fb._function.function_script_file, "dummy.py") self.assertEqual(func.get_user_function(), self.dummy) def test_validate_function_missing_trigger(self): + def test_validate_function_missing_trigger(): + return "dummy" + + self.dummy = test_validate_function_missing_trigger + self.fb = FunctionBuilder(self.dummy, "dummy.py") + with self.assertRaises(ValueError) as err: # self.fb.configure_function_name('dummy').build() self.fb.build() self.assertEqual(err.exception.args[0], - "Function dummy does not have a trigger. A valid " - "function must have one and only one trigger " - "registered.") + "Function test_validate_function_missing_trigger" + " does not have a trigger. A valid function must have" + " one and only one trigger registered.") def test_validate_function_trigger_not_in_bindings(self): + def test_validate_function_trigger_not_in_bindings(): + return "dummy" + + self.dummy = test_validate_function_trigger_not_in_bindings + self.fb = FunctionBuilder(self.dummy, "dummy.py") + trigger = HttpTrigger(name='req', methods=(HttpMethod.GET,), data_type=DataType.UNDEFINED, auth_level=AuthLevel.ANONYMOUS, @@ -147,37 +160,59 @@ def test_validate_function_trigger_not_in_bindings(self): getattr(self.fb, "_function").get_bindings().clear() self.fb.build() - self.assertEqual(err.exception.args[0], - f"Function dummy trigger {trigger} not present" - f" in bindings {[]}") + self.assertEqual( + err.exception.args[0], + f"Function test_validate_function_trigger_not_in_bindings" + f" trigger {trigger} not present" + f" in bindings {[]}") def test_validate_function_working(self): + def test_validate_function_working(): + return "dummy" + + self.dummy = test_validate_function_working + self.fb = FunctionBuilder(self.dummy, "dummy.py") + trigger = HttpTrigger(name='req', methods=(HttpMethod.GET,), data_type=DataType.UNDEFINED, - auth_level=AuthLevel.ANONYMOUS) + auth_level=AuthLevel.ANONYMOUS, + route='test_validate_function_working') self.fb.add_trigger(trigger) self.fb.build() def test_build_function_http_route_default(self): + def test_build_function_http_route_default(): + return "dummy" + + self.dummy = test_build_function_http_route_default + self.fb = FunctionBuilder(self.dummy, "dummy.py") + trigger = HttpTrigger(name='req', methods=(HttpMethod.GET,), data_type=DataType.UNDEFINED, auth_level=AuthLevel.ANONYMOUS) self.fb.add_trigger(trigger) func = self.fb.build() - self.assertEqual(func.get_trigger().route, "dummy") + self.assertEqual(func.get_trigger().route, + "test_build_function_http_route_default") def test_build_function_with_bindings(self): + def test_build_function_with_bindings(): + return "dummy" + + self.dummy = test_build_function_with_bindings + self.fb = FunctionBuilder(self.dummy, "dummy.py") + test_trigger = HttpTrigger(name='req', methods=(HttpMethod.GET,), data_type=DataType.UNDEFINED, - auth_level=AuthLevel.ANONYMOUS, - route='dummy') + auth_level=AuthLevel.ANONYMOUS) test_input = HttpOutput(name='out', data_type=DataType.UNDEFINED) func = self.fb.add_trigger( test_trigger).add_binding(test_input).build() - self.assertEqual(func.get_function_name(), "dummy") + self.assertEqual(func.get_function_name(), + "test_build_function_with_bindings") assert_json(self, func, { "scriptFile": "dummy.py", "bindings": [ @@ -187,7 +222,7 @@ def test_build_function_with_bindings(self): "direction": BindingDirection.IN, "name": "req", "dataType": DataType.UNDEFINED, - "route": "dummy", + "route": "test_build_function_with_bindings", "methods": [ HttpMethod.GET ] @@ -202,6 +237,12 @@ def test_build_function_with_bindings(self): }) def test_build_function_with_function_app_auth_level(self): + def test_build_function_with_function_app_auth_level(): + return "dummy" + + self.dummy = test_build_function_with_function_app_auth_level + self.fb = FunctionBuilder(self.dummy, "dummy.py") + trigger = HttpTrigger(name='req', methods=(HttpMethod.GET,), data_type=DataType.UNDEFINED) self.fb.add_trigger(trigger) @@ -210,11 +251,19 @@ def test_build_function_with_function_app_auth_level(self): self.assertEqual(func.get_trigger().auth_level, AuthLevel.ANONYMOUS) def test_build_function_with_retry_policy_setting(self): + def test_build_function_with_retry_policy_setting(): + return "dummy" + + self.dummy = test_build_function_with_retry_policy_setting + self.fb = FunctionBuilder(self.dummy, "dummy.py") + setting = RetryPolicy(strategy="exponential", max_retry_count="2", minimum_interval="1", maximum_interval="5") - trigger = HttpTrigger(name='req', methods=(HttpMethod.GET,), - data_type=DataType.UNDEFINED, - auth_level=AuthLevel.ANONYMOUS) + trigger = HttpTrigger( + name='req', methods=(HttpMethod.GET,), + data_type=DataType.UNDEFINED, + auth_level=AuthLevel.ANONYMOUS, + route='test_build_function_with_retry_policy_setting') self.fb.add_trigger(trigger) self.fb.add_setting(setting) func = self.fb.build() @@ -224,6 +273,293 @@ def test_build_function_with_retry_policy_setting(self): 'strategy': 'exponential', 'max_retry_count': '2', 'minimum_interval': '1', 'maximum_interval': '5'}) + def test_unique_method_names(self): + app = FunctionApp() + + @app.schedule(arg_name="name", schedule="10****") + def test_unique_method_names(name: str): + return name + + @app.schedule(arg_name="name", schedule="10****") + def test_unique_method_names2(name: str): + return name + + functions = app.get_functions() + self.assertEqual(len(functions), 2) + + self.assertEqual(functions[0].get_function_name(), + "test_unique_method_names") + self.assertEqual(functions[1].get_function_name(), + "test_unique_method_names2") + self.assertIsInstance(app._function_builders[0].function_bindings.get( + "test_unique_method_names")[0], TimerTrigger) + self.assertIsInstance(app._function_builders[0].function_bindings.get( + "test_unique_method_names2")[0], TimerTrigger) + + def test_unique_function_names(self): + app = FunctionApp() + + @app.function_name("test_unique_function_names") + @app.schedule(arg_name="name", schedule="10****") + def test_unique_function_names(name: str): + return name + + @app.function_name("test_unique_function_names2") + @app.schedule(arg_name="name", schedule="10****") + def test_unique_function_names2(name: str): + return name + + functions = app.get_functions() + self.assertEqual(len(functions), 2) + + self.assertEqual(functions[0].get_function_name(), + "test_unique_function_names") + self.assertEqual(functions[1].get_function_name(), + "test_unique_function_names2") + self.assertIsInstance(app._function_builders[0].function_bindings.get( + "test_unique_function_names")[0], TimerTrigger) + self.assertIsInstance(app._function_builders[0].function_bindings.get( + "test_unique_function_names2")[0], TimerTrigger) + + def test_same_method_names(self): + app = FunctionApp() + + @app.schedule(arg_name="name", schedule="10****") # NoQA + def test_same_method_names(name: str): # NoQA + return name + + @app.schedule(arg_name="name", schedule="10****") # NoQA + def test_same_method_names(name: str): # NoQA + return name + + with self.assertRaises(ValueError) as err: + app.get_functions() + self.assertEqual(err.exception.args[0], + "Function test_same_method_names does not have" + " a unique function name." + " Please change @app.function_name()" + " or the function method name to be unique.") + + def test_same_function_names(self): + app = FunctionApp() + + @app.function_name("test_same_function_names") # NoQA + @app.schedule(arg_name="name", schedule="10****") + def test_same_function_names(name: str): + return name + + @app.function_name("test_same_function_names") # NoQA + @app.schedule(arg_name="name", schedule="10****") + def test_same_function_names(name: str): # NoQA + return name + + with self.assertRaises(ValueError) as err: + app.get_functions() + self.assertEqual(err.exception.args[0], + "Function test_same_function_names does not have" + " a unique function name." + " Please change @app.function_name()" + " or the function method name to be unique.") + + def test_same_function_name_different_method_name(self): + app = FunctionApp() + + @app.function_name("test_same_function_name_different_method_name") + @app.schedule(arg_name="name", schedule="10****") + def test_same_function_name_different_method_name(name: str): + return name + + @app.function_name("test_same_function_name_different_method_name") + @app.schedule(arg_name="name", schedule="10****") + def test_same_function_name_different_method_name2(name: str): + return name + + with self.assertRaises(ValueError) as err: + app.get_functions() + self.assertEqual( + err.exception.args[0], + "Function test_same_function_name_different_method_name" + " does not have a unique function name." + " Please change @app.function_name()" + " or the function method name to be unique.") + + def test_same_function_and_method_name(self): + app = FunctionApp() + + @app.function_name("test_same_function_and_method_name") + @app.schedule(arg_name="name", schedule="10****") + def test_same_function_and_method_name2(name: str): + return name + + @app.schedule(arg_name="name", schedule="10****") + def test_same_function_and_method_name(name: str): + return name + + with self.assertRaises(ValueError) as err: + app.get_functions() + self.assertEqual(err.exception.args[0], + "Function test_same_function_and_method_name" + " does not have a unique function name." + " Please change @app.function_name()" + " or the function method name to be unique.") + + def test_blueprint_unique_method_names(self): + app = FunctionApp() + + @app.schedule(arg_name="name", schedule="10****") + def test_blueprint_unique_method_names(name: str): + return name + + bp = Blueprint() + + @bp.schedule(arg_name="name", schedule="10****") + def test_blueprint_unique_method_names2(name: str): + return name + + app.register_blueprint(bp) + + functions = app.get_functions() + self.assertEqual(len(functions), 2) + + self.assertEqual(functions[0].get_function_name(), + "test_blueprint_unique_method_names") + self.assertEqual(functions[1].get_function_name(), + "test_blueprint_unique_method_names2") + self.assertIsInstance(app._function_builders[0].function_bindings.get( + "test_blueprint_unique_method_names")[0], TimerTrigger) + self.assertIsInstance(app._function_builders[0].function_bindings.get( + "test_blueprint_unique_method_names2")[0], TimerTrigger) + + def test_blueprint_unique_function_names(self): + app = FunctionApp() + + @app.function_name("test_blueprint_unique_function_names") + @app.schedule(arg_name="name", schedule="10****") + def test_blueprint_unique_function_names(name: str): + return name + + bp = Blueprint() + + @bp.function_name("test_blueprint_unique_function_names2") + @bp.schedule(arg_name="name", schedule="10****") + def test_blueprint_unique_function_names2(name: str): + return name + + app.register_blueprint(bp) + + functions = app.get_functions() + self.assertEqual(len(functions), 2) + + self.assertEqual(functions[0].get_function_name(), + "test_blueprint_unique_function_names") + self.assertEqual(functions[1].get_function_name(), + "test_blueprint_unique_function_names2") + self.assertIsInstance(app._function_builders[0].function_bindings.get( + "test_blueprint_unique_function_names")[0], TimerTrigger) + self.assertIsInstance(app._function_builders[0].function_bindings.get( + "test_blueprint_unique_function_names2")[0], TimerTrigger) + + def test_blueprint_same_method_names(self): + app = FunctionApp() + + @app.schedule(arg_name="name", schedule="10****") # NoQA + def test_blueprint_same_method_names(name: str): # NoQA + return name + + bp = Blueprint() + + @bp.schedule(arg_name="name", schedule="10****") # NoQA + def test_blueprint_same_method_names(name: str): # NoQA + return name + + app.register_blueprint(bp) + + with self.assertRaises(ValueError) as err: + app.get_functions() + self.assertEqual(err.exception.args[0], + "Function test_blueprint_same_method_names" + " does not have a unique function name." + " Please change @app.function_name()" + " or the function method name to be unique.") + + def test_blueprint_same_function_names(self): + app = FunctionApp() + + @app.function_name("test_blueprint_same_function_names") # NoQA + @app.schedule(arg_name="name", schedule="10****") + def test_blueprint_same_function_names(name: str): # NoQA + return name + + bp = Blueprint() + + @bp.function_name("test_blueprint_same_function_names") # NoQA + @bp.schedule(arg_name="name", schedule="10****") + def test_blueprint_same_function_names(name: str): # NoQA + return name + + app.register_blueprint(bp) + + with self.assertRaises(ValueError) as err: + app.get_functions() + self.assertEqual(err.exception.args[0], + "Function test_blueprint_same_function_names" + " does not have a unique function name. Please change" + " @app.function_name() or the function method name" + " to be unique.") + + def test_blueprint_same_function_name_different_method_name(self): + app = FunctionApp() + + @app.function_name( + "test_blueprint_same_function_name_different_method_name") + @app.schedule(arg_name="name", schedule="10****") + def test_blueprint_same_function_name_different_method_name(name: str): + return name + + bp = Blueprint() + + @bp.function_name( + "test_blueprint_same_function_name_different_method_name") + @bp.schedule(arg_name="name", schedule="10****") + def test_blueprint_same_function_name_different_method_name2( + name: str): + return name + + app.register_blueprint(bp) + + with self.assertRaises(ValueError) as err: + app.get_functions() + self.assertEqual( + err.exception.args[0], + "Function test_blueprint_same_function_name_different_method_name" + " does not have a unique function name. Please change" + " @app.function_name() or the function method name to be unique.") + + def test_blueprint_same_function_and_method_name(self): + app = FunctionApp() + + @app.function_name("test_blueprint_same_function_and_method_name") + @app.schedule(arg_name="name", schedule="10****") + def test_blueprint_same_function_and_method_name2(name: str): + return name + + bp = Blueprint() + + @bp.schedule(arg_name="name", schedule="10****") + def test_blueprint_same_function_and_method_name(name: str): + return name + + app.register_blueprint(bp) + + with self.assertRaises(ValueError) as err: + app.get_functions() + self.assertEqual( + err.exception.args[0], + "Function test_blueprint_same_function_and_method_name" + " does not have a unique function name." + " Please change @app.function_name() or the function" + " method name to be unique.") + class TestScaffold(unittest.TestCase): def setUp(self): @@ -249,12 +585,12 @@ def test_has_function_builders(self): def test_dummy_app_trigger(self): @self.dummy.dummy_trigger(name="dummy") - def dummy(): + def test_dummy_app_trigger(): return "dummy" self.assertEqual(len(self.dummy._function_builders), 1) func = self.dummy._function_builders[0].build() - self.assertEqual(func.get_function_name(), "dummy") + self.assertEqual(func.get_function_name(), "test_dummy_app_trigger") self.assertEqual(func.get_function_json(), '{"scriptFile": "function_app.py", "bindings": [{' '"direction": "IN", "dataType": "UNDEFINED", ' @@ -308,7 +644,7 @@ def test_function_builder_decorated_type(self): '._add_http_app') def test_add_asgi(self, add_http_app_mock): mock_asgi_app = object() - AsgiFunctionApp(app=mock_asgi_app) + AsgiFunctionApp(app=mock_asgi_app, function_name='test_add_asgi') add_http_app_mock.assert_called_once() @@ -319,7 +655,7 @@ def test_add_asgi(self, add_http_app_mock): '._add_http_app') def test_add_wsgi(self, add_http_app_mock): mock_wsgi_app = object() - WsgiFunctionApp(app=mock_wsgi_app) + WsgiFunctionApp(app=mock_wsgi_app, function_name='test_add_wsgi') add_http_app_mock.assert_called_once() self.assertIsInstance(add_http_app_mock.call_args[0][0], @@ -335,10 +671,18 @@ def test_extends_required_classes(self): self.assertTrue(issubclass(WsgiFunctionApp, ExternalHttpFunctionApp)) def test_add_asgi_app(self): - self._test_http_external_app(AsgiFunctionApp(app=object()), True) + self._test_http_external_app(AsgiFunctionApp( + app=object(), + function_name='test_add_asgi_app'), + True, + function_name='test_add_asgi_app') def test_add_wsgi_app(self): - self._test_http_external_app(WsgiFunctionApp(app=object()), False) + self._test_http_external_app(WsgiFunctionApp( + app=object(), + function_name='test_add_wsgi_app'), + False, + function_name='test_add_wsgi_app') def test_register_function_app_error(self): with self.assertRaises(TypeError) as err: @@ -351,7 +695,7 @@ def test_register_blueprint(self): bp = Blueprint() @bp.schedule(arg_name="name", schedule="10****") - def hello(name: str): + def test_register_blueprint(name: str): return "hello" app = FunctionApp() @@ -365,14 +709,15 @@ def test_register_app_auth_level(self): bp = Blueprint() @bp.route("name") - def hello(name: str): + def test_register_app_auth_level(name: str): return "hello" app = FunctionApp(http_auth_level=AuthLevel.ANONYMOUS) app.register_blueprint(bp) - self.assertEqual(len(app.get_functions()), 1) - self.assertEqual(app.get_functions()[0].get_trigger().auth_level, + functions = app.get_functions() + self.assertEqual(len(functions), 1) + self.assertEqual(functions[0].get_trigger().auth_level, AuthLevel.ANONYMOUS) def test_default_function_http_type(self): @@ -393,12 +738,12 @@ def test_set_http_type(self): @app.route("name1") @app.http_type("dummy1") - def hello(name: str): + def test_set_http_type(name: str): return "hello" @app.route("name2") @app.http_type("dummy2") - def hello2(name: str): + def test_set_http_type2(name: str): return "hello" funcs = app.get_functions() @@ -471,7 +816,7 @@ class DummyFunctionApp(FunctionRegister, TriggerApi): blueprint = Blueprint() @blueprint.schedule(arg_name="name", schedule="10****") - def hello(name: str): + def test_function_register_non_http_function_app(name: str): return name app.register_blueprint(blueprint) @@ -511,7 +856,7 @@ class DummyFunctionApp(FunctionRegister, TriggerApi): app = DummyFunctionApp(auth_level=AuthLevel.ANONYMOUS) blueprint = LegacyBluePrint() - @blueprint.function_name("timer_function") + @blueprint.function_name("test_legacy_blueprints_with_function_name") @blueprint.schedule(arg_name="name", schedule="10****") def hello(name: str): return name @@ -524,7 +869,7 @@ def hello(name: str): setting = functions[0].get_setting("function_name") self.assertEqual(setting.get_settings_value("function_name"), - "timer_function") + "test_legacy_blueprints_with_function_name") def test_function_register_register_function_register_error(self): class DummyFunctionApp(FunctionRegister): @@ -545,7 +890,8 @@ class DummyFunctionApp(FunctionRegister): blueprint = Blueprint() @blueprint.schedule(arg_name="name", schedule="10****") - def hello(name: str): + def test_function_register_register_functions_from_blueprint( + name: str): return name app.register_blueprint(blueprint) @@ -558,7 +904,9 @@ def hello(name: str): self.assertEqual(trigger.type, TIMER_TRIGGER) self.assertEqual(trigger.schedule, "10****") self.assertEqual(trigger.name, "name") - self.assertEqual(functions[0].get_function_name(), "hello") + self.assertEqual( + functions[0].get_function_name(), + "test_function_register_register_functions_from_blueprint") self.assertEqual(functions[0].get_user_function()("timer"), "timer") def test_asgi_function_app_default(self): @@ -587,7 +935,9 @@ def test_wsgi_function_app_custom(self): self.assertEqual(app.auth_level, AuthLevel.ANONYMOUS) def test_wsgi_function_app_is_http_function(self): - app = WsgiFunctionApp(app=object()) + app = WsgiFunctionApp( + app=object(), + function_name='test_wsgi_function_app_is_http_function') funcs = app.get_functions() self.assertEqual(len(funcs), 1) @@ -618,11 +968,11 @@ def test_external_http_function_app(self): app = ExternalHttpFunctionApp(auth_level=AuthLevel.ANONYMOUS) app._add_http_app(AsgiMiddleware(object())) - def _test_http_external_app(self, app, is_async): + def _test_http_external_app(self, app, is_async, function_name): funcs = app.get_functions() self.assertEqual(len(funcs), 1) func = funcs[0] - self.assertEqual(func.get_function_name(), "http_app_func") + self.assertEqual(func.get_function_name(), function_name) raw_bindings = func.get_raw_bindings() raw_trigger = raw_bindings[0] raw_output_binding = raw_bindings[0]