ccd4c9615c
Update all tests to be compatible with requests-async Cleanup testing client changes with black and isort Remove Python 3.5 and other meta doc cleanup rename pyproject and fix pep517 error Add black config to tox.ini Cleanup tests and remove aiohttp tox.ini change for easier development commands Remove aiohttp from changelog and requirements Cleanup imports and Makefile
357 lines
9.8 KiB
Python
357 lines
9.8 KiB
Python
import string
|
|
|
|
from urllib.parse import parse_qsl, urlsplit
|
|
|
|
import pytest as pytest
|
|
|
|
from sanic.blueprints import Blueprint
|
|
from sanic.exceptions import URLBuildError
|
|
from sanic.response import text
|
|
from sanic.testing import HOST as test_host
|
|
from sanic.testing import PORT as test_port
|
|
from sanic.views import HTTPMethodView
|
|
|
|
|
|
URL_FOR_ARGS1 = dict(arg1=["v1", "v2"])
|
|
URL_FOR_VALUE1 = "/myurl?arg1=v1&arg1=v2"
|
|
URL_FOR_ARGS2 = dict(arg1=["v1", "v2"], _anchor="anchor")
|
|
URL_FOR_VALUE2 = "/myurl?arg1=v1&arg1=v2#anchor"
|
|
URL_FOR_ARGS3 = dict(
|
|
arg1="v1",
|
|
_anchor="anchor",
|
|
_scheme="http",
|
|
_server="{}:{}".format(test_host, test_port),
|
|
_external=True,
|
|
)
|
|
URL_FOR_VALUE3 = "http://{}:{}/myurl?arg1=v1#anchor".format(
|
|
test_host, test_port
|
|
)
|
|
URL_FOR_ARGS4 = dict(
|
|
arg1="v1",
|
|
_anchor="anchor",
|
|
_external=True,
|
|
_server="http://{}:{}".format(test_host, test_port),
|
|
)
|
|
URL_FOR_VALUE4 = "http://{}:{}/myurl?arg1=v1#anchor".format(
|
|
test_host, test_port
|
|
)
|
|
|
|
|
|
def _generate_handlers_from_names(app, l):
|
|
for name in l:
|
|
# this is the easiest way to generate functions with dynamic names
|
|
exec(
|
|
'@app.route(name)\ndef {}(request):\n\treturn text("{}")'.format(
|
|
name, name
|
|
)
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def simple_app(app):
|
|
handler_names = list(string.ascii_letters)
|
|
|
|
_generate_handlers_from_names(app, handler_names)
|
|
|
|
return app
|
|
|
|
|
|
def test_simple_url_for_getting(simple_app):
|
|
for letter in string.ascii_letters:
|
|
url = simple_app.url_for(letter)
|
|
|
|
assert url == "/{}".format(letter)
|
|
request, response = simple_app.test_client.get(url)
|
|
assert response.status == 200
|
|
assert response.text == letter
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"args,url",
|
|
[
|
|
(URL_FOR_ARGS1, URL_FOR_VALUE1),
|
|
(URL_FOR_ARGS2, URL_FOR_VALUE2),
|
|
(URL_FOR_ARGS3, URL_FOR_VALUE3),
|
|
(URL_FOR_ARGS4, URL_FOR_VALUE4),
|
|
],
|
|
)
|
|
def test_simple_url_for_getting_with_more_params(app, args, url):
|
|
@app.route("/myurl")
|
|
def passes(request):
|
|
return text("this should pass")
|
|
|
|
assert url == app.url_for("passes", **args)
|
|
request, response = app.test_client.get(url)
|
|
assert response.status == 200
|
|
assert response.text == "this should pass"
|
|
|
|
|
|
def test_url_for_with_server_name(app):
|
|
|
|
server_name = "{}:{}".format(test_host, test_port)
|
|
app.config.update({"SERVER_NAME": server_name})
|
|
path = "/myurl"
|
|
|
|
@app.route(path)
|
|
def passes(request):
|
|
return text("this should pass")
|
|
|
|
url = "http://{}{}".format(server_name, path)
|
|
assert url == app.url_for("passes", _server=None, _external=True)
|
|
request, response = app.test_client.get(url)
|
|
assert response.status == 200
|
|
assert response.text == "this should pass"
|
|
|
|
|
|
def test_fails_if_endpoint_not_found(app):
|
|
@app.route("/fail")
|
|
def fail(request):
|
|
return text("this should fail")
|
|
|
|
with pytest.raises(URLBuildError) as e:
|
|
app.url_for("passes")
|
|
|
|
assert str(e.value) == "Endpoint with name `passes` was not found"
|
|
|
|
|
|
def test_fails_url_build_if_param_not_passed(app):
|
|
url = "/"
|
|
|
|
for letter in string.ascii_letters:
|
|
url += "<{}>/".format(letter)
|
|
|
|
@app.route(url)
|
|
def fail(request):
|
|
return text("this should fail")
|
|
|
|
fail_args = list(string.ascii_letters)
|
|
fail_args.pop()
|
|
|
|
fail_kwargs = {l: l for l in fail_args}
|
|
|
|
with pytest.raises(URLBuildError) as e:
|
|
app.url_for("fail", **fail_kwargs)
|
|
|
|
assert "Required parameter `Z` was not passed to url_for" in str(e.value)
|
|
|
|
|
|
def test_fails_url_build_if_params_not_passed(app):
|
|
@app.route("/fail")
|
|
def fail(request):
|
|
return text("this should fail")
|
|
|
|
with pytest.raises(ValueError) as e:
|
|
app.url_for("fail", _scheme="http")
|
|
|
|
assert str(e.value) == "When specifying _scheme, _external must be True"
|
|
|
|
|
|
COMPLEX_PARAM_URL = (
|
|
"/<foo:int>/<four_letter_string:[A-z]{4}>/"
|
|
"<two_letter_string:[A-z]{2}>/<normal_string>/<some_number:number>"
|
|
)
|
|
PASSING_KWARGS = {
|
|
"foo": 4,
|
|
"four_letter_string": "woof",
|
|
"two_letter_string": "ba",
|
|
"normal_string": "normal",
|
|
"some_number": "1.001",
|
|
}
|
|
EXPECTED_BUILT_URL = "/4/woof/ba/normal/1.001"
|
|
|
|
|
|
def test_fails_with_int_message(app):
|
|
@app.route(COMPLEX_PARAM_URL)
|
|
def fail(request):
|
|
return text("this should fail")
|
|
|
|
failing_kwargs = dict(PASSING_KWARGS)
|
|
failing_kwargs["foo"] = "not_int"
|
|
|
|
with pytest.raises(URLBuildError) as e:
|
|
app.url_for("fail", **failing_kwargs)
|
|
|
|
expected_error = (
|
|
r'Value "not_int" for parameter `foo` '
|
|
r"does not match pattern for type `int`: -?\d+"
|
|
)
|
|
assert str(e.value) == expected_error
|
|
|
|
|
|
def test_passes_with_negative_int_message(app):
|
|
@app.route("path/<possibly_neg:int>/another-word")
|
|
def good(request, possibly_neg):
|
|
assert isinstance(possibly_neg, int)
|
|
return text("this should pass with `{}`".format(possibly_neg))
|
|
|
|
u_plus_3 = app.url_for("good", possibly_neg=3)
|
|
assert u_plus_3 == "/path/3/another-word", u_plus_3
|
|
request, response = app.test_client.get(u_plus_3)
|
|
assert response.text == "this should pass with `3`"
|
|
u_neg_3 = app.url_for("good", possibly_neg=-3)
|
|
assert u_neg_3 == "/path/-3/another-word", u_neg_3
|
|
request, response = app.test_client.get(u_neg_3)
|
|
assert response.text == "this should pass with `-3`"
|
|
|
|
|
|
def test_fails_with_two_letter_string_message(app):
|
|
@app.route(COMPLEX_PARAM_URL)
|
|
def fail(request):
|
|
return text("this should fail")
|
|
|
|
failing_kwargs = dict(PASSING_KWARGS)
|
|
failing_kwargs["two_letter_string"] = "foobar"
|
|
|
|
with pytest.raises(URLBuildError) as e:
|
|
app.url_for("fail", **failing_kwargs)
|
|
|
|
expected_error = (
|
|
'Value "foobar" for parameter `two_letter_string` '
|
|
"does not satisfy pattern [A-z]{2}"
|
|
)
|
|
|
|
assert str(e.value) == expected_error
|
|
|
|
|
|
def test_fails_with_number_message(app):
|
|
@app.route(COMPLEX_PARAM_URL)
|
|
def fail(request):
|
|
return text("this should fail")
|
|
|
|
failing_kwargs = dict(PASSING_KWARGS)
|
|
failing_kwargs["some_number"] = "foo"
|
|
|
|
with pytest.raises(URLBuildError) as e:
|
|
app.url_for("fail", **failing_kwargs)
|
|
|
|
expected_error = (
|
|
'Value "foo" for parameter `some_number` '
|
|
r"does not match pattern for type `float`: -?(?:\d+(?:\.\d*)?|\.\d+)"
|
|
)
|
|
|
|
assert str(e.value) == expected_error
|
|
|
|
|
|
@pytest.mark.parametrize("number", [3, -3, 13.123, -13.123])
|
|
def test_passes_with_negative_number_message(app, number):
|
|
@app.route("path/<possibly_neg:number>/another-word")
|
|
def good(request, possibly_neg):
|
|
assert isinstance(possibly_neg, (int, float))
|
|
return text("this should pass with `{}`".format(possibly_neg))
|
|
|
|
u = app.url_for("good", possibly_neg=number)
|
|
assert u == "/path/{}/another-word".format(number), u
|
|
request, response = app.test_client.get(u)
|
|
# For ``number``, it has been cast to a float - so a ``3`` becomes a ``3.0``
|
|
assert response.text == "this should pass with `{}`".format(float(number))
|
|
|
|
|
|
def test_adds_other_supplied_values_as_query_string(app):
|
|
@app.route(COMPLEX_PARAM_URL)
|
|
def passes(request):
|
|
return text("this should pass")
|
|
|
|
new_kwargs = dict(PASSING_KWARGS)
|
|
new_kwargs["added_value_one"] = "one"
|
|
new_kwargs["added_value_two"] = "two"
|
|
|
|
url = app.url_for("passes", **new_kwargs)
|
|
|
|
query = dict(parse_qsl(urlsplit(url).query))
|
|
|
|
assert query["added_value_one"] == "one"
|
|
assert query["added_value_two"] == "two"
|
|
|
|
|
|
@pytest.fixture
|
|
def blueprint_app(app):
|
|
|
|
first_print = Blueprint("first", url_prefix="/first")
|
|
second_print = Blueprint("second", url_prefix="/second")
|
|
|
|
@first_print.route("/foo")
|
|
def foo(request):
|
|
return text("foo from first")
|
|
|
|
@first_print.route("/foo/<param>")
|
|
def foo_with_param(request, param):
|
|
return text("foo from first : {}".format(param))
|
|
|
|
@second_print.route("/foo") # noqa
|
|
def foo(request):
|
|
return text("foo from second")
|
|
|
|
@second_print.route("/foo/<param>") # noqa
|
|
def foo_with_param(request, param):
|
|
return text("foo from second : {}".format(param))
|
|
|
|
app.blueprint(first_print)
|
|
app.blueprint(second_print)
|
|
|
|
return app
|
|
|
|
|
|
def test_blueprints_are_named_correctly(blueprint_app):
|
|
first_url = blueprint_app.url_for("first.foo")
|
|
assert first_url == "/first/foo"
|
|
|
|
second_url = blueprint_app.url_for("second.foo")
|
|
assert second_url == "/second/foo"
|
|
|
|
|
|
def test_blueprints_work_with_params(blueprint_app):
|
|
first_url = blueprint_app.url_for("first.foo_with_param", param="bar")
|
|
assert first_url == "/first/foo/bar"
|
|
|
|
second_url = blueprint_app.url_for("second.foo_with_param", param="bar")
|
|
assert second_url == "/second/foo/bar"
|
|
|
|
|
|
@pytest.fixture
|
|
def methodview_app(app):
|
|
class ViewOne(HTTPMethodView):
|
|
def get(self, request):
|
|
return text("I am get method")
|
|
|
|
def post(self, request):
|
|
return text("I am post method")
|
|
|
|
def put(self, request):
|
|
return text("I am put method")
|
|
|
|
def patch(self, request):
|
|
return text("I am patch method")
|
|
|
|
def delete(self, request):
|
|
return text("I am delete method")
|
|
|
|
app.add_route(ViewOne.as_view("view_one"), "/view_one")
|
|
|
|
class ViewTwo(HTTPMethodView):
|
|
def get(self, request):
|
|
return text("I am get method")
|
|
|
|
def post(self, request):
|
|
return text("I am post method")
|
|
|
|
def put(self, request):
|
|
return text("I am put method")
|
|
|
|
def patch(self, request):
|
|
return text("I am patch method")
|
|
|
|
def delete(self, request):
|
|
return text("I am delete method")
|
|
|
|
app.add_route(ViewTwo.as_view(), "/view_two")
|
|
|
|
return app
|
|
|
|
|
|
def test_methodview_naming(methodview_app):
|
|
viewone_url = methodview_app.url_for("ViewOne")
|
|
viewtwo_url = methodview_app.url_for("ViewTwo")
|
|
|
|
assert viewone_url == "/view_one"
|
|
assert viewtwo_url == "/view_two"
|