Skip to content

add asgi/wsgi stein tests #1019

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ exclude = .git, __pycache__, build, dist, .eggs, .github, .local, docs/,
azure_functions_worker/_thirdparty/typing_inspect.py,
tests/unittests/test_typing_inspect.py,
tests/unittests/broken_functions/syntax_error/main.py,
.env*, .vscode, venv*, *.venv*,
.env*, .vscode, venv*, *.venv*

max-line-length = 80
7 changes: 5 additions & 2 deletions azure_functions_worker/testutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -745,10 +745,13 @@ def __init__(self, proc, addr):
def request(self, meth, funcname, *args, **kwargs):
request_method = getattr(requests, meth.lower())
params = dict(kwargs.pop('params', {}))
no_prefix = kwargs.pop('no_prefix', False)
if 'code' not in params:
params['code'] = 'testFunctionKey'
return request_method(self._addr + '/api/' + funcname,
*args, params=params, **kwargs)

return request_method(
self._addr + ('/' if no_prefix else '/api/') + funcname,
*args, params=params, **kwargs)

def close(self):
if self._proc.stdout:
Expand Down
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@
"dev": [
"azure-eventhub~=5.7.0", # Used for EventHub E2E tests
"python-dateutil~=2.8.2",
"flask",
"fastapi",
"pydantic",
"pycryptodome~=3.10.1",
"flake8~=4.0.1",
"mypy",
Expand Down
168 changes: 168 additions & 0 deletions tests/endtoend/test_third_party_http_functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
import os
from unittest.mock import patch

import requests

from azure_functions_worker import testutils as utils, testutils

# from tests.stein_tests import testutils
# from tests.stein_tests.constants import E2E_TESTS_ROOT
from azure_functions_worker.testutils import E2E_TESTS_ROOT

HOST_JSON_TEMPLATE = """\
{
"version": "2.0",
"logging": {
"logLevel": {
"default": "Trace"
}
},
"extensions": {
"http": {
"routePrefix": ""
}
},
"functionTimeout": "00:05:00"
}
"""


class ThirdPartyHttpFunctionsTestBase:
"""Base test class containing common asgi/wsgi testcases, only testcases
in classes extending TestThirdPartyHttpFunctions will by run"""

class TestThirdPartyHttpFunctions(testutils.WebHostTestCase):
@classmethod
def setUpClass(cls):
host_json = cls.get_script_dir() / 'host.json'
with open(host_json, 'w+') as f:
f.write(HOST_JSON_TEMPLATE)
os_environ = os.environ.copy()
# Turn on feature flag
os_environ['AzureWebJobsFeatureFlags'] = 'EnableWorkerIndexing'
cls._patch_environ = patch.dict('os.environ', os_environ)
cls._patch_environ.start()
super().setUpClass()

@classmethod
def tearDownClass(cls):
super().tearDownClass()
cls._patch_environ.stop()

@classmethod
def get_script_dir(cls):
pass

@utils.retryable_test(3, 5)
def test_function_index_page_should_return_undefined(self):
root_url = self.webhost._addr
r = requests.get(root_url)
self.assertEqual(r.status_code, 404)

@utils.retryable_test(3, 5)
def test_get_endpoint_should_return_ok(self):
"""Test if the default template of Http trigger in Python
Function app
will return OK
"""
r = self.webhost.request('GET', 'get_query_param', no_prefix=True)
self.assertTrue(r.ok)
self.assertEqual(r.text, "hello world")

@utils.retryable_test(3, 5)
def test_get_endpoint_should_accept_query_param(self):
"""Test if the azure.functions SDK is able to deserialize query
parameter from the default template
"""
r = self.webhost.request('GET', 'get_query_param',
params={'name': 'dummy'}, no_prefix=True)
self.assertTrue(r.ok)
self.assertEqual(
r.text,
"hello dummy"
)

@utils.retryable_test(3, 5)
def test_post_endpoint_should_accept_body(self):
"""Test if the azure.functions SDK is able to deserialize http body
and pass it to default template
"""
r = self.webhost.request('POST', 'post_str',
data="dummy",
headers={'content-type': 'text/plain'},
no_prefix=True)
self.assertTrue(r.ok)
self.assertEqual(
r.text,
"hello dummy"
)

@utils.retryable_test(3, 5)
def test_worker_status_endpoint_should_return_ok(self):
"""Test if the worker status endpoint will trigger
_handle__worker_status_request and sends a worker status
response back
to host
"""
root_url = self.webhost._addr
health_check_url = f'{root_url}/admin/host/ping'
r = requests.post(health_check_url,
params={'checkHealth': '1'})
self.assertTrue(r.ok)

@utils.retryable_test(3, 5)
def test_worker_status_endpoint_should_return_ok_when_disabled(self):
"""Test if the worker status endpoint will trigger
_handle__worker_status_request and sends a worker status
response back
to host
"""
os.environ['WEBSITE_PING_METRICS_SCALE_ENABLED'] = '0'
root_url = self.webhost._addr
health_check_url = f'{root_url}/admin/host/ping'
r = requests.post(health_check_url,
params={'checkHealth': '1'})
self.assertTrue(r.ok)

@utils.retryable_test(3, 5)
def test_get_endpoint_should_accept_path_param(self):
r = self.webhost.request('GET', 'get_path_param/1', no_prefix=True)
self.assertTrue(r.ok)
self.assertEqual(r.text, "hello 1")

@utils.retryable_test(3, 5)
def test_post_json_body_and_return_json_response(self):
test_data = {
"name": "apple",
"description": "yummy"
}
r = self.webhost.request('POST', 'post_json_return_json_response',
json=test_data,
no_prefix=True)
self.assertTrue(r.ok)
self.assertEqual(r.json(), test_data)

@utils.retryable_test(3, 5)
def test_raise_exception_should_return_not_found(self):
r = self.webhost.request('GET', 'raise_http_exception',
no_prefix=True)
self.assertEqual(r.status_code, 404)
self.assertEqual(r.json(), {"detail": "Item not found"})


class TestAsgiHttpFunctions(
ThirdPartyHttpFunctionsTestBase.TestThirdPartyHttpFunctions):
@classmethod
def get_script_dir(cls):
return E2E_TESTS_ROOT / 'third_party_http_functions' / 'stein' / \
'asgi_function'


class TestWsgiHttpFunctions(
ThirdPartyHttpFunctionsTestBase.TestThirdPartyHttpFunctions):
@classmethod
def get_script_dir(cls):
return E2E_TESTS_ROOT / 'third_party_http_functions' / 'stein' / \
'wsgi_function'
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from typing import Optional

import azure.functions as func
from fastapi import FastAPI, Response, Body, HTTPException
from pydantic import BaseModel

fast_app = FastAPI()


class Fruit(BaseModel):
name: str
description: Optional[str] = None


@fast_app.get("/get_query_param")
async def get_query_param(name: str = "world"):
return Response(content=f"hello {name}", media_type="text/plain")


@fast_app.post("/post_str")
async def post_str(person: str = Body(...)):
return Response(content=f"hello {person}", media_type="text/plain")


@fast_app.post("/post_json_return_json_response")
async def post_json_return_json_response(fruit: Fruit):
return fruit


@fast_app.get("/get_path_param/{id}")
async def get_path_param(id):
return Response(content=f"hello {id}", media_type="text/plain")


@fast_app.get("/raise_http_exception")
async def raise_http_exception():
raise HTTPException(status_code=404, detail="Item not found")


app = func.FunctionApp(asgi_app=fast_app,
http_auth_level=func.AuthLevel.ANONYMOUS)
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import azure.functions as func
from flask import Flask, request

flask_app = Flask(__name__)


@flask_app.get("/get_query_param")
def get_query_param():
name = request.args.get("name")
if name is None:
name = "world"
return f"hello {name}"


@flask_app.post("/post_str")
def post_str():
return f"hello {request.data.decode()}"


@flask_app.post("/post_json_return_json_response")
def post_json_return_json_response():
return request.get_json()


@flask_app.get("/get_path_param/<id>")
def get_path_param(id):
return f"hello {id}"


@flask_app.get("/raise_http_exception")
def raise_http_exception():
return {"detail": "Item not found"}, 404


app = func.FunctionApp(wsgi_app=flask_app.wsgi_app,
http_auth_level=func.AuthLevel.ANONYMOUS)
Loading