From aae243ef5c2363c5e5eb98e87b5f2e5f0f4dc45b Mon Sep 17 00:00:00 2001 From: iscai-msft <43154838+iscai-msft@users.noreply.github.com> Date: Fri, 25 Jun 2021 16:04:01 -0400 Subject: [PATCH] [core] add testserver (#19153) --- eng/.docsettings.yml | 1 + sdk/core/azure-core/dev_requirements.txt | 1 + .../tests/testserver_tests/conftest.py | 88 ++++++++++++++++++ .../coretestserver/README.rst | 22 +++++ .../coretestserver/coretestserver/__init__.py | 33 +++++++ .../coretestserver/test_routes/__init__.py | 24 +++++ .../coretestserver/test_routes/basic.py | 66 +++++++++++++ .../coretestserver/test_routes/encoding.py | 92 +++++++++++++++++++ .../coretestserver/test_routes/errors.py | 28 ++++++ .../coretestserver/test_routes/helpers.py | 12 +++ .../coretestserver/test_routes/multipart.py | 88 ++++++++++++++++++ .../coretestserver/test_routes/streams.py | 38 ++++++++ .../coretestserver/test_routes/urlencoded.py | 26 ++++++ .../coretestserver/test_routes/xml_route.py | 46 ++++++++++ .../testserver_tests/coretestserver/setup.py | 35 +++++++ .../tests/testserver_tests/test_testserver.py | 35 +++++++ .../testserver_tests/test_testserver_async.py | 37 ++++++++ 17 files changed, 672 insertions(+) create mode 100644 sdk/core/azure-core/tests/testserver_tests/conftest.py create mode 100644 sdk/core/azure-core/tests/testserver_tests/coretestserver/README.rst create mode 100644 sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/__init__.py create mode 100644 sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/__init__.py create mode 100644 sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/basic.py create mode 100644 sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/encoding.py create mode 100644 sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/errors.py create mode 100644 sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/helpers.py create mode 100644 sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/multipart.py create mode 100644 sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/streams.py create mode 100644 sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/urlencoded.py create mode 100644 sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/xml_route.py create mode 100644 sdk/core/azure-core/tests/testserver_tests/coretestserver/setup.py create mode 100644 sdk/core/azure-core/tests/testserver_tests/test_testserver.py create mode 100644 sdk/core/azure-core/tests/testserver_tests/test_testserver_async.py diff --git a/eng/.docsettings.yml b/eng/.docsettings.yml index 9d880e244dfe1..2e57789818344 100644 --- a/eng/.docsettings.yml +++ b/eng/.docsettings.yml @@ -94,6 +94,7 @@ known_content_issues: - ['sdk/containerregistry/azure-containerregistry/swagger/README.md', '#4554'] - ['sdk/appconfiguration/azure-appconfiguration/swagger/README.md', '#4554'] - ['sdk/attestation/azure-security-attestation/swagger/README.md', '#4554'] + - ['sdk/core/azure-core/tests/testserver_tests/coretestserver/README.rst', '#4554'] # common. - ['sdk/appconfiguration/azure-appconfiguration/README.md', 'common'] diff --git a/sdk/core/azure-core/dev_requirements.txt b/sdk/core/azure-core/dev_requirements.txt index f1cabe1cb1279..27a3bb04ce643 100644 --- a/sdk/core/azure-core/dev_requirements.txt +++ b/sdk/core/azure-core/dev_requirements.txt @@ -7,3 +7,4 @@ opencensus-ext-threading mock; python_version < '3.3' -e ../../../tools/azure-sdk-tools -e ../../../tools/azure-devtools +-e tests/testserver_tests/coretestserver \ No newline at end of file diff --git a/sdk/core/azure-core/tests/testserver_tests/conftest.py b/sdk/core/azure-core/tests/testserver_tests/conftest.py new file mode 100644 index 0000000000000..10a99fb3ce212 --- /dev/null +++ b/sdk/core/azure-core/tests/testserver_tests/conftest.py @@ -0,0 +1,88 @@ +# -------------------------------------------------------------------------- +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# +# The MIT License (MIT) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the ""Software""), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# +# -------------------------------------------------------------------------- +import time +import pytest +import signal +import os +import subprocess +import sys +import random +from six.moves import urllib + +def is_port_available(port_num): + req = urllib.request.Request("http://localhost:{}/health".format(port_num)) + try: + return urllib.request.urlopen(req).code != 200 + except Exception as e: + return True + +def get_port(): + count = 3 + for _ in range(count): + port_num = random.randrange(3000, 5000) + if is_port_available(port_num): + return port_num + raise TypeError("Tried {} times, can't find an open port".format(count)) + +@pytest.fixture +def port(): + return os.environ["FLASK_PORT"] + +def start_testserver(): + port = get_port() + os.environ["FLASK_APP"] = "coretestserver" + os.environ["FLASK_PORT"] = str(port) + cmd = "flask run -p {}".format(port) + if os.name == 'nt': #On windows, subprocess creation works without being in the shell + child_process = subprocess.Popen(cmd, env=dict(os.environ)) + else: + #On linux, have to set shell=True + child_process = subprocess.Popen(cmd, shell=True, preexec_fn=os.setsid, env=dict(os.environ)) + count = 5 + for _ in range(count): + if not is_port_available(port): + return child_process + time.sleep(1) + raise ValueError("Didn't start!") + +def terminate_testserver(process): + if os.name == 'nt': + process.kill() + else: + os.killpg(os.getpgid(process.pid), signal.SIGTERM) # Send the signal to all the process groups + +@pytest.fixture(autouse=True, scope="package") +def testserver(): + """Start the Autorest testserver.""" + server = start_testserver() + yield + terminate_testserver(server) + + +# Ignore collection of async tests for Python 2 +collect_ignore_glob = [] +if sys.version_info < (3, 5): + collect_ignore_glob.append("*_async.py") diff --git a/sdk/core/azure-core/tests/testserver_tests/coretestserver/README.rst b/sdk/core/azure-core/tests/testserver_tests/coretestserver/README.rst new file mode 100644 index 0000000000000..9ac3fbf88847a --- /dev/null +++ b/sdk/core/azure-core/tests/testserver_tests/coretestserver/README.rst @@ -0,0 +1,22 @@ +Testserver for Python Azure Core +============================================== + +This package contains a testserver to aid in testing of Python's Azure Core package + +It has the following component: + +coretestserver +-------------- + +A testing server for Azure Core tests to use + +Contributing +============ + +This project has adopted the +`Microsoft Open Source Code of Conduct `__. +For more information see the +`Code of Conduct FAQ `__ +or contact +`opencode@microsoft.com `__ +with any additional questions or comments. diff --git a/sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/__init__.py b/sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/__init__.py new file mode 100644 index 0000000000000..63560847a01fe --- /dev/null +++ b/sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/__init__.py @@ -0,0 +1,33 @@ +# coding: utf-8 +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See LICENSE.txt in the project root for +# license information. +# ------------------------------------------------------------------------- + +from flask import Flask, Response +from .test_routes import ( + basic_api, + encoding_api, + errors_api, + streams_api, + urlencoded_api, + multipart_api, + xml_api +) + +app = Flask(__name__) +app.register_blueprint(basic_api, url_prefix="/basic") +app.register_blueprint(encoding_api, url_prefix="/encoding") +app.register_blueprint(errors_api, url_prefix="/errors") +app.register_blueprint(streams_api, url_prefix="/streams") +app.register_blueprint(urlencoded_api, url_prefix="/urlencoded") +app.register_blueprint(multipart_api, url_prefix="/multipart") +app.register_blueprint(xml_api, url_prefix="/xml") + +@app.route('/health', methods=['GET']) +def latin_1_charset_utf8(): + return Response(status=200) + +if __name__ == "__main__": + app.run(debug=True) diff --git a/sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/__init__.py b/sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/__init__.py new file mode 100644 index 0000000000000..82f4e7ac4566d --- /dev/null +++ b/sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/__init__.py @@ -0,0 +1,24 @@ +# coding: utf-8 +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See LICENSE.txt in the project root for +# license information. +# ------------------------------------------------------------------------- + +from .basic import basic_api +from .encoding import encoding_api +from .errors import errors_api +from .multipart import multipart_api +from .streams import streams_api +from .urlencoded import urlencoded_api +from .xml_route import xml_api + +__all__ = [ + "basic_api", + "encoding_api", + "errors_api", + "multipart_api", + "streams_api", + "urlencoded_api", + "xml_api", +] diff --git a/sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/basic.py b/sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/basic.py new file mode 100644 index 0000000000000..4b7d5ae92ad4c --- /dev/null +++ b/sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/basic.py @@ -0,0 +1,66 @@ +# coding: utf-8 +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See LICENSE.txt in the project root for +# license information. +# ------------------------------------------------------------------------- + +from flask import ( + Response, + Blueprint, + request +) + +basic_api = Blueprint('basic_api', __name__) + +@basic_api.route('/string', methods=['GET']) +def string(): + return Response( + "Hello, world!", status=200, mimetype="text/plain" + ) + +@basic_api.route('/lines', methods=['GET']) +def lines(): + return Response( + "Hello,\nworld!", status=200, mimetype="text/plain" + ) + +@basic_api.route("/bytes", methods=['GET']) +def bytes(): + return Response( + "Hello, world!".encode(), status=200, mimetype="text/plain" + ) + +@basic_api.route("/html", methods=['GET']) +def html(): + return Response( + "Hello, world!", status=200, mimetype="text/html" + ) + +@basic_api.route("/json", methods=['GET']) +def json(): + return Response( + '{"greeting": "hello", "recipient": "world"}', status=200, mimetype="application/json" + ) + +@basic_api.route("/complicated-json", methods=['POST']) +def complicated_json(): + # thanks to Sean Kane for this test! + assert request.json['EmptyByte'] == '' + assert request.json['EmptyUnicode'] == '' + assert request.json['SpacesOnlyByte'] == ' ' + assert request.json['SpacesOnlyUnicode'] == ' ' + assert request.json['SpacesBeforeByte'] == ' Text' + assert request.json['SpacesBeforeUnicode'] == ' Text' + assert request.json['SpacesAfterByte'] == 'Text ' + assert request.json['SpacesAfterUnicode'] == 'Text ' + assert request.json['SpacesBeforeAndAfterByte'] == ' Text ' + assert request.json['SpacesBeforeAndAfterUnicode'] == ' Text ' + assert request.json['啊齄丂狛'] == 'ꀕ' + assert request.json['RowKey'] == 'test2' + assert request.json['啊齄丂狛狜'] == 'hello' + assert request.json["singlequote"] == "a''''b" + assert request.json["doublequote"] == 'a""""b' + assert request.json["None"] == None + + return Response(status=200) diff --git a/sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/encoding.py b/sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/encoding.py new file mode 100644 index 0000000000000..12224e568ee5d --- /dev/null +++ b/sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/encoding.py @@ -0,0 +1,92 @@ +# coding: utf-8 +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See LICENSE.txt in the project root for +# license information. +# ------------------------------------------------------------------------- +from json import dumps +from flask import ( + Response, + Blueprint, +) + +encoding_api = Blueprint('encoding_api', __name__) + +@encoding_api.route('/latin-1', methods=['GET']) +def latin_1(): + r = Response( + "Latin 1: ÿ".encode("latin-1"), status=200 + ) + r.headers["Content-Type"] = "text/plain; charset=latin-1" + return r + +@encoding_api.route('/latin-1-with-utf-8', methods=['GET']) +def latin_1_charset_utf8(): + r = Response( + "Latin 1: ÿ".encode("latin-1"), status=200 + ) + r.headers["Content-Type"] = "text/plain; charset=utf-8" + return r + +@encoding_api.route('/no-charset', methods=['GET']) +def latin_1_no_charset(): + r = Response( + "Hello, world!", status=200 + ) + r.headers["Content-Type"] = "text/plain" + return r + +@encoding_api.route('/iso-8859-1', methods=['GET']) +def iso_8859_1(): + r = Response( + "Accented: Österreich".encode("iso-8859-1"), status=200 + ) + r.headers["Content-Type"] = "text/plain" + return r + +@encoding_api.route('/emoji', methods=['GET']) +def emoji(): + r = Response( + "👩", status=200 + ) + return r + +@encoding_api.route('/emoji-family-skin-tone-modifier', methods=['GET']) +def emoji_family_skin_tone_modifier(): + r = Response( + "👩🏻‍👩🏽‍👧🏾‍👦🏿 SSN: 859-98-0987", status=200 + ) + return r + +@encoding_api.route('/korean', methods=['GET']) +def korean(): + r = Response( + "아가", status=200 + ) + return r + +@encoding_api.route('/json', methods=['GET']) +def json(): + data = {"greeting": "hello", "recipient": "world"} + content = dumps(data).encode("utf-16") + r = Response( + content, status=200 + ) + r.headers["Content-Type"] = "application/json; charset=utf-16" + return r + +@encoding_api.route('/invalid-codec-name', methods=['GET']) +def invalid_codec_name(): + r = Response( + "おはようございます。".encode("utf-8"), status=200 + ) + r.headers["Content-Type"] = "text/plain; charset=invalid-codec-name" + return r + +@encoding_api.route('/no-charset', methods=['GET']) +def no_charset(): + r = Response( + "Hello, world!", status=200 + ) + r.headers["Content-Type"] = "text/plain" + return r diff --git a/sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/errors.py b/sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/errors.py new file mode 100644 index 0000000000000..221f598e063ad --- /dev/null +++ b/sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/errors.py @@ -0,0 +1,28 @@ +# coding: utf-8 +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See LICENSE.txt in the project root for +# license information. +# ------------------------------------------------------------------------- +from flask import ( + Response, + Blueprint, +) + +errors_api = Blueprint('errors_api', __name__) + +@errors_api.route('/403', methods=['GET']) +def get_403(): + return Response(status=403) + +@errors_api.route('/500', methods=['GET']) +def get_500(): + return Response(status=500) + +@errors_api.route('/stream', methods=['GET']) +def get_stream(): + class StreamingBody: + def __iter__(self): + yield b"Hello, " + yield b"world!" + return Response(StreamingBody(), status=500) diff --git a/sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/helpers.py b/sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/helpers.py new file mode 100644 index 0000000000000..46680f65d3f93 --- /dev/null +++ b/sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/helpers.py @@ -0,0 +1,12 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See LICENSE.txt in the project root for +# license information. +# ------------------------------------------------------------------------- + +def assert_with_message(param_name, expected_value, actual_value): + assert expected_value == actual_value, "Expected '{}' to be '{}', got '{}'".format( + param_name, expected_value, actual_value + ) + +__all__ = ["assert_with_message"] diff --git a/sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/multipart.py b/sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/multipart.py new file mode 100644 index 0000000000000..236496673a2f0 --- /dev/null +++ b/sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/multipart.py @@ -0,0 +1,88 @@ +# coding: utf-8 +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See LICENSE.txt in the project root for +# license information. +# ------------------------------------------------------------------------- +from copy import copy +from flask import ( + Response, + Blueprint, + request, +) +from .helpers import assert_with_message + +multipart_api = Blueprint('multipart_api', __name__) + +multipart_header_start = "multipart/form-data; boundary=" + +# NOTE: the flask behavior is different for aiohttp and requests +# in requests, we see the file content through request.form +# in aiohttp, we see the file through request.files + +@multipart_api.route('/basic', methods=['POST']) +def basic(): + assert_with_message("content type", multipart_header_start, request.content_type[:len(multipart_header_start)]) + if request.files: + # aiohttp + assert_with_message("content length", 258, request.content_length) + assert_with_message("num files", 1, len(request.files)) + assert_with_message("has file named fileContent", True, bool(request.files.get('fileContent'))) + file_content = request.files['fileContent'] + assert_with_message("file content type", "application/octet-stream", file_content.content_type) + assert_with_message("file content length", 14, file_content.content_length) + assert_with_message("filename", "fileContent", file_content.filename) + assert_with_message("has content disposition header", True, bool(file_content.headers.get("Content-Disposition"))) + assert_with_message( + "content disposition", + 'form-data; name="fileContent"; filename="fileContent"; filename*=utf-8\'\'fileContent', + file_content.headers["Content-Disposition"] + ) + elif request.form: + # requests + assert_with_message("content length", 184, request.content_length) + assert_with_message("fileContent", "", request.form["fileContent"]) + else: + return Response(status=400) # should be either of these + return Response(status=200) + +@multipart_api.route('/data-and-files', methods=['POST']) +def data_and_files(): + assert_with_message("content type", multipart_header_start, request.content_type[:len(multipart_header_start)]) + assert_with_message("message", "Hello, world!", request.form["message"]) + assert_with_message("message", "", request.form["fileContent"]) + return Response(status=200) + +@multipart_api.route('/data-and-files-tuple', methods=['POST']) +def data_and_files_tuple(): + assert_with_message("content type", multipart_header_start, request.content_type[:len(multipart_header_start)]) + assert_with_message("message", ["abc"], request.form["message"]) + assert_with_message("message", [""], request.form["fileContent"]) + return Response(status=200) + +@multipart_api.route('/non-seekable-filelike', methods=['POST']) +def non_seekable_filelike(): + assert_with_message("content type", multipart_header_start, request.content_type[:len(multipart_header_start)]) + if request.files: + # aiohttp + len_files = len(request.files) + assert_with_message("num files", 1, len_files) + # assert_with_message("content length", 258, request.content_length) + assert_with_message("num files", 1, len(request.files)) + assert_with_message("has file named file", True, bool(request.files.get('file'))) + file = request.files['file'] + assert_with_message("file content type", "application/octet-stream", file.content_type) + assert_with_message("file content length", 14, file.content_length) + assert_with_message("filename", "file", file.filename) + assert_with_message("has content disposition header", True, bool(file.headers.get("Content-Disposition"))) + assert_with_message( + "content disposition", + 'form-data; name="fileContent"; filename="fileContent"; filename*=utf-8\'\'fileContent', + file.headers["Content-Disposition"] + ) + elif request.form: + # requests + assert_with_message("num files", 1, len(request.form)) + else: + return Response(status=400) + return Response(status=200) diff --git a/sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/streams.py b/sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/streams.py new file mode 100644 index 0000000000000..1aeb7c05cc21d --- /dev/null +++ b/sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/streams.py @@ -0,0 +1,38 @@ +# coding: utf-8 +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See LICENSE.txt in the project root for +# license information. +# ------------------------------------------------------------------------- +from flask import ( + Response, + Blueprint, +) + +streams_api = Blueprint('streams_api', __name__) + +class StreamingBody: + def __iter__(self): + yield b"Hello, " + yield b"world!" + + +def streaming_body(): + yield b"Hello, " + yield b"world!" + +def stream_json_error(): + yield '{"error": {"code": "BadRequest", ' + yield' "message": "You made a bad request"}}' + +@streams_api.route('/basic', methods=['GET']) +def basic(): + return Response(streaming_body(), status=200) + +@streams_api.route('/iterable', methods=['GET']) +def iterable(): + return Response(StreamingBody(), status=200) + +@streams_api.route('/error', methods=['GET']) +def error(): + return Response(stream_json_error(), status=400) diff --git a/sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/urlencoded.py b/sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/urlencoded.py new file mode 100644 index 0000000000000..4ea2bdd2795d4 --- /dev/null +++ b/sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/urlencoded.py @@ -0,0 +1,26 @@ +# coding: utf-8 +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See LICENSE.txt in the project root for +# license information. +# ------------------------------------------------------------------------- +from flask import ( + Response, + Blueprint, + request, +) +from .helpers import assert_with_message + +urlencoded_api = Blueprint('urlencoded_api', __name__) + +@urlencoded_api.route('/pet/add/', methods=['POST']) +def basic(pet_id): + assert_with_message("pet_id", "1", pet_id) + assert_with_message("content type", "application/x-www-form-urlencoded", request.content_type) + assert_with_message("content length", 47, request.content_length) + assert len(request.form) == 4 + assert_with_message("pet_type", "dog", request.form["pet_type"]) + assert_with_message("pet_food", "meat", request.form["pet_food"]) + assert_with_message("name", "Fido", request.form["name"]) + assert_with_message("pet_age", "42", request.form["pet_age"]) + return Response(status=200) diff --git a/sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/xml_route.py b/sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/xml_route.py new file mode 100644 index 0000000000000..c19aed97b6b58 --- /dev/null +++ b/sdk/core/azure-core/tests/testserver_tests/coretestserver/coretestserver/test_routes/xml_route.py @@ -0,0 +1,46 @@ +# coding: utf-8 +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See LICENSE.txt in the project root for +# license information. +# ------------------------------------------------------------------------- +import xml.etree.ElementTree as ET +from flask import ( + Response, + Blueprint, + request, +) +from .helpers import assert_with_message + +xml_api = Blueprint('xml_api', __name__) + +@xml_api.route('/basic', methods=['GET', 'PUT']) +def basic(): + basic_body = """ + + + Wake up to WonderWidgets! + + + Overview + Why WonderWidgets are great + + Who buys WonderWidgets + +""" + + if request.method == 'GET': + return Response(basic_body, status=200) + elif request.method == 'PUT': + assert_with_message("content length", str(len(request.data)), request.headers["Content-Length"]) + parsed_xml = ET.fromstring(request.data.decode("utf-8")) + assert_with_message("tag", "slideshow", parsed_xml.tag) + attributes = parsed_xml.attrib + assert_with_message("title attribute", "Sample Slide Show", attributes['title']) + assert_with_message("date attribute", "Date of publication", attributes['date']) + assert_with_message("author attribute", "Yours Truly", attributes['author']) + return Response(status=200) + return Response("You have passed in method '{}' that is not 'GET' or 'PUT'".format(request.method), status=400) diff --git a/sdk/core/azure-core/tests/testserver_tests/coretestserver/setup.py b/sdk/core/azure-core/tests/testserver_tests/coretestserver/setup.py new file mode 100644 index 0000000000000..a43288221498f --- /dev/null +++ b/sdk/core/azure-core/tests/testserver_tests/coretestserver/setup.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python + +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +from setuptools import setup, find_packages + +version = "1.0.0b1" + +setup( + name="coretestserver", + version=version, + include_package_data=True, + description='Testserver for Python Core', + long_description='Testserver for Python Core', + license='MIT License', + author='Microsoft Corporation', + author_email='azpysdkhelp@microsoft.com', + url='https://github.com/iscai-msft/core.testserver', + classifiers=[ + 'Development Status :: 4 - Beta', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'License :: OSI Approved :: MIT License', + ], + packages=find_packages(), + install_requires=[ + "flask" + ] +) diff --git a/sdk/core/azure-core/tests/testserver_tests/test_testserver.py b/sdk/core/azure-core/tests/testserver_tests/test_testserver.py new file mode 100644 index 0000000000000..a31d449fbd53b --- /dev/null +++ b/sdk/core/azure-core/tests/testserver_tests/test_testserver.py @@ -0,0 +1,35 @@ +# -------------------------------------------------------------------------- +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# +# The MIT License (MIT) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the ""Software""), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# -------------------------------------------------------------------------- +import pytest +from azure.core.pipeline.transport import HttpRequest, RequestsTransport +"""This file does a simple call to the testserver to make sure we can use the testserver""" + +def test_smoke(port): + request = HttpRequest(method="GET", url="http://localhost:{}/basic/string".format(port)) + with RequestsTransport() as sender: + response = sender.send(request) + response.raise_for_status() + assert response.text() == "Hello, world!" diff --git a/sdk/core/azure-core/tests/testserver_tests/test_testserver_async.py b/sdk/core/azure-core/tests/testserver_tests/test_testserver_async.py new file mode 100644 index 0000000000000..623033080bd16 --- /dev/null +++ b/sdk/core/azure-core/tests/testserver_tests/test_testserver_async.py @@ -0,0 +1,37 @@ +# -------------------------------------------------------------------------- +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# +# The MIT License (MIT) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the ""Software""), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# -------------------------------------------------------------------------- +import pytest +from azure.core.pipeline.transport import HttpRequest, AioHttpTransport +"""This file does a simple call to the testserver to make sure we can use the testserver""" + +@pytest.mark.asyncio +async def test_smoke(port): + request = HttpRequest(method="GET", url="http://localhost:{}/basic/string".format(port)) + async with AioHttpTransport() as sender: + response = await sender.send(request) + response.raise_for_status() + await response.load_body() + assert response.text() == "Hello, world!" \ No newline at end of file