mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-18 15:33:02 +00:00
Merge pull request #1281 from minrk/no-test-threads
eliminate test application thread
This commit is contained in:
@@ -5,6 +5,9 @@ python:
|
|||||||
- 3.6
|
- 3.6
|
||||||
- 3.5
|
- 3.5
|
||||||
- 3.4
|
- 3.4
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
- ASYNC_TEST_TIMEOUT=15
|
||||||
|
|
||||||
# installing dependencies
|
# installing dependencies
|
||||||
before_install:
|
before_install:
|
||||||
|
@@ -16,7 +16,6 @@ import shutil
|
|||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
import threading
|
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
if sys.version_info[:2] < (3, 3):
|
if sys.version_info[:2] < (3, 3):
|
||||||
@@ -436,7 +435,6 @@ class JupyterHub(Application):
|
|||||||
@observe('base_url')
|
@observe('base_url')
|
||||||
def _update_hub_prefix(self, change):
|
def _update_hub_prefix(self, change):
|
||||||
"""add base URL to hub prefix"""
|
"""add base URL to hub prefix"""
|
||||||
base_url = change['new']
|
|
||||||
self.hub_prefix = self._hub_prefix_default()
|
self.hub_prefix = self._hub_prefix_default()
|
||||||
|
|
||||||
cookie_secret = Bytes(
|
cookie_secret = Bytes(
|
||||||
@@ -826,15 +824,6 @@ class JupyterHub(Application):
|
|||||||
# store the loaded trait value
|
# store the loaded trait value
|
||||||
self.cookie_secret = secret
|
self.cookie_secret = secret
|
||||||
|
|
||||||
# thread-local storage of db objects
|
|
||||||
_local = Instance(threading.local, ())
|
|
||||||
|
|
||||||
@property
|
|
||||||
def db(self):
|
|
||||||
if not hasattr(self._local, 'db'):
|
|
||||||
self._local.db = scoped_session(self.session_factory)()
|
|
||||||
return self._local.db
|
|
||||||
|
|
||||||
def init_db(self):
|
def init_db(self):
|
||||||
"""Create the database connection"""
|
"""Create the database connection"""
|
||||||
self.log.debug("Connecting to db: %s", self.db_url)
|
self.log.debug("Connecting to db: %s", self.db_url)
|
||||||
@@ -846,7 +835,7 @@ class JupyterHub(Application):
|
|||||||
**self.db_kwargs
|
**self.db_kwargs
|
||||||
)
|
)
|
||||||
# trigger constructing thread local db property
|
# trigger constructing thread local db property
|
||||||
_ = self.db
|
self.db = scoped_session(self.session_factory)()
|
||||||
except OperationalError as e:
|
except OperationalError as e:
|
||||||
self.log.error("Failed to connect to db: %s", self.db_url)
|
self.log.error("Failed to connect to db: %s", self.db_url)
|
||||||
self.log.debug("Database error was:", exc_info=True)
|
self.log.debug("Database error was:", exc_info=True)
|
||||||
|
@@ -36,25 +36,34 @@ def db():
|
|||||||
return _db
|
return _db
|
||||||
|
|
||||||
|
|
||||||
@fixture
|
@fixture(scope='module')
|
||||||
def io_loop():
|
def io_loop(request):
|
||||||
"""Get the current IOLoop"""
|
"""Same as pytest-tornado.io_loop, but re-scoped to module-level"""
|
||||||
loop = ioloop.IOLoop()
|
io_loop = ioloop.IOLoop()
|
||||||
loop.make_current()
|
io_loop.make_current()
|
||||||
return loop
|
|
||||||
|
|
||||||
|
def _close():
|
||||||
|
io_loop.clear_current()
|
||||||
|
if (not ioloop.IOLoop.initialized() or
|
||||||
|
io_loop is not ioloop.IOLoop.instance()):
|
||||||
|
io_loop.close(all_fds=True)
|
||||||
|
|
||||||
|
request.addfinalizer(_close)
|
||||||
|
return io_loop
|
||||||
|
|
||||||
@fixture(scope='module')
|
@fixture(scope='module')
|
||||||
def app(request):
|
def app(request, io_loop):
|
||||||
"""Mock a jupyterhub app for testing"""
|
"""Mock a jupyterhub app for testing"""
|
||||||
mocked_app = MockHub.instance(log_level=logging.DEBUG)
|
mocked_app = MockHub.instance(log_level=logging.DEBUG)
|
||||||
mocked_app.start([])
|
@gen.coroutine
|
||||||
|
def make_app():
|
||||||
|
yield mocked_app.initialize([])
|
||||||
|
yield mocked_app.start()
|
||||||
|
io_loop.run_sync(make_app)
|
||||||
|
|
||||||
def fin():
|
def fin():
|
||||||
# disconnect logging during cleanup because pytest closes captured FDs prematurely
|
# disconnect logging during cleanup because pytest closes captured FDs prematurely
|
||||||
mocked_app.log.handlers = []
|
mocked_app.log.handlers = []
|
||||||
|
|
||||||
MockHub.clear_instance()
|
MockHub.clear_instance()
|
||||||
mocked_app.stop()
|
mocked_app.stop()
|
||||||
request.addfinalizer(fin)
|
request.addfinalizer(fin)
|
||||||
@@ -80,6 +89,8 @@ def _mockservice(request, app, url=False):
|
|||||||
if url:
|
if url:
|
||||||
spec['url'] = 'http://127.0.0.1:%i' % random_port()
|
spec['url'] = 'http://127.0.0.1:%i' % random_port()
|
||||||
|
|
||||||
|
io_loop = app.io_loop
|
||||||
|
|
||||||
with mock.patch.object(jupyterhub.services.service, '_ServiceSpawner', MockServiceSpawner):
|
with mock.patch.object(jupyterhub.services.service, '_ServiceSpawner', MockServiceSpawner):
|
||||||
app.services = [spec]
|
app.services = [spec]
|
||||||
app.init_services()
|
app.init_services()
|
||||||
@@ -90,20 +101,17 @@ def _mockservice(request, app, url=False):
|
|||||||
# wait for proxy to be updated before starting the service
|
# wait for proxy to be updated before starting the service
|
||||||
yield app.proxy.add_all_services(app._service_map)
|
yield app.proxy.add_all_services(app._service_map)
|
||||||
service.start()
|
service.start()
|
||||||
app.io_loop.add_callback(start)
|
io_loop.run_sync(start)
|
||||||
def cleanup():
|
def cleanup():
|
||||||
service.stop()
|
service.stop()
|
||||||
app.services[:] = []
|
app.services[:] = []
|
||||||
app._service_map.clear()
|
app._service_map.clear()
|
||||||
request.addfinalizer(cleanup)
|
request.addfinalizer(cleanup)
|
||||||
for i in range(20):
|
|
||||||
if not getattr(service, 'proc', False):
|
|
||||||
time.sleep(0.2)
|
|
||||||
# ensure process finishes starting
|
# ensure process finishes starting
|
||||||
with raises(TimeoutExpired):
|
with raises(TimeoutExpired):
|
||||||
service.proc.wait(1)
|
service.proc.wait(1)
|
||||||
if url:
|
if url:
|
||||||
ioloop.IOLoop().run_sync(service.server.wait_up)
|
io_loop.run_sync(service.server.wait_up)
|
||||||
return service
|
return service
|
||||||
|
|
||||||
|
|
||||||
|
@@ -22,6 +22,7 @@ from ..objects import Server
|
|||||||
from ..spawner import LocalProcessSpawner
|
from ..spawner import LocalProcessSpawner
|
||||||
from ..singleuser import SingleUserNotebookApp
|
from ..singleuser import SingleUserNotebookApp
|
||||||
from ..utils import random_port, url_path_join
|
from ..utils import random_port, url_path_join
|
||||||
|
from .utils import async_requests
|
||||||
|
|
||||||
from pamela import PAMError
|
from pamela import PAMError
|
||||||
|
|
||||||
@@ -159,50 +160,30 @@ class MockHub(JupyterHub):
|
|||||||
|
|
||||||
def load_config_file(self, *args, **kwargs):
|
def load_config_file(self, *args, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def start(self, argv=None):
|
@gen.coroutine
|
||||||
|
def initialize(self, argv=None):
|
||||||
self.db_file = NamedTemporaryFile()
|
self.db_file = NamedTemporaryFile()
|
||||||
self.pid_file = NamedTemporaryFile(delete=False).name
|
self.pid_file = NamedTemporaryFile(delete=False).name
|
||||||
self.db_url = self.db_file.name
|
self.db_url = self.db_file.name
|
||||||
|
yield super().initialize([])
|
||||||
|
|
||||||
evt = threading.Event()
|
user = orm.User(name='user')
|
||||||
|
self.db.add(user)
|
||||||
@gen.coroutine
|
self.db.commit()
|
||||||
def _start_co():
|
|
||||||
assert self.io_loop._running
|
|
||||||
# put initialize in start for SQLAlchemy threading reasons
|
|
||||||
yield super(MockHub, self).initialize(argv=argv)
|
|
||||||
# add an initial user
|
|
||||||
user = orm.User(name='user')
|
|
||||||
self.db.add(user)
|
|
||||||
self.db.commit()
|
|
||||||
yield super(MockHub, self).start()
|
|
||||||
yield self.hub.wait_up(http=True)
|
|
||||||
self.io_loop.add_callback(evt.set)
|
|
||||||
|
|
||||||
def _start():
|
|
||||||
self.io_loop = IOLoop()
|
|
||||||
self.io_loop.make_current()
|
|
||||||
self.io_loop.add_callback(_start_co)
|
|
||||||
self.io_loop.start()
|
|
||||||
|
|
||||||
self._thread = threading.Thread(target=_start)
|
|
||||||
self._thread.start()
|
|
||||||
ready = evt.wait(timeout=10)
|
|
||||||
assert ready
|
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
super().stop()
|
super().stop()
|
||||||
self._thread.join()
|
|
||||||
IOLoop().run_sync(self.cleanup)
|
IOLoop().run_sync(self.cleanup)
|
||||||
# ignore the call that will fire in atexit
|
# ignore the call that will fire in atexit
|
||||||
self.cleanup = lambda : None
|
self.cleanup = lambda : None
|
||||||
self.db_file.close()
|
self.db_file.close()
|
||||||
|
|
||||||
|
@gen.coroutine
|
||||||
def login_user(self, name):
|
def login_user(self, name):
|
||||||
"""Login a user by name, returning her cookies."""
|
"""Login a user by name, returning her cookies."""
|
||||||
base_url = public_url(self)
|
base_url = public_url(self)
|
||||||
r = requests.post(base_url + 'hub/login',
|
r = yield async_requests.post(base_url + 'hub/login',
|
||||||
data={
|
data={
|
||||||
'username': name,
|
'username': name,
|
||||||
'password': name,
|
'password': name,
|
||||||
|
@@ -2,12 +2,11 @@
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
from queue import Queue
|
|
||||||
import sys
|
import sys
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
from urllib.parse import urlparse, quote
|
from urllib.parse import urlparse, quote
|
||||||
|
|
||||||
from pytest import mark, yield_fixture
|
from pytest import mark
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from tornado import gen
|
from tornado import gen
|
||||||
@@ -18,6 +17,7 @@ from ..user import User
|
|||||||
from ..utils import url_path_join as ujoin
|
from ..utils import url_path_join as ujoin
|
||||||
from . import mocking
|
from . import mocking
|
||||||
from .mocking import public_host, public_url
|
from .mocking import public_host, public_url
|
||||||
|
from .utils import async_requests
|
||||||
|
|
||||||
|
|
||||||
def check_db_locks(func):
|
def check_db_locks(func):
|
||||||
@@ -81,6 +81,7 @@ def auth_header(db, name):
|
|||||||
|
|
||||||
|
|
||||||
@check_db_locks
|
@check_db_locks
|
||||||
|
@gen.coroutine
|
||||||
def api_request(app, *api_path, **kwargs):
|
def api_request(app, *api_path, **kwargs):
|
||||||
"""Make an API request"""
|
"""Make an API request"""
|
||||||
base_url = app.hub.url
|
base_url = app.hub.url
|
||||||
@@ -91,8 +92,8 @@ def api_request(app, *api_path, **kwargs):
|
|||||||
|
|
||||||
url = ujoin(base_url, 'api', *api_path)
|
url = ujoin(base_url, 'api', *api_path)
|
||||||
method = kwargs.pop('method', 'get')
|
method = kwargs.pop('method', 'get')
|
||||||
f = getattr(requests, method)
|
f = getattr(async_requests, method)
|
||||||
resp = f(url, **kwargs)
|
resp = yield f(url, **kwargs)
|
||||||
assert "frame-ancestors 'self'" in resp.headers['Content-Security-Policy']
|
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 ujoin(app.hub.base_url, "security/csp-report") in resp.headers['Content-Security-Policy']
|
||||||
assert 'http' not 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):
|
def test_auth_api(app):
|
||||||
db = app.db
|
db = app.db
|
||||||
r = api_request(app, 'authorizations', 'gobbledygook')
|
r = yield api_request(app, 'authorizations', 'gobbledygook')
|
||||||
assert r.status_code == 404
|
assert r.status_code == 404
|
||||||
|
|
||||||
# make a new cookie token
|
# make a new cookie token
|
||||||
@@ -114,36 +116,37 @@ def test_auth_api(app):
|
|||||||
api_token = user.new_api_token()
|
api_token = user.new_api_token()
|
||||||
|
|
||||||
# check success:
|
# check success:
|
||||||
r = api_request(app, 'authorizations/token', api_token)
|
r = yield api_request(app, 'authorizations/token', api_token)
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
reply = r.json()
|
reply = r.json()
|
||||||
assert reply['name'] == user.name
|
assert reply['name'] == user.name
|
||||||
|
|
||||||
# check fail
|
# check fail
|
||||||
r = api_request(app, 'authorizations/token', api_token,
|
r = yield api_request(app, 'authorizations/token', api_token,
|
||||||
headers={'Authorization': 'no sir'},
|
headers={'Authorization': 'no sir'},
|
||||||
)
|
)
|
||||||
assert r.status_code == 403
|
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},
|
headers={'Authorization': 'token: %s' % user.cookie_id},
|
||||||
)
|
)
|
||||||
assert r.status_code == 403
|
assert r.status_code == 403
|
||||||
|
|
||||||
|
|
||||||
def test_referer_check(app, io_loop):
|
@mark.gen_test
|
||||||
|
def test_referer_check(app):
|
||||||
url = ujoin(public_host(app), app.hub.base_url)
|
url = ujoin(public_host(app), app.hub.base_url)
|
||||||
host = urlparse(url).netloc
|
host = urlparse(url).netloc
|
||||||
user = find_user(app.db, 'admin')
|
user = find_user(app.db, 'admin')
|
||||||
if user is None:
|
if user is None:
|
||||||
user = add_user(app.db, name='admin', admin=True)
|
user = add_user(app.db, name='admin', admin=True)
|
||||||
cookies = app.login_user('admin')
|
cookies = yield app.login_user('admin')
|
||||||
app_user = get_app_user(app, 'admin')
|
app_user = app.users[user]
|
||||||
# stop the admin's server so we don't mess up future tests
|
# stop the admin's server so we don't mess up future tests
|
||||||
io_loop.run_sync(lambda: app.proxy.delete_user(app_user))
|
yield app.proxy.delete_user(app_user)
|
||||||
io_loop.run_sync(app_user.stop)
|
yield app_user.stop()
|
||||||
|
|
||||||
r = api_request(app, 'users',
|
r = yield api_request(app, 'users',
|
||||||
headers={
|
headers={
|
||||||
'Authorization': '',
|
'Authorization': '',
|
||||||
'Referer': 'null',
|
'Referer': 'null',
|
||||||
@@ -151,7 +154,7 @@ def test_referer_check(app, io_loop):
|
|||||||
)
|
)
|
||||||
assert r.status_code == 403
|
assert r.status_code == 403
|
||||||
|
|
||||||
r = api_request(app, 'users',
|
r = yield api_request(app, 'users',
|
||||||
headers={
|
headers={
|
||||||
'Authorization': '',
|
'Authorization': '',
|
||||||
'Referer': 'http://attack.com/csrf/vulnerability',
|
'Referer': 'http://attack.com/csrf/vulnerability',
|
||||||
@@ -159,7 +162,7 @@ def test_referer_check(app, io_loop):
|
|||||||
)
|
)
|
||||||
assert r.status_code == 403
|
assert r.status_code == 403
|
||||||
|
|
||||||
r = api_request(app, 'users',
|
r = yield api_request(app, 'users',
|
||||||
headers={
|
headers={
|
||||||
'Authorization': '',
|
'Authorization': '',
|
||||||
'Referer': url,
|
'Referer': url,
|
||||||
@@ -168,7 +171,7 @@ def test_referer_check(app, io_loop):
|
|||||||
)
|
)
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
|
|
||||||
r = api_request(app, 'users',
|
r = yield api_request(app, 'users',
|
||||||
headers={
|
headers={
|
||||||
'Authorization': '',
|
'Authorization': '',
|
||||||
'Referer': ujoin(url, 'foo/bar/baz/bat'),
|
'Referer': ujoin(url, 'foo/bar/baz/bat'),
|
||||||
@@ -184,9 +187,10 @@ def test_referer_check(app, io_loop):
|
|||||||
|
|
||||||
|
|
||||||
@mark.user
|
@mark.user
|
||||||
|
@mark.gen_test
|
||||||
def test_get_users(app):
|
def test_get_users(app):
|
||||||
db = app.db
|
db = app.db
|
||||||
r = api_request(app, 'users')
|
r = yield api_request(app, 'users')
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
|
|
||||||
users = sorted(r.json(), key=lambda d: d['name'])
|
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'),
|
headers=auth_header(db, 'user'),
|
||||||
)
|
)
|
||||||
assert r.status_code == 403
|
assert r.status_code == 403
|
||||||
|
|
||||||
|
|
||||||
@mark.user
|
@mark.user
|
||||||
|
@mark.gen_test
|
||||||
def test_add_user(app):
|
def test_add_user(app):
|
||||||
db = app.db
|
db = app.db
|
||||||
name = 'newuser'
|
name = 'newuser'
|
||||||
r = api_request(app, 'users', name, method='post')
|
r = yield api_request(app, 'users', name, method='post')
|
||||||
assert r.status_code == 201
|
assert r.status_code == 201
|
||||||
user = find_user(db, name)
|
user = find_user(db, name)
|
||||||
assert user is not None
|
assert user is not None
|
||||||
@@ -230,9 +235,10 @@ def test_add_user(app):
|
|||||||
|
|
||||||
|
|
||||||
@mark.user
|
@mark.user
|
||||||
|
@mark.gen_test
|
||||||
def test_get_user(app):
|
def test_get_user(app):
|
||||||
name = 'user'
|
name = 'user'
|
||||||
r = api_request(app, 'users', name)
|
r = yield api_request(app, 'users', name)
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
user = r.json()
|
user = r.json()
|
||||||
user.pop('last_activity')
|
user.pop('last_activity')
|
||||||
@@ -247,19 +253,21 @@ def test_get_user(app):
|
|||||||
|
|
||||||
|
|
||||||
@mark.user
|
@mark.user
|
||||||
|
@mark.gen_test
|
||||||
def test_add_multi_user_bad(app):
|
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
|
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
|
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
|
assert r.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
@mark.user
|
@mark.user
|
||||||
|
@mark.gen_test
|
||||||
def test_add_multi_user_invalid(app):
|
def test_add_multi_user_invalid(app):
|
||||||
app.authenticator.username_pattern = r'w.*'
|
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']})
|
data=json.dumps({'usernames': ['Willow', 'Andrew', 'Tara']})
|
||||||
)
|
)
|
||||||
app.authenticator.username_pattern = ''
|
app.authenticator.username_pattern = ''
|
||||||
@@ -268,10 +276,11 @@ def test_add_multi_user_invalid(app):
|
|||||||
|
|
||||||
|
|
||||||
@mark.user
|
@mark.user
|
||||||
|
@mark.gen_test
|
||||||
def test_add_multi_user(app):
|
def test_add_multi_user(app):
|
||||||
db = app.db
|
db = app.db
|
||||||
names = ['a', 'b']
|
names = ['a', 'b']
|
||||||
r = api_request(app, 'users', method='post',
|
r = yield api_request(app, 'users', method='post',
|
||||||
data=json.dumps({'usernames': names}),
|
data=json.dumps({'usernames': names}),
|
||||||
)
|
)
|
||||||
assert r.status_code == 201
|
assert r.status_code == 201
|
||||||
@@ -286,7 +295,7 @@ def test_add_multi_user(app):
|
|||||||
assert not user.admin
|
assert not user.admin
|
||||||
|
|
||||||
# try to create the same users again
|
# 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}),
|
data=json.dumps({'usernames': names}),
|
||||||
)
|
)
|
||||||
assert r.status_code == 400
|
assert r.status_code == 400
|
||||||
@@ -294,7 +303,7 @@ def test_add_multi_user(app):
|
|||||||
names = ['a', 'b', 'ab']
|
names = ['a', 'b', 'ab']
|
||||||
|
|
||||||
# try to create the same users again
|
# 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}),
|
data=json.dumps({'usernames': names}),
|
||||||
)
|
)
|
||||||
assert r.status_code == 201
|
assert r.status_code == 201
|
||||||
@@ -304,10 +313,11 @@ def test_add_multi_user(app):
|
|||||||
|
|
||||||
|
|
||||||
@mark.user
|
@mark.user
|
||||||
|
@mark.gen_test
|
||||||
def test_add_multi_user_admin(app):
|
def test_add_multi_user_admin(app):
|
||||||
db = app.db
|
db = app.db
|
||||||
names = ['c', 'd']
|
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}),
|
data=json.dumps({'usernames': names, 'admin': True}),
|
||||||
)
|
)
|
||||||
assert r.status_code == 201
|
assert r.status_code == 201
|
||||||
@@ -323,20 +333,22 @@ def test_add_multi_user_admin(app):
|
|||||||
|
|
||||||
|
|
||||||
@mark.user
|
@mark.user
|
||||||
|
@mark.gen_test
|
||||||
def test_add_user_bad(app):
|
def test_add_user_bad(app):
|
||||||
db = app.db
|
db = app.db
|
||||||
name = 'dne_newuser'
|
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
|
assert r.status_code == 400
|
||||||
user = find_user(db, name)
|
user = find_user(db, name)
|
||||||
assert user is None
|
assert user is None
|
||||||
|
|
||||||
|
|
||||||
@mark.user
|
@mark.user
|
||||||
|
@mark.gen_test
|
||||||
def test_add_admin(app):
|
def test_add_admin(app):
|
||||||
db = app.db
|
db = app.db
|
||||||
name = 'newadmin'
|
name = 'newadmin'
|
||||||
r = api_request(app, 'users', name, method='post',
|
r = yield api_request(app, 'users', name, method='post',
|
||||||
data=json.dumps({'admin': True}),
|
data=json.dumps({'admin': True}),
|
||||||
)
|
)
|
||||||
assert r.status_code == 201
|
assert r.status_code == 201
|
||||||
@@ -347,25 +359,27 @@ def test_add_admin(app):
|
|||||||
|
|
||||||
|
|
||||||
@mark.user
|
@mark.user
|
||||||
|
@mark.gen_test
|
||||||
def test_delete_user(app):
|
def test_delete_user(app):
|
||||||
db = app.db
|
db = app.db
|
||||||
mal = add_user(db, name='mal')
|
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
|
assert r.status_code == 204
|
||||||
|
|
||||||
|
|
||||||
@mark.user
|
@mark.user
|
||||||
|
@mark.gen_test
|
||||||
def test_make_admin(app):
|
def test_make_admin(app):
|
||||||
db = app.db
|
db = app.db
|
||||||
name = 'admin2'
|
name = 'admin2'
|
||||||
r = api_request(app, 'users', name, method='post')
|
r = yield api_request(app, 'users', name, method='post')
|
||||||
assert r.status_code == 201
|
assert r.status_code == 201
|
||||||
user = find_user(db, name)
|
user = find_user(db, name)
|
||||||
assert user is not None
|
assert user is not None
|
||||||
assert user.name == name
|
assert user.name == name
|
||||||
assert not user.admin
|
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})
|
data=json.dumps({'admin': True})
|
||||||
)
|
)
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
@@ -375,24 +389,8 @@ def test_make_admin(app):
|
|||||||
assert user.admin
|
assert user.admin
|
||||||
|
|
||||||
|
|
||||||
def get_app_user(app, name):
|
@mark.gen_test
|
||||||
"""Helper to get the User object from the main thread.
|
def test_spawn(app):
|
||||||
|
|
||||||
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]
|
|
||||||
|
|
||||||
|
|
||||||
def test_spawn(app, io_loop):
|
|
||||||
db = app.db
|
db = app.db
|
||||||
name = 'wash'
|
name = 'wash'
|
||||||
user = add_user(db, app=app, name=name)
|
user = add_user(db, app=app, name=name)
|
||||||
@@ -401,41 +399,41 @@ def test_spawn(app, io_loop):
|
|||||||
'i': 5,
|
'i': 5,
|
||||||
}
|
}
|
||||||
before_servers = sorted(db.query(orm.Server), key=lambda s: s.url)
|
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),
|
data=json.dumps(options),
|
||||||
)
|
)
|
||||||
assert r.status_code == 201
|
assert r.status_code == 201
|
||||||
assert 'pid' in user.orm_spawners[''].state
|
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
|
assert app_user.spawner is not None
|
||||||
spawner = app_user.spawner
|
spawner = app_user.spawner
|
||||||
assert app_user.spawner.user_options == options
|
assert app_user.spawner.user_options == options
|
||||||
assert not app_user.spawner._spawn_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 None
|
assert status is None
|
||||||
|
|
||||||
assert spawner.server.base_url == ujoin(app.base_url, 'user/%s' % name) + '/'
|
assert spawner.server.base_url == ujoin(app.base_url, 'user/%s' % name) + '/'
|
||||||
url = public_url(app, user)
|
url = public_url(app, user)
|
||||||
r = requests.get(url)
|
r = yield async_requests.get(url)
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
assert r.text == spawner.server.base_url
|
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
|
assert r.status_code == 200
|
||||||
argv = r.json()
|
argv = r.json()
|
||||||
assert '--port' in ' '.join(argv)
|
assert '--port' in ' '.join(argv)
|
||||||
r = requests.get(ujoin(url, 'env'))
|
r = yield async_requests.get(ujoin(url, 'env'))
|
||||||
env = r.json()
|
env = r.json()
|
||||||
for expected in ['JUPYTERHUB_USER', 'JUPYTERHUB_BASE_URL', 'JUPYTERHUB_API_TOKEN']:
|
for expected in ['JUPYTERHUB_USER', 'JUPYTERHUB_BASE_URL', 'JUPYTERHUB_API_TOKEN']:
|
||||||
assert expected in env
|
assert expected in env
|
||||||
if app.subdomain_host:
|
if app.subdomain_host:
|
||||||
assert env['JUPYTERHUB_HOST'] == 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 r.status_code == 204
|
||||||
|
|
||||||
assert 'pid' not in user.orm_spawners[''].state
|
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
|
assert status == 0
|
||||||
|
|
||||||
# check that we cleaned up after ourselves
|
# check that we cleaned up after ourselves
|
||||||
@@ -446,29 +444,30 @@ def test_spawn(app, io_loop):
|
|||||||
assert tokens == []
|
assert tokens == []
|
||||||
|
|
||||||
|
|
||||||
def test_slow_spawn(app, io_loop, no_patience, request):
|
@mark.gen_test
|
||||||
|
def test_slow_spawn(app, no_patience, request):
|
||||||
patch = mock.patch.dict(app.tornado_settings, {'spawner_class': mocking.SlowSpawner})
|
patch = mock.patch.dict(app.tornado_settings, {'spawner_class': mocking.SlowSpawner})
|
||||||
patch.start()
|
patch.start()
|
||||||
request.addfinalizer(patch.stop)
|
request.addfinalizer(patch.stop)
|
||||||
db = app.db
|
db = app.db
|
||||||
name = 'zoe'
|
name = 'zoe'
|
||||||
user = add_user(db, app=app, name=name)
|
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()
|
r.raise_for_status()
|
||||||
assert r.status_code == 202
|
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 is not None
|
||||||
assert app_user.spawner._spawn_pending
|
assert app_user.spawner._spawn_pending
|
||||||
assert not app_user.spawner._stop_pending
|
assert not app_user.spawner._stop_pending
|
||||||
|
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
def wait_spawn():
|
def wait_spawn():
|
||||||
while app_user.spawner._spawn_pending:
|
while not app_user.running(''):
|
||||||
yield gen.sleep(0.1)
|
yield gen.sleep(0.1)
|
||||||
|
|
||||||
io_loop.run_sync(wait_spawn)
|
yield wait_spawn()
|
||||||
assert not app_user.spawner._spawn_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 None
|
assert status is None
|
||||||
|
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
@@ -476,26 +475,27 @@ def test_slow_spawn(app, io_loop, no_patience, request):
|
|||||||
while app_user.spawner._stop_pending:
|
while app_user.spawner._stop_pending:
|
||||||
yield gen.sleep(0.1)
|
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()
|
r.raise_for_status()
|
||||||
assert r.status_code == 202
|
assert r.status_code == 202
|
||||||
assert app_user.spawner is not None
|
assert app_user.spawner is not None
|
||||||
assert app_user.spawner._stop_pending
|
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()
|
r.raise_for_status()
|
||||||
assert r.status_code == 202
|
assert r.status_code == 202
|
||||||
assert app_user.spawner is not None
|
assert app_user.spawner is not None
|
||||||
assert app_user.spawner._stop_pending
|
assert app_user.spawner._stop_pending
|
||||||
|
|
||||||
io_loop.run_sync(wait_stop)
|
yield wait_stop()
|
||||||
assert not app_user.spawner._stop_pending
|
assert not app_user.spawner._stop_pending
|
||||||
assert app_user.spawner is not None
|
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
|
assert r.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
def test_never_spawn(app, io_loop, no_patience, request):
|
@mark.gen_test
|
||||||
|
def test_never_spawn(app, no_patience, request):
|
||||||
patch = mock.patch.dict(app.tornado_settings, {'spawner_class': mocking.NeverSpawner})
|
patch = mock.patch.dict(app.tornado_settings, {'spawner_class': mocking.NeverSpawner})
|
||||||
patch.start()
|
patch.start()
|
||||||
request.addfinalizer(patch.stop)
|
request.addfinalizer(patch.stop)
|
||||||
@@ -503,8 +503,8 @@ def test_never_spawn(app, io_loop, no_patience, request):
|
|||||||
db = app.db
|
db = app.db
|
||||||
name = 'badger'
|
name = 'badger'
|
||||||
user = add_user(db, app=app, name=name)
|
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')
|
||||||
app_user = get_app_user(app, name)
|
app_user = app.users[name]
|
||||||
assert app_user.spawner is not None
|
assert app_user.spawner is not None
|
||||||
assert app_user.spawner._spawn_pending
|
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:
|
while app_user.spawner._spawn_pending:
|
||||||
yield gen.sleep(0.1)
|
yield gen.sleep(0.1)
|
||||||
|
|
||||||
io_loop.run_sync(wait_pending)
|
yield wait_pending()
|
||||||
assert not app_user.spawner._spawn_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
|
assert status is not None
|
||||||
|
|
||||||
|
|
||||||
def test_get_proxy(app, io_loop):
|
@mark.gen_test
|
||||||
r = api_request(app, 'proxy')
|
def test_get_proxy(app):
|
||||||
|
r = yield api_request(app, 'proxy')
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
reply = r.json()
|
reply = r.json()
|
||||||
assert list(reply.keys()) == ['/']
|
assert list(reply.keys()) == ['/']
|
||||||
|
|
||||||
|
|
||||||
|
@mark.gen_test
|
||||||
def test_cookie(app):
|
def test_cookie(app):
|
||||||
db = app.db
|
db = app.db
|
||||||
name = 'patience'
|
name = 'patience'
|
||||||
user = add_user(db, app=app, name=name)
|
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 r.status_code == 201
|
||||||
assert 'pid' in user.orm_spawners[''].state
|
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_name = app.hub.cookie_name
|
||||||
# cookie jar gives '"cookie-value"', we want 'cookie-value'
|
# cookie jar gives '"cookie-value"', we want 'cookie-value'
|
||||||
cookie = cookies[cookie_name][1:-1]
|
cookie = cookies[cookie_name][1:-1]
|
||||||
r = api_request(app, 'authorizations/cookie',
|
r = yield api_request(app, 'authorizations/cookie',
|
||||||
cookie_name, "nothintoseehere",
|
cookie_name, "nothintoseehere",
|
||||||
)
|
)
|
||||||
assert r.status_code == 404
|
assert r.status_code == 404
|
||||||
|
|
||||||
r = api_request(app, 'authorizations/cookie',
|
r = yield api_request(app, 'authorizations/cookie',
|
||||||
cookie_name, quote(cookie, safe=''),
|
cookie_name, quote(cookie, safe=''),
|
||||||
)
|
)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
@@ -552,7 +554,7 @@ def test_cookie(app):
|
|||||||
assert reply['name'] == name
|
assert reply['name'] == name
|
||||||
|
|
||||||
# deprecated cookie in body:
|
# deprecated cookie in body:
|
||||||
r = api_request(app, 'authorizations/cookie',
|
r = yield api_request(app, 'authorizations/cookie',
|
||||||
cookie_name, data=cookie,
|
cookie_name, data=cookie,
|
||||||
)
|
)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
@@ -560,18 +562,20 @@ def test_cookie(app):
|
|||||||
assert reply['name'] == name
|
assert reply['name'] == name
|
||||||
|
|
||||||
|
|
||||||
|
@mark.gen_test
|
||||||
def test_token(app):
|
def test_token(app):
|
||||||
name = 'book'
|
name = 'book'
|
||||||
user = add_user(app.db, app=app, name=name)
|
user = add_user(app.db, app=app, name=name)
|
||||||
token = user.new_api_token()
|
token = user.new_api_token()
|
||||||
r = api_request(app, 'authorizations/token', token)
|
r = yield api_request(app, 'authorizations/token', token)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
user_model = r.json()
|
user_model = r.json()
|
||||||
assert user_model['name'] == name
|
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
|
assert r.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
@mark.gen_test
|
||||||
@mark.parametrize("headers, data, status", [
|
@mark.parametrize("headers, data, status", [
|
||||||
({}, None, 200),
|
({}, None, 200),
|
||||||
({'Authorization': ''}, None, 403),
|
({'Authorization': ''}, None, 403),
|
||||||
@@ -581,13 +585,13 @@ def test_get_new_token(app, headers, data, status):
|
|||||||
if data:
|
if data:
|
||||||
data = json.dumps(data)
|
data = json.dumps(data)
|
||||||
# request a new token
|
# 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
|
assert r.status_code == status
|
||||||
if status != 200:
|
if status != 200:
|
||||||
return
|
return
|
||||||
reply = r.json()
|
reply = r.json()
|
||||||
assert 'token' in reply
|
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()
|
r.raise_for_status()
|
||||||
assert 'name' in r.json()
|
assert 'name' in r.json()
|
||||||
|
|
||||||
@@ -598,8 +602,9 @@ def test_get_new_token(app, headers, data, status):
|
|||||||
|
|
||||||
|
|
||||||
@mark.group
|
@mark.group
|
||||||
|
@mark.gen_test
|
||||||
def test_groups_list(app):
|
def test_groups_list(app):
|
||||||
r = api_request(app, 'groups')
|
r = yield api_request(app, 'groups')
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
reply = r.json()
|
reply = r.json()
|
||||||
assert reply == []
|
assert reply == []
|
||||||
@@ -609,7 +614,7 @@ def test_groups_list(app):
|
|||||||
app.db.add(group)
|
app.db.add(group)
|
||||||
app.db.commit()
|
app.db.commit()
|
||||||
|
|
||||||
r = api_request(app, 'groups')
|
r = yield api_request(app, 'groups')
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
reply = r.json()
|
reply = r.json()
|
||||||
assert reply == [{
|
assert reply == [{
|
||||||
@@ -620,16 +625,17 @@ def test_groups_list(app):
|
|||||||
|
|
||||||
|
|
||||||
@mark.group
|
@mark.group
|
||||||
|
@mark.gen_test
|
||||||
def test_group_get(app):
|
def test_group_get(app):
|
||||||
group = orm.Group.find(app.db, name='alphaflight')
|
group = orm.Group.find(app.db, name='alphaflight')
|
||||||
user = add_user(app.db, app=app, name='sasquatch')
|
user = add_user(app.db, app=app, name='sasquatch')
|
||||||
group.users.append(user)
|
group.users.append(user)
|
||||||
app.db.commit()
|
app.db.commit()
|
||||||
|
|
||||||
r = api_request(app, 'groups/runaways')
|
r = yield api_request(app, 'groups/runaways')
|
||||||
assert r.status_code == 404
|
assert r.status_code == 404
|
||||||
|
|
||||||
r = api_request(app, 'groups/alphaflight')
|
r = yield api_request(app, 'groups/alphaflight')
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
reply = r.json()
|
reply = r.json()
|
||||||
assert reply == {
|
assert reply == {
|
||||||
@@ -640,18 +646,19 @@ def test_group_get(app):
|
|||||||
|
|
||||||
|
|
||||||
@mark.group
|
@mark.group
|
||||||
|
@mark.gen_test
|
||||||
def test_group_create_delete(app):
|
def test_group_create_delete(app):
|
||||||
db = app.db
|
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
|
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']}),
|
data=json.dumps({'users': ['doesntexist']}),
|
||||||
)
|
)
|
||||||
assert r.status_code == 400
|
assert r.status_code == 400
|
||||||
assert orm.Group.find(db, name='new') is None
|
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']}),
|
data=json.dumps({'users': ['sasquatch']}),
|
||||||
)
|
)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
@@ -662,29 +669,30 @@ def test_group_create_delete(app):
|
|||||||
assert sasquatch in omegaflight.users
|
assert sasquatch in omegaflight.users
|
||||||
|
|
||||||
# create duplicate raises 400
|
# 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
|
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 r.status_code == 204
|
||||||
assert omegaflight not in sasquatch.groups
|
assert omegaflight not in sasquatch.groups
|
||||||
assert orm.Group.find(db, name='omegaflight') is None
|
assert orm.Group.find(db, name='omegaflight') is None
|
||||||
|
|
||||||
# delete nonexistent gives 404
|
# 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
|
assert r.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
@mark.group
|
@mark.group
|
||||||
|
@mark.gen_test
|
||||||
def test_group_add_users(app):
|
def test_group_add_users(app):
|
||||||
db = app.db
|
db = app.db
|
||||||
# must specify users
|
# 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
|
assert r.status_code == 400
|
||||||
|
|
||||||
names = ['aurora', 'guardian', 'northstar', 'sasquatch', 'shaman', 'snowbird']
|
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 ]
|
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,
|
'users': names,
|
||||||
}))
|
}))
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
@@ -698,15 +706,16 @@ def test_group_add_users(app):
|
|||||||
|
|
||||||
|
|
||||||
@mark.group
|
@mark.group
|
||||||
|
@mark.gen_test
|
||||||
def test_group_delete_users(app):
|
def test_group_delete_users(app):
|
||||||
db = app.db
|
db = app.db
|
||||||
# must specify users
|
# 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
|
assert r.status_code == 400
|
||||||
|
|
||||||
names = ['aurora', 'guardian', 'northstar', 'sasquatch', 'shaman', 'snowbird']
|
names = ['aurora', 'guardian', 'northstar', 'sasquatch', 'shaman', 'snowbird']
|
||||||
users = [ find_user(db, name=name) for name in names ]
|
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],
|
'users': names[:2],
|
||||||
}))
|
}))
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
@@ -726,10 +735,11 @@ def test_group_delete_users(app):
|
|||||||
|
|
||||||
|
|
||||||
@mark.services
|
@mark.services
|
||||||
|
@mark.gen_test
|
||||||
def test_get_services(app, mockservice_url):
|
def test_get_services(app, mockservice_url):
|
||||||
mockservice = mockservice_url
|
mockservice = mockservice_url
|
||||||
db = app.db
|
db = app.db
|
||||||
r = api_request(app, 'services')
|
r = yield api_request(app, 'services')
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
assert r.status_code == 200
|
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'),
|
headers=auth_header(db, 'user'),
|
||||||
)
|
)
|
||||||
assert r.status_code == 403
|
assert r.status_code == 403
|
||||||
|
|
||||||
|
|
||||||
@mark.services
|
@mark.services
|
||||||
|
@mark.gen_test
|
||||||
def test_get_service(app, mockservice_url):
|
def test_get_service(app, mockservice_url):
|
||||||
mockservice = mockservice_url
|
mockservice = mockservice_url
|
||||||
db = app.db
|
db = app.db
|
||||||
r = api_request(app, 'services/%s' % mockservice.name)
|
r = yield api_request(app, 'services/%s' % mockservice.name)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
|
|
||||||
@@ -769,22 +780,23 @@ def test_get_service(app, mockservice_url):
|
|||||||
'url': mockservice.url,
|
'url': mockservice.url,
|
||||||
}
|
}
|
||||||
|
|
||||||
r = api_request(app, 'services/%s' % mockservice.name,
|
r = yield api_request(app, 'services/%s' % mockservice.name,
|
||||||
headers={
|
headers={
|
||||||
'Authorization': 'token %s' % mockservice.api_token
|
'Authorization': 'token %s' % mockservice.api_token
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
r.raise_for_status()
|
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'),
|
headers=auth_header(db, 'user'),
|
||||||
)
|
)
|
||||||
assert r.status_code == 403
|
assert r.status_code == 403
|
||||||
|
|
||||||
|
|
||||||
|
@mark.gen_test
|
||||||
def test_root_api(app):
|
def test_root_api(app):
|
||||||
base_url = app.hub.url
|
base_url = app.hub.url
|
||||||
url = ujoin(base_url, 'api')
|
url = ujoin(base_url, 'api')
|
||||||
r = requests.get(url)
|
r = yield async_requests.get(url)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
expected = {
|
expected = {
|
||||||
'version': jupyterhub.__version__
|
'version': jupyterhub.__version__
|
||||||
@@ -792,8 +804,9 @@ def test_root_api(app):
|
|||||||
assert r.json() == expected
|
assert r.json() == expected
|
||||||
|
|
||||||
|
|
||||||
|
@mark.gen_test
|
||||||
def test_info(app):
|
def test_info(app):
|
||||||
r = api_request(app, 'info')
|
r = yield api_request(app, 'info')
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
data = r.json()
|
data = r.json()
|
||||||
assert data['version'] == jupyterhub.__version__
|
assert data['version'] == jupyterhub.__version__
|
||||||
@@ -821,14 +834,16 @@ def test_info(app):
|
|||||||
# -----------------
|
# -----------------
|
||||||
|
|
||||||
|
|
||||||
|
@mark.gen_test
|
||||||
def test_options(app):
|
def test_options(app):
|
||||||
r = api_request(app, 'users', method='options')
|
r = yield api_request(app, 'users', method='options')
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
assert 'Access-Control-Allow-Headers' in r.headers
|
assert 'Access-Control-Allow-Headers' in r.headers
|
||||||
|
|
||||||
|
|
||||||
|
@mark.gen_test
|
||||||
def test_bad_json_body(app):
|
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
|
assert r.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
@@ -838,14 +853,24 @@ def test_bad_json_body(app):
|
|||||||
|
|
||||||
|
|
||||||
def test_shutdown(app):
|
def test_shutdown(app):
|
||||||
r = api_request(app, 'shutdown', method='post',
|
loop = app.io_loop
|
||||||
data=json.dumps({'servers': True, 'proxy': True,}),
|
|
||||||
)
|
# 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()
|
r.raise_for_status()
|
||||||
reply = r.json()
|
reply = r.json()
|
||||||
for i in range(100):
|
assert stop.called
|
||||||
if app.io_loop._running:
|
|
||||||
time.sleep(0.1)
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
assert not app.io_loop._running
|
|
||||||
|
@@ -56,7 +56,9 @@ def test_generate_config():
|
|||||||
assert 'Spawner.cmd' in cfg_text
|
assert 'Spawner.cmd' in cfg_text
|
||||||
assert 'Authenticator.whitelist' in cfg_text
|
assert 'Authenticator.whitelist' in cfg_text
|
||||||
|
|
||||||
def test_init_tokens(io_loop):
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
|
def test_init_tokens():
|
||||||
with TemporaryDirectory() as td:
|
with TemporaryDirectory() as td:
|
||||||
db_file = os.path.join(td, 'jupyterhub.sqlite')
|
db_file = os.path.join(td, 'jupyterhub.sqlite')
|
||||||
tokens = {
|
tokens = {
|
||||||
@@ -65,7 +67,7 @@ def test_init_tokens(io_loop):
|
|||||||
'boagasdfasdf': 'chell',
|
'boagasdfasdf': 'chell',
|
||||||
}
|
}
|
||||||
app = MockHub(db_url=db_file, api_tokens=tokens)
|
app = MockHub(db_url=db_file, api_tokens=tokens)
|
||||||
io_loop.run_sync(lambda : app.initialize([]))
|
yield app.initialize([])
|
||||||
db = app.db
|
db = app.db
|
||||||
for token, username in tokens.items():
|
for token, username in tokens.items():
|
||||||
api_token = orm.APIToken.find(db, token)
|
api_token = orm.APIToken.find(db, token)
|
||||||
@@ -75,7 +77,7 @@ def test_init_tokens(io_loop):
|
|||||||
|
|
||||||
# simulate second startup, reloading same tokens:
|
# simulate second startup, reloading same tokens:
|
||||||
app = MockHub(db_url=db_file, api_tokens=tokens)
|
app = MockHub(db_url=db_file, api_tokens=tokens)
|
||||||
io_loop.run_sync(lambda : app.initialize([]))
|
yield app.initialize([])
|
||||||
db = app.db
|
db = app.db
|
||||||
for token, username in tokens.items():
|
for token, username in tokens.items():
|
||||||
api_token = orm.APIToken.find(db, token)
|
api_token = orm.APIToken.find(db, token)
|
||||||
@@ -87,7 +89,7 @@ def test_init_tokens(io_loop):
|
|||||||
tokens['short'] = 'gman'
|
tokens['short'] = 'gman'
|
||||||
app = MockHub(db_url=db_file, api_tokens=tokens)
|
app = MockHub(db_url=db_file, api_tokens=tokens)
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
io_loop.run_sync(lambda : app.initialize([]))
|
yield app.initialize([])
|
||||||
assert orm.User.find(app.db, 'gman') is None
|
assert orm.User.find(app.db, 'gman') is None
|
||||||
|
|
||||||
|
|
||||||
@@ -141,15 +143,16 @@ def test_cookie_secret_env(tmpdir):
|
|||||||
assert not os.path.exists(hub.cookie_secret_file)
|
assert not os.path.exists(hub.cookie_secret_file)
|
||||||
|
|
||||||
|
|
||||||
def test_load_groups(io_loop):
|
@pytest.mark.gen_test
|
||||||
|
def test_load_groups():
|
||||||
to_load = {
|
to_load = {
|
||||||
'blue': ['cyclops', 'rogue', 'wolverine'],
|
'blue': ['cyclops', 'rogue', 'wolverine'],
|
||||||
'gold': ['storm', 'jean-grey', 'colossus'],
|
'gold': ['storm', 'jean-grey', 'colossus'],
|
||||||
}
|
}
|
||||||
hub = MockHub(load_groups=to_load)
|
hub = MockHub(load_groups=to_load)
|
||||||
hub.init_db()
|
hub.init_db()
|
||||||
io_loop.run_sync(hub.init_users)
|
yield hub.init_users()
|
||||||
hub.init_groups()
|
yield hub.init_groups()
|
||||||
db = hub.db
|
db = hub.db
|
||||||
blue = orm.Group.find(db, name='blue')
|
blue = orm.Group.find(db, name='blue')
|
||||||
assert blue is not None
|
assert blue is not None
|
||||||
@@ -158,6 +161,3 @@ def test_load_groups(io_loop):
|
|||||||
assert gold is not None
|
assert gold is not None
|
||||||
assert sorted([ u.name for u in gold.users ]) == sorted(to_load['gold'])
|
assert sorted([ u.name for u in gold.users ]) == sorted(to_load['gold'])
|
||||||
|
|
||||||
def test_version():
|
|
||||||
if sys.version_info[:2] < (3, 3):
|
|
||||||
assertRaises(ValueError)
|
|
||||||
|
@@ -10,38 +10,42 @@ from .mocking import MockPAMAuthenticator
|
|||||||
|
|
||||||
from jupyterhub import auth, orm
|
from jupyterhub import auth, orm
|
||||||
|
|
||||||
def test_pam_auth(io_loop):
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
|
def test_pam_auth():
|
||||||
authenticator = MockPAMAuthenticator()
|
authenticator = MockPAMAuthenticator()
|
||||||
authorized = io_loop.run_sync(lambda : authenticator.get_authenticated_user(None, {
|
authorized = yield authenticator.get_authenticated_user(None, {
|
||||||
'username': 'match',
|
'username': 'match',
|
||||||
'password': 'match',
|
'password': 'match',
|
||||||
}))
|
})
|
||||||
assert authorized['name'] == 'match'
|
assert authorized['name'] == 'match'
|
||||||
|
|
||||||
authorized = io_loop.run_sync(lambda : authenticator.get_authenticated_user(None, {
|
authorized = yield authenticator.get_authenticated_user(None, {
|
||||||
'username': 'match',
|
'username': 'match',
|
||||||
'password': 'nomatch',
|
'password': 'nomatch',
|
||||||
}))
|
})
|
||||||
assert authorized is None
|
assert authorized is None
|
||||||
|
|
||||||
def test_pam_auth_whitelist(io_loop):
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
|
def test_pam_auth_whitelist():
|
||||||
authenticator = MockPAMAuthenticator(whitelist={'wash', 'kaylee'})
|
authenticator = MockPAMAuthenticator(whitelist={'wash', 'kaylee'})
|
||||||
authorized = io_loop.run_sync(lambda : authenticator.get_authenticated_user(None, {
|
authorized = yield authenticator.get_authenticated_user(None, {
|
||||||
'username': 'kaylee',
|
'username': 'kaylee',
|
||||||
'password': 'kaylee',
|
'password': 'kaylee',
|
||||||
}))
|
})
|
||||||
assert authorized['name'] == 'kaylee'
|
assert authorized['name'] == 'kaylee'
|
||||||
|
|
||||||
authorized = io_loop.run_sync(lambda : authenticator.get_authenticated_user(None, {
|
authorized = yield authenticator.get_authenticated_user(None, {
|
||||||
'username': 'wash',
|
'username': 'wash',
|
||||||
'password': 'nomatch',
|
'password': 'nomatch',
|
||||||
}))
|
})
|
||||||
assert authorized is None
|
assert authorized is None
|
||||||
|
|
||||||
authorized = io_loop.run_sync(lambda : authenticator.get_authenticated_user(None, {
|
authorized = yield authenticator.get_authenticated_user(None, {
|
||||||
'username': 'mal',
|
'username': 'mal',
|
||||||
'password': 'mal',
|
'password': 'mal',
|
||||||
}))
|
})
|
||||||
assert authorized is None
|
assert authorized is None
|
||||||
|
|
||||||
|
|
||||||
@@ -50,7 +54,8 @@ class MockGroup:
|
|||||||
self.gr_mem = names
|
self.gr_mem = names
|
||||||
|
|
||||||
|
|
||||||
def test_pam_auth_group_whitelist(io_loop):
|
@pytest.mark.gen_test
|
||||||
|
def test_pam_auth_group_whitelist():
|
||||||
g = MockGroup('kaylee')
|
g = MockGroup('kaylee')
|
||||||
def getgrnam(name):
|
def getgrnam(name):
|
||||||
return g
|
return g
|
||||||
@@ -58,38 +63,41 @@ def test_pam_auth_group_whitelist(io_loop):
|
|||||||
authenticator = MockPAMAuthenticator(group_whitelist={'group'})
|
authenticator = MockPAMAuthenticator(group_whitelist={'group'})
|
||||||
|
|
||||||
with mock.patch.object(auth, 'getgrnam', getgrnam):
|
with mock.patch.object(auth, 'getgrnam', getgrnam):
|
||||||
authorized = io_loop.run_sync(lambda : authenticator.get_authenticated_user(None, {
|
authorized = yield authenticator.get_authenticated_user(None, {
|
||||||
'username': 'kaylee',
|
'username': 'kaylee',
|
||||||
'password': 'kaylee',
|
'password': 'kaylee',
|
||||||
}))
|
})
|
||||||
assert authorized['name'] == 'kaylee'
|
assert authorized['name'] == 'kaylee'
|
||||||
|
|
||||||
with mock.patch.object(auth, 'getgrnam', getgrnam):
|
with mock.patch.object(auth, 'getgrnam', getgrnam):
|
||||||
authorized = io_loop.run_sync(lambda : authenticator.get_authenticated_user(None, {
|
authorized = yield authenticator.get_authenticated_user(None, {
|
||||||
'username': 'mal',
|
'username': 'mal',
|
||||||
'password': 'mal',
|
'password': 'mal',
|
||||||
}))
|
})
|
||||||
assert authorized is None
|
assert authorized is None
|
||||||
|
|
||||||
|
|
||||||
def test_pam_auth_no_such_group(io_loop):
|
@pytest.mark.gen_test
|
||||||
|
def test_pam_auth_no_such_group():
|
||||||
authenticator = MockPAMAuthenticator(group_whitelist={'nosuchcrazygroup'})
|
authenticator = MockPAMAuthenticator(group_whitelist={'nosuchcrazygroup'})
|
||||||
authorized = io_loop.run_sync(lambda : authenticator.get_authenticated_user(None, {
|
authorized = yield authenticator.get_authenticated_user(None, {
|
||||||
'username': 'kaylee',
|
'username': 'kaylee',
|
||||||
'password': 'kaylee',
|
'password': 'kaylee',
|
||||||
}))
|
})
|
||||||
assert authorized is None
|
assert authorized is None
|
||||||
|
|
||||||
|
|
||||||
def test_wont_add_system_user(io_loop):
|
@pytest.mark.gen_test
|
||||||
|
def test_wont_add_system_user():
|
||||||
user = orm.User(name='lioness4321')
|
user = orm.User(name='lioness4321')
|
||||||
authenticator = auth.PAMAuthenticator(whitelist={'mal'})
|
authenticator = auth.PAMAuthenticator(whitelist={'mal'})
|
||||||
authenticator.create_system_users = False
|
authenticator.create_system_users = False
|
||||||
with pytest.raises(KeyError):
|
with pytest.raises(KeyError):
|
||||||
io_loop.run_sync(lambda : authenticator.add_user(user))
|
yield authenticator.add_user(user)
|
||||||
|
|
||||||
|
|
||||||
def test_cant_add_system_user(io_loop):
|
@pytest.mark.gen_test
|
||||||
|
def test_cant_add_system_user():
|
||||||
user = orm.User(name='lioness4321')
|
user = orm.User(name='lioness4321')
|
||||||
authenticator = auth.PAMAuthenticator(whitelist={'mal'})
|
authenticator = auth.PAMAuthenticator(whitelist={'mal'})
|
||||||
authenticator.add_user_cmd = ['jupyterhub-fake-command']
|
authenticator.add_user_cmd = ['jupyterhub-fake-command']
|
||||||
@@ -111,11 +119,12 @@ def test_cant_add_system_user(io_loop):
|
|||||||
|
|
||||||
with mock.patch.object(auth, 'Popen', DummyPopen):
|
with mock.patch.object(auth, 'Popen', DummyPopen):
|
||||||
with pytest.raises(RuntimeError) as exc:
|
with pytest.raises(RuntimeError) as exc:
|
||||||
io_loop.run_sync(lambda : authenticator.add_user(user))
|
yield authenticator.add_user(user)
|
||||||
assert str(exc.value) == 'Failed to create system user lioness4321: dummy error'
|
assert str(exc.value) == 'Failed to create system user lioness4321: dummy error'
|
||||||
|
|
||||||
|
|
||||||
def test_add_system_user(io_loop):
|
@pytest.mark.gen_test
|
||||||
|
def test_add_system_user():
|
||||||
user = orm.User(name='lioness4321')
|
user = orm.User(name='lioness4321')
|
||||||
authenticator = auth.PAMAuthenticator(whitelist={'mal'})
|
authenticator = auth.PAMAuthenticator(whitelist={'mal'})
|
||||||
authenticator.create_system_users = True
|
authenticator.create_system_users = True
|
||||||
@@ -131,11 +140,12 @@ def test_add_system_user(io_loop):
|
|||||||
return
|
return
|
||||||
|
|
||||||
with mock.patch.object(auth, 'Popen', DummyPopen):
|
with mock.patch.object(auth, 'Popen', DummyPopen):
|
||||||
io_loop.run_sync(lambda : authenticator.add_user(user))
|
yield authenticator.add_user(user)
|
||||||
assert record['cmd'] == ['echo', '/home/lioness4321', 'lioness4321']
|
assert record['cmd'] == ['echo', '/home/lioness4321', 'lioness4321']
|
||||||
|
|
||||||
|
|
||||||
def test_delete_user(io_loop):
|
@pytest.mark.gen_test
|
||||||
|
def test_delete_user():
|
||||||
user = orm.User(name='zoe')
|
user = orm.User(name='zoe')
|
||||||
a = MockPAMAuthenticator(whitelist={'mal'})
|
a = MockPAMAuthenticator(whitelist={'mal'})
|
||||||
|
|
||||||
@@ -160,49 +170,52 @@ def test_handlers(app):
|
|||||||
assert handlers[0][0] == '/login'
|
assert handlers[0][0] == '/login'
|
||||||
|
|
||||||
|
|
||||||
def test_normalize_names(io_loop):
|
@pytest.mark.gen_test
|
||||||
|
def test_normalize_names():
|
||||||
a = MockPAMAuthenticator()
|
a = MockPAMAuthenticator()
|
||||||
authorized = io_loop.run_sync(lambda : a.get_authenticated_user(None, {
|
authorized = yield a.get_authenticated_user(None, {
|
||||||
'username': 'ZOE',
|
'username': 'ZOE',
|
||||||
'password': 'ZOE',
|
'password': 'ZOE',
|
||||||
}))
|
})
|
||||||
assert authorized['name'] == 'zoe'
|
assert authorized['name'] == 'zoe'
|
||||||
|
|
||||||
authorized = io_loop.run_sync(lambda: a.get_authenticated_user(None, {
|
authorized = yield a.get_authenticated_user(None, {
|
||||||
'username': 'Glenn',
|
'username': 'Glenn',
|
||||||
'password': 'Glenn',
|
'password': 'Glenn',
|
||||||
}))
|
})
|
||||||
assert authorized['name'] == 'glenn'
|
assert authorized['name'] == 'glenn'
|
||||||
|
|
||||||
authorized = io_loop.run_sync(lambda: a.get_authenticated_user(None, {
|
authorized = yield a.get_authenticated_user(None, {
|
||||||
'username': 'hExi',
|
'username': 'hExi',
|
||||||
'password': 'hExi',
|
'password': 'hExi',
|
||||||
}))
|
})
|
||||||
assert authorized['name'] == 'hexi'
|
assert authorized['name'] == 'hexi'
|
||||||
|
|
||||||
authorized = io_loop.run_sync(lambda: a.get_authenticated_user(None, {
|
authorized = yield a.get_authenticated_user(None, {
|
||||||
'username': 'Test',
|
'username': 'Test',
|
||||||
'password': 'Test',
|
'password': 'Test',
|
||||||
}))
|
})
|
||||||
assert authorized['name'] == 'test'
|
assert authorized['name'] == 'test'
|
||||||
|
|
||||||
def test_username_map(io_loop):
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
|
def test_username_map():
|
||||||
a = MockPAMAuthenticator(username_map={'wash': 'alpha'})
|
a = MockPAMAuthenticator(username_map={'wash': 'alpha'})
|
||||||
authorized = io_loop.run_sync(lambda : a.get_authenticated_user(None, {
|
authorized = yield a.get_authenticated_user(None, {
|
||||||
'username': 'WASH',
|
'username': 'WASH',
|
||||||
'password': 'WASH',
|
'password': 'WASH',
|
||||||
}))
|
})
|
||||||
|
|
||||||
assert authorized['name'] == 'alpha'
|
assert authorized['name'] == 'alpha'
|
||||||
|
|
||||||
authorized = io_loop.run_sync(lambda : a.get_authenticated_user(None, {
|
authorized = yield a.get_authenticated_user(None, {
|
||||||
'username': 'Inara',
|
'username': 'Inara',
|
||||||
'password': 'Inara',
|
'password': 'Inara',
|
||||||
}))
|
})
|
||||||
assert authorized['name'] == 'inara'
|
assert authorized['name'] == 'inara'
|
||||||
|
|
||||||
|
|
||||||
def test_validate_names(io_loop):
|
def test_validate_names():
|
||||||
a = auth.PAMAuthenticator()
|
a = auth.PAMAuthenticator()
|
||||||
assert a.validate_username('willow')
|
assert a.validate_username('willow')
|
||||||
assert a.validate_username('giles')
|
assert a.validate_username('giles')
|
||||||
|
@@ -3,6 +3,8 @@ import os
|
|||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from sqlalchemy.exc import OperationalError
|
from sqlalchemy.exc import OperationalError
|
||||||
|
|
||||||
|
import pytest
|
||||||
from pytest import raises
|
from pytest import raises
|
||||||
|
|
||||||
from ..dbutil import upgrade
|
from ..dbutil import upgrade
|
||||||
@@ -24,7 +26,8 @@ def test_upgrade(tmpdir):
|
|||||||
print(db_url)
|
print(db_url)
|
||||||
upgrade(db_url)
|
upgrade(db_url)
|
||||||
|
|
||||||
def test_upgrade_entrypoint(tmpdir, io_loop):
|
@pytest.mark.gen_test
|
||||||
|
def test_upgrade_entrypoint(tmpdir):
|
||||||
generate_old_db(str(tmpdir))
|
generate_old_db(str(tmpdir))
|
||||||
tmpdir.chdir()
|
tmpdir.chdir()
|
||||||
tokenapp = NewToken()
|
tokenapp = NewToken()
|
||||||
@@ -36,7 +39,7 @@ def test_upgrade_entrypoint(tmpdir, io_loop):
|
|||||||
assert len(sqlite_files) == 1
|
assert len(sqlite_files) == 1
|
||||||
|
|
||||||
upgradeapp = UpgradeDB()
|
upgradeapp = UpgradeDB()
|
||||||
io_loop.run_sync(lambda : upgradeapp.initialize([]))
|
yield upgradeapp.initialize([])
|
||||||
upgradeapp.start()
|
upgradeapp.start()
|
||||||
|
|
||||||
# check that backup was created:
|
# check that backup was created:
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
"""Tests for named servers"""
|
"""Tests for named servers"""
|
||||||
import pytest
|
import pytest
|
||||||
import requests
|
|
||||||
|
|
||||||
from ..utils import url_path_join
|
from ..utils import url_path_join
|
||||||
|
|
||||||
from .test_api import api_request, add_user, find_user
|
from .test_api import api_request, add_user
|
||||||
from .mocking import public_url
|
from .mocking import public_url
|
||||||
|
from .utils import async_requests
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def named_servers(app):
|
def named_servers(app):
|
||||||
@@ -17,19 +17,20 @@ def named_servers(app):
|
|||||||
app.tornado_application.settings[key] = app.tornado_settings[key] = False
|
app.tornado_application.settings[key] = app.tornado_settings[key] = False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
def test_create_named_server(app, named_servers):
|
def test_create_named_server(app, named_servers):
|
||||||
username = 'walnut'
|
username = 'walnut'
|
||||||
user = add_user(app.db, app, name=username)
|
user = add_user(app.db, app, name=username)
|
||||||
# assert user.allow_named_servers == True
|
# assert user.allow_named_servers == True
|
||||||
cookies = app.login_user(username)
|
cookies = yield app.login_user(username)
|
||||||
servername = 'trevor'
|
servername = 'trevor'
|
||||||
r = api_request(app, 'users', username, 'servers', servername, method='post')
|
r = yield api_request(app, 'users', username, 'servers', servername, method='post')
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
assert r.status_code == 201
|
assert r.status_code == 201
|
||||||
assert r.text == ''
|
assert r.text == ''
|
||||||
|
|
||||||
url = url_path_join(public_url(app, user), servername, 'env')
|
url = url_path_join(public_url(app, user), servername, 'env')
|
||||||
r = requests.get(url, cookies=cookies)
|
r = yield async_requests.get(url, cookies=cookies)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
assert r.url == url
|
assert r.url == url
|
||||||
env = r.json()
|
env = r.json()
|
||||||
@@ -38,21 +39,22 @@ def test_create_named_server(app, named_servers):
|
|||||||
assert prefix.endswith('/user/%s/%s/' % (username, servername))
|
assert prefix.endswith('/user/%s/%s/' % (username, servername))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
def test_delete_named_server(app, named_servers):
|
def test_delete_named_server(app, named_servers):
|
||||||
username = 'donaar'
|
username = 'donaar'
|
||||||
user = add_user(app.db, app, name=username)
|
user = add_user(app.db, app, name=username)
|
||||||
assert user.allow_named_servers
|
assert user.allow_named_servers
|
||||||
cookies = app.login_user(username)
|
cookies = app.login_user(username)
|
||||||
servername = 'splugoth'
|
servername = 'splugoth'
|
||||||
r = api_request(app, 'users', username, 'servers', servername, method='post')
|
r = yield api_request(app, 'users', username, 'servers', servername, method='post')
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
assert r.status_code == 201
|
assert r.status_code == 201
|
||||||
|
|
||||||
r = api_request(app, 'users', username, 'servers', servername, method='delete')
|
r = yield api_request(app, 'users', username, 'servers', servername, method='delete')
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
assert r.status_code == 204
|
assert r.status_code == 204
|
||||||
|
|
||||||
r = api_request(app, 'users', username)
|
r = yield api_request(app, 'users', username)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
|
|
||||||
user_model = r.json()
|
user_model = r.json()
|
||||||
@@ -73,10 +75,11 @@ def test_delete_named_server(app, named_servers):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
def test_named_server_disabled(app):
|
def test_named_server_disabled(app):
|
||||||
username = 'user'
|
username = 'user'
|
||||||
servername = 'okay'
|
servername = 'okay'
|
||||||
r = api_request(app, 'users', username, 'servers', servername, method='post')
|
r = yield api_request(app, 'users', username, 'servers', servername, method='post')
|
||||||
assert r.status_code == 400
|
assert r.status_code == 400
|
||||||
r = api_request(app, 'users', username, 'servers', servername, method='delete')
|
r = yield api_request(app, 'users', username, 'servers', servername, method='delete')
|
||||||
assert r.status_code == 400
|
assert r.status_code == 400
|
||||||
|
@@ -140,7 +140,8 @@ def test_token_find(db):
|
|||||||
assert found is None
|
assert found is None
|
||||||
|
|
||||||
|
|
||||||
def test_spawn_fails(db, io_loop):
|
@pytest.mark.gen_test
|
||||||
|
def test_spawn_fails(db):
|
||||||
orm_user = orm.User(name='aeofel')
|
orm_user = orm.User(name='aeofel')
|
||||||
db.add(orm_user)
|
db.add(orm_user)
|
||||||
db.commit()
|
db.commit()
|
||||||
@@ -156,7 +157,7 @@ def test_spawn_fails(db, io_loop):
|
|||||||
})
|
})
|
||||||
|
|
||||||
with pytest.raises(RuntimeError) as exc:
|
with pytest.raises(RuntimeError) as exc:
|
||||||
io_loop.run_sync(user.spawn)
|
yield user.spawn()
|
||||||
assert user.spawners[''].server is None
|
assert user.spawners[''].server is None
|
||||||
assert not user.running('')
|
assert not user.running('')
|
||||||
|
|
||||||
|
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
from urllib.parse import urlencode, urlparse
|
from urllib.parse import urlencode, urlparse
|
||||||
|
|
||||||
import requests
|
|
||||||
from tornado import gen
|
from tornado import gen
|
||||||
|
|
||||||
from ..handlers import BaseHandler
|
from ..handlers import BaseHandler
|
||||||
@@ -11,8 +10,11 @@ from .. import orm
|
|||||||
from ..auth import Authenticator
|
from ..auth import Authenticator
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
|
import pytest
|
||||||
|
|
||||||
from .mocking import FormSpawner, public_url, public_host
|
from .mocking import FormSpawner, public_url, public_host
|
||||||
from .test_api import api_request
|
from .test_api import api_request
|
||||||
|
from .utils import async_requests
|
||||||
|
|
||||||
def get_page(path, app, hub=True, **kw):
|
def get_page(path, app, hub=True, **kw):
|
||||||
if hub:
|
if hub:
|
||||||
@@ -20,113 +22,128 @@ def get_page(path, app, hub=True, **kw):
|
|||||||
else:
|
else:
|
||||||
prefix = app.base_url
|
prefix = app.base_url
|
||||||
base_url = ujoin(public_host(app), prefix)
|
base_url = ujoin(public_host(app), prefix)
|
||||||
print(base_url)
|
return async_requests.get(ujoin(base_url, path), **kw)
|
||||||
return requests.get(ujoin(base_url, path), **kw)
|
|
||||||
|
|
||||||
def test_root_no_auth(app, io_loop):
|
|
||||||
print(app.hub.is_up())
|
@pytest.mark.gen_test
|
||||||
routes = io_loop.run_sync(app.proxy.get_all_routes)
|
def test_root_no_auth(app):
|
||||||
print(routes)
|
|
||||||
print(app.hub)
|
|
||||||
url = ujoin(public_host(app), app.hub.base_url)
|
url = ujoin(public_host(app), app.hub.base_url)
|
||||||
print(url)
|
r = yield async_requests.get(url)
|
||||||
r = requests.get(url)
|
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
assert r.url == ujoin(url, 'login')
|
assert r.url == ujoin(url, 'login')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
def test_root_auth(app):
|
def test_root_auth(app):
|
||||||
cookies = app.login_user('river')
|
cookies = yield app.login_user('river')
|
||||||
r = requests.get(public_url(app), cookies=cookies)
|
r = yield async_requests.get(public_url(app), cookies=cookies)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
assert r.url == public_url(app, app.users['river'])
|
assert r.url == public_url(app, app.users['river'])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
def test_root_redirect(app):
|
def test_root_redirect(app):
|
||||||
name = 'wash'
|
name = 'wash'
|
||||||
cookies = app.login_user(name)
|
cookies = yield app.login_user(name)
|
||||||
next_url = ujoin(app.base_url, 'user/other/test.ipynb')
|
next_url = ujoin(app.base_url, 'user/other/test.ipynb')
|
||||||
url = '/?' + urlencode({'next': next_url})
|
url = '/?' + urlencode({'next': next_url})
|
||||||
r = get_page(url, app, cookies=cookies)
|
r = yield get_page(url, app, cookies=cookies)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
path = urlparse(r.url).path
|
path = urlparse(r.url).path
|
||||||
assert path == ujoin(app.base_url, 'user/%s/test.ipynb' % name)
|
assert path == ujoin(app.base_url, 'user/%s/test.ipynb' % name)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
def test_home_no_auth(app):
|
def test_home_no_auth(app):
|
||||||
r = get_page('home', app, allow_redirects=False)
|
r = yield get_page('home', app, allow_redirects=False)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
assert r.status_code == 302
|
assert r.status_code == 302
|
||||||
assert '/hub/login' in r.headers['Location']
|
assert '/hub/login' in r.headers['Location']
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
def test_home_auth(app):
|
def test_home_auth(app):
|
||||||
cookies = app.login_user('river')
|
cookies = yield app.login_user('river')
|
||||||
r = get_page('home', app, cookies=cookies)
|
r = yield get_page('home', app, cookies=cookies)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
assert r.url.endswith('home')
|
assert r.url.endswith('home')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
def test_admin_no_auth(app):
|
def test_admin_no_auth(app):
|
||||||
r = get_page('admin', app)
|
r = yield get_page('admin', app)
|
||||||
assert r.status_code == 403
|
assert r.status_code == 403
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
def test_admin_not_admin(app):
|
def test_admin_not_admin(app):
|
||||||
cookies = app.login_user('wash')
|
cookies = yield app.login_user('wash')
|
||||||
r = get_page('admin', app, cookies=cookies)
|
r = yield get_page('admin', app, cookies=cookies)
|
||||||
assert r.status_code == 403
|
assert r.status_code == 403
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
def test_admin(app):
|
def test_admin(app):
|
||||||
cookies = app.login_user('admin')
|
cookies = yield app.login_user('admin')
|
||||||
r = get_page('admin', app, cookies=cookies)
|
r = yield get_page('admin', app, cookies=cookies)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
assert r.url.endswith('/admin')
|
assert r.url.endswith('/admin')
|
||||||
|
|
||||||
|
|
||||||
def test_spawn_redirect(app, io_loop):
|
@pytest.mark.gen_test
|
||||||
|
def test_spawn_redirect(app):
|
||||||
name = 'wash'
|
name = 'wash'
|
||||||
cookies = app.login_user(name)
|
cookies = yield app.login_user(name)
|
||||||
u = app.users[orm.User.find(app.db, name)]
|
u = app.users[orm.User.find(app.db, name)]
|
||||||
|
|
||||||
# ensure wash's server isn't running:
|
# ensure wash's server isn't running:
|
||||||
r = api_request(app, 'users', name, 'server', method='delete', cookies=cookies)
|
r = yield api_request(app, 'users', name, 'server', method='delete', cookies=cookies)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
status = io_loop.run_sync(u.spawner.poll)
|
status = yield u.spawner.poll()
|
||||||
assert status is not None
|
assert status is not None
|
||||||
|
|
||||||
# test spawn page when no server is running
|
# test spawn page when no server is running
|
||||||
r = get_page('spawn', app, cookies=cookies)
|
r = yield get_page('spawn', app, cookies=cookies)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
print(urlparse(r.url))
|
print(urlparse(r.url))
|
||||||
path = urlparse(r.url).path
|
path = urlparse(r.url).path
|
||||||
assert path == ujoin(app.base_url, 'user/%s/' % name)
|
assert path == ujoin(app.base_url, 'user/%s/' % name)
|
||||||
|
|
||||||
# should have started server
|
# should have started server
|
||||||
status = io_loop.run_sync(u.spawner.poll)
|
status = yield u.spawner.poll()
|
||||||
assert status is None
|
assert status is None
|
||||||
|
|
||||||
# test spawn page when server is already running (just redirect)
|
# test spawn page when server is already running (just redirect)
|
||||||
r = get_page('spawn', app, cookies=cookies)
|
r = yield get_page('spawn', app, cookies=cookies)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
print(urlparse(r.url))
|
print(urlparse(r.url))
|
||||||
path = urlparse(r.url).path
|
path = urlparse(r.url).path
|
||||||
assert path == ujoin(app.base_url, '/user/%s/' % name)
|
assert path == ujoin(app.base_url, '/user/%s/' % name)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
def test_spawn_page(app):
|
def test_spawn_page(app):
|
||||||
with mock.patch.dict(app.users.settings, {'spawner_class': FormSpawner}):
|
with mock.patch.dict(app.users.settings, {'spawner_class': FormSpawner}):
|
||||||
cookies = app.login_user('jones')
|
cookies = yield app.login_user('jones')
|
||||||
r = get_page('spawn', app, cookies=cookies)
|
r = yield get_page('spawn', app, cookies=cookies)
|
||||||
assert r.url.endswith('/spawn')
|
assert r.url.endswith('/spawn')
|
||||||
assert FormSpawner.options_form in r.text
|
assert FormSpawner.options_form in r.text
|
||||||
|
|
||||||
r = get_page('spawn?next=foo', app, cookies=cookies)
|
r = yield get_page('spawn?next=foo', app, cookies=cookies)
|
||||||
assert r.url.endswith('/spawn?next=foo')
|
assert r.url.endswith('/spawn?next=foo')
|
||||||
assert FormSpawner.options_form in r.text
|
assert FormSpawner.options_form in r.text
|
||||||
|
|
||||||
def test_spawn_form(app, io_loop):
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
|
def test_spawn_form(app):
|
||||||
with mock.patch.dict(app.users.settings, {'spawner_class': FormSpawner}):
|
with mock.patch.dict(app.users.settings, {'spawner_class': FormSpawner}):
|
||||||
base_url = ujoin(public_host(app), app.hub.base_url)
|
base_url = ujoin(public_host(app), app.hub.base_url)
|
||||||
cookies = app.login_user('jones')
|
cookies = yield app.login_user('jones')
|
||||||
orm_u = orm.User.find(app.db, 'jones')
|
orm_u = orm.User.find(app.db, 'jones')
|
||||||
u = app.users[orm_u]
|
u = app.users[orm_u]
|
||||||
io_loop.run_sync(u.stop)
|
yield u.stop()
|
||||||
|
|
||||||
r = requests.post(ujoin(base_url, 'spawn?next=/user/jones/tree'), cookies=cookies, data={
|
r = yield async_requests.post(ujoin(base_url, 'spawn?next=/user/jones/tree'), cookies=cookies, data={
|
||||||
'bounds': ['-1', '1'],
|
'bounds': ['-1', '1'],
|
||||||
'energy': '511keV',
|
'energy': '511keV',
|
||||||
})
|
})
|
||||||
@@ -140,15 +157,17 @@ def test_spawn_form(app, io_loop):
|
|||||||
'notspecified': 5,
|
'notspecified': 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_spawn_form_with_file(app, io_loop):
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
|
def test_spawn_form_with_file(app):
|
||||||
with mock.patch.dict(app.users.settings, {'spawner_class': FormSpawner}):
|
with mock.patch.dict(app.users.settings, {'spawner_class': FormSpawner}):
|
||||||
base_url = ujoin(public_host(app), app.hub.base_url)
|
base_url = ujoin(public_host(app), app.hub.base_url)
|
||||||
cookies = app.login_user('jones')
|
cookies = yield app.login_user('jones')
|
||||||
orm_u = orm.User.find(app.db, 'jones')
|
orm_u = orm.User.find(app.db, 'jones')
|
||||||
u = app.users[orm_u]
|
u = app.users[orm_u]
|
||||||
io_loop.run_sync(u.stop)
|
yield u.stop()
|
||||||
|
|
||||||
r = requests.post(ujoin(base_url, 'spawn'),
|
r = yield async_requests.post(ujoin(base_url, 'spawn'),
|
||||||
cookies=cookies,
|
cookies=cookies,
|
||||||
data={
|
data={
|
||||||
'bounds': ['-1', '1'],
|
'bounds': ['-1', '1'],
|
||||||
@@ -167,11 +186,12 @@ def test_spawn_form_with_file(app, io_loop):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
def test_user_redirect(app):
|
def test_user_redirect(app):
|
||||||
name = 'wash'
|
name = 'wash'
|
||||||
cookies = app.login_user(name)
|
cookies = yield app.login_user(name)
|
||||||
|
|
||||||
r = get_page('/user-redirect/tree/top/', app)
|
r = yield get_page('/user-redirect/tree/top/', app)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
print(urlparse(r.url))
|
print(urlparse(r.url))
|
||||||
path = urlparse(r.url).path
|
path = urlparse(r.url).path
|
||||||
@@ -181,31 +201,32 @@ def test_user_redirect(app):
|
|||||||
'next': ujoin(app.hub.base_url, '/user-redirect/tree/top/')
|
'next': ujoin(app.hub.base_url, '/user-redirect/tree/top/')
|
||||||
})
|
})
|
||||||
|
|
||||||
r = get_page('/user-redirect/notebooks/test.ipynb', app, cookies=cookies)
|
r = yield get_page('/user-redirect/notebooks/test.ipynb', app, cookies=cookies)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
print(urlparse(r.url))
|
print(urlparse(r.url))
|
||||||
path = urlparse(r.url).path
|
path = urlparse(r.url).path
|
||||||
assert path == ujoin(app.base_url, '/user/%s/notebooks/test.ipynb' % name)
|
assert path == ujoin(app.base_url, '/user/%s/notebooks/test.ipynb' % name)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
def test_user_redirect_deprecated(app):
|
def test_user_redirect_deprecated(app):
|
||||||
"""redirecting from /user/someonelse/ URLs (deprecated)"""
|
"""redirecting from /user/someonelse/ URLs (deprecated)"""
|
||||||
name = 'wash'
|
name = 'wash'
|
||||||
cookies = app.login_user(name)
|
cookies = yield app.login_user(name)
|
||||||
|
|
||||||
r = get_page('/user/baduser', app, cookies=cookies, hub=False)
|
r = yield get_page('/user/baduser', app, cookies=cookies, hub=False)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
print(urlparse(r.url))
|
print(urlparse(r.url))
|
||||||
path = urlparse(r.url).path
|
path = urlparse(r.url).path
|
||||||
assert path == ujoin(app.base_url, '/user/%s' % name)
|
assert path == ujoin(app.base_url, '/user/%s' % name)
|
||||||
|
|
||||||
r = get_page('/user/baduser/test.ipynb', app, cookies=cookies, hub=False)
|
r = yield get_page('/user/baduser/test.ipynb', app, cookies=cookies, hub=False)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
print(urlparse(r.url))
|
print(urlparse(r.url))
|
||||||
path = urlparse(r.url).path
|
path = urlparse(r.url).path
|
||||||
assert path == ujoin(app.base_url, '/user/%s/test.ipynb' % name)
|
assert path == ujoin(app.base_url, '/user/%s/test.ipynb' % name)
|
||||||
|
|
||||||
r = get_page('/user/baduser/test.ipynb', app, hub=False)
|
r = yield get_page('/user/baduser/test.ipynb', app, hub=False)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
print(urlparse(r.url))
|
print(urlparse(r.url))
|
||||||
path = urlparse(r.url).path
|
path = urlparse(r.url).path
|
||||||
@@ -216,10 +237,11 @@ def test_user_redirect_deprecated(app):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
def test_login_fail(app):
|
def test_login_fail(app):
|
||||||
name = 'wash'
|
name = 'wash'
|
||||||
base_url = public_url(app)
|
base_url = public_url(app)
|
||||||
r = requests.post(base_url + 'hub/login',
|
r = yield async_requests.post(base_url + 'hub/login',
|
||||||
data={
|
data={
|
||||||
'username': name,
|
'username': name,
|
||||||
'password': 'wrong',
|
'password': 'wrong',
|
||||||
@@ -229,6 +251,7 @@ def test_login_fail(app):
|
|||||||
assert not r.cookies
|
assert not r.cookies
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
def test_login_strip(app):
|
def test_login_strip(app):
|
||||||
"""Test that login form doesn't strip whitespace from passwords"""
|
"""Test that login form doesn't strip whitespace from passwords"""
|
||||||
form_data = {
|
form_data = {
|
||||||
@@ -242,7 +265,7 @@ def test_login_strip(app):
|
|||||||
called_with.append(data)
|
called_with.append(data)
|
||||||
|
|
||||||
with mock.patch.object(app.authenticator, 'authenticate', mock_authenticate):
|
with mock.patch.object(app.authenticator, 'authenticate', mock_authenticate):
|
||||||
r = requests.post(base_url + 'hub/login',
|
yield async_requests.post(base_url + 'hub/login',
|
||||||
data=form_data,
|
data=form_data,
|
||||||
allow_redirects=False,
|
allow_redirects=False,
|
||||||
)
|
)
|
||||||
@@ -250,31 +273,33 @@ def test_login_strip(app):
|
|||||||
assert called_with == [form_data]
|
assert called_with == [form_data]
|
||||||
|
|
||||||
|
|
||||||
def test_login_redirect(app, io_loop):
|
@pytest.mark.gen_test
|
||||||
cookies = app.login_user('river')
|
def test_login_redirect(app):
|
||||||
|
cookies = yield app.login_user('river')
|
||||||
user = app.users['river']
|
user = app.users['river']
|
||||||
# no next_url, server running
|
# no next_url, server running
|
||||||
io_loop.run_sync(user.spawn)
|
yield user.spawn()
|
||||||
r = get_page('login', app, cookies=cookies, allow_redirects=False)
|
r = yield get_page('login', app, cookies=cookies, allow_redirects=False)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
assert r.status_code == 302
|
assert r.status_code == 302
|
||||||
assert '/user/river' in r.headers['Location']
|
assert '/user/river' in r.headers['Location']
|
||||||
|
|
||||||
# no next_url, server not running
|
# no next_url, server not running
|
||||||
io_loop.run_sync(user.stop)
|
yield user.stop()
|
||||||
r = get_page('login', app, cookies=cookies, allow_redirects=False)
|
r = yield get_page('login', app, cookies=cookies, allow_redirects=False)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
assert r.status_code == 302
|
assert r.status_code == 302
|
||||||
assert '/hub/' in r.headers['Location']
|
assert '/hub/' in r.headers['Location']
|
||||||
|
|
||||||
# next URL given, use it
|
# next URL given, use it
|
||||||
r = get_page('login?next=/hub/admin', app, cookies=cookies, allow_redirects=False)
|
r = yield get_page('login?next=/hub/admin', app, cookies=cookies, allow_redirects=False)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
assert r.status_code == 302
|
assert r.status_code == 302
|
||||||
assert r.headers['Location'].endswith('/hub/admin')
|
assert r.headers['Location'].endswith('/hub/admin')
|
||||||
|
|
||||||
|
|
||||||
def test_auto_login(app, io_loop, request):
|
@pytest.mark.gen_test
|
||||||
|
def test_auto_login(app, request):
|
||||||
class DummyLoginHandler(BaseHandler):
|
class DummyLoginHandler(BaseHandler):
|
||||||
def get(self):
|
def get(self):
|
||||||
self.write('ok!')
|
self.write('ok!')
|
||||||
@@ -283,7 +308,7 @@ def test_auto_login(app, io_loop, request):
|
|||||||
(ujoin(app.hub.base_url, 'dummy'), DummyLoginHandler),
|
(ujoin(app.hub.base_url, 'dummy'), DummyLoginHandler),
|
||||||
])
|
])
|
||||||
# no auto_login: end up at /hub/login
|
# no auto_login: end up at /hub/login
|
||||||
r = requests.get(base_url)
|
r = yield async_requests.get(base_url)
|
||||||
assert r.url == public_url(app, path='hub/login')
|
assert r.url == public_url(app, path='hub/login')
|
||||||
# enable auto_login: redirect from /hub/login to /hub/dummy
|
# enable auto_login: redirect from /hub/login to /hub/dummy
|
||||||
authenticator = Authenticator(auto_login=True)
|
authenticator = Authenticator(auto_login=True)
|
||||||
@@ -292,38 +317,41 @@ def test_auto_login(app, io_loop, request):
|
|||||||
with mock.patch.dict(app.tornado_application.settings, {
|
with mock.patch.dict(app.tornado_application.settings, {
|
||||||
'authenticator': authenticator,
|
'authenticator': authenticator,
|
||||||
}):
|
}):
|
||||||
r = requests.get(base_url)
|
r = yield async_requests.get(base_url)
|
||||||
assert r.url == public_url(app, path='hub/dummy')
|
assert r.url == public_url(app, path='hub/dummy')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
def test_logout(app):
|
def test_logout(app):
|
||||||
name = 'wash'
|
name = 'wash'
|
||||||
cookies = app.login_user(name)
|
cookies = yield app.login_user(name)
|
||||||
r = requests.get(public_host(app) + app.tornado_settings['logout_url'], cookies=cookies)
|
r = yield async_requests.get(public_host(app) + app.tornado_settings['logout_url'], cookies=cookies)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
login_url = public_host(app) + app.tornado_settings['login_url']
|
login_url = public_host(app) + app.tornado_settings['login_url']
|
||||||
assert r.url == login_url
|
assert r.url == login_url
|
||||||
assert r.cookies == {}
|
assert r.cookies == {}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
def test_login_no_whitelist_adds_user(app):
|
def test_login_no_whitelist_adds_user(app):
|
||||||
auth = app.authenticator
|
auth = app.authenticator
|
||||||
mock_add_user = mock.Mock()
|
mock_add_user = mock.Mock()
|
||||||
with mock.patch.object(auth, 'add_user', mock_add_user):
|
with mock.patch.object(auth, 'add_user', mock_add_user):
|
||||||
cookies = app.login_user('jubal')
|
cookies = yield app.login_user('jubal')
|
||||||
|
|
||||||
user = app.users['jubal']
|
user = app.users['jubal']
|
||||||
assert mock_add_user.mock_calls == [mock.call(user)]
|
assert mock_add_user.mock_calls == [mock.call(user)]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
def test_static_files(app):
|
def test_static_files(app):
|
||||||
base_url = ujoin(public_host(app), app.hub.base_url)
|
base_url = ujoin(public_host(app), app.hub.base_url)
|
||||||
r = requests.get(ujoin(base_url, 'logo'))
|
r = yield async_requests.get(ujoin(base_url, 'logo'))
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
assert r.headers['content-type'] == 'image/png'
|
assert r.headers['content-type'] == 'image/png'
|
||||||
r = requests.get(ujoin(base_url, 'static', 'images', 'jupyter.png'))
|
r = yield async_requests.get(ujoin(base_url, 'static', 'images', 'jupyter.png'))
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
assert r.headers['content-type'] == 'image/png'
|
assert r.headers['content-type'] == 'image/png'
|
||||||
r = requests.get(ujoin(base_url, 'static', 'css', 'style.min.css'))
|
r = yield async_requests.get(ujoin(base_url, 'static', 'css', 'style.min.css'))
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
assert r.headers['content-type'] == 'text/css'
|
assert r.headers['content-type'] == 'text/css'
|
||||||
|
@@ -16,7 +16,8 @@ from .test_api import api_request
|
|||||||
from ..utils import wait_for_http_server, url_path_join as ujoin
|
from ..utils import wait_for_http_server, url_path_join as ujoin
|
||||||
|
|
||||||
|
|
||||||
def test_external_proxy(request, io_loop):
|
@pytest.mark.gen_test
|
||||||
|
def test_external_proxy(request):
|
||||||
|
|
||||||
auth_token = 'secret!'
|
auth_token = 'secret!'
|
||||||
proxy_ip = '127.0.0.1'
|
proxy_ip = '127.0.0.1'
|
||||||
@@ -27,10 +28,13 @@ def test_external_proxy(request, io_loop):
|
|||||||
cfg.ConfigurableHTTPProxy.should_start = False
|
cfg.ConfigurableHTTPProxy.should_start = False
|
||||||
|
|
||||||
app = MockHub.instance(config=cfg)
|
app = MockHub.instance(config=cfg)
|
||||||
|
# disable last_activity polling to avoid check_routes being called during the test,
|
||||||
|
# which races with some of our test conditions
|
||||||
|
app.last_activity_interval = 0
|
||||||
|
|
||||||
def fin():
|
def fin():
|
||||||
MockHub.clear_instance()
|
MockHub.clear_instance()
|
||||||
app.stop()
|
app.http_server.stop()
|
||||||
|
|
||||||
request.addfinalizer(fin)
|
request.addfinalizer(fin)
|
||||||
|
|
||||||
@@ -56,24 +60,25 @@ def test_external_proxy(request, io_loop):
|
|||||||
request.addfinalizer(_cleanup_proxy)
|
request.addfinalizer(_cleanup_proxy)
|
||||||
|
|
||||||
def wait_for_proxy():
|
def wait_for_proxy():
|
||||||
io_loop.run_sync(lambda: wait_for_http_server('http://%s:%i' % (proxy_ip, proxy_port)))
|
return wait_for_http_server('http://%s:%i' % (proxy_ip, proxy_port))
|
||||||
wait_for_proxy()
|
yield wait_for_proxy()
|
||||||
|
|
||||||
app.start([])
|
yield app.initialize([])
|
||||||
|
yield app.start()
|
||||||
assert app.proxy.proxy_process is None
|
assert app.proxy.proxy_process is None
|
||||||
|
|
||||||
# test if api service has a root route '/'
|
# test if api service has a root route '/'
|
||||||
routes = io_loop.run_sync(app.proxy.get_all_routes)
|
routes = yield app.proxy.get_all_routes()
|
||||||
assert list(routes.keys()) == ['/']
|
assert list(routes.keys()) == ['/']
|
||||||
|
|
||||||
# add user to the db and start a single user server
|
# add user to the db and start a single user server
|
||||||
name = 'river'
|
name = 'river'
|
||||||
r = api_request(app, 'users', name, method='post')
|
r = yield api_request(app, 'users', name, method='post')
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
r = api_request(app, 'users', name, 'server', method='post')
|
r = yield api_request(app, 'users', name, 'server', method='post')
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
|
|
||||||
routes = io_loop.run_sync(app.proxy.get_all_routes)
|
routes = yield app.proxy.get_all_routes()
|
||||||
# sets the desired path result
|
# sets the desired path result
|
||||||
user_path = ujoin(app.base_url, 'user/river') + '/'
|
user_path = ujoin(app.base_url, 'user/river') + '/'
|
||||||
print(app.base_url, user_path)
|
print(app.base_url, user_path)
|
||||||
@@ -86,18 +91,18 @@ def test_external_proxy(request, io_loop):
|
|||||||
# teardown the proxy and start a new one in the same place
|
# teardown the proxy and start a new one in the same place
|
||||||
proxy.terminate()
|
proxy.terminate()
|
||||||
proxy = Popen(cmd, env=env)
|
proxy = Popen(cmd, env=env)
|
||||||
wait_for_proxy()
|
yield wait_for_proxy()
|
||||||
|
|
||||||
routes = io_loop.run_sync(app.proxy.get_all_routes)
|
routes = yield app.proxy.get_all_routes()
|
||||||
|
|
||||||
assert list(routes.keys()) == []
|
assert list(routes.keys()) == []
|
||||||
|
|
||||||
# poke the server to update the proxy
|
# poke the server to update the proxy
|
||||||
r = api_request(app, 'proxy', method='post')
|
r = yield api_request(app, 'proxy', method='post')
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
|
|
||||||
# check that the routes are correct
|
# check that the routes are correct
|
||||||
routes = io_loop.run_sync(app.proxy.get_all_routes)
|
routes = yield app.proxy.get_all_routes()
|
||||||
assert sorted(routes.keys()) == ['/', user_spec]
|
assert sorted(routes.keys()) == ['/', user_spec]
|
||||||
|
|
||||||
# teardown the proxy, and start a new one with different auth and port
|
# teardown the proxy, and start a new one with different auth and port
|
||||||
@@ -115,41 +120,35 @@ def test_external_proxy(request, io_loop):
|
|||||||
if app.subdomain_host:
|
if app.subdomain_host:
|
||||||
cmd.append('--host-routing')
|
cmd.append('--host-routing')
|
||||||
proxy = Popen(cmd, env=env)
|
proxy = Popen(cmd, env=env)
|
||||||
wait_for_proxy()
|
yield wait_for_proxy()
|
||||||
|
|
||||||
# tell the hub where the new proxy is
|
# tell the hub where the new proxy is
|
||||||
new_api_url = 'http://{}:{}'.format(proxy_ip, proxy_port)
|
new_api_url = 'http://{}:{}'.format(proxy_ip, proxy_port)
|
||||||
r = api_request(app, 'proxy', method='patch', data=json.dumps({
|
r = yield api_request(app, 'proxy', method='patch', data=json.dumps({
|
||||||
'api_url': new_api_url,
|
'api_url': new_api_url,
|
||||||
'auth_token': new_auth_token,
|
'auth_token': new_auth_token,
|
||||||
}))
|
}))
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
assert app.proxy.api_url == new_api_url
|
assert app.proxy.api_url == new_api_url
|
||||||
|
|
||||||
# get updated auth token from main thread
|
assert app.proxy.auth_token == new_auth_token
|
||||||
def get_app_proxy_token():
|
|
||||||
q = Queue()
|
|
||||||
app.io_loop.add_callback(lambda: q.put(app.proxy.auth_token))
|
|
||||||
return q.get(timeout=2)
|
|
||||||
|
|
||||||
assert get_app_proxy_token() == new_auth_token
|
|
||||||
app.proxy.auth_token = new_auth_token
|
|
||||||
|
|
||||||
# check that the routes are correct
|
# check that the routes are correct
|
||||||
routes = io_loop.run_sync(app.proxy.get_all_routes)
|
routes = yield app.proxy.get_all_routes()
|
||||||
assert sorted(routes.keys()) == ['/', user_spec]
|
assert sorted(routes.keys()) == ['/', user_spec]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
@pytest.mark.parametrize("username, endpoints", [
|
@pytest.mark.parametrize("username, endpoints", [
|
||||||
('zoe', ['users/zoe', 'users/zoe/server']),
|
('zoe', ['users/zoe', 'users/zoe/server']),
|
||||||
('50fia', ['users/50fia', 'users/50fia/server']),
|
('50fia', ['users/50fia', 'users/50fia/server']),
|
||||||
('秀樹', ['users/秀樹', 'users/秀樹/server']),
|
('秀樹', ['users/秀樹', 'users/秀樹/server']),
|
||||||
])
|
])
|
||||||
def test_check_routes(app, io_loop, username, endpoints):
|
def test_check_routes(app, username, endpoints):
|
||||||
proxy = app.proxy
|
proxy = app.proxy
|
||||||
|
|
||||||
for endpoint in endpoints:
|
for endpoint in endpoints:
|
||||||
r = api_request(app, endpoint, method='post')
|
r = yield api_request(app, endpoint, method='post')
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
|
|
||||||
test_user = orm.User.find(app.db, username)
|
test_user = orm.User.find(app.db, username)
|
||||||
@@ -157,18 +156,21 @@ def test_check_routes(app, io_loop, username, endpoints):
|
|||||||
|
|
||||||
# check a valid route exists for user
|
# check a valid route exists for user
|
||||||
test_user = app.users[username]
|
test_user = app.users[username]
|
||||||
before = sorted(io_loop.run_sync(app.proxy.get_all_routes))
|
routes = yield app.proxy.get_all_routes()
|
||||||
|
before = sorted(routes)
|
||||||
assert test_user.proxy_spec() in before
|
assert test_user.proxy_spec() in before
|
||||||
|
|
||||||
# check if a route is removed when user deleted
|
# check if a route is removed when user deleted
|
||||||
io_loop.run_sync(lambda: app.proxy.check_routes(app.users, app._service_map))
|
yield app.proxy.check_routes(app.users, app._service_map)
|
||||||
io_loop.run_sync(lambda: proxy.delete_user(test_user))
|
yield proxy.delete_user(test_user)
|
||||||
during = sorted(io_loop.run_sync(app.proxy.get_all_routes))
|
routes = yield app.proxy.get_all_routes()
|
||||||
|
during = sorted(routes)
|
||||||
assert test_user.proxy_spec() not in during
|
assert test_user.proxy_spec() not in during
|
||||||
|
|
||||||
# check if a route exists for user
|
# check if a route exists for user
|
||||||
io_loop.run_sync(lambda: app.proxy.check_routes(app.users, app._service_map))
|
yield app.proxy.check_routes(app.users, app._service_map)
|
||||||
after = sorted(io_loop.run_sync(app.proxy.get_all_routes))
|
routes = yield app.proxy.get_all_routes()
|
||||||
|
after = sorted(routes)
|
||||||
assert test_user.proxy_spec() in after
|
assert test_user.proxy_spec() in after
|
||||||
|
|
||||||
# check that before and after state are the same
|
# check that before and after state are the same
|
||||||
@@ -222,7 +224,8 @@ def test_add_get_delete(app, routespec):
|
|||||||
assert route is None
|
assert route is None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
@pytest.mark.parametrize("test_data", [None, 'notjson', json.dumps([])])
|
@pytest.mark.parametrize("test_data", [None, 'notjson', json.dumps([])])
|
||||||
def test_proxy_patch_bad_request_data(app, test_data):
|
def test_proxy_patch_bad_request_data(app, test_data):
|
||||||
r = api_request(app, 'proxy', method='patch', data=test_data)
|
r = yield api_request(app, 'proxy', method='patch', data=test_data)
|
||||||
assert r.status_code == 400
|
assert r.status_code == 400
|
||||||
|
@@ -8,12 +8,14 @@ import sys
|
|||||||
from threading import Event
|
from threading import Event
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
import pytest
|
||||||
import requests
|
import requests
|
||||||
from tornado import gen
|
from tornado import gen
|
||||||
from tornado.ioloop import IOLoop
|
from tornado.ioloop import IOLoop
|
||||||
|
|
||||||
from .mocking import public_url
|
from .mocking import public_url
|
||||||
from ..utils import url_path_join, wait_for_http_server, random_port
|
from ..utils import url_path_join, wait_for_http_server, random_port
|
||||||
|
from .utils import async_requests
|
||||||
|
|
||||||
mockservice_path = os.path.dirname(os.path.abspath(__file__))
|
mockservice_path = os.path.dirname(os.path.abspath(__file__))
|
||||||
mockservice_py = os.path.join(mockservice_path, 'mockservice.py')
|
mockservice_py = os.path.join(mockservice_path, 'mockservice.py')
|
||||||
@@ -38,6 +40,7 @@ def external_service(app, name='mockservice'):
|
|||||||
proc.terminate()
|
proc.terminate()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
def test_managed_service(mockservice):
|
def test_managed_service(mockservice):
|
||||||
service = mockservice
|
service = mockservice
|
||||||
proc = service.proc
|
proc = service.proc
|
||||||
@@ -55,18 +58,19 @@ def test_managed_service(mockservice):
|
|||||||
if service.proc is not proc:
|
if service.proc is not proc:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
time.sleep(0.2)
|
yield gen.sleep(0.2)
|
||||||
|
|
||||||
assert service.proc.pid != first_pid
|
assert service.proc.pid != first_pid
|
||||||
assert service.proc.poll() is None
|
assert service.proc.poll() is None
|
||||||
|
|
||||||
|
|
||||||
def test_proxy_service(app, mockservice_url, io_loop):
|
@pytest.mark.gen_test
|
||||||
|
def test_proxy_service(app, mockservice_url):
|
||||||
service = mockservice_url
|
service = mockservice_url
|
||||||
name = service.name
|
name = service.name
|
||||||
io_loop.run_sync(app.proxy.get_all_routes)
|
yield app.proxy.get_all_routes()
|
||||||
url = public_url(app, service) + '/foo'
|
url = public_url(app, service) + '/foo'
|
||||||
r = requests.get(url, allow_redirects=False)
|
r = yield async_requests.get(url, allow_redirects=False)
|
||||||
path = '/services/{}/foo'.format(name)
|
path = '/services/{}/foo'.format(name)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
|
|
||||||
@@ -74,6 +78,7 @@ def test_proxy_service(app, mockservice_url, io_loop):
|
|||||||
assert r.text.endswith(path)
|
assert r.text.endswith(path)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
def test_external_service(app):
|
def test_external_service(app):
|
||||||
name = 'external'
|
name = 'external'
|
||||||
with external_service(app, name=name) as env:
|
with external_service(app, name=name) as env:
|
||||||
@@ -85,17 +90,11 @@ def test_external_service(app):
|
|||||||
}]
|
}]
|
||||||
app.init_services()
|
app.init_services()
|
||||||
app.init_api_tokens()
|
app.init_api_tokens()
|
||||||
evt = Event()
|
yield app.proxy.add_all_services(app._service_map)
|
||||||
|
|
||||||
@gen.coroutine
|
|
||||||
def add_services():
|
|
||||||
yield app.proxy.add_all_services(app._service_map)
|
|
||||||
evt.set()
|
|
||||||
app.io_loop.add_callback(add_services)
|
|
||||||
assert evt.wait(10)
|
|
||||||
service = app._service_map[name]
|
service = app._service_map[name]
|
||||||
url = public_url(app, service) + '/api/users'
|
url = public_url(app, service) + '/api/users'
|
||||||
r = requests.get(url, allow_redirects=False)
|
r = yield async_requests.get(url, allow_redirects=False)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
resp = r.json()
|
resp = r.json()
|
||||||
|
@@ -8,6 +8,7 @@ import time
|
|||||||
from unittest import mock
|
from unittest import mock
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
import pytest
|
||||||
from pytest import raises
|
from pytest import raises
|
||||||
import requests
|
import requests
|
||||||
import requests_mock
|
import requests_mock
|
||||||
@@ -20,6 +21,7 @@ from ..services.auth import _ExpiringDict, HubAuth, HubAuthenticated
|
|||||||
from ..utils import url_path_join
|
from ..utils import url_path_join
|
||||||
from .mocking import public_url, public_host
|
from .mocking import public_url, public_host
|
||||||
from .test_api import add_user
|
from .test_api import add_user
|
||||||
|
from .utils import async_requests
|
||||||
|
|
||||||
# mock for sending monotonic counter way into the future
|
# mock for sending monotonic counter way into the future
|
||||||
monotonic_future = mock.patch('time.monotonic', lambda : sys.maxsize)
|
monotonic_future = mock.patch('time.monotonic', lambda : sys.maxsize)
|
||||||
@@ -215,10 +217,11 @@ def test_hub_authenticated(request):
|
|||||||
assert r.status_code == 403
|
assert r.status_code == 403
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
def test_hubauth_cookie(app, mockservice_url):
|
def test_hubauth_cookie(app, mockservice_url):
|
||||||
"""Test HubAuthenticated service with user cookies"""
|
"""Test HubAuthenticated service with user cookies"""
|
||||||
cookies = app.login_user('badger')
|
cookies = yield app.login_user('badger')
|
||||||
r = requests.get(public_url(app, mockservice_url) + '/whoami/', cookies=cookies)
|
r = yield async_requests.get(public_url(app, mockservice_url) + '/whoami/', cookies=cookies)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
print(r.text)
|
print(r.text)
|
||||||
reply = r.json()
|
reply = r.json()
|
||||||
@@ -229,6 +232,7 @@ def test_hubauth_cookie(app, mockservice_url):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
def test_hubauth_token(app, mockservice_url):
|
def test_hubauth_token(app, mockservice_url):
|
||||||
"""Test HubAuthenticated service with user API tokens"""
|
"""Test HubAuthenticated service with user API tokens"""
|
||||||
u = add_user(app.db, name='river')
|
u = add_user(app.db, name='river')
|
||||||
@@ -236,7 +240,7 @@ def test_hubauth_token(app, mockservice_url):
|
|||||||
app.db.commit()
|
app.db.commit()
|
||||||
|
|
||||||
# token in Authorization header
|
# token in Authorization header
|
||||||
r = requests.get(public_url(app, mockservice_url) + '/whoami/',
|
r = yield async_requests.get(public_url(app, mockservice_url) + '/whoami/',
|
||||||
headers={
|
headers={
|
||||||
'Authorization': 'token %s' % token,
|
'Authorization': 'token %s' % token,
|
||||||
})
|
})
|
||||||
@@ -248,7 +252,7 @@ def test_hubauth_token(app, mockservice_url):
|
|||||||
}
|
}
|
||||||
|
|
||||||
# token in ?token parameter
|
# token in ?token parameter
|
||||||
r = requests.get(public_url(app, mockservice_url) + '/whoami/?token=%s' % token)
|
r = yield async_requests.get(public_url(app, mockservice_url) + '/whoami/?token=%s' % token)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
reply = r.json()
|
reply = r.json()
|
||||||
sub_reply = { key: reply.get(key, 'missing') for key in ['name', 'admin']}
|
sub_reply = { key: reply.get(key, 'missing') for key in ['name', 'admin']}
|
||||||
@@ -257,7 +261,7 @@ def test_hubauth_token(app, mockservice_url):
|
|||||||
'admin': False,
|
'admin': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
r = requests.get(public_url(app, mockservice_url) + '/whoami/?token=no-such-token',
|
r = yield async_requests.get(public_url(app, mockservice_url) + '/whoami/?token=no-such-token',
|
||||||
allow_redirects=False,
|
allow_redirects=False,
|
||||||
)
|
)
|
||||||
assert r.status_code == 302
|
assert r.status_code == 302
|
||||||
@@ -267,16 +271,17 @@ def test_hubauth_token(app, mockservice_url):
|
|||||||
assert path.endswith('/hub/login')
|
assert path.endswith('/hub/login')
|
||||||
|
|
||||||
|
|
||||||
def test_hubauth_service_token(app, mockservice_url, io_loop):
|
@pytest.mark.gen_test
|
||||||
|
def test_hubauth_service_token(app, mockservice_url):
|
||||||
"""Test HubAuthenticated service with service API tokens"""
|
"""Test HubAuthenticated service with service API tokens"""
|
||||||
|
|
||||||
token = hexlify(os.urandom(5)).decode('utf8')
|
token = hexlify(os.urandom(5)).decode('utf8')
|
||||||
name = 'test-api-service'
|
name = 'test-api-service'
|
||||||
app.service_tokens[token] = name
|
app.service_tokens[token] = name
|
||||||
io_loop.run_sync(app.init_api_tokens)
|
yield app.init_api_tokens()
|
||||||
|
|
||||||
# token in Authorization header
|
# token in Authorization header
|
||||||
r = requests.get(public_url(app, mockservice_url) + '/whoami/',
|
r = yield async_requests.get(public_url(app, mockservice_url) + '/whoami/',
|
||||||
headers={
|
headers={
|
||||||
'Authorization': 'token %s' % token,
|
'Authorization': 'token %s' % token,
|
||||||
})
|
})
|
||||||
@@ -289,7 +294,7 @@ def test_hubauth_service_token(app, mockservice_url, io_loop):
|
|||||||
}
|
}
|
||||||
|
|
||||||
# token in ?token parameter
|
# token in ?token parameter
|
||||||
r = requests.get(public_url(app, mockservice_url) + '/whoami/?token=%s' % token)
|
r = yield async_requests.get(public_url(app, mockservice_url) + '/whoami/?token=%s' % token)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
reply = r.json()
|
reply = r.json()
|
||||||
assert reply == {
|
assert reply == {
|
||||||
@@ -298,7 +303,7 @@ def test_hubauth_service_token(app, mockservice_url, io_loop):
|
|||||||
'admin': False,
|
'admin': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
r = requests.get(public_url(app, mockservice_url) + '/whoami/?token=no-such-token',
|
r = yield async_requests.get(public_url(app, mockservice_url) + '/whoami/?token=no-such-token',
|
||||||
allow_redirects=False,
|
allow_redirects=False,
|
||||||
)
|
)
|
||||||
assert r.status_code == 302
|
assert r.status_code == 302
|
||||||
@@ -308,16 +313,19 @@ def test_hubauth_service_token(app, mockservice_url, io_loop):
|
|||||||
assert path.endswith('/hub/login')
|
assert path.endswith('/hub/login')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
def test_oauth_service(app, mockservice_url):
|
def test_oauth_service(app, mockservice_url):
|
||||||
url = url_path_join(public_url(app, mockservice_url) + 'owhoami/')
|
url = url_path_join(public_url(app, mockservice_url) + 'owhoami/')
|
||||||
# first request is only going to set login cookie
|
# first request is only going to set login cookie
|
||||||
# FIXME: redirect to originating URL (OAuth loses this info)
|
# FIXME: redirect to originating URL (OAuth loses this info)
|
||||||
s = requests.Session()
|
s = requests.Session()
|
||||||
s.cookies = app.login_user('link')
|
s.cookies = yield app.login_user('link')
|
||||||
r = s.get(url)
|
# run session.get in async_requests thread
|
||||||
|
s_get = lambda *args, **kwargs: async_requests.executor.submit(s.get, *args, **kwargs)
|
||||||
|
r = yield s_get(url)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
# second request should be authenticated
|
# second request should be authenticated
|
||||||
r = s.get(url, allow_redirects=False)
|
r = yield s_get(url, allow_redirects=False)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
reply = r.json()
|
reply = r.json()
|
||||||
|
@@ -3,68 +3,72 @@
|
|||||||
from subprocess import check_output
|
from subprocess import check_output
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import requests
|
import pytest
|
||||||
|
|
||||||
import jupyterhub
|
import jupyterhub
|
||||||
from .mocking import StubSingleUserSpawner, public_url
|
from .mocking import StubSingleUserSpawner, public_url
|
||||||
from ..utils import url_path_join
|
from ..utils import url_path_join
|
||||||
|
|
||||||
|
from .utils import async_requests
|
||||||
|
|
||||||
def test_singleuser_auth(app, io_loop):
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
|
def test_singleuser_auth(app):
|
||||||
# use StubSingleUserSpawner to launch a single-user app in a thread
|
# use StubSingleUserSpawner to launch a single-user app in a thread
|
||||||
app.spawner_class = StubSingleUserSpawner
|
app.spawner_class = StubSingleUserSpawner
|
||||||
app.tornado_settings['spawner_class'] = StubSingleUserSpawner
|
app.tornado_settings['spawner_class'] = StubSingleUserSpawner
|
||||||
|
|
||||||
# login, start the server
|
# login, start the server
|
||||||
cookies = app.login_user('nandy')
|
cookies = yield app.login_user('nandy')
|
||||||
user = app.users['nandy']
|
user = app.users['nandy']
|
||||||
if not user.running(''):
|
if not user.running(''):
|
||||||
io_loop.run_sync(user.spawn)
|
yield user.spawn()
|
||||||
url = public_url(app, user)
|
url = public_url(app, user)
|
||||||
|
|
||||||
# no cookies, redirects to login page
|
# no cookies, redirects to login page
|
||||||
r = requests.get(url)
|
r = yield async_requests.get(url)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
assert '/hub/login' in r.url
|
assert '/hub/login' in r.url
|
||||||
|
|
||||||
# with cookies, login successful
|
# with cookies, login successful
|
||||||
r = requests.get(url, cookies=cookies)
|
r = yield async_requests.get(url, cookies=cookies)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
assert r.url.rstrip('/').endswith('/user/nandy/tree')
|
assert r.url.rstrip('/').endswith('/user/nandy/tree')
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
|
|
||||||
# logout
|
# logout
|
||||||
r = requests.get(url_path_join(url, 'logout'), cookies=cookies)
|
r = yield async_requests.get(url_path_join(url, 'logout'), cookies=cookies)
|
||||||
assert len(r.cookies) == 0
|
assert len(r.cookies) == 0
|
||||||
|
|
||||||
# another user accessing should get 403, not redirect to login
|
# another user accessing should get 403, not redirect to login
|
||||||
cookies = app.login_user('burgess')
|
cookies = yield app.login_user('burgess')
|
||||||
r = requests.get(url, cookies=cookies)
|
r = yield async_requests.get(url, cookies=cookies)
|
||||||
assert r.status_code == 403
|
assert r.status_code == 403
|
||||||
assert 'burgess' in r.text
|
assert 'burgess' in r.text
|
||||||
|
|
||||||
|
|
||||||
def test_disable_user_config(app, io_loop):
|
@pytest.mark.gen_test
|
||||||
|
def test_disable_user_config(app):
|
||||||
# use StubSingleUserSpawner to launch a single-user app in a thread
|
# use StubSingleUserSpawner to launch a single-user app in a thread
|
||||||
app.spawner_class = StubSingleUserSpawner
|
app.spawner_class = StubSingleUserSpawner
|
||||||
app.tornado_settings['spawner_class'] = StubSingleUserSpawner
|
app.tornado_settings['spawner_class'] = StubSingleUserSpawner
|
||||||
# login, start the server
|
# login, start the server
|
||||||
cookies = app.login_user('nandy')
|
cookies = yield app.login_user('nandy')
|
||||||
user = app.users['nandy']
|
user = app.users['nandy']
|
||||||
# stop spawner, if running:
|
# stop spawner, if running:
|
||||||
if user.running(''):
|
if user.running(''):
|
||||||
print("stopping")
|
print("stopping")
|
||||||
io_loop.run_sync(user.stop)
|
yield user.stop()
|
||||||
# start with new config:
|
# start with new config:
|
||||||
user.spawner.debug = True
|
user.spawner.debug = True
|
||||||
user.spawner.disable_user_config = True
|
user.spawner.disable_user_config = True
|
||||||
io_loop.run_sync(user.spawn)
|
yield user.spawn()
|
||||||
io_loop.run_sync(lambda : app.proxy.add_user(user))
|
yield app.proxy.add_user(user)
|
||||||
|
|
||||||
url = public_url(app, user)
|
url = public_url(app, user)
|
||||||
|
|
||||||
# with cookies, login successful
|
# with cookies, login successful
|
||||||
r = requests.get(url, cookies=cookies)
|
r = yield async_requests.get(url, cookies=cookies)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
assert r.url.rstrip('/').endswith('/user/nandy/tree')
|
assert r.url.rstrip('/').endswith('/user/nandy/tree')
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
|
@@ -13,7 +13,6 @@ import time
|
|||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import requests
|
|
||||||
from tornado import gen
|
from tornado import gen
|
||||||
|
|
||||||
from ..user import User
|
from ..user import User
|
||||||
@@ -21,6 +20,7 @@ from ..objects import Hub
|
|||||||
from .. import spawner as spawnermod
|
from .. import spawner as spawnermod
|
||||||
from ..spawner import LocalProcessSpawner
|
from ..spawner import LocalProcessSpawner
|
||||||
from .. import orm
|
from .. import orm
|
||||||
|
from .utils import async_requests
|
||||||
|
|
||||||
_echo_sleep = """
|
_echo_sleep = """
|
||||||
import sys, time
|
import sys, time
|
||||||
@@ -96,7 +96,7 @@ def wait_for_spawner(spawner, timeout=10):
|
|||||||
yield wait()
|
yield wait()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.gen_test(run_sync=False)
|
@pytest.mark.gen_test
|
||||||
def test_single_user_spawner(app, request):
|
def test_single_user_spawner(app, request):
|
||||||
user = next(iter(app.users.values()), None)
|
user = next(iter(app.users.values()), None)
|
||||||
spawner = user.spawner
|
spawner = user.spawner
|
||||||
@@ -112,42 +112,45 @@ def test_single_user_spawner(app, request):
|
|||||||
assert status == 0
|
assert status == 0
|
||||||
|
|
||||||
|
|
||||||
def test_stop_spawner_sigint_fails(db, io_loop):
|
@pytest.mark.gen_test
|
||||||
|
def test_stop_spawner_sigint_fails(db):
|
||||||
spawner = new_spawner(db, cmd=[sys.executable, '-c', _uninterruptible])
|
spawner = new_spawner(db, cmd=[sys.executable, '-c', _uninterruptible])
|
||||||
io_loop.run_sync(spawner.start)
|
yield spawner.start()
|
||||||
|
|
||||||
# wait for the process to get to the while True: loop
|
# wait for the process to get to the while True: loop
|
||||||
time.sleep(1)
|
yield gen.sleep(1)
|
||||||
|
|
||||||
status = io_loop.run_sync(spawner.poll)
|
status = yield spawner.poll()
|
||||||
assert status is None
|
assert status is None
|
||||||
|
|
||||||
io_loop.run_sync(spawner.stop)
|
yield spawner.stop()
|
||||||
status = io_loop.run_sync(spawner.poll)
|
status = yield spawner.poll()
|
||||||
assert status == -signal.SIGTERM
|
assert status == -signal.SIGTERM
|
||||||
|
|
||||||
|
|
||||||
def test_stop_spawner_stop_now(db, io_loop):
|
@pytest.mark.gen_test
|
||||||
|
def test_stop_spawner_stop_now(db):
|
||||||
spawner = new_spawner(db)
|
spawner = new_spawner(db)
|
||||||
io_loop.run_sync(spawner.start)
|
yield spawner.start()
|
||||||
|
|
||||||
# wait for the process to get to the while True: loop
|
# wait for the process to get to the while True: loop
|
||||||
time.sleep(1)
|
yield gen.sleep(1)
|
||||||
|
|
||||||
status = io_loop.run_sync(spawner.poll)
|
status = yield spawner.poll()
|
||||||
assert status is None
|
assert status is None
|
||||||
|
|
||||||
io_loop.run_sync(lambda : spawner.stop(now=True))
|
yield spawner.stop(now=True)
|
||||||
status = io_loop.run_sync(spawner.poll)
|
status = yield spawner.poll()
|
||||||
assert status == -signal.SIGTERM
|
assert status == -signal.SIGTERM
|
||||||
|
|
||||||
|
|
||||||
def test_spawner_poll(db, io_loop):
|
@pytest.mark.gen_test
|
||||||
|
def test_spawner_poll(db):
|
||||||
first_spawner = new_spawner(db)
|
first_spawner = new_spawner(db)
|
||||||
user = first_spawner.user
|
user = first_spawner.user
|
||||||
io_loop.run_sync(first_spawner.start)
|
yield first_spawner.start()
|
||||||
proc = first_spawner.proc
|
proc = first_spawner.proc
|
||||||
status = io_loop.run_sync(first_spawner.poll)
|
status = yield first_spawner.poll()
|
||||||
assert status is None
|
assert status is None
|
||||||
if user.state is None:
|
if user.state is None:
|
||||||
user.state = {}
|
user.state = {}
|
||||||
@@ -159,21 +162,21 @@ def test_spawner_poll(db, io_loop):
|
|||||||
spawner.start_polling()
|
spawner.start_polling()
|
||||||
|
|
||||||
# wait for the process to get to the while True: loop
|
# wait for the process to get to the while True: loop
|
||||||
io_loop.run_sync(lambda : gen.sleep(1))
|
yield gen.sleep(1)
|
||||||
status = io_loop.run_sync(spawner.poll)
|
status = yield spawner.poll()
|
||||||
assert status is None
|
assert status is None
|
||||||
|
|
||||||
# kill the process
|
# kill the process
|
||||||
proc.terminate()
|
proc.terminate()
|
||||||
for i in range(10):
|
for i in range(10):
|
||||||
if proc.poll() is None:
|
if proc.poll() is None:
|
||||||
time.sleep(1)
|
yield gen.sleep(1)
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
assert proc.poll() is not None
|
assert proc.poll() is not None
|
||||||
|
|
||||||
io_loop.run_sync(lambda : gen.sleep(2))
|
yield gen.sleep(2)
|
||||||
status = io_loop.run_sync(spawner.poll)
|
status = yield spawner.poll()
|
||||||
assert status is not None
|
assert status is not None
|
||||||
|
|
||||||
|
|
||||||
@@ -239,7 +242,7 @@ def test_shell_cmd(db, tmpdir, request):
|
|||||||
s.server.port = port
|
s.server.port = port
|
||||||
db.commit()
|
db.commit()
|
||||||
yield wait_for_spawner(s)
|
yield wait_for_spawner(s)
|
||||||
r = requests.get('http://%s:%i/env' % (ip, port))
|
r = yield async_requests.get('http://%s:%i/env' % (ip, port))
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
env = r.json()
|
env = r.json()
|
||||||
assert env['TESTVAR'] == 'foo'
|
assert env['TESTVAR'] == 'foo'
|
||||||
|
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()
|
||||||
|
|
@@ -98,27 +98,7 @@ class User(HasTraits):
|
|||||||
if self.orm_user:
|
if self.orm_user:
|
||||||
return inspect(self.orm_user).session
|
return inspect(self.orm_user).session
|
||||||
|
|
||||||
@observe('db')
|
|
||||||
def _db_changed(self, change):
|
|
||||||
"""Changing db session reacquires ORM User object"""
|
|
||||||
# db session changed, re-get orm User
|
|
||||||
db = change.new
|
|
||||||
if self._user_id is not None:
|
|
||||||
# fetch our orm.User from the new db session
|
|
||||||
self.orm_user = db.query(orm.User).filter(orm.User.id == self._user_id).first()
|
|
||||||
# update our spawners' ORM objects with the new session,
|
|
||||||
# which can be found on our new orm_user.
|
|
||||||
for name, spawner in self.spawners.items():
|
|
||||||
spawner.orm_spawner = self.orm_user.orm_spawners[name]
|
|
||||||
|
|
||||||
_user_id = None
|
|
||||||
orm_user = Any(allow_none=True)
|
orm_user = Any(allow_none=True)
|
||||||
@observe('orm_user')
|
|
||||||
def _orm_user_changed(self, change):
|
|
||||||
if change.new:
|
|
||||||
self._user_id = change.new.id
|
|
||||||
else:
|
|
||||||
self._user_id = None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def authenticator(self):
|
def authenticator(self):
|
||||||
|
Reference in New Issue
Block a user