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