mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-14 05:23:01 +00:00
test_api passes without threads
just put requests in a thread via `utils.async_requests` eliminates db threads issue
This commit is contained in:
@@ -38,8 +38,7 @@ def db():
|
||||
|
||||
@fixture(scope='module')
|
||||
def io_loop(request):
|
||||
"""Same as pytest-tornado.gen"""
|
||||
print("my io_loop fixture")
|
||||
"""Same as pytest-tornado.io_loop, but re-scoped to module-level"""
|
||||
io_loop = ioloop.IOLoop()
|
||||
io_loop.make_current()
|
||||
|
||||
@@ -78,7 +77,7 @@ class MockServiceSpawner(jupyterhub.services.service._ServiceSpawner):
|
||||
|
||||
_mock_service_counter = 0
|
||||
|
||||
def _mockservice(request, app, io_loop, url=False):
|
||||
def _mockservice(request, app, url=False):
|
||||
global _mock_service_counter
|
||||
_mock_service_counter += 1
|
||||
name = 'mock-service-%i' % _mock_service_counter
|
||||
@@ -90,6 +89,8 @@ def _mockservice(request, app, io_loop, url=False):
|
||||
if url:
|
||||
spec['url'] = 'http://127.0.0.1:%i' % random_port()
|
||||
|
||||
io_loop = app.io_loop
|
||||
|
||||
with mock.patch.object(jupyterhub.services.service, '_ServiceSpawner', MockServiceSpawner):
|
||||
app.services = [spec]
|
||||
app.init_services()
|
||||
@@ -100,20 +101,17 @@ def _mockservice(request, app, io_loop, url=False):
|
||||
# wait for proxy to be updated before starting the service
|
||||
yield app.proxy.add_all_services(app._service_map)
|
||||
service.start()
|
||||
app.io_loop.add_callback(start)
|
||||
io_loop.run_sync(start)
|
||||
def cleanup():
|
||||
service.stop()
|
||||
app.services[:] = []
|
||||
app._service_map.clear()
|
||||
request.addfinalizer(cleanup)
|
||||
for i in range(20):
|
||||
if not getattr(service, 'proc', False):
|
||||
time.sleep(0.2)
|
||||
# ensure process finishes starting
|
||||
with raises(TimeoutExpired):
|
||||
service.proc.wait(1)
|
||||
if url:
|
||||
ioloop.IOLoop().run_sync(service.server.wait_up)
|
||||
io_loop.run_sync(service.server.wait_up)
|
||||
return service
|
||||
|
||||
|
||||
|
@@ -22,6 +22,7 @@ from ..objects import Server
|
||||
from ..spawner import LocalProcessSpawner
|
||||
from ..singleuser import SingleUserNotebookApp
|
||||
from ..utils import random_port, url_path_join
|
||||
from .utils import async_requests
|
||||
|
||||
from pamela import PAMError
|
||||
|
||||
@@ -178,10 +179,11 @@ class MockHub(JupyterHub):
|
||||
self.cleanup = lambda : None
|
||||
self.db_file.close()
|
||||
|
||||
@gen.coroutine
|
||||
def login_user(self, name):
|
||||
"""Login a user by name, returning her cookies."""
|
||||
base_url = public_url(self)
|
||||
r = requests.post(base_url + 'hub/login',
|
||||
r = yield async_requests.post(base_url + 'hub/login',
|
||||
data={
|
||||
'username': name,
|
||||
'password': name,
|
||||
|
@@ -2,12 +2,11 @@
|
||||
|
||||
import json
|
||||
import time
|
||||
from queue import Queue
|
||||
import sys
|
||||
from unittest import mock
|
||||
from urllib.parse import urlparse, quote
|
||||
|
||||
from pytest import mark, yield_fixture
|
||||
from pytest import mark
|
||||
import requests
|
||||
|
||||
from tornado import gen
|
||||
@@ -18,6 +17,7 @@ from ..user import User
|
||||
from ..utils import url_path_join as ujoin
|
||||
from . import mocking
|
||||
from .mocking import public_host, public_url
|
||||
from .utils import async_requests
|
||||
|
||||
|
||||
def check_db_locks(func):
|
||||
@@ -81,6 +81,7 @@ def auth_header(db, name):
|
||||
|
||||
|
||||
@check_db_locks
|
||||
@gen.coroutine
|
||||
def api_request(app, *api_path, **kwargs):
|
||||
"""Make an API request"""
|
||||
base_url = app.hub.url
|
||||
@@ -91,8 +92,8 @@ def api_request(app, *api_path, **kwargs):
|
||||
|
||||
url = ujoin(base_url, 'api', *api_path)
|
||||
method = kwargs.pop('method', 'get')
|
||||
f = getattr(requests, method)
|
||||
resp = f(url, **kwargs)
|
||||
f = getattr(async_requests, method)
|
||||
resp = yield f(url, **kwargs)
|
||||
assert "frame-ancestors 'self'" in resp.headers['Content-Security-Policy']
|
||||
assert ujoin(app.hub.base_url, "security/csp-report") in resp.headers['Content-Security-Policy']
|
||||
assert 'http' not in resp.headers['Content-Security-Policy']
|
||||
@@ -104,9 +105,10 @@ def api_request(app, *api_path, **kwargs):
|
||||
# --------------------
|
||||
|
||||
|
||||
@mark.gen_test
|
||||
def test_auth_api(app):
|
||||
db = app.db
|
||||
r = api_request(app, 'authorizations', 'gobbledygook')
|
||||
r = yield api_request(app, 'authorizations', 'gobbledygook')
|
||||
assert r.status_code == 404
|
||||
|
||||
# make a new cookie token
|
||||
@@ -114,36 +116,37 @@ def test_auth_api(app):
|
||||
api_token = user.new_api_token()
|
||||
|
||||
# check success:
|
||||
r = api_request(app, 'authorizations/token', api_token)
|
||||
r = yield api_request(app, 'authorizations/token', api_token)
|
||||
assert r.status_code == 200
|
||||
reply = r.json()
|
||||
assert reply['name'] == user.name
|
||||
|
||||
# check fail
|
||||
r = api_request(app, 'authorizations/token', api_token,
|
||||
r = yield api_request(app, 'authorizations/token', api_token,
|
||||
headers={'Authorization': 'no sir'},
|
||||
)
|
||||
assert r.status_code == 403
|
||||
|
||||
r = api_request(app, 'authorizations/token', api_token,
|
||||
r = yield api_request(app, 'authorizations/token', api_token,
|
||||
headers={'Authorization': 'token: %s' % user.cookie_id},
|
||||
)
|
||||
assert r.status_code == 403
|
||||
|
||||
|
||||
@mark.gen_test
|
||||
def test_referer_check(app, io_loop):
|
||||
url = ujoin(public_host(app), app.hub.base_url)
|
||||
host = urlparse(url).netloc
|
||||
user = find_user(app.db, 'admin')
|
||||
if user is None:
|
||||
user = add_user(app.db, name='admin', admin=True)
|
||||
cookies = app.login_user('admin')
|
||||
app_user = get_app_user(app, 'admin')
|
||||
cookies = yield app.login_user('admin')
|
||||
app_user = app.users[user]
|
||||
# stop the admin's server so we don't mess up future tests
|
||||
io_loop.run_sync(lambda: app.proxy.delete_user(app_user))
|
||||
io_loop.run_sync(app_user.stop)
|
||||
yield app.proxy.delete_user(app_user)
|
||||
yield app_user.stop()
|
||||
|
||||
r = api_request(app, 'users',
|
||||
r = yield api_request(app, 'users',
|
||||
headers={
|
||||
'Authorization': '',
|
||||
'Referer': 'null',
|
||||
@@ -151,7 +154,7 @@ def test_referer_check(app, io_loop):
|
||||
)
|
||||
assert r.status_code == 403
|
||||
|
||||
r = api_request(app, 'users',
|
||||
r = yield api_request(app, 'users',
|
||||
headers={
|
||||
'Authorization': '',
|
||||
'Referer': 'http://attack.com/csrf/vulnerability',
|
||||
@@ -159,7 +162,7 @@ def test_referer_check(app, io_loop):
|
||||
)
|
||||
assert r.status_code == 403
|
||||
|
||||
r = api_request(app, 'users',
|
||||
r = yield api_request(app, 'users',
|
||||
headers={
|
||||
'Authorization': '',
|
||||
'Referer': url,
|
||||
@@ -168,7 +171,7 @@ def test_referer_check(app, io_loop):
|
||||
)
|
||||
assert r.status_code == 200
|
||||
|
||||
r = api_request(app, 'users',
|
||||
r = yield api_request(app, 'users',
|
||||
headers={
|
||||
'Authorization': '',
|
||||
'Referer': ujoin(url, 'foo/bar/baz/bat'),
|
||||
@@ -184,9 +187,10 @@ def test_referer_check(app, io_loop):
|
||||
|
||||
|
||||
@mark.user
|
||||
@mark.gen_test
|
||||
def test_get_users(app):
|
||||
db = app.db
|
||||
r = api_request(app, 'users')
|
||||
r = yield api_request(app, 'users')
|
||||
assert r.status_code == 200
|
||||
|
||||
users = sorted(r.json(), key=lambda d: d['name'])
|
||||
@@ -211,17 +215,18 @@ def test_get_users(app):
|
||||
}
|
||||
]
|
||||
|
||||
r = api_request(app, 'users',
|
||||
r = yield api_request(app, 'users',
|
||||
headers=auth_header(db, 'user'),
|
||||
)
|
||||
assert r.status_code == 403
|
||||
|
||||
|
||||
@mark.user
|
||||
@mark.gen_test
|
||||
def test_add_user(app):
|
||||
db = app.db
|
||||
name = 'newuser'
|
||||
r = api_request(app, 'users', name, method='post')
|
||||
r = yield api_request(app, 'users', name, method='post')
|
||||
assert r.status_code == 201
|
||||
user = find_user(db, name)
|
||||
assert user is not None
|
||||
@@ -230,9 +235,10 @@ def test_add_user(app):
|
||||
|
||||
|
||||
@mark.user
|
||||
@mark.gen_test
|
||||
def test_get_user(app):
|
||||
name = 'user'
|
||||
r = api_request(app, 'users', name)
|
||||
r = yield api_request(app, 'users', name)
|
||||
assert r.status_code == 200
|
||||
user = r.json()
|
||||
user.pop('last_activity')
|
||||
@@ -247,19 +253,21 @@ def test_get_user(app):
|
||||
|
||||
|
||||
@mark.user
|
||||
@mark.gen_test
|
||||
def test_add_multi_user_bad(app):
|
||||
r = api_request(app, 'users', method='post')
|
||||
r = yield api_request(app, 'users', method='post')
|
||||
assert r.status_code == 400
|
||||
r = api_request(app, 'users', method='post', data='{}')
|
||||
r = yield api_request(app, 'users', method='post', data='{}')
|
||||
assert r.status_code == 400
|
||||
r = api_request(app, 'users', method='post', data='[]')
|
||||
r = yield api_request(app, 'users', method='post', data='[]')
|
||||
assert r.status_code == 400
|
||||
|
||||
|
||||
@mark.user
|
||||
@mark.gen_test
|
||||
def test_add_multi_user_invalid(app):
|
||||
app.authenticator.username_pattern = r'w.*'
|
||||
r = api_request(app, 'users', method='post',
|
||||
r = yield api_request(app, 'users', method='post',
|
||||
data=json.dumps({'usernames': ['Willow', 'Andrew', 'Tara']})
|
||||
)
|
||||
app.authenticator.username_pattern = ''
|
||||
@@ -268,10 +276,11 @@ def test_add_multi_user_invalid(app):
|
||||
|
||||
|
||||
@mark.user
|
||||
@mark.gen_test
|
||||
def test_add_multi_user(app):
|
||||
db = app.db
|
||||
names = ['a', 'b']
|
||||
r = api_request(app, 'users', method='post',
|
||||
r = yield api_request(app, 'users', method='post',
|
||||
data=json.dumps({'usernames': names}),
|
||||
)
|
||||
assert r.status_code == 201
|
||||
@@ -286,7 +295,7 @@ def test_add_multi_user(app):
|
||||
assert not user.admin
|
||||
|
||||
# try to create the same users again
|
||||
r = api_request(app, 'users', method='post',
|
||||
r = yield api_request(app, 'users', method='post',
|
||||
data=json.dumps({'usernames': names}),
|
||||
)
|
||||
assert r.status_code == 400
|
||||
@@ -294,7 +303,7 @@ def test_add_multi_user(app):
|
||||
names = ['a', 'b', 'ab']
|
||||
|
||||
# try to create the same users again
|
||||
r = api_request(app, 'users', method='post',
|
||||
r = yield api_request(app, 'users', method='post',
|
||||
data=json.dumps({'usernames': names}),
|
||||
)
|
||||
assert r.status_code == 201
|
||||
@@ -304,10 +313,11 @@ def test_add_multi_user(app):
|
||||
|
||||
|
||||
@mark.user
|
||||
@mark.gen_test
|
||||
def test_add_multi_user_admin(app):
|
||||
db = app.db
|
||||
names = ['c', 'd']
|
||||
r = api_request(app, 'users', method='post',
|
||||
r = yield api_request(app, 'users', method='post',
|
||||
data=json.dumps({'usernames': names, 'admin': True}),
|
||||
)
|
||||
assert r.status_code == 201
|
||||
@@ -323,20 +333,22 @@ def test_add_multi_user_admin(app):
|
||||
|
||||
|
||||
@mark.user
|
||||
@mark.gen_test
|
||||
def test_add_user_bad(app):
|
||||
db = app.db
|
||||
name = 'dne_newuser'
|
||||
r = api_request(app, 'users', name, method='post')
|
||||
r = yield api_request(app, 'users', name, method='post')
|
||||
assert r.status_code == 400
|
||||
user = find_user(db, name)
|
||||
assert user is None
|
||||
|
||||
|
||||
@mark.user
|
||||
@mark.gen_test
|
||||
def test_add_admin(app):
|
||||
db = app.db
|
||||
name = 'newadmin'
|
||||
r = api_request(app, 'users', name, method='post',
|
||||
r = yield api_request(app, 'users', name, method='post',
|
||||
data=json.dumps({'admin': True}),
|
||||
)
|
||||
assert r.status_code == 201
|
||||
@@ -347,25 +359,27 @@ def test_add_admin(app):
|
||||
|
||||
|
||||
@mark.user
|
||||
@mark.gen_test
|
||||
def test_delete_user(app):
|
||||
db = app.db
|
||||
mal = add_user(db, name='mal')
|
||||
r = api_request(app, 'users', 'mal', method='delete')
|
||||
r = yield api_request(app, 'users', 'mal', method='delete')
|
||||
assert r.status_code == 204
|
||||
|
||||
|
||||
@mark.user
|
||||
@mark.gen_test
|
||||
def test_make_admin(app):
|
||||
db = app.db
|
||||
name = 'admin2'
|
||||
r = api_request(app, 'users', name, method='post')
|
||||
r = yield api_request(app, 'users', name, method='post')
|
||||
assert r.status_code == 201
|
||||
user = find_user(db, name)
|
||||
assert user is not None
|
||||
assert user.name == name
|
||||
assert not user.admin
|
||||
|
||||
r = api_request(app, 'users', name, method='patch',
|
||||
r = yield api_request(app, 'users', name, method='patch',
|
||||
data=json.dumps({'admin': True})
|
||||
)
|
||||
assert r.status_code == 200
|
||||
@@ -375,23 +389,7 @@ def test_make_admin(app):
|
||||
assert user.admin
|
||||
|
||||
|
||||
def get_app_user(app, name):
|
||||
"""Helper to get the User object from the main thread.
|
||||
|
||||
Needed for access to the Spawner during testing.
|
||||
|
||||
No ORM methods should be called on the result.
|
||||
"""
|
||||
q = Queue()
|
||||
|
||||
def get_user_id():
|
||||
user = find_user(app.db, name)
|
||||
q.put(user.id)
|
||||
app.io_loop.add_callback(get_user_id)
|
||||
user_id = q.get(timeout=2)
|
||||
return app.users[user_id]
|
||||
|
||||
|
||||
@mark.gen_test
|
||||
def test_spawn(app, io_loop):
|
||||
db = app.db
|
||||
name = 'wash'
|
||||
@@ -401,41 +399,41 @@ def test_spawn(app, io_loop):
|
||||
'i': 5,
|
||||
}
|
||||
before_servers = sorted(db.query(orm.Server), key=lambda s: s.url)
|
||||
r = api_request(app, 'users', name, 'server', method='post',
|
||||
r = yield api_request(app, 'users', name, 'server', method='post',
|
||||
data=json.dumps(options),
|
||||
)
|
||||
assert r.status_code == 201
|
||||
assert 'pid' in user.orm_spawners[''].state
|
||||
app_user = get_app_user(app, name)
|
||||
app_user = app.users[name]
|
||||
assert app_user.spawner is not None
|
||||
spawner = app_user.spawner
|
||||
assert app_user.spawner.user_options == options
|
||||
assert not app_user.spawner._spawn_pending
|
||||
status = io_loop.run_sync(app_user.spawner.poll)
|
||||
status = yield app_user.spawner.poll()
|
||||
assert status is None
|
||||
|
||||
assert spawner.server.base_url == ujoin(app.base_url, 'user/%s' % name) + '/'
|
||||
url = public_url(app, user)
|
||||
r = requests.get(url)
|
||||
r = yield async_requests.get(url)
|
||||
assert r.status_code == 200
|
||||
assert r.text == spawner.server.base_url
|
||||
|
||||
r = requests.get(ujoin(url, 'args'))
|
||||
r = yield async_requests.get(ujoin(url, 'args'))
|
||||
assert r.status_code == 200
|
||||
argv = r.json()
|
||||
assert '--port' in ' '.join(argv)
|
||||
r = requests.get(ujoin(url, 'env'))
|
||||
r = yield async_requests.get(ujoin(url, 'env'))
|
||||
env = r.json()
|
||||
for expected in ['JUPYTERHUB_USER', 'JUPYTERHUB_BASE_URL', 'JUPYTERHUB_API_TOKEN']:
|
||||
assert expected in env
|
||||
if app.subdomain_host:
|
||||
assert env['JUPYTERHUB_HOST'] == app.subdomain_host
|
||||
|
||||
r = api_request(app, 'users', name, 'server', method='delete')
|
||||
r = yield api_request(app, 'users', name, 'server', method='delete')
|
||||
assert r.status_code == 204
|
||||
|
||||
assert 'pid' not in user.orm_spawners[''].state
|
||||
status = io_loop.run_sync(app_user.spawner.poll)
|
||||
status = yield app_user.spawner.poll()
|
||||
assert status == 0
|
||||
|
||||
# check that we cleaned up after ourselves
|
||||
@@ -446,6 +444,7 @@ def test_spawn(app, io_loop):
|
||||
assert tokens == []
|
||||
|
||||
|
||||
@mark.gen_test
|
||||
def test_slow_spawn(app, io_loop, no_patience, request):
|
||||
patch = mock.patch.dict(app.tornado_settings, {'spawner_class': mocking.SlowSpawner})
|
||||
patch.start()
|
||||
@@ -453,10 +452,10 @@ def test_slow_spawn(app, io_loop, no_patience, request):
|
||||
db = app.db
|
||||
name = 'zoe'
|
||||
user = add_user(db, app=app, name=name)
|
||||
r = api_request(app, 'users', name, 'server', method='post')
|
||||
r = yield api_request(app, 'users', name, 'server', method='post')
|
||||
r.raise_for_status()
|
||||
assert r.status_code == 202
|
||||
app_user = get_app_user(app, name)
|
||||
app_user = app.users[name]
|
||||
assert app_user.spawner is not None
|
||||
assert app_user.spawner._spawn_pending
|
||||
assert not app_user.spawner._stop_pending
|
||||
@@ -466,9 +465,9 @@ def test_slow_spawn(app, io_loop, no_patience, request):
|
||||
while app_user.spawner._spawn_pending:
|
||||
yield gen.sleep(0.1)
|
||||
|
||||
io_loop.run_sync(wait_spawn)
|
||||
yield wait_spawn()
|
||||
assert not app_user.spawner._spawn_pending
|
||||
status = io_loop.run_sync(app_user.spawner.poll)
|
||||
status = yield app_user.spawner.poll()
|
||||
assert status is None
|
||||
|
||||
@gen.coroutine
|
||||
@@ -476,25 +475,26 @@ def test_slow_spawn(app, io_loop, no_patience, request):
|
||||
while app_user.spawner._stop_pending:
|
||||
yield gen.sleep(0.1)
|
||||
|
||||
r = api_request(app, 'users', name, 'server', method='delete')
|
||||
r = yield api_request(app, 'users', name, 'server', method='delete')
|
||||
r.raise_for_status()
|
||||
assert r.status_code == 202
|
||||
assert app_user.spawner is not None
|
||||
assert app_user.spawner._stop_pending
|
||||
|
||||
r = api_request(app, 'users', name, 'server', method='delete')
|
||||
r = yield api_request(app, 'users', name, 'server', method='delete')
|
||||
r.raise_for_status()
|
||||
assert r.status_code == 202
|
||||
assert app_user.spawner is not None
|
||||
assert app_user.spawner._stop_pending
|
||||
|
||||
io_loop.run_sync(wait_stop)
|
||||
yield wait_stop()
|
||||
assert not app_user.spawner._stop_pending
|
||||
assert app_user.spawner is not None
|
||||
r = api_request(app, 'users', name, 'server', method='delete')
|
||||
r = yield api_request(app, 'users', name, 'server', method='delete')
|
||||
assert r.status_code == 400
|
||||
|
||||
|
||||
@mark.gen_test
|
||||
def test_never_spawn(app, io_loop, no_patience, request):
|
||||
patch = mock.patch.dict(app.tornado_settings, {'spawner_class': mocking.NeverSpawner})
|
||||
patch.start()
|
||||
@@ -503,8 +503,8 @@ def test_never_spawn(app, io_loop, no_patience, request):
|
||||
db = app.db
|
||||
name = 'badger'
|
||||
user = add_user(db, app=app, name=name)
|
||||
r = api_request(app, 'users', name, 'server', method='post')
|
||||
app_user = get_app_user(app, name)
|
||||
r = yield api_request(app, 'users', name, 'server', method='post')
|
||||
app_user = app.users[name]
|
||||
assert app_user.spawner is not None
|
||||
assert app_user.spawner._spawn_pending
|
||||
|
||||
@@ -513,38 +513,40 @@ def test_never_spawn(app, io_loop, no_patience, request):
|
||||
while app_user.spawner._spawn_pending:
|
||||
yield gen.sleep(0.1)
|
||||
|
||||
io_loop.run_sync(wait_pending)
|
||||
yield wait_pending()
|
||||
assert not app_user.spawner._spawn_pending
|
||||
status = io_loop.run_sync(app_user.spawner.poll)
|
||||
status = yield app_user.spawner.poll()
|
||||
assert status is not None
|
||||
|
||||
|
||||
@mark.gen_test
|
||||
def test_get_proxy(app, io_loop):
|
||||
r = api_request(app, 'proxy')
|
||||
r = yield api_request(app, 'proxy')
|
||||
r.raise_for_status()
|
||||
reply = r.json()
|
||||
assert list(reply.keys()) == ['/']
|
||||
|
||||
|
||||
@mark.gen_test
|
||||
def test_cookie(app):
|
||||
db = app.db
|
||||
name = 'patience'
|
||||
user = add_user(db, app=app, name=name)
|
||||
r = api_request(app, 'users', name, 'server', method='post')
|
||||
r = yield api_request(app, 'users', name, 'server', method='post')
|
||||
assert r.status_code == 201
|
||||
assert 'pid' in user.orm_spawners[''].state
|
||||
app_user = get_app_user(app, name)
|
||||
app_user = app.users[name]
|
||||
|
||||
cookies = app.login_user(name)
|
||||
cookies = yield app.login_user(name)
|
||||
cookie_name = app.hub.cookie_name
|
||||
# cookie jar gives '"cookie-value"', we want 'cookie-value'
|
||||
cookie = cookies[cookie_name][1:-1]
|
||||
r = api_request(app, 'authorizations/cookie',
|
||||
r = yield api_request(app, 'authorizations/cookie',
|
||||
cookie_name, "nothintoseehere",
|
||||
)
|
||||
assert r.status_code == 404
|
||||
|
||||
r = api_request(app, 'authorizations/cookie',
|
||||
r = yield api_request(app, 'authorizations/cookie',
|
||||
cookie_name, quote(cookie, safe=''),
|
||||
)
|
||||
r.raise_for_status()
|
||||
@@ -552,7 +554,7 @@ def test_cookie(app):
|
||||
assert reply['name'] == name
|
||||
|
||||
# deprecated cookie in body:
|
||||
r = api_request(app, 'authorizations/cookie',
|
||||
r = yield api_request(app, 'authorizations/cookie',
|
||||
cookie_name, data=cookie,
|
||||
)
|
||||
r.raise_for_status()
|
||||
@@ -560,18 +562,20 @@ def test_cookie(app):
|
||||
assert reply['name'] == name
|
||||
|
||||
|
||||
@mark.gen_test
|
||||
def test_token(app):
|
||||
name = 'book'
|
||||
user = add_user(app.db, app=app, name=name)
|
||||
token = user.new_api_token()
|
||||
r = api_request(app, 'authorizations/token', token)
|
||||
r = yield api_request(app, 'authorizations/token', token)
|
||||
r.raise_for_status()
|
||||
user_model = r.json()
|
||||
assert user_model['name'] == name
|
||||
r = api_request(app, 'authorizations/token', 'notauthorized')
|
||||
r = yield api_request(app, 'authorizations/token', 'notauthorized')
|
||||
assert r.status_code == 404
|
||||
|
||||
|
||||
@mark.gen_test
|
||||
@mark.parametrize("headers, data, status", [
|
||||
({}, None, 200),
|
||||
({'Authorization': ''}, None, 403),
|
||||
@@ -581,13 +585,13 @@ def test_get_new_token(app, headers, data, status):
|
||||
if data:
|
||||
data = json.dumps(data)
|
||||
# request a new token
|
||||
r = api_request(app, 'authorizations', 'token', method='post', data=data, headers=headers)
|
||||
r = yield api_request(app, 'authorizations', 'token', method='post', data=data, headers=headers)
|
||||
assert r.status_code == status
|
||||
if status != 200:
|
||||
return
|
||||
reply = r.json()
|
||||
assert 'token' in reply
|
||||
r = api_request(app, 'authorizations', 'token', reply['token'])
|
||||
r = yield api_request(app, 'authorizations', 'token', reply['token'])
|
||||
r.raise_for_status()
|
||||
assert 'name' in r.json()
|
||||
|
||||
@@ -598,8 +602,9 @@ def test_get_new_token(app, headers, data, status):
|
||||
|
||||
|
||||
@mark.group
|
||||
@mark.gen_test
|
||||
def test_groups_list(app):
|
||||
r = api_request(app, 'groups')
|
||||
r = yield api_request(app, 'groups')
|
||||
r.raise_for_status()
|
||||
reply = r.json()
|
||||
assert reply == []
|
||||
@@ -609,7 +614,7 @@ def test_groups_list(app):
|
||||
app.db.add(group)
|
||||
app.db.commit()
|
||||
|
||||
r = api_request(app, 'groups')
|
||||
r = yield api_request(app, 'groups')
|
||||
r.raise_for_status()
|
||||
reply = r.json()
|
||||
assert reply == [{
|
||||
@@ -620,16 +625,17 @@ def test_groups_list(app):
|
||||
|
||||
|
||||
@mark.group
|
||||
@mark.gen_test
|
||||
def test_group_get(app):
|
||||
group = orm.Group.find(app.db, name='alphaflight')
|
||||
user = add_user(app.db, app=app, name='sasquatch')
|
||||
group.users.append(user)
|
||||
app.db.commit()
|
||||
|
||||
r = api_request(app, 'groups/runaways')
|
||||
r = yield api_request(app, 'groups/runaways')
|
||||
assert r.status_code == 404
|
||||
|
||||
r = api_request(app, 'groups/alphaflight')
|
||||
r = yield api_request(app, 'groups/alphaflight')
|
||||
r.raise_for_status()
|
||||
reply = r.json()
|
||||
assert reply == {
|
||||
@@ -640,18 +646,19 @@ def test_group_get(app):
|
||||
|
||||
|
||||
@mark.group
|
||||
@mark.gen_test
|
||||
def test_group_create_delete(app):
|
||||
db = app.db
|
||||
r = api_request(app, 'groups/runaways', method='delete')
|
||||
r = yield api_request(app, 'groups/runaways', method='delete')
|
||||
assert r.status_code == 404
|
||||
|
||||
r = api_request(app, 'groups/new', method='post',
|
||||
r = yield api_request(app, 'groups/new', method='post',
|
||||
data=json.dumps({'users': ['doesntexist']}),
|
||||
)
|
||||
assert r.status_code == 400
|
||||
assert orm.Group.find(db, name='new') is None
|
||||
|
||||
r = api_request(app, 'groups/omegaflight', method='post',
|
||||
r = yield api_request(app, 'groups/omegaflight', method='post',
|
||||
data=json.dumps({'users': ['sasquatch']}),
|
||||
)
|
||||
r.raise_for_status()
|
||||
@@ -662,29 +669,30 @@ def test_group_create_delete(app):
|
||||
assert sasquatch in omegaflight.users
|
||||
|
||||
# create duplicate raises 400
|
||||
r = api_request(app, 'groups/omegaflight', method='post')
|
||||
r = yield api_request(app, 'groups/omegaflight', method='post')
|
||||
assert r.status_code == 400
|
||||
|
||||
r = api_request(app, 'groups/omegaflight', method='delete')
|
||||
r = yield api_request(app, 'groups/omegaflight', method='delete')
|
||||
assert r.status_code == 204
|
||||
assert omegaflight not in sasquatch.groups
|
||||
assert orm.Group.find(db, name='omegaflight') is None
|
||||
|
||||
# delete nonexistent gives 404
|
||||
r = api_request(app, 'groups/omegaflight', method='delete')
|
||||
r = yield api_request(app, 'groups/omegaflight', method='delete')
|
||||
assert r.status_code == 404
|
||||
|
||||
|
||||
@mark.group
|
||||
@mark.gen_test
|
||||
def test_group_add_users(app):
|
||||
db = app.db
|
||||
# must specify users
|
||||
r = api_request(app, 'groups/alphaflight/users', method='post', data='{}')
|
||||
r = yield api_request(app, 'groups/alphaflight/users', method='post', data='{}')
|
||||
assert r.status_code == 400
|
||||
|
||||
names = ['aurora', 'guardian', 'northstar', 'sasquatch', 'shaman', 'snowbird']
|
||||
users = [ find_user(db, name=name) or add_user(db, app=app, name=name) for name in names ]
|
||||
r = api_request(app, 'groups/alphaflight/users', method='post', data=json.dumps({
|
||||
r = yield api_request(app, 'groups/alphaflight/users', method='post', data=json.dumps({
|
||||
'users': names,
|
||||
}))
|
||||
r.raise_for_status()
|
||||
@@ -698,15 +706,16 @@ def test_group_add_users(app):
|
||||
|
||||
|
||||
@mark.group
|
||||
@mark.gen_test
|
||||
def test_group_delete_users(app):
|
||||
db = app.db
|
||||
# must specify users
|
||||
r = api_request(app, 'groups/alphaflight/users', method='delete', data='{}')
|
||||
r = yield api_request(app, 'groups/alphaflight/users', method='delete', data='{}')
|
||||
assert r.status_code == 400
|
||||
|
||||
names = ['aurora', 'guardian', 'northstar', 'sasquatch', 'shaman', 'snowbird']
|
||||
users = [ find_user(db, name=name) for name in names ]
|
||||
r = api_request(app, 'groups/alphaflight/users', method='delete', data=json.dumps({
|
||||
r = yield api_request(app, 'groups/alphaflight/users', method='delete', data=json.dumps({
|
||||
'users': names[:2],
|
||||
}))
|
||||
r.raise_for_status()
|
||||
@@ -726,10 +735,11 @@ def test_group_delete_users(app):
|
||||
|
||||
|
||||
@mark.services
|
||||
@mark.gen_test
|
||||
def test_get_services(app, mockservice_url):
|
||||
mockservice = mockservice_url
|
||||
db = app.db
|
||||
r = api_request(app, 'services')
|
||||
r = yield api_request(app, 'services')
|
||||
r.raise_for_status()
|
||||
assert r.status_code == 200
|
||||
|
||||
@@ -745,17 +755,18 @@ def test_get_services(app, mockservice_url):
|
||||
}
|
||||
}
|
||||
|
||||
r = api_request(app, 'services',
|
||||
r = yield api_request(app, 'services',
|
||||
headers=auth_header(db, 'user'),
|
||||
)
|
||||
assert r.status_code == 403
|
||||
|
||||
|
||||
@mark.services
|
||||
@mark.gen_test
|
||||
def test_get_service(app, mockservice_url):
|
||||
mockservice = mockservice_url
|
||||
db = app.db
|
||||
r = api_request(app, 'services/%s' % mockservice.name)
|
||||
r = yield api_request(app, 'services/%s' % mockservice.name)
|
||||
r.raise_for_status()
|
||||
assert r.status_code == 200
|
||||
|
||||
@@ -769,22 +780,23 @@ def test_get_service(app, mockservice_url):
|
||||
'url': mockservice.url,
|
||||
}
|
||||
|
||||
r = api_request(app, 'services/%s' % mockservice.name,
|
||||
r = yield api_request(app, 'services/%s' % mockservice.name,
|
||||
headers={
|
||||
'Authorization': 'token %s' % mockservice.api_token
|
||||
}
|
||||
)
|
||||
r.raise_for_status()
|
||||
r = api_request(app, 'services/%s' % mockservice.name,
|
||||
r = yield api_request(app, 'services/%s' % mockservice.name,
|
||||
headers=auth_header(db, 'user'),
|
||||
)
|
||||
assert r.status_code == 403
|
||||
|
||||
|
||||
@mark.gen_test
|
||||
def test_root_api(app):
|
||||
base_url = app.hub.url
|
||||
url = ujoin(base_url, 'api')
|
||||
r = requests.get(url)
|
||||
r = yield async_requests.get(url)
|
||||
r.raise_for_status()
|
||||
expected = {
|
||||
'version': jupyterhub.__version__
|
||||
@@ -792,8 +804,9 @@ def test_root_api(app):
|
||||
assert r.json() == expected
|
||||
|
||||
|
||||
@mark.gen_test
|
||||
def test_info(app):
|
||||
r = api_request(app, 'info')
|
||||
r = yield api_request(app, 'info')
|
||||
r.raise_for_status()
|
||||
data = r.json()
|
||||
assert data['version'] == jupyterhub.__version__
|
||||
@@ -821,14 +834,16 @@ def test_info(app):
|
||||
# -----------------
|
||||
|
||||
|
||||
@mark.gen_test
|
||||
def test_options(app):
|
||||
r = api_request(app, 'users', method='options')
|
||||
r = yield api_request(app, 'users', method='options')
|
||||
r.raise_for_status()
|
||||
assert 'Access-Control-Allow-Headers' in r.headers
|
||||
|
||||
|
||||
@mark.gen_test
|
||||
def test_bad_json_body(app):
|
||||
r = api_request(app, 'users', method='post', data='notjson')
|
||||
r = yield api_request(app, 'users', method='post', data='notjson')
|
||||
assert r.status_code == 400
|
||||
|
||||
|
||||
@@ -838,14 +853,24 @@ def test_bad_json_body(app):
|
||||
|
||||
|
||||
def test_shutdown(app):
|
||||
r = api_request(app, 'shutdown', method='post',
|
||||
data=json.dumps({'servers': True, 'proxy': True,}),
|
||||
)
|
||||
loop = app.io_loop
|
||||
|
||||
# have to do things a little funky since we are going to stop the loop,
|
||||
# which makes gen_test unhappy. So we run the loop ourselves.
|
||||
|
||||
@gen.coroutine
|
||||
def shutdown():
|
||||
r = yield api_request(app, 'shutdown', method='post',
|
||||
data=json.dumps({'servers': True, 'proxy': True,}),
|
||||
)
|
||||
return r
|
||||
|
||||
real_stop = loop.stop
|
||||
def stop():
|
||||
stop.called = True
|
||||
loop.call_later(1, real_stop)
|
||||
with mock.patch.object(loop, 'stop', stop):
|
||||
r = loop.run_sync(shutdown, timeout=5)
|
||||
r.raise_for_status()
|
||||
reply = r.json()
|
||||
for i in range(100):
|
||||
if app.io_loop._running:
|
||||
time.sleep(0.1)
|
||||
else:
|
||||
break
|
||||
assert not app.io_loop._running
|
||||
assert stop.called
|
||||
|
18
jupyterhub/tests/utils.py
Normal file
18
jupyterhub/tests/utils.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
import requests
|
||||
|
||||
class _AsyncRequests:
|
||||
"""Wrapper around requests to return a Future from request methods
|
||||
|
||||
A single thread is allocated to avoid blocking the IOLoop thread.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.executor = ThreadPoolExecutor(1)
|
||||
|
||||
def __getattr__(self, name):
|
||||
requests_method = getattr(requests, name)
|
||||
return lambda *args, **kwargs: self.executor.submit(requests_method, *args, **kwargs)
|
||||
|
||||
# async_requests.get = requests.get returning a Future, etc.
|
||||
async_requests = _AsyncRequests()
|
||||
|
Reference in New Issue
Block a user