diff --git a/.ci/e2e_integration_test/pipeline.yml b/.ci/e2e_integration_test/pipeline.yml index 181e73577..8117a5418 100644 --- a/.ci/e2e_integration_test/pipeline.yml +++ b/.ci/e2e_integration_test/pipeline.yml @@ -42,6 +42,7 @@ steps: AzureWebJobsCosmosDBConnectionString: $(AzureWebJobsCosmosDBConnectionString) AzureWebJobsEventHubConnectionString: $(AzureWebJobsEventHubConnectionString) AzureWebJobsServiceBusConnectionString: $(AzureWebJobsServiceBusConnectionString) + AzureWebJobsSqlConnectionString: $(AzureWebJobsSqlConnectionString) AzureWebJobsEventGridTopicUri: $(AzureWebJobsEventGridTopicUri) AzureWebJobsEventGridConnectionKey: $(AzureWebJobsEventGridConnectionKey) PythonVersion: $(PYTHON_VERSION) diff --git a/.ci/linux_devops_e2e_tests.sh b/.ci/linux_devops_e2e_tests.sh index 83939d38e..7a5d5105e 100644 --- a/.ci/linux_devops_e2e_tests.sh +++ b/.ci/linux_devops_e2e_tests.sh @@ -5,6 +5,7 @@ export AzureWebJobsStorage=$LINUXSTORAGECONNECTIONSTRING export AzureWebJobsCosmosDBConnectionString=$LINUXCOSMOSDBCONNECTIONSTRING export AzureWebJobsEventHubConnectionString=$LINUXEVENTHUBCONNECTIONSTRING export AzureWebJobsServiceBusConnectionString=$LINUXSERVICEBUSCONNECTIONSTRING +export AzureWebJobsSqlConnectionString=$LINUXSQLCONNECTIONSTRING export AzureWebJobsEventGridTopicUri=$LINUXEVENTGRIDTOPICURI export AzureWebJobsEventGridConnectionKey=$LINUXEVENTGRIDTOPICCONNECTIONKEY diff --git a/.github/workflows/ci_e2e_workflow.yml b/.github/workflows/ci_e2e_workflow.yml index 6b783c582..10b86bfc6 100644 --- a/.github/workflows/ci_e2e_workflow.yml +++ b/.github/workflows/ci_e2e_workflow.yml @@ -76,6 +76,7 @@ jobs: AzureWebJobsCosmosDBConnectionString: ${{ secrets.LinuxCosmosDBConnectionString36 }} AzureWebJobsEventHubConnectionString: ${{ secrets.LinuxEventHubConnectionString36 }} AzureWebJobsServiceBusConnectionString: ${{ secrets.LinuxServiceBusConnectionString36 }} + AzureWebJobsSqlConnectionString: ${{ secrets.LinuxSqlConnectionString36 }} AzureWebJobsEventGridTopicUri: ${{ secrets.LinuxEventGridTopicUriString36 }} AzureWebJobsEventGridConnectionKey: ${{ secrets.LinuxEventGridConnectionKeyString36 }} run: | @@ -87,6 +88,7 @@ jobs: AzureWebJobsCosmosDBConnectionString: ${{ secrets.LinuxCosmosDBConnectionString37 }} AzureWebJobsEventHubConnectionString: ${{ secrets.LinuxEventHubConnectionString37 }} AzureWebJobsServiceBusConnectionString: ${{ secrets.LinuxServiceBusConnectionString37 }} + AzureWebJobsSqlConnectionString: ${{ secrets.LinuxSqlConnectionString37 }} AzureWebJobsEventGridTopicUri: ${{ secrets.LinuxEventGridTopicUriString37 }} AzureWebJobsEventGridConnectionKey: ${{ secrets.LinuxEventGridConnectionKeyString37 }} run: | @@ -98,6 +100,7 @@ jobs: AzureWebJobsCosmosDBConnectionString: ${{ secrets.LinuxCosmosDBConnectionString38 }} AzureWebJobsEventHubConnectionString: ${{ secrets.LinuxEventHubConnectionString38 }} AzureWebJobsServiceBusConnectionString: ${{ secrets.LinuxServiceBusConnectionString38 }} + AzureWebJobsSqlConnectionString: ${{ secrets.LinuxSqlConnectionString38 }} AzureWebJobsEventGridTopicUri: ${{ secrets.LinuxEventGridTopicUriString38 }} AzureWebJobsEventGridConnectionKey: ${{ secrets.LinuxEventGridConnectionKeyString38 }} run: | @@ -109,6 +112,7 @@ jobs: AzureWebJobsCosmosDBConnectionString: ${{ secrets.LinuxCosmosDBConnectionString39 }} AzureWebJobsEventHubConnectionString: ${{ secrets.LinuxEventHubConnectionString39 }} AzureWebJobsServiceBusConnectionString: ${{ secrets.LinuxServiceBusConnectionString39 }} + AzureWebJobsSqlConnectionString: ${{ secrets.LinuxSqlConnectionString39 }} AzureWebJobsEventGridTopicUri: ${{ secrets.LinuxEventGridTopicUriString39 }} AzureWebJobsEventGridConnectionKey: ${{ secrets.LinuxEventGridConnectionKeyString39 }} run: | @@ -120,6 +124,7 @@ jobs: AzureWebJobsCosmosDBConnectionString: ${{ secrets.LinuxCosmosDBConnectionString310 }} AzureWebJobsEventHubConnectionString: ${{ secrets.LinuxEventHubConnectionString310 }} AzureWebJobsServiceBusConnectionString: ${{ secrets.LinuxServiceBusConnectionString310 }} + AzureWebJobsSqlConnectionString: ${{ secrets.LinuxSqlConnectionString310 }} AzureWebJobsEventGridTopicUri: ${{ secrets.LinuxEventGridTopicUriString310 }} AzureWebJobsEventGridConnectionKey: ${{ secrets.LinuxEventGridConnectionKeyString310 }} run: | diff --git a/azure_functions_worker/testutils.py b/azure_functions_worker/testutils.py index 62e14a2e3..6641e2701 100644 --- a/azure_functions_worker/testutils.py +++ b/azure_functions_worker/testutils.py @@ -878,6 +878,10 @@ def popen_webhost(*, stdout, stderr, script_root=FUNCS_PATH, port=None): if servicebus: extra_env['AzureWebJobsServiceBusConnectionString'] = servicebus + sql = testconfig['azure'].get('sql_key') + if sql: + extra_env['AzureWebJobsSqlConnectionString'] = sql + eventgrid_topic_uri = testconfig['azure'].get('eventgrid_topic_uri') if eventgrid_topic_uri: extra_env['AzureWebJobsEventGridTopicUri'] = eventgrid_topic_uri diff --git a/setup.py b/setup.py index 0ffe06ec9..9d4b50482 100644 --- a/setup.py +++ b/setup.py @@ -50,6 +50,8 @@ Version="4.0.5" /> + diff --git a/tests/endtoend/sql_functions/sql_input/__init__.py b/tests/endtoend/sql_functions/sql_input/__init__.py new file mode 100644 index 000000000..a8e3c3b3b --- /dev/null +++ b/tests/endtoend/sql_functions/sql_input/__init__.py @@ -0,0 +1,15 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import json +import azure.functions as func + + +def main(req: func.HttpRequest, products: func.SqlRowList) -> func.HttpResponse: + rows = list(map(lambda r: json.loads(r.to_json()), products)) + + return func.HttpResponse( + json.dumps(rows), + status_code=200, + mimetype="application/json" + ) diff --git a/tests/endtoend/sql_functions/sql_input/function.json b/tests/endtoend/sql_functions/sql_input/function.json new file mode 100644 index 000000000..1c5e9d552 --- /dev/null +++ b/tests/endtoend/sql_functions/sql_input/function.json @@ -0,0 +1,27 @@ +{ + "scriptFile": "__init__.py", + "bindings": [ + { + "authLevel": "function", + "name": "req", + "type": "httpTrigger", + "direction": "in", + "methods": [ + "get" + ] + }, + { + "name": "$return", + "type": "http", + "direction": "out" + }, + { + "name": "products", + "type": "sql", + "direction": "in", + "commandText": "SELECT * FROM Products", + "commandType": "Text", + "connectionStringSetting": "AzureWebJobsSqlConnectionString" + } + ] +} \ No newline at end of file diff --git a/tests/endtoend/sql_functions/sql_output/__init__.py b/tests/endtoend/sql_functions/sql_output/__init__.py new file mode 100644 index 000000000..1a8226b51 --- /dev/null +++ b/tests/endtoend/sql_functions/sql_output/__init__.py @@ -0,0 +1,17 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import json +import azure.functions as func + + +def main(req: func.HttpRequest, r: func.Out[func.SqlRow]) -> func.HttpResponse: + body = json.loads(req.get_body()) + row = func.SqlRow.from_dict(body) + r.set(row) + + return func.HttpResponse( + body=req.get_body(), + status_code=201, + mimetype="application/json" + ) diff --git a/tests/endtoend/sql_functions/sql_output/function.json b/tests/endtoend/sql_functions/sql_output/function.json new file mode 100644 index 000000000..e58c18cfd --- /dev/null +++ b/tests/endtoend/sql_functions/sql_output/function.json @@ -0,0 +1,26 @@ +{ + "scriptFile": "__init__.py", + "bindings": [ + { + "authLevel": "function", + "name": "req", + "type": "httpTrigger", + "direction": "in", + "methods": [ + "post" + ] + }, + { + "name": "$return", + "type": "http", + "direction": "out" + }, + { + "name": "r", + "type": "sql", + "direction": "out", + "commandText": "[dbo].[Products]", + "connectionStringSetting": "AzureWebJobsSqlConnectionString" + } + ] +} diff --git a/tests/endtoend/test_sql_functions.py b/tests/endtoend/test_sql_functions.py new file mode 100644 index 000000000..ba8c442c4 --- /dev/null +++ b/tests/endtoend/test_sql_functions.py @@ -0,0 +1,24 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +import json + +from azure_functions_worker import testutils + + +class TestSqlFunctions(testutils.WebHostTestCase): + + @classmethod + def get_script_dir(cls): + return testutils.E2E_TESTS_FOLDER / 'sql_functions' + + @testutils.retryable_test(3, 5) + def test_sql_output_and_input(self): + row = {"ProductId": 0, "Name": "test", "Cost": 100} + r = self.webhost.request('POST', 'sql_output', + data=json.dumps(row)) + self.assertEqual(r.status_code, 201) + + r = self.webhost.request('GET', 'sql_input') + self.assertEqual(r.status_code, 200) + expectedText = "[{\"ProductId\": 0, \"Name\": \"test\", \"Cost\": 100}]" + self.assertEqual(r.text, expectedText)