2017-02-02 17:21:14 +00:00
|
|
|
import pytest as pytest
|
|
|
|
from urllib.parse import urlsplit, parse_qsl
|
|
|
|
|
|
|
|
from sanic.response import text
|
2017-02-02 17:52:48 +00:00
|
|
|
from sanic.views import HTTPMethodView
|
2017-02-02 17:21:14 +00:00
|
|
|
from sanic.blueprints import Blueprint
|
2018-03-16 04:28:52 +00:00
|
|
|
from sanic.testing import PORT as test_port, HOST as test_host
|
2017-02-02 17:21:14 +00:00
|
|
|
from sanic.exceptions import URLBuildError
|
|
|
|
|
|
|
|
import string
|
|
|
|
|
2017-02-14 02:26:30 +00:00
|
|
|
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',
|
2018-03-16 04:28:52 +00:00
|
|
|
_server='{}:{}'.format(test_host, test_port), _external=True)
|
|
|
|
URL_FOR_VALUE3 = 'http://{}:{}/myurl?arg1=v1#anchor'.format(test_host, test_port)
|
2017-09-06 12:17:52 +01:00
|
|
|
URL_FOR_ARGS4 = dict(arg1='v1', _anchor='anchor', _external=True,
|
2018-03-16 04:28:52 +00:00
|
|
|
_server='http://{}:{}'.format(test_host, test_port))
|
|
|
|
URL_FOR_VALUE4 = 'http://{}:{}/myurl?arg1=v1#anchor'.format(test_host, test_port)
|
2017-02-14 02:26:30 +00:00
|
|
|
|
2017-02-02 17:21:14 +00:00
|
|
|
|
|
|
|
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
|
2018-08-26 15:43:14 +01:00
|
|
|
def simple_app(app):
|
2017-02-02 17:21:14 +00:00
|
|
|
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)
|
2017-02-14 19:51:20 +00:00
|
|
|
request, response = simple_app.test_client.get(url)
|
2017-02-02 17:21:14 +00:00
|
|
|
assert response.status == 200
|
|
|
|
assert response.text == letter
|
|
|
|
|
|
|
|
|
2017-02-14 02:26:30 +00:00
|
|
|
@pytest.mark.parametrize('args,url',
|
|
|
|
[(URL_FOR_ARGS1, URL_FOR_VALUE1),
|
|
|
|
(URL_FOR_ARGS2, URL_FOR_VALUE2),
|
2017-09-06 12:17:52 +01:00
|
|
|
(URL_FOR_ARGS3, URL_FOR_VALUE3),
|
|
|
|
(URL_FOR_ARGS4, URL_FOR_VALUE4)])
|
2018-08-26 15:43:14 +01:00
|
|
|
def test_simple_url_for_getting_with_more_params(app, args, url):
|
2017-02-09 08:44:23 +00:00
|
|
|
|
2017-02-14 02:26:30 +00:00
|
|
|
@app.route('/myurl')
|
|
|
|
def passes(request):
|
|
|
|
return text('this should pass')
|
2017-02-09 08:44:23 +00:00
|
|
|
|
2017-02-14 02:26:30 +00:00
|
|
|
assert url == app.url_for('passes', **args)
|
2017-02-14 19:51:20 +00:00
|
|
|
request, response = app.test_client.get(url)
|
2017-02-14 02:26:30 +00:00
|
|
|
assert response.status == 200
|
|
|
|
assert response.text == 'this should pass'
|
2017-02-09 08:44:23 +00:00
|
|
|
|
|
|
|
|
2018-08-26 15:43:14 +01:00
|
|
|
def test_fails_if_endpoint_not_found(app):
|
2017-02-02 17:21:14 +00:00
|
|
|
|
|
|
|
@app.route('/fail')
|
2018-01-22 06:52:30 +00:00
|
|
|
def fail(request):
|
2017-02-02 17:21:14 +00:00
|
|
|
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'
|
|
|
|
|
|
|
|
|
2018-08-26 15:43:14 +01:00
|
|
|
def test_fails_url_build_if_param_not_passed(app):
|
2017-02-02 17:21:14 +00:00
|
|
|
url = '/'
|
|
|
|
|
|
|
|
for letter in string.ascii_letters:
|
|
|
|
url += '<{}>/'.format(letter)
|
|
|
|
|
|
|
|
@app.route(url)
|
2018-01-22 06:52:30 +00:00
|
|
|
def fail(request):
|
2017-02-02 17:21:14 +00:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2018-08-26 15:43:14 +01:00
|
|
|
def test_fails_url_build_if_params_not_passed(app):
|
2017-02-09 08:44:23 +00:00
|
|
|
|
|
|
|
@app.route('/fail')
|
2018-01-22 06:52:30 +00:00
|
|
|
def fail(request):
|
2017-02-09 08:44:23 +00:00
|
|
|
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'
|
|
|
|
|
|
|
|
|
2017-02-02 17:21:14 +00:00
|
|
|
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'
|
|
|
|
|
|
|
|
|
2018-08-26 15:43:14 +01:00
|
|
|
def test_fails_with_int_message(app):
|
2017-02-02 17:21:14 +00:00
|
|
|
|
|
|
|
@app.route(COMPLEX_PARAM_URL)
|
2018-01-22 06:52:30 +00:00
|
|
|
def fail(request):
|
2017-02-02 17:21:14 +00:00
|
|
|
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 = (
|
|
|
|
'Value "not_int" for parameter `foo` '
|
|
|
|
'does not match pattern for type `int`: \d+')
|
|
|
|
assert str(e.value) == expected_error
|
|
|
|
|
|
|
|
|
2018-08-26 15:43:14 +01:00
|
|
|
def test_fails_with_two_letter_string_message(app):
|
2017-02-02 17:21:14 +00:00
|
|
|
|
|
|
|
@app.route(COMPLEX_PARAM_URL)
|
2018-01-22 06:52:30 +00:00
|
|
|
def fail(request):
|
2017-02-02 17:21:14 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
|
2018-08-26 15:43:14 +01:00
|
|
|
def test_fails_with_number_message(app):
|
2017-02-02 17:21:14 +00:00
|
|
|
|
|
|
|
@app.route(COMPLEX_PARAM_URL)
|
2018-01-22 06:52:30 +00:00
|
|
|
def fail(request):
|
2017-02-02 17:21:14 +00:00
|
|
|
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` '
|
|
|
|
'does not match pattern for type `float`: [0-9\\\\.]+')
|
|
|
|
|
|
|
|
assert str(e.value) == expected_error
|
|
|
|
|
|
|
|
|
2018-08-26 15:43:14 +01:00
|
|
|
def test_adds_other_supplied_values_as_query_string(app):
|
2017-02-02 17:21:14 +00:00
|
|
|
|
|
|
|
@app.route(COMPLEX_PARAM_URL)
|
2018-01-22 06:52:30 +00:00
|
|
|
def passes(request):
|
2017-02-02 17:21:14 +00:00
|
|
|
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
|
2018-08-26 15:43:14 +01:00
|
|
|
def blueprint_app(app):
|
2017-02-02 17:21:14 +00:00
|
|
|
|
|
|
|
first_print = Blueprint('first', url_prefix='/first')
|
|
|
|
second_print = Blueprint('second', url_prefix='/second')
|
|
|
|
|
|
|
|
@first_print.route('/foo')
|
2018-01-22 06:52:30 +00:00
|
|
|
def foo(request):
|
2017-02-02 17:21:14 +00:00
|
|
|
return text('foo from first')
|
|
|
|
|
|
|
|
@first_print.route('/foo/<param>')
|
|
|
|
def foo_with_param(request, param):
|
|
|
|
return text(
|
|
|
|
'foo from first : {}'.format(param))
|
|
|
|
|
2017-02-09 08:44:23 +00:00
|
|
|
@second_print.route('/foo') # noqa
|
2018-01-22 06:52:30 +00:00
|
|
|
def foo(request):
|
2017-02-02 17:21:14 +00:00
|
|
|
return text('foo from second')
|
|
|
|
|
2017-02-09 08:44:23 +00:00
|
|
|
@second_print.route('/foo/<param>') # noqa
|
2017-02-02 17:21:14 +00:00
|
|
|
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
|
2018-08-26 15:43:14 +01:00
|
|
|
def methodview_app(app):
|
2017-02-02 17:21:14 +00:00
|
|
|
|
|
|
|
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'
|