mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-11 12:03:00 +00:00
Merge branch 'master' into master
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# Contributing
|
||||
|
||||
|
||||
See the [contribution guide](https://jupyterhub.readthedocs.io/en/latest/index.html#contributor) section
|
||||
See the [contribution guide](https://jupyterhub.readthedocs.io/en/latest/index.html#contributing) section
|
||||
at the JupyterHub documentation.
|
@@ -6,7 +6,7 @@ coverage
|
||||
cryptography
|
||||
html5lib # needed for beautifulsoup
|
||||
pytest-cov
|
||||
pytest-tornado
|
||||
pytest-asyncio
|
||||
pytest>=3.3
|
||||
notebook
|
||||
requests-mock
|
||||
|
@@ -185,6 +185,15 @@ paths:
|
||||
in: path
|
||||
required: true
|
||||
type: string
|
||||
- options:
|
||||
description: |
|
||||
Spawn options can be passed as a JSON body
|
||||
when spawning via the API instead of spawn form.
|
||||
The structure of the options
|
||||
will depend on the Spawner's configuration.
|
||||
in: body
|
||||
required: false
|
||||
type: object
|
||||
responses:
|
||||
'201':
|
||||
description: The user's notebook server has started
|
||||
@@ -217,13 +226,15 @@ paths:
|
||||
in: path
|
||||
required: true
|
||||
type: string
|
||||
- name: remove
|
||||
- options:
|
||||
description: |
|
||||
Whether to fully remove the server, rather than just stop it.
|
||||
Removing a server deletes things like the state of the stopped server.
|
||||
Spawn options can be passed as a JSON body
|
||||
when spawning via the API instead of spawn form.
|
||||
The structure of the options
|
||||
will depend on the Spawner's configuration.
|
||||
in: body
|
||||
required: false
|
||||
type: boolean
|
||||
type: object
|
||||
responses:
|
||||
'201':
|
||||
description: The user's notebook named-server has started
|
||||
@@ -242,6 +253,13 @@ paths:
|
||||
in: path
|
||||
required: true
|
||||
type: string
|
||||
- name: remove
|
||||
description: |
|
||||
Whether to fully remove the server, rather than just stop it.
|
||||
Removing a server deletes things like the state of the stopped server.
|
||||
in: body
|
||||
required: false
|
||||
type: boolean
|
||||
responses:
|
||||
'204':
|
||||
description: The user's notebook named-server has stopped
|
||||
|
@@ -30,7 +30,7 @@ For convenient administration of the Hub, its users, and services,
|
||||
JupyterHub also provides a `REST API`_.
|
||||
|
||||
The JupyterHub team and Project Jupyter value our community, and JupyterHub
|
||||
follows the Jupyter [Community Guides](https://jupyter.readthedocs.io/en/latest/community/content-community.html).
|
||||
follows the Jupyter `Community Guides <https://jupyter.readthedocs.io/en/latest/community/content-community.html>`_.
|
||||
|
||||
Contents
|
||||
========
|
||||
|
@@ -1692,6 +1692,40 @@ class JupyterHub(Application):
|
||||
spawner._log_name)
|
||||
status = -1
|
||||
|
||||
if status is None:
|
||||
# poll claims it's running.
|
||||
# Check if it's really there
|
||||
url_in_db = spawner.server.url
|
||||
url = await spawner.get_url()
|
||||
if url != url_in_db:
|
||||
self.log.warning(
|
||||
"%s had invalid url %s. Updating to %s",
|
||||
spawner._log_name, url_in_db, url,
|
||||
)
|
||||
urlinfo = urlparse(url)
|
||||
spawner.server.protocol = urlinfo.scheme
|
||||
spawner.server.ip = urlinfo.hostname
|
||||
if urlinfo.port:
|
||||
spawner.server.port = urlinfo.port
|
||||
elif urlinfo.scheme == 'http':
|
||||
spawner.server.port = 80
|
||||
elif urlinfo.scheme == 'https':
|
||||
spawner.server.port = 443
|
||||
self.db.commit()
|
||||
|
||||
self.log.debug(
|
||||
"Verifying that %s is running at %s",
|
||||
spawner._log_name, url,
|
||||
)
|
||||
try:
|
||||
await user._wait_up(spawner)
|
||||
except TimeoutError:
|
||||
self.log.error(
|
||||
"%s does not appear to be running at %s, shutting it down.",
|
||||
spawner._log_name, url,
|
||||
)
|
||||
status = -1
|
||||
|
||||
if status is None:
|
||||
self.log.info("%s still running", user.name)
|
||||
spawner.add_poll_callback(user_stopped, user, name)
|
||||
|
@@ -17,7 +17,6 @@ except Exception as e:
|
||||
_pamela_error = e
|
||||
|
||||
from tornado.concurrent import run_on_executor
|
||||
from tornado import gen
|
||||
|
||||
from traitlets.config import LoggingConfigurable
|
||||
from traitlets import Bool, Set, Unicode, Dict, Any, default, observe
|
||||
@@ -704,7 +703,7 @@ class PAMAuthenticator(LocalAuthenticator):
|
||||
# (returning None instead of just the username) as this indicates some sort of system failure
|
||||
|
||||
admin_group_gids = {self._getgrnam(x).gr_gid for x in self.admin_groups}
|
||||
user_group_gids = {x.gr_gid for x in self._getgrouplist(username, self._getpwnam(username).pw_gid)}
|
||||
user_group_gids = set(self._getgrouplist(username, self._getpwnam(username).pw_gid))
|
||||
admin_status = len(admin_group_gids & user_group_gids) != 0
|
||||
|
||||
except Exception as e:
|
||||
|
@@ -19,7 +19,6 @@ from sqlalchemy import (
|
||||
DateTime, Enum, Table,
|
||||
)
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.interfaces import PoolListener
|
||||
from sqlalchemy.orm import (
|
||||
Session,
|
||||
interfaces, object_session, relationship, sessionmaker,
|
||||
@@ -559,10 +558,13 @@ class DatabaseSchemaMismatch(Exception):
|
||||
"""
|
||||
|
||||
|
||||
class ForeignKeysListener(PoolListener):
|
||||
"""Enable foreign keys on sqlite"""
|
||||
def connect(self, dbapi_con, con_record):
|
||||
dbapi_con.execute('pragma foreign_keys=ON')
|
||||
def register_foreign_keys(engine):
|
||||
"""register PRAGMA foreign_keys=on on connection"""
|
||||
@event.listens_for(engine, "connect")
|
||||
def connect(dbapi_con, con_record):
|
||||
cursor = dbapi_con.cursor()
|
||||
cursor.execute("PRAGMA foreign_keys=ON")
|
||||
cursor.close()
|
||||
|
||||
|
||||
def _expire_relationship(target, relationship_prop):
|
||||
@@ -735,8 +737,6 @@ def new_session_factory(url="sqlite:///:memory:",
|
||||
"""Create a new session at url"""
|
||||
if url.startswith('sqlite'):
|
||||
kwargs.setdefault('connect_args', {'check_same_thread': False})
|
||||
listeners = kwargs.setdefault('listeners', [])
|
||||
listeners.append(ForeignKeysListener())
|
||||
|
||||
elif url.startswith('mysql'):
|
||||
kwargs.setdefault('pool_recycle', 60)
|
||||
@@ -747,6 +747,9 @@ def new_session_factory(url="sqlite:///:memory:",
|
||||
kwargs.setdefault('poolclass', StaticPool)
|
||||
|
||||
engine = create_engine(url, **kwargs)
|
||||
if url.startswith('sqlite'):
|
||||
register_foreign_keys(engine)
|
||||
|
||||
# enable pessimistic disconnect handling
|
||||
register_ping_connection(engine)
|
||||
|
||||
|
@@ -852,8 +852,8 @@ class HubAuthenticated(object):
|
||||
self._hub_auth_user_cache = None
|
||||
raise
|
||||
|
||||
# store ?token=... tokens passed via url in a cookie for future requests
|
||||
url_token = self.get_argument('token', '')
|
||||
# store tokens passed via url or header in a cookie for future requests
|
||||
url_token = self.hub_auth.get_token(self)
|
||||
if (
|
||||
user_model
|
||||
and url_token
|
||||
|
@@ -660,6 +660,22 @@ class Spawner(LoggingConfigurable):
|
||||
|
||||
return env
|
||||
|
||||
async def get_url(self):
|
||||
"""Get the URL to connect to the server
|
||||
|
||||
Sometimes JupyterHub may ask the Spawner for its url.
|
||||
This can occur e.g. when JupyterHub has restarted while a server was not finished starting,
|
||||
giving Spawners a chance to recover the URL where their server is running.
|
||||
|
||||
The default is to trust that JupyterHub has the right information.
|
||||
Only override this method in Spawners that know how to
|
||||
check the correct URL for the servers they start.
|
||||
|
||||
This will only be asked of Spawners that claim to be running
|
||||
(`poll()` returns `None`).
|
||||
"""
|
||||
return self.server.url
|
||||
|
||||
def template_namespace(self):
|
||||
"""Return the template namespace for format-string formatting.
|
||||
|
||||
|
@@ -29,6 +29,7 @@ Fixtures to add functionality or spawning behavior
|
||||
|
||||
import asyncio
|
||||
from getpass import getuser
|
||||
import inspect
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
@@ -55,6 +56,16 @@ import jupyterhub.services.service
|
||||
_db = None
|
||||
|
||||
|
||||
def pytest_collection_modifyitems(items):
|
||||
"""add asyncio marker to all async tests"""
|
||||
for item in items:
|
||||
if inspect.iscoroutinefunction(item.obj):
|
||||
item.add_marker('asyncio')
|
||||
if hasattr(inspect, 'isasyncgenfunction'):
|
||||
# double-check that we aren't mixing yield and async def
|
||||
assert not inspect.isasyncgenfunction(item.obj)
|
||||
|
||||
|
||||
@fixture(scope='module')
|
||||
def ssl_tmpdir(tmpdir_factory):
|
||||
return tmpdir_factory.mktemp('ssl')
|
||||
@@ -126,15 +137,21 @@ def db():
|
||||
|
||||
|
||||
@fixture(scope='module')
|
||||
def io_loop(request):
|
||||
def event_loop(request):
|
||||
"""Same as pytest-asyncio.event_loop, but re-scoped to module-level"""
|
||||
event_loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(event_loop)
|
||||
return event_loop
|
||||
|
||||
|
||||
@fixture(scope='module')
|
||||
def io_loop(event_loop, request):
|
||||
"""Same as pytest-tornado.io_loop, but re-scoped to module-level"""
|
||||
ioloop.IOLoop.configure(AsyncIOMainLoop)
|
||||
aio_loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(aio_loop)
|
||||
io_loop = ioloop.IOLoop()
|
||||
io_loop = AsyncIOMainLoop()
|
||||
io_loop.make_current()
|
||||
assert asyncio.get_event_loop() is aio_loop
|
||||
assert io_loop.asyncio_loop is aio_loop
|
||||
assert asyncio.get_event_loop() is event_loop
|
||||
assert io_loop.asyncio_loop is event_loop
|
||||
|
||||
def _close():
|
||||
io_loop.clear_current()
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -63,8 +63,7 @@ def test_generate_config():
|
||||
assert 'Authenticator.whitelist' in cfg_text
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_init_tokens(request):
|
||||
async def test_init_tokens(request):
|
||||
with TemporaryDirectory() as td:
|
||||
db_file = os.path.join(td, 'jupyterhub.sqlite')
|
||||
tokens = {
|
||||
@@ -77,7 +76,7 @@ def test_init_tokens(request):
|
||||
if ssl_enabled:
|
||||
kwargs['internal_certs_location'] = td
|
||||
app = MockHub(**kwargs)
|
||||
yield app.initialize([])
|
||||
await app.initialize([])
|
||||
db = app.db
|
||||
for token, username in tokens.items():
|
||||
api_token = orm.APIToken.find(db, token)
|
||||
@@ -87,7 +86,7 @@ def test_init_tokens(request):
|
||||
|
||||
# simulate second startup, reloading same tokens:
|
||||
app = MockHub(**kwargs)
|
||||
yield app.initialize([])
|
||||
await app.initialize([])
|
||||
db = app.db
|
||||
for token, username in tokens.items():
|
||||
api_token = orm.APIToken.find(db, token)
|
||||
@@ -99,7 +98,7 @@ def test_init_tokens(request):
|
||||
tokens['short'] = 'gman'
|
||||
app = MockHub(**kwargs)
|
||||
with pytest.raises(ValueError):
|
||||
yield app.initialize([])
|
||||
await app.initialize([])
|
||||
assert orm.User.find(app.db, 'gman') is None
|
||||
|
||||
|
||||
@@ -169,8 +168,7 @@ def test_cookie_secret_env(tmpdir, request):
|
||||
assert not os.path.exists(hub.cookie_secret_file)
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_load_groups(tmpdir, request):
|
||||
async def test_load_groups(tmpdir, request):
|
||||
to_load = {
|
||||
'blue': ['cyclops', 'rogue', 'wolverine'],
|
||||
'gold': ['storm', 'jean-grey', 'colossus'],
|
||||
@@ -181,8 +179,8 @@ def test_load_groups(tmpdir, request):
|
||||
kwargs['internal_certs_location'] = str(tmpdir)
|
||||
hub = MockHub(**kwargs)
|
||||
hub.init_db()
|
||||
yield hub.init_users()
|
||||
yield hub.init_groups()
|
||||
await hub.init_users()
|
||||
await hub.init_groups()
|
||||
db = hub.db
|
||||
blue = orm.Group.find(db, name='blue')
|
||||
assert blue is not None
|
||||
@@ -192,16 +190,15 @@ def test_load_groups(tmpdir, request):
|
||||
assert sorted([ u.name for u in gold.users ]) == sorted(to_load['gold'])
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_resume_spawners(tmpdir, request):
|
||||
async def test_resume_spawners(tmpdir, request):
|
||||
if not os.getenv('JUPYTERHUB_TEST_DB_URL'):
|
||||
p = patch.dict(os.environ, {
|
||||
'JUPYTERHUB_TEST_DB_URL': 'sqlite:///%s' % tmpdir.join('jupyterhub.sqlite'),
|
||||
})
|
||||
p.start()
|
||||
request.addfinalizer(p.stop)
|
||||
@gen.coroutine
|
||||
def new_hub():
|
||||
|
||||
async def new_hub():
|
||||
kwargs = {}
|
||||
ssl_enabled = getattr(request.module, "ssl_enabled", False)
|
||||
if ssl_enabled:
|
||||
@@ -209,26 +206,27 @@ def test_resume_spawners(tmpdir, request):
|
||||
app = MockHub(test_clean_db=False, **kwargs)
|
||||
app.config.ConfigurableHTTPProxy.should_start = False
|
||||
app.config.ConfigurableHTTPProxy.auth_token = 'unused'
|
||||
yield app.initialize([])
|
||||
await app.initialize([])
|
||||
return app
|
||||
app = yield new_hub()
|
||||
|
||||
app = await new_hub()
|
||||
db = app.db
|
||||
# spawn a user's server
|
||||
name = 'kurt'
|
||||
user = add_user(db, app, name=name)
|
||||
yield user.spawn()
|
||||
await user.spawn()
|
||||
proc = user.spawner.proc
|
||||
assert proc is not None
|
||||
|
||||
# stop the Hub without cleaning up servers
|
||||
app.cleanup_servers = False
|
||||
yield app.stop()
|
||||
app.stop()
|
||||
|
||||
# proc is still running
|
||||
assert proc.poll() is None
|
||||
|
||||
# resume Hub, should still be running
|
||||
app = yield new_hub()
|
||||
app = await new_hub()
|
||||
db = app.db
|
||||
user = app.users[name]
|
||||
assert user.running
|
||||
@@ -236,7 +234,7 @@ def test_resume_spawners(tmpdir, request):
|
||||
|
||||
# stop the Hub without cleaning up servers
|
||||
app.cleanup_servers = False
|
||||
yield app.stop()
|
||||
app.stop()
|
||||
|
||||
# stop the server while the Hub is down. BAMF!
|
||||
proc.terminate()
|
||||
@@ -244,7 +242,7 @@ def test_resume_spawners(tmpdir, request):
|
||||
assert proc.poll() is not None
|
||||
|
||||
# resume Hub, should be stopped
|
||||
app = yield new_hub()
|
||||
app = await new_hub()
|
||||
db = app.db
|
||||
user = app.users[name]
|
||||
assert not user.running
|
||||
|
@@ -14,47 +14,44 @@ from jupyterhub import auth, crypto, orm
|
||||
from .mocking import MockPAMAuthenticator, MockStructGroup, MockStructPasswd
|
||||
from .test_api import add_user
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_pam_auth():
|
||||
async def test_pam_auth():
|
||||
authenticator = MockPAMAuthenticator()
|
||||
authorized = yield authenticator.get_authenticated_user(None, {
|
||||
authorized = await authenticator.get_authenticated_user(None, {
|
||||
'username': 'match',
|
||||
'password': 'match',
|
||||
})
|
||||
assert authorized['name'] == 'match'
|
||||
|
||||
authorized = yield authenticator.get_authenticated_user(None, {
|
||||
authorized = await authenticator.get_authenticated_user(None, {
|
||||
'username': 'match',
|
||||
'password': 'nomatch',
|
||||
})
|
||||
assert authorized is None
|
||||
|
||||
# Account check is on by default for increased security
|
||||
authorized = yield authenticator.get_authenticated_user(None, {
|
||||
authorized = await authenticator.get_authenticated_user(None, {
|
||||
'username': 'notallowedmatch',
|
||||
'password': 'notallowedmatch',
|
||||
})
|
||||
assert authorized is None
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_pam_auth_account_check_disabled():
|
||||
async def test_pam_auth_account_check_disabled():
|
||||
authenticator = MockPAMAuthenticator(check_account=False)
|
||||
authorized = yield authenticator.get_authenticated_user(None, {
|
||||
authorized = await authenticator.get_authenticated_user(None, {
|
||||
'username': 'allowedmatch',
|
||||
'password': 'allowedmatch',
|
||||
})
|
||||
assert authorized['name'] == 'allowedmatch'
|
||||
|
||||
authorized = yield authenticator.get_authenticated_user(None, {
|
||||
authorized = await authenticator.get_authenticated_user(None, {
|
||||
'username': 'notallowedmatch',
|
||||
'password': 'notallowedmatch',
|
||||
})
|
||||
assert authorized['name'] == 'notallowedmatch'
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_pam_auth_admin_groups():
|
||||
async def test_pam_auth_admin_groups():
|
||||
jh_users = MockStructGroup('jh_users', ['group_admin', 'also_group_admin', 'override_admin', 'non_admin'], 1234)
|
||||
jh_admins = MockStructGroup('jh_admins', ['group_admin'], 5678)
|
||||
wheel = MockStructGroup('wheel', ['also_group_admin'], 9999)
|
||||
@@ -67,10 +64,10 @@ def test_pam_auth_admin_groups():
|
||||
system_users = [group_admin, also_group_admin, override_admin, non_admin]
|
||||
|
||||
user_group_map = {
|
||||
'group_admin': [jh_users, jh_admins],
|
||||
'also_group_admin': [jh_users, wheel],
|
||||
'override_admin': [jh_users],
|
||||
'non_admin': [jh_users]
|
||||
'group_admin': [jh_users.gr_gid, jh_admins.gr_gid],
|
||||
'also_group_admin': [jh_users.gr_gid, wheel.gr_gid],
|
||||
'override_admin': [jh_users.gr_gid],
|
||||
'non_admin': [jh_users.gr_gid]
|
||||
}
|
||||
|
||||
def getgrnam(name):
|
||||
@@ -90,7 +87,7 @@ def test_pam_auth_admin_groups():
|
||||
_getgrnam=getgrnam,
|
||||
_getpwnam=getpwnam,
|
||||
_getgrouplist=getgrouplist):
|
||||
authorized = yield authenticator.get_authenticated_user(None, {
|
||||
authorized = await authenticator.get_authenticated_user(None, {
|
||||
'username': 'group_admin',
|
||||
'password': 'group_admin'
|
||||
})
|
||||
@@ -102,7 +99,7 @@ def test_pam_auth_admin_groups():
|
||||
_getgrnam=getgrnam,
|
||||
_getpwnam=getpwnam,
|
||||
_getgrouplist=getgrouplist):
|
||||
authorized = yield authenticator.get_authenticated_user(None, {
|
||||
authorized = await authenticator.get_authenticated_user(None, {
|
||||
'username': 'also_group_admin',
|
||||
'password': 'also_group_admin'
|
||||
})
|
||||
@@ -114,7 +111,7 @@ def test_pam_auth_admin_groups():
|
||||
_getgrnam=getgrnam,
|
||||
_getpwnam=getpwnam,
|
||||
_getgrouplist=getgrouplist):
|
||||
authorized = yield authenticator.get_authenticated_user(None, {
|
||||
authorized = await authenticator.get_authenticated_user(None, {
|
||||
'username': 'override_admin',
|
||||
'password': 'override_admin'
|
||||
})
|
||||
@@ -126,7 +123,7 @@ def test_pam_auth_admin_groups():
|
||||
_getgrnam=getgrnam,
|
||||
_getpwnam=getpwnam,
|
||||
_getgrouplist=getgrouplist):
|
||||
authorized = yield authenticator.get_authenticated_user(None, {
|
||||
authorized = await authenticator.get_authenticated_user(None, {
|
||||
'username': 'non_admin',
|
||||
'password': 'non_admin'
|
||||
})
|
||||
@@ -134,55 +131,52 @@ def test_pam_auth_admin_groups():
|
||||
assert authorized['admin'] is False
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_pam_auth_whitelist():
|
||||
async def test_pam_auth_whitelist():
|
||||
authenticator = MockPAMAuthenticator(whitelist={'wash', 'kaylee'})
|
||||
authorized = yield authenticator.get_authenticated_user(None, {
|
||||
authorized = await authenticator.get_authenticated_user(None, {
|
||||
'username': 'kaylee',
|
||||
'password': 'kaylee',
|
||||
})
|
||||
assert authorized['name'] == 'kaylee'
|
||||
|
||||
authorized = yield authenticator.get_authenticated_user(None, {
|
||||
authorized = await authenticator.get_authenticated_user(None, {
|
||||
'username': 'wash',
|
||||
'password': 'nomatch',
|
||||
})
|
||||
assert authorized is None
|
||||
|
||||
authorized = yield authenticator.get_authenticated_user(None, {
|
||||
authorized = await authenticator.get_authenticated_user(None, {
|
||||
'username': 'mal',
|
||||
'password': 'mal',
|
||||
})
|
||||
assert authorized is None
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_pam_auth_group_whitelist():
|
||||
async def test_pam_auth_group_whitelist():
|
||||
def getgrnam(name):
|
||||
return MockStructGroup('grp', ['kaylee'])
|
||||
|
||||
authenticator = MockPAMAuthenticator(group_whitelist={'group'})
|
||||
|
||||
with mock.patch.object(authenticator, '_getgrnam', getgrnam):
|
||||
authorized = yield authenticator.get_authenticated_user(None, {
|
||||
authorized = await authenticator.get_authenticated_user(None, {
|
||||
'username': 'kaylee',
|
||||
'password': 'kaylee',
|
||||
})
|
||||
assert authorized['name'] == 'kaylee'
|
||||
|
||||
with mock.patch.object(authenticator, '_getgrnam', getgrnam):
|
||||
authorized = yield authenticator.get_authenticated_user(None, {
|
||||
authorized = await authenticator.get_authenticated_user(None, {
|
||||
'username': 'mal',
|
||||
'password': 'mal',
|
||||
})
|
||||
assert authorized is None
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_pam_auth_blacklist():
|
||||
async def test_pam_auth_blacklist():
|
||||
# Null case compared to next case
|
||||
authenticator = MockPAMAuthenticator()
|
||||
authorized = yield authenticator.get_authenticated_user(None, {
|
||||
authorized = await authenticator.get_authenticated_user(None, {
|
||||
'username': 'wash',
|
||||
'password': 'wash',
|
||||
})
|
||||
@@ -190,7 +184,7 @@ def test_pam_auth_blacklist():
|
||||
|
||||
# Blacklist basics
|
||||
authenticator = MockPAMAuthenticator(blacklist={'wash'})
|
||||
authorized = yield authenticator.get_authenticated_user(None, {
|
||||
authorized = await authenticator.get_authenticated_user(None, {
|
||||
'username': 'wash',
|
||||
'password': 'wash',
|
||||
})
|
||||
@@ -198,7 +192,7 @@ def test_pam_auth_blacklist():
|
||||
|
||||
# User in both white and blacklists: default deny. Make error someday?
|
||||
authenticator = MockPAMAuthenticator(blacklist={'wash'}, whitelist={'wash', 'kaylee'})
|
||||
authorized = yield authenticator.get_authenticated_user(None, {
|
||||
authorized = await authenticator.get_authenticated_user(None, {
|
||||
'username': 'wash',
|
||||
'password': 'wash',
|
||||
})
|
||||
@@ -206,7 +200,7 @@ def test_pam_auth_blacklist():
|
||||
|
||||
# User not in blacklist can log in
|
||||
authenticator = MockPAMAuthenticator(blacklist={'wash'}, whitelist={'wash', 'kaylee'})
|
||||
authorized = yield authenticator.get_authenticated_user(None, {
|
||||
authorized = await authenticator.get_authenticated_user(None, {
|
||||
'username': 'kaylee',
|
||||
'password': 'kaylee',
|
||||
})
|
||||
@@ -214,7 +208,7 @@ def test_pam_auth_blacklist():
|
||||
|
||||
# User in whitelist, blacklist irrelevent
|
||||
authenticator = MockPAMAuthenticator(blacklist={'mal'}, whitelist={'wash', 'kaylee'})
|
||||
authorized = yield authenticator.get_authenticated_user(None, {
|
||||
authorized = await authenticator.get_authenticated_user(None, {
|
||||
'username': 'wash',
|
||||
'password': 'wash',
|
||||
})
|
||||
@@ -222,7 +216,7 @@ def test_pam_auth_blacklist():
|
||||
|
||||
# User in neither list
|
||||
authenticator = MockPAMAuthenticator(blacklist={'mal'}, whitelist={'wash', 'kaylee'})
|
||||
authorized = yield authenticator.get_authenticated_user(None, {
|
||||
authorized = await authenticator.get_authenticated_user(None, {
|
||||
'username': 'simon',
|
||||
'password': 'simon',
|
||||
})
|
||||
@@ -230,34 +224,31 @@ def test_pam_auth_blacklist():
|
||||
|
||||
# blacklist == {}
|
||||
authenticator = MockPAMAuthenticator(blacklist=set(), whitelist={'wash', 'kaylee'})
|
||||
authorized = yield authenticator.get_authenticated_user(None, {
|
||||
authorized = await authenticator.get_authenticated_user(None, {
|
||||
'username': 'kaylee',
|
||||
'password': 'kaylee',
|
||||
})
|
||||
assert authorized['name'] == 'kaylee'
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_pam_auth_no_such_group():
|
||||
async def test_pam_auth_no_such_group():
|
||||
authenticator = MockPAMAuthenticator(group_whitelist={'nosuchcrazygroup'})
|
||||
authorized = yield authenticator.get_authenticated_user(None, {
|
||||
authorized = await authenticator.get_authenticated_user(None, {
|
||||
'username': 'kaylee',
|
||||
'password': 'kaylee',
|
||||
})
|
||||
assert authorized is None
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_wont_add_system_user():
|
||||
async def test_wont_add_system_user():
|
||||
user = orm.User(name='lioness4321')
|
||||
authenticator = auth.PAMAuthenticator(whitelist={'mal'})
|
||||
authenticator.create_system_users = False
|
||||
with pytest.raises(KeyError):
|
||||
yield authenticator.add_user(user)
|
||||
await authenticator.add_user(user)
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_cant_add_system_user():
|
||||
async def test_cant_add_system_user():
|
||||
user = orm.User(name='lioness4321')
|
||||
authenticator = auth.PAMAuthenticator(whitelist={'mal'})
|
||||
authenticator.add_user_cmd = ['jupyterhub-fake-command']
|
||||
@@ -279,12 +270,11 @@ def test_cant_add_system_user():
|
||||
|
||||
with mock.patch.object(auth, 'Popen', DummyPopen):
|
||||
with pytest.raises(RuntimeError) as exc:
|
||||
yield authenticator.add_user(user)
|
||||
await authenticator.add_user(user)
|
||||
assert str(exc.value) == 'Failed to create system user lioness4321: dummy error'
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_add_system_user():
|
||||
async def test_add_system_user():
|
||||
user = orm.User(name='lioness4321')
|
||||
authenticator = auth.PAMAuthenticator(whitelist={'mal'})
|
||||
authenticator.create_system_users = True
|
||||
@@ -300,17 +290,16 @@ def test_add_system_user():
|
||||
return
|
||||
|
||||
with mock.patch.object(auth, 'Popen', DummyPopen):
|
||||
yield authenticator.add_user(user)
|
||||
await authenticator.add_user(user)
|
||||
assert record['cmd'] == ['echo', '/home/lioness4321', 'lioness4321']
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_delete_user():
|
||||
async def test_delete_user():
|
||||
user = orm.User(name='zoe')
|
||||
a = MockPAMAuthenticator(whitelist={'mal'})
|
||||
|
||||
assert 'zoe' not in a.whitelist
|
||||
yield a.add_user(user)
|
||||
await a.add_user(user)
|
||||
assert 'zoe' in a.whitelist
|
||||
a.delete_user(user)
|
||||
assert 'zoe' not in a.whitelist
|
||||
@@ -330,47 +319,43 @@ def test_handlers(app):
|
||||
assert handlers[0][0] == '/login'
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_auth_state(app, auth_state_enabled):
|
||||
async def test_auth_state(app, auth_state_enabled):
|
||||
"""auth_state enabled and available"""
|
||||
name = 'kiwi'
|
||||
user = add_user(app.db, app, name=name)
|
||||
assert user.encrypted_auth_state is None
|
||||
cookies = yield app.login_user(name)
|
||||
auth_state = yield user.get_auth_state()
|
||||
cookies = await app.login_user(name)
|
||||
auth_state = await user.get_auth_state()
|
||||
assert auth_state == app.authenticator.auth_state
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_auth_admin_non_admin(app):
|
||||
async def test_auth_admin_non_admin(app):
|
||||
"""admin should be passed through for non-admin users"""
|
||||
name = 'kiwi'
|
||||
user = add_user(app.db, app, name=name, admin=False)
|
||||
assert user.admin is False
|
||||
cookies = yield app.login_user(name)
|
||||
cookies = await app.login_user(name)
|
||||
assert user.admin is False
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_auth_admin_is_admin(app):
|
||||
async def test_auth_admin_is_admin(app):
|
||||
"""admin should be passed through for admin users"""
|
||||
# Admin user defined in MockPAMAuthenticator.
|
||||
name = 'admin'
|
||||
user = add_user(app.db, app, name=name, admin=False)
|
||||
assert user.admin is False
|
||||
cookies = yield app.login_user(name)
|
||||
cookies = await app.login_user(name)
|
||||
assert user.admin is True
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_auth_admin_retained_if_unset(app):
|
||||
async def test_auth_admin_retained_if_unset(app):
|
||||
"""admin should be unchanged if authenticator doesn't return admin value"""
|
||||
name = 'kiwi'
|
||||
# Add user as admin.
|
||||
user = add_user(app.db, app, name=name, admin=True)
|
||||
assert user.admin is True
|
||||
# User should remain unchanged.
|
||||
cookies = yield app.login_user(name)
|
||||
cookies = await app.login_user(name)
|
||||
assert user.admin is True
|
||||
|
||||
|
||||
@@ -384,56 +369,53 @@ def auth_state_unavailable(auth_state_enabled):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_auth_state_disabled(app, auth_state_unavailable):
|
||||
async def test_auth_state_disabled(app, auth_state_unavailable):
|
||||
name = 'driebus'
|
||||
user = add_user(app.db, app, name=name)
|
||||
assert user.encrypted_auth_state is None
|
||||
with pytest.raises(HTTPError):
|
||||
cookies = yield app.login_user(name)
|
||||
auth_state = yield user.get_auth_state()
|
||||
cookies = await app.login_user(name)
|
||||
auth_state = await user.get_auth_state()
|
||||
assert auth_state is None
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_normalize_names():
|
||||
async def test_normalize_names():
|
||||
a = MockPAMAuthenticator()
|
||||
authorized = yield a.get_authenticated_user(None, {
|
||||
authorized = await a.get_authenticated_user(None, {
|
||||
'username': 'ZOE',
|
||||
'password': 'ZOE',
|
||||
})
|
||||
assert authorized['name'] == 'zoe'
|
||||
|
||||
authorized = yield a.get_authenticated_user(None, {
|
||||
authorized = await a.get_authenticated_user(None, {
|
||||
'username': 'Glenn',
|
||||
'password': 'Glenn',
|
||||
})
|
||||
assert authorized['name'] == 'glenn'
|
||||
|
||||
authorized = yield a.get_authenticated_user(None, {
|
||||
authorized = await a.get_authenticated_user(None, {
|
||||
'username': 'hExi',
|
||||
'password': 'hExi',
|
||||
})
|
||||
assert authorized['name'] == 'hexi'
|
||||
|
||||
authorized = yield a.get_authenticated_user(None, {
|
||||
authorized = await a.get_authenticated_user(None, {
|
||||
'username': 'Test',
|
||||
'password': 'Test',
|
||||
})
|
||||
assert authorized['name'] == 'test'
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_username_map():
|
||||
async def test_username_map():
|
||||
a = MockPAMAuthenticator(username_map={'wash': 'alpha'})
|
||||
authorized = yield a.get_authenticated_user(None, {
|
||||
authorized = await a.get_authenticated_user(None, {
|
||||
'username': 'WASH',
|
||||
'password': 'WASH',
|
||||
})
|
||||
|
||||
assert authorized['name'] == 'alpha'
|
||||
|
||||
authorized = yield a.get_authenticated_user(None, {
|
||||
authorized = await a.get_authenticated_user(None, {
|
||||
'username': 'Inara',
|
||||
'password': 'Inara',
|
||||
})
|
||||
|
@@ -11,6 +11,7 @@ keys = [('%i' % i).encode('ascii') * 32 for i in range(3)]
|
||||
hex_keys = [ b2a_hex(key).decode('ascii') for key in keys ]
|
||||
b64_keys = [ b2a_base64(key).decode('ascii').strip() for key in keys ]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("key_env, keys", [
|
||||
(hex_keys[0], [keys[0]]),
|
||||
(';'.join([b64_keys[0], hex_keys[1]]), keys[:2]),
|
||||
@@ -52,30 +53,27 @@ def crypt_keeper():
|
||||
ck.keys = save_keys
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_roundtrip(crypt_keeper):
|
||||
async def test_roundtrip(crypt_keeper):
|
||||
data = {'key': 'value'}
|
||||
encrypted = yield encrypt(data)
|
||||
decrypted = yield decrypt(encrypted)
|
||||
encrypted = await encrypt(data)
|
||||
decrypted = await decrypt(encrypted)
|
||||
assert decrypted == data
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_missing_crypto(crypt_keeper):
|
||||
async def test_missing_crypto(crypt_keeper):
|
||||
with patch.object(crypto, 'cryptography', None):
|
||||
with pytest.raises(crypto.CryptographyUnavailable):
|
||||
yield encrypt({})
|
||||
await encrypt({})
|
||||
|
||||
with pytest.raises(crypto.CryptographyUnavailable):
|
||||
yield decrypt(b'whatever')
|
||||
await decrypt(b'whatever')
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_missing_keys(crypt_keeper):
|
||||
async def test_missing_keys(crypt_keeper):
|
||||
crypt_keeper.keys = []
|
||||
with pytest.raises(crypto.NoEncryptionKeys):
|
||||
yield encrypt({})
|
||||
await encrypt({})
|
||||
|
||||
with pytest.raises(crypto.NoEncryptionKeys):
|
||||
yield decrypt(b'whatever')
|
||||
await decrypt(b'whatever')
|
||||
|
||||
|
@@ -40,8 +40,7 @@ def generate_old_db(env_dir, hub_version, db_url):
|
||||
'0.8.1',
|
||||
],
|
||||
)
|
||||
@pytest.mark.gen_test
|
||||
def test_upgrade(tmpdir, hub_version):
|
||||
async def test_upgrade(tmpdir, hub_version):
|
||||
db_url = os.getenv('JUPYTERHUB_TEST_DB_URL')
|
||||
if db_url:
|
||||
db_url += '_upgrade_' + hub_version.replace('.', '')
|
||||
@@ -69,7 +68,7 @@ def test_upgrade(tmpdir, hub_version):
|
||||
assert len(sqlite_files) == 1
|
||||
|
||||
upgradeapp = UpgradeDB(config=cfg)
|
||||
yield upgradeapp.initialize([])
|
||||
upgradeapp.initialize([])
|
||||
upgradeapp.start()
|
||||
|
||||
# check that backup was created:
|
||||
|
@@ -7,48 +7,45 @@ import pytest
|
||||
|
||||
from jupyterhub.auth import DummyAuthenticator
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_dummy_auth_without_global_password():
|
||||
async def test_dummy_auth_without_global_password():
|
||||
authenticator = DummyAuthenticator()
|
||||
authorized = yield authenticator.get_authenticated_user(None, {
|
||||
authorized = await authenticator.get_authenticated_user(None, {
|
||||
'username': 'test_user',
|
||||
'password': 'test_pass',
|
||||
})
|
||||
assert authorized['name'] == 'test_user'
|
||||
|
||||
authorized = yield authenticator.get_authenticated_user(None, {
|
||||
authorized = await authenticator.get_authenticated_user(None, {
|
||||
'username': 'test_user',
|
||||
'password': '',
|
||||
})
|
||||
assert authorized['name'] == 'test_user'
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_dummy_auth_without_username():
|
||||
async def test_dummy_auth_without_username():
|
||||
authenticator = DummyAuthenticator()
|
||||
authorized = yield authenticator.get_authenticated_user(None, {
|
||||
authorized = await authenticator.get_authenticated_user(None, {
|
||||
'username': '',
|
||||
'password': 'test_pass',
|
||||
})
|
||||
assert authorized is None
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_dummy_auth_with_global_password():
|
||||
async def test_dummy_auth_with_global_password():
|
||||
authenticator = DummyAuthenticator()
|
||||
authenticator.password = "test_password"
|
||||
|
||||
authorized = yield authenticator.get_authenticated_user(None, {
|
||||
authorized = await authenticator.get_authenticated_user(None, {
|
||||
'username': 'test_user',
|
||||
'password': 'test_password',
|
||||
})
|
||||
assert authorized['name'] == 'test_user'
|
||||
|
||||
authorized = yield authenticator.get_authenticated_user(None, {
|
||||
authorized = await authenticator.get_authenticated_user(None, {
|
||||
'username': 'test_user',
|
||||
'password': 'qwerty',
|
||||
})
|
||||
assert authorized is None
|
||||
|
||||
authorized = yield authenticator.get_authenticated_user(None, {
|
||||
authorized = await authenticator.get_authenticated_user(None, {
|
||||
'username': 'some_other_user',
|
||||
'password': 'test_password',
|
||||
})
|
||||
|
@@ -39,39 +39,36 @@ def wait_for_spawner(spawner, timeout=10):
|
||||
yield wait()
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_connection_hub_wrong_certs(app):
|
||||
async def test_connection_hub_wrong_certs(app):
|
||||
"""Connecting to the internal hub url fails without correct certs"""
|
||||
with pytest.raises(SSLError):
|
||||
kwargs = {'verify': False}
|
||||
r = yield async_requests.get(app.hub.url, **kwargs)
|
||||
r = await async_requests.get(app.hub.url, **kwargs)
|
||||
r.raise_for_status()
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_connection_proxy_api_wrong_certs(app):
|
||||
async def test_connection_proxy_api_wrong_certs(app):
|
||||
"""Connecting to the proxy api fails without correct certs"""
|
||||
with pytest.raises(SSLError):
|
||||
kwargs = {'verify': False}
|
||||
r = yield async_requests.get(app.proxy.api_url, **kwargs)
|
||||
r = await async_requests.get(app.proxy.api_url, **kwargs)
|
||||
r.raise_for_status()
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_connection_notebook_wrong_certs(app):
|
||||
async def test_connection_notebook_wrong_certs(app):
|
||||
"""Connecting to a notebook fails without correct certs"""
|
||||
with mock.patch.dict(
|
||||
app.config.LocalProcessSpawner,
|
||||
{'cmd': [sys.executable, '-m', 'jupyterhub.tests.mocksu']}
|
||||
):
|
||||
user = add_user(app.db, app, name='foo')
|
||||
yield user.spawn()
|
||||
yield wait_for_spawner(user.spawner)
|
||||
await user.spawn()
|
||||
await wait_for_spawner(user.spawner)
|
||||
spawner = user.spawner
|
||||
status = yield spawner.poll()
|
||||
status = await spawner.poll()
|
||||
assert status is None
|
||||
|
||||
with pytest.raises(SSLError):
|
||||
kwargs = {'verify': False}
|
||||
r = yield async_requests.get(spawner.server.url, **kwargs)
|
||||
r = await async_requests.get(spawner.server.url, **kwargs)
|
||||
r.raise_for_status()
|
||||
|
@@ -17,16 +17,15 @@ def named_servers(app):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_default_server(app, named_servers):
|
||||
async def test_default_server(app, named_servers):
|
||||
"""Test the default /users/:user/server handler when named servers are enabled"""
|
||||
username = 'rosie'
|
||||
user = add_user(app.db, app, name=username)
|
||||
r = yield api_request(app, 'users', username, 'server', method='post')
|
||||
r = await api_request(app, 'users', username, 'server', method='post')
|
||||
assert r.status_code == 201
|
||||
assert r.text == ''
|
||||
|
||||
r = yield api_request(app, 'users', username)
|
||||
r = await api_request(app, 'users', username)
|
||||
r.raise_for_status()
|
||||
|
||||
user_model = normalize_user(r.json())
|
||||
@@ -51,11 +50,11 @@ def test_default_server(app, named_servers):
|
||||
|
||||
|
||||
# now stop the server
|
||||
r = yield api_request(app, 'users', username, 'server', method='delete')
|
||||
r = await api_request(app, 'users', username, 'server', method='delete')
|
||||
assert r.status_code == 204
|
||||
assert r.text == ''
|
||||
|
||||
r = yield api_request(app, 'users', username)
|
||||
r = await api_request(app, 'users', username)
|
||||
r.raise_for_status()
|
||||
|
||||
user_model = normalize_user(r.json())
|
||||
@@ -66,20 +65,19 @@ def test_default_server(app, named_servers):
|
||||
})
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_create_named_server(app, named_servers):
|
||||
async def test_create_named_server(app, named_servers):
|
||||
username = 'walnut'
|
||||
user = add_user(app.db, app, name=username)
|
||||
# assert user.allow_named_servers == True
|
||||
cookies = yield app.login_user(username)
|
||||
cookies = await app.login_user(username)
|
||||
servername = 'trevor'
|
||||
r = yield api_request(app, 'users', username, 'servers', servername, method='post')
|
||||
r = await api_request(app, 'users', username, 'servers', servername, method='post')
|
||||
r.raise_for_status()
|
||||
assert r.status_code == 201
|
||||
assert r.text == ''
|
||||
|
||||
url = url_path_join(public_url(app, user), servername, 'env')
|
||||
r = yield async_requests.get(url, cookies=cookies)
|
||||
r = await async_requests.get(url, cookies=cookies)
|
||||
r.raise_for_status()
|
||||
assert r.url == url
|
||||
env = r.json()
|
||||
@@ -87,7 +85,7 @@ def test_create_named_server(app, named_servers):
|
||||
assert prefix == user.spawners[servername].server.base_url
|
||||
assert prefix.endswith('/user/%s/%s/' % (username, servername))
|
||||
|
||||
r = yield api_request(app, 'users', username)
|
||||
r = await api_request(app, 'users', username)
|
||||
r.raise_for_status()
|
||||
|
||||
user_model = normalize_user(r.json())
|
||||
@@ -111,22 +109,21 @@ def test_create_named_server(app, named_servers):
|
||||
})
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_delete_named_server(app, named_servers):
|
||||
async def test_delete_named_server(app, named_servers):
|
||||
username = 'donaar'
|
||||
user = add_user(app.db, app, name=username)
|
||||
assert user.allow_named_servers
|
||||
cookies = app.login_user(username)
|
||||
servername = 'splugoth'
|
||||
r = yield api_request(app, 'users', username, 'servers', servername, method='post')
|
||||
r = await api_request(app, 'users', username, 'servers', servername, method='post')
|
||||
r.raise_for_status()
|
||||
assert r.status_code == 201
|
||||
|
||||
r = yield api_request(app, 'users', username, 'servers', servername, method='delete')
|
||||
r = await api_request(app, 'users', username, 'servers', servername, method='delete')
|
||||
r.raise_for_status()
|
||||
assert r.status_code == 204
|
||||
|
||||
r = yield api_request(app, 'users', username)
|
||||
r = await api_request(app, 'users', username)
|
||||
r.raise_for_status()
|
||||
|
||||
user_model = normalize_user(r.json())
|
||||
@@ -140,7 +137,7 @@ def test_delete_named_server(app, named_servers):
|
||||
# low-level record still exists
|
||||
assert servername in user.orm_spawners
|
||||
|
||||
r = yield api_request(
|
||||
r = await api_request(
|
||||
app, 'users', username, 'servers', servername,
|
||||
method='delete',
|
||||
data=json.dumps({'remove': True}),
|
||||
@@ -151,11 +148,10 @@ def test_delete_named_server(app, named_servers):
|
||||
assert servername not in user.orm_spawners
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_named_server_disabled(app):
|
||||
async def test_named_server_disabled(app):
|
||||
username = 'user'
|
||||
servername = 'okay'
|
||||
r = yield api_request(app, 'users', username, 'servers', servername, method='post')
|
||||
r = await api_request(app, 'users', username, 'servers', servername, method='post')
|
||||
assert r.status_code == 400
|
||||
r = yield api_request(app, 'users', username, 'servers', servername, method='delete')
|
||||
r = await api_request(app, 'users', username, 'servers', servername, method='delete')
|
||||
assert r.status_code == 400
|
||||
|
@@ -205,8 +205,7 @@ def test_token_find(db):
|
||||
assert found is None
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_spawn_fails(db):
|
||||
async def test_spawn_fails(db):
|
||||
orm_user = orm.User(name='aeofel')
|
||||
db.add(orm_user)
|
||||
db.commit()
|
||||
@@ -223,7 +222,7 @@ def test_spawn_fails(db):
|
||||
})
|
||||
|
||||
with pytest.raises(RuntimeError) as exc:
|
||||
yield user.spawn()
|
||||
await user.spawn()
|
||||
assert user.spawners[''].server is None
|
||||
assert not user.running
|
||||
|
||||
@@ -246,8 +245,7 @@ def test_groups(db):
|
||||
assert group.users == []
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_auth_state(db):
|
||||
async def test_auth_state(db):
|
||||
orm_user = orm.User(name='eve')
|
||||
db.add(orm_user)
|
||||
db.commit()
|
||||
@@ -262,51 +260,51 @@ def test_auth_state(db):
|
||||
state = {'key': 'value'}
|
||||
ck.keys = []
|
||||
with pytest.raises(crypto.EncryptionUnavailable):
|
||||
yield user.save_auth_state(state)
|
||||
await user.save_auth_state(state)
|
||||
|
||||
assert user.encrypted_auth_state is None
|
||||
# saving/loading None doesn't require keys
|
||||
yield user.save_auth_state(None)
|
||||
current = yield user.get_auth_state()
|
||||
await user.save_auth_state(None)
|
||||
current = await user.get_auth_state()
|
||||
assert current is None
|
||||
|
||||
first_key = os.urandom(32)
|
||||
second_key = os.urandom(32)
|
||||
ck.keys = [first_key]
|
||||
yield user.save_auth_state(state)
|
||||
await user.save_auth_state(state)
|
||||
assert user.encrypted_auth_state is not None
|
||||
decrypted_state = yield user.get_auth_state()
|
||||
decrypted_state = await user.get_auth_state()
|
||||
assert decrypted_state == state
|
||||
|
||||
# can't read auth_state without keys
|
||||
ck.keys = []
|
||||
auth_state = yield user.get_auth_state()
|
||||
auth_state = await user.get_auth_state()
|
||||
assert auth_state is None
|
||||
|
||||
# key rotation works
|
||||
db.rollback()
|
||||
ck.keys = [second_key, first_key]
|
||||
decrypted_state = yield user.get_auth_state()
|
||||
decrypted_state = await user.get_auth_state()
|
||||
assert decrypted_state == state
|
||||
|
||||
new_state = {'key': 'newvalue'}
|
||||
yield user.save_auth_state(new_state)
|
||||
await user.save_auth_state(new_state)
|
||||
db.commit()
|
||||
|
||||
ck.keys = [first_key]
|
||||
db.rollback()
|
||||
# can't read anymore with new-key after encrypting with second-key
|
||||
decrypted_state = yield user.get_auth_state()
|
||||
decrypted_state = await user.get_auth_state()
|
||||
assert decrypted_state is None
|
||||
|
||||
yield user.save_auth_state(new_state)
|
||||
decrypted_state = yield user.get_auth_state()
|
||||
await user.save_auth_state(new_state)
|
||||
decrypted_state = await user.get_auth_state()
|
||||
assert decrypted_state == new_state
|
||||
|
||||
ck.keys = []
|
||||
db.rollback()
|
||||
|
||||
decrypted_state = yield user.get_auth_state()
|
||||
decrypted_state = await user.get_auth_state()
|
||||
assert decrypted_state is None
|
||||
|
||||
|
||||
|
@@ -30,91 +30,81 @@ def get_page(path, app, hub=True, **kw):
|
||||
return async_requests.get(ujoin(base_url, path), **kw)
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_root_no_auth(app):
|
||||
async def test_root_no_auth(app):
|
||||
url = ujoin(public_host(app), app.hub.base_url)
|
||||
r = yield async_requests.get(url)
|
||||
r = await async_requests.get(url)
|
||||
r.raise_for_status()
|
||||
assert r.url == ujoin(url, 'login')
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_root_auth(app):
|
||||
cookies = yield app.login_user('river')
|
||||
r = yield async_requests.get(public_url(app), cookies=cookies)
|
||||
async def test_root_auth(app):
|
||||
cookies = await app.login_user('river')
|
||||
r = await async_requests.get(public_url(app), cookies=cookies)
|
||||
r.raise_for_status()
|
||||
assert r.url.startswith(public_url(app, app.users['river']))
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_root_redirect(app):
|
||||
async def test_root_redirect(app):
|
||||
name = 'wash'
|
||||
cookies = yield app.login_user(name)
|
||||
cookies = await app.login_user(name)
|
||||
next_url = ujoin(app.base_url, 'user/other/test.ipynb')
|
||||
url = '/?' + urlencode({'next': next_url})
|
||||
r = yield get_page(url, app, cookies=cookies)
|
||||
r = await get_page(url, app, cookies=cookies)
|
||||
r.raise_for_status()
|
||||
path = urlparse(r.url).path
|
||||
assert path == ujoin(app.base_url, 'user/%s/test.ipynb' % name)
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_root_default_url_noauth(app):
|
||||
async def test_root_default_url_noauth(app):
|
||||
with mock.patch.dict(app.tornado_settings,
|
||||
{'default_url': '/foo/bar'}):
|
||||
r = yield get_page('/', app, allow_redirects=False)
|
||||
r = await get_page('/', app, allow_redirects=False)
|
||||
r.raise_for_status()
|
||||
url = r.headers.get('Location', '')
|
||||
path = urlparse(url).path
|
||||
assert path == '/foo/bar'
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_root_default_url_auth(app):
|
||||
async def test_root_default_url_auth(app):
|
||||
name = 'wash'
|
||||
cookies = yield app.login_user(name)
|
||||
cookies = await app.login_user(name)
|
||||
with mock.patch.dict(app.tornado_settings,
|
||||
{'default_url': '/foo/bar'}):
|
||||
r = yield get_page('/', app, cookies=cookies, allow_redirects=False)
|
||||
r = await get_page('/', app, cookies=cookies, allow_redirects=False)
|
||||
r.raise_for_status()
|
||||
url = r.headers.get('Location', '')
|
||||
path = urlparse(url).path
|
||||
assert path == '/foo/bar'
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_home_no_auth(app):
|
||||
r = yield get_page('home', app, allow_redirects=False)
|
||||
async def test_home_no_auth(app):
|
||||
r = await get_page('home', app, allow_redirects=False)
|
||||
r.raise_for_status()
|
||||
assert r.status_code == 302
|
||||
assert '/hub/login' in r.headers['Location']
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_home_auth(app):
|
||||
cookies = yield app.login_user('river')
|
||||
r = yield get_page('home', app, cookies=cookies)
|
||||
async def test_home_auth(app):
|
||||
cookies = await app.login_user('river')
|
||||
r = await get_page('home', app, cookies=cookies)
|
||||
r.raise_for_status()
|
||||
assert r.url.endswith('home')
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_admin_no_auth(app):
|
||||
r = yield get_page('admin', app)
|
||||
async def test_admin_no_auth(app):
|
||||
r = await get_page('admin', app)
|
||||
assert r.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_admin_not_admin(app):
|
||||
cookies = yield app.login_user('wash')
|
||||
r = yield get_page('admin', app, cookies=cookies)
|
||||
async def test_admin_not_admin(app):
|
||||
cookies = await app.login_user('wash')
|
||||
r = await get_page('admin', app, cookies=cookies)
|
||||
assert r.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_admin(app):
|
||||
cookies = yield app.login_user('admin')
|
||||
r = yield get_page('admin', app, cookies=cookies, allow_redirects=False)
|
||||
async def test_admin(app):
|
||||
cookies = await app.login_user('admin')
|
||||
r = await get_page('admin', app, cookies=cookies, allow_redirects=False)
|
||||
r.raise_for_status()
|
||||
assert r.url.endswith('/admin')
|
||||
|
||||
@@ -125,127 +115,120 @@ def test_admin(app):
|
||||
'admin',
|
||||
'name',
|
||||
])
|
||||
@pytest.mark.gen_test
|
||||
def test_admin_sort(app, sort):
|
||||
cookies = yield app.login_user('admin')
|
||||
r = yield get_page('admin?sort=%s' % sort, app, cookies=cookies)
|
||||
async def test_admin_sort(app, sort):
|
||||
cookies = await app.login_user('admin')
|
||||
r = await get_page('admin?sort=%s' % sort, app, cookies=cookies)
|
||||
r.raise_for_status()
|
||||
assert r.status_code == 200
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_spawn_redirect(app):
|
||||
async def test_spawn_redirect(app):
|
||||
name = 'wash'
|
||||
cookies = yield app.login_user(name)
|
||||
cookies = await app.login_user(name)
|
||||
u = app.users[orm.User.find(app.db, name)]
|
||||
|
||||
status = yield u.spawner.poll()
|
||||
status = await u.spawner.poll()
|
||||
assert status is not None
|
||||
|
||||
# test spawn page when no server is running
|
||||
r = yield get_page('spawn', app, cookies=cookies)
|
||||
r = await get_page('spawn', app, cookies=cookies)
|
||||
r.raise_for_status()
|
||||
print(urlparse(r.url))
|
||||
path = urlparse(r.url).path
|
||||
assert path == ujoin(app.base_url, 'user/%s/' % name)
|
||||
|
||||
# should have started server
|
||||
status = yield u.spawner.poll()
|
||||
status = await u.spawner.poll()
|
||||
assert status is None
|
||||
|
||||
# test spawn page when server is already running (just redirect)
|
||||
r = yield get_page('spawn', app, cookies=cookies)
|
||||
r = await get_page('spawn', app, cookies=cookies)
|
||||
r.raise_for_status()
|
||||
print(urlparse(r.url))
|
||||
path = urlparse(r.url).path
|
||||
assert path == ujoin(app.base_url, '/user/%s/' % name)
|
||||
|
||||
# stop server to ensure /user/name is handled by the Hub
|
||||
r = yield api_request(app, 'users', name, 'server', method='delete', cookies=cookies)
|
||||
r = await api_request(app, 'users', name, 'server', method='delete', cookies=cookies)
|
||||
r.raise_for_status()
|
||||
|
||||
# test handing of trailing slash on `/user/name`
|
||||
r = yield get_page('user/' + name, app, hub=False, cookies=cookies)
|
||||
r = await get_page('user/' + name, app, hub=False, cookies=cookies)
|
||||
r.raise_for_status()
|
||||
path = urlparse(r.url).path
|
||||
assert path == ujoin(app.base_url, '/user/%s/' % name)
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_spawn_handler_access(app):
|
||||
async def test_spawn_handler_access(app):
|
||||
name = 'winston'
|
||||
cookies = yield app.login_user(name)
|
||||
cookies = await app.login_user(name)
|
||||
u = app.users[orm.User.find(app.db, name)]
|
||||
|
||||
status = yield u.spawner.poll()
|
||||
status = await u.spawner.poll()
|
||||
assert status is not None
|
||||
|
||||
# spawn server via browser link with ?arg=value
|
||||
r = yield get_page('spawn', app, cookies=cookies, params={'arg': 'value'})
|
||||
r = await get_page('spawn', app, cookies=cookies, params={'arg': 'value'})
|
||||
r.raise_for_status()
|
||||
|
||||
# verify that request params got passed down
|
||||
# implemented in MockSpawner
|
||||
r = yield async_requests.get(ujoin(public_url(app, u), 'env'))
|
||||
r = await async_requests.get(ujoin(public_url(app, u), 'env'))
|
||||
env = r.json()
|
||||
assert 'HANDLER_ARGS' in env
|
||||
assert env['HANDLER_ARGS'] == 'arg=value'
|
||||
|
||||
# stop server
|
||||
r = yield api_request(app, 'users', name, 'server', method='delete')
|
||||
r = await api_request(app, 'users', name, 'server', method='delete')
|
||||
r.raise_for_status()
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_spawn_admin_access(app, admin_access):
|
||||
async def test_spawn_admin_access(app, admin_access):
|
||||
"""GET /user/:name as admin with admin-access spawns user's server"""
|
||||
cookies = yield app.login_user('admin')
|
||||
cookies = await app.login_user('admin')
|
||||
name = 'mariel'
|
||||
user = add_user(app.db, app=app, name=name)
|
||||
app.db.commit()
|
||||
r = yield get_page('user/' + name, app, cookies=cookies)
|
||||
r = await get_page('user/' + name, app, cookies=cookies)
|
||||
r.raise_for_status()
|
||||
assert (r.url.split('?')[0] + '/').startswith(public_url(app, user))
|
||||
r = yield get_page('user/{}/env'.format(name), app, hub=False, cookies=cookies)
|
||||
r = await get_page('user/{}/env'.format(name), app, hub=False, cookies=cookies)
|
||||
r.raise_for_status()
|
||||
env = r.json()
|
||||
assert env['JUPYTERHUB_USER'] == name
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_spawn_page(app):
|
||||
async def test_spawn_page(app):
|
||||
with mock.patch.dict(app.users.settings, {'spawner_class': FormSpawner}):
|
||||
cookies = yield app.login_user('jones')
|
||||
r = yield get_page('spawn', app, cookies=cookies)
|
||||
cookies = await app.login_user('jones')
|
||||
r = await get_page('spawn', app, cookies=cookies)
|
||||
assert r.url.endswith('/spawn')
|
||||
assert FormSpawner.options_form in r.text
|
||||
|
||||
r = yield get_page('spawn?next=foo', app, cookies=cookies)
|
||||
r = await get_page('spawn?next=foo', app, cookies=cookies)
|
||||
assert r.url.endswith('/spawn?next=foo')
|
||||
assert FormSpawner.options_form in r.text
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_spawn_page_admin(app, admin_access):
|
||||
async def test_spawn_page_admin(app, admin_access):
|
||||
with mock.patch.dict(app.users.settings, {'spawner_class': FormSpawner}):
|
||||
cookies = yield app.login_user('admin')
|
||||
cookies = await app.login_user('admin')
|
||||
u = add_user(app.db, app=app, name='melanie')
|
||||
r = yield get_page('spawn/' + u.name, app, cookies=cookies)
|
||||
r = await get_page('spawn/' + u.name, app, cookies=cookies)
|
||||
assert r.url.endswith('/spawn/' + u.name)
|
||||
assert FormSpawner.options_form in r.text
|
||||
assert "Spawning server for {}".format(u.name) in r.text
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_spawn_form(app):
|
||||
async def test_spawn_form(app):
|
||||
with mock.patch.dict(app.users.settings, {'spawner_class': FormSpawner}):
|
||||
base_url = ujoin(public_host(app), app.hub.base_url)
|
||||
cookies = yield app.login_user('jones')
|
||||
cookies = await app.login_user('jones')
|
||||
orm_u = orm.User.find(app.db, 'jones')
|
||||
u = app.users[orm_u]
|
||||
yield u.stop()
|
||||
await u.stop()
|
||||
next_url = ujoin(app.base_url, 'user/jones/tree')
|
||||
r = yield async_requests.post(
|
||||
r = await async_requests.post(
|
||||
url_concat(ujoin(base_url, 'spawn'), {'next': next_url}),
|
||||
cookies=cookies,
|
||||
data={'bounds': ['-1', '1'], 'energy': '511keV'},
|
||||
@@ -259,15 +242,14 @@ def test_spawn_form(app):
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_spawn_form_admin_access(app, admin_access):
|
||||
async def test_spawn_form_admin_access(app, admin_access):
|
||||
with mock.patch.dict(app.tornado_settings, {'spawner_class': FormSpawner}):
|
||||
base_url = ujoin(public_host(app), app.hub.base_url)
|
||||
cookies = yield app.login_user('admin')
|
||||
cookies = await app.login_user('admin')
|
||||
u = add_user(app.db, app=app, name='martha')
|
||||
next_url = ujoin(app.base_url, 'user', u.name, 'tree')
|
||||
|
||||
r = yield async_requests.post(
|
||||
r = await async_requests.post(
|
||||
url_concat(ujoin(base_url, 'spawn', u.name), {'next': next_url}),
|
||||
cookies=cookies,
|
||||
data={'bounds': ['-3', '3'], 'energy': '938MeV'},
|
||||
@@ -282,16 +264,15 @@ def test_spawn_form_admin_access(app, admin_access):
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_spawn_form_with_file(app):
|
||||
async def test_spawn_form_with_file(app):
|
||||
with mock.patch.dict(app.tornado_settings, {'spawner_class': FormSpawner}):
|
||||
base_url = ujoin(public_host(app), app.hub.base_url)
|
||||
cookies = yield app.login_user('jones')
|
||||
cookies = await app.login_user('jones')
|
||||
orm_u = orm.User.find(app.db, 'jones')
|
||||
u = app.users[orm_u]
|
||||
yield u.stop()
|
||||
await u.stop()
|
||||
|
||||
r = yield async_requests.post(ujoin(base_url, 'spawn'),
|
||||
r = await async_requests.post(ujoin(base_url, 'spawn'),
|
||||
cookies=cookies,
|
||||
data={
|
||||
'bounds': ['-1', '1'],
|
||||
@@ -310,12 +291,11 @@ def test_spawn_form_with_file(app):
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_user_redirect(app):
|
||||
async def test_user_redirect(app):
|
||||
name = 'wash'
|
||||
cookies = yield app.login_user(name)
|
||||
cookies = await app.login_user(name)
|
||||
|
||||
r = yield get_page('/user-redirect/tree/top/', app)
|
||||
r = await get_page('/user-redirect/tree/top/', app)
|
||||
r.raise_for_status()
|
||||
print(urlparse(r.url))
|
||||
path = urlparse(r.url).path
|
||||
@@ -325,32 +305,31 @@ def test_user_redirect(app):
|
||||
'next': ujoin(app.hub.base_url, '/user-redirect/tree/top/')
|
||||
})
|
||||
|
||||
r = yield get_page('/user-redirect/notebooks/test.ipynb', app, cookies=cookies)
|
||||
r = await get_page('/user-redirect/notebooks/test.ipynb', app, cookies=cookies)
|
||||
r.raise_for_status()
|
||||
print(urlparse(r.url))
|
||||
path = urlparse(r.url).path
|
||||
assert path == ujoin(app.base_url, '/user/%s/notebooks/test.ipynb' % name)
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_user_redirect_deprecated(app):
|
||||
async def test_user_redirect_deprecated(app):
|
||||
"""redirecting from /user/someonelse/ URLs (deprecated)"""
|
||||
name = 'wash'
|
||||
cookies = yield app.login_user(name)
|
||||
cookies = await app.login_user(name)
|
||||
|
||||
r = yield get_page('/user/baduser', app, cookies=cookies, hub=False)
|
||||
r = await get_page('/user/baduser', app, cookies=cookies, hub=False)
|
||||
r.raise_for_status()
|
||||
print(urlparse(r.url))
|
||||
path = urlparse(r.url).path
|
||||
assert path == ujoin(app.base_url, '/user/%s/' % name)
|
||||
|
||||
r = yield get_page('/user/baduser/test.ipynb', app, cookies=cookies, hub=False)
|
||||
r = await get_page('/user/baduser/test.ipynb', app, cookies=cookies, hub=False)
|
||||
r.raise_for_status()
|
||||
print(urlparse(r.url))
|
||||
path = urlparse(r.url).path
|
||||
assert path == ujoin(app.base_url, '/user/%s/test.ipynb' % name)
|
||||
|
||||
r = yield get_page('/user/baduser/test.ipynb', app, hub=False)
|
||||
r = await get_page('/user/baduser/test.ipynb', app, hub=False)
|
||||
r.raise_for_status()
|
||||
print(urlparse(r.url))
|
||||
path = urlparse(r.url).path
|
||||
@@ -361,11 +340,10 @@ def test_user_redirect_deprecated(app):
|
||||
})
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_login_fail(app):
|
||||
async def test_login_fail(app):
|
||||
name = 'wash'
|
||||
base_url = public_url(app)
|
||||
r = yield async_requests.post(base_url + 'hub/login',
|
||||
r = await async_requests.post(base_url + 'hub/login',
|
||||
data={
|
||||
'username': name,
|
||||
'password': 'wrong',
|
||||
@@ -375,8 +353,7 @@ def test_login_fail(app):
|
||||
assert not r.cookies
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_login_strip(app):
|
||||
async def test_login_strip(app):
|
||||
"""Test that login form doesn't strip whitespace from passwords"""
|
||||
form_data = {
|
||||
'username': 'spiff',
|
||||
@@ -389,7 +366,7 @@ def test_login_strip(app):
|
||||
called_with.append(data)
|
||||
|
||||
with mock.patch.object(app.authenticator, 'authenticate', mock_authenticate):
|
||||
yield async_requests.post(base_url + 'hub/login',
|
||||
await async_requests.post(base_url + 'hub/login',
|
||||
data=form_data,
|
||||
allow_redirects=False,
|
||||
)
|
||||
@@ -415,9 +392,8 @@ def test_login_strip(app):
|
||||
(False, '//other.domain', ''),
|
||||
]
|
||||
)
|
||||
@pytest.mark.gen_test
|
||||
def test_login_redirect(app, running, next_url, location):
|
||||
cookies = yield app.login_user('river')
|
||||
async def test_login_redirect(app, running, next_url, location):
|
||||
cookies = await app.login_user('river')
|
||||
user = app.users['river']
|
||||
if location:
|
||||
location = ujoin(app.base_url, location)
|
||||
@@ -433,18 +409,17 @@ def test_login_redirect(app, running, next_url, location):
|
||||
|
||||
if running and not user.active:
|
||||
# ensure running
|
||||
yield user.spawn()
|
||||
await user.spawn()
|
||||
elif user.active and not running:
|
||||
# ensure not running
|
||||
yield user.stop()
|
||||
r = yield get_page(url, app, cookies=cookies, allow_redirects=False)
|
||||
await user.stop()
|
||||
r = await get_page(url, app, cookies=cookies, allow_redirects=False)
|
||||
r.raise_for_status()
|
||||
assert r.status_code == 302
|
||||
assert location == r.headers['Location']
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_auto_login(app, request):
|
||||
async def test_auto_login(app, request):
|
||||
class DummyLoginHandler(BaseHandler):
|
||||
def get(self):
|
||||
self.write('ok!')
|
||||
@@ -453,7 +428,7 @@ def test_auto_login(app, request):
|
||||
(ujoin(app.hub.base_url, 'dummy'), DummyLoginHandler),
|
||||
])
|
||||
# no auto_login: end up at /hub/login
|
||||
r = yield async_requests.get(base_url)
|
||||
r = await async_requests.get(base_url)
|
||||
assert r.url == public_url(app, path='hub/login')
|
||||
# enable auto_login: redirect from /hub/login to /hub/dummy
|
||||
authenticator = Authenticator(auto_login=True)
|
||||
@@ -462,28 +437,28 @@ def test_auto_login(app, request):
|
||||
with mock.patch.dict(app.tornado_settings, {
|
||||
'authenticator': authenticator,
|
||||
}):
|
||||
r = yield async_requests.get(base_url)
|
||||
r = await async_requests.get(base_url)
|
||||
assert r.url == public_url(app, path='hub/dummy')
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_auto_login_logout(app):
|
||||
|
||||
async def test_auto_login_logout(app):
|
||||
name = 'burnham'
|
||||
cookies = yield app.login_user(name)
|
||||
cookies = await app.login_user(name)
|
||||
|
||||
with mock.patch.dict(app.tornado_settings, {
|
||||
'authenticator': Authenticator(auto_login=True),
|
||||
}):
|
||||
r = yield async_requests.get(public_host(app) + app.tornado_settings['logout_url'], cookies=cookies)
|
||||
r = await async_requests.get(public_host(app) + app.tornado_settings['logout_url'], cookies=cookies)
|
||||
r.raise_for_status()
|
||||
logout_url = public_host(app) + app.tornado_settings['logout_url']
|
||||
assert r.url == logout_url
|
||||
assert r.cookies == {}
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_logout(app):
|
||||
|
||||
async def test_logout(app):
|
||||
name = 'wash'
|
||||
cookies = yield app.login_user(name)
|
||||
r = yield async_requests.get(public_host(app) + app.tornado_settings['logout_url'], cookies=cookies)
|
||||
cookies = await app.login_user(name)
|
||||
r = await async_requests.get(public_host(app) + app.tornado_settings['logout_url'], cookies=cookies)
|
||||
r.raise_for_status()
|
||||
login_url = public_host(app) + app.tornado_settings['login_url']
|
||||
assert r.url == login_url
|
||||
@@ -491,7 +466,6 @@ def test_logout(app):
|
||||
|
||||
|
||||
@pytest.mark.parametrize('shutdown_on_logout', [True, False])
|
||||
@pytest.mark.gen_test
|
||||
async def test_shutdown_on_logout(app, shutdown_on_logout):
|
||||
name = 'shutitdown'
|
||||
cookies = await app.login_user(name)
|
||||
@@ -535,50 +509,46 @@ async def test_shutdown_on_logout(app, shutdown_on_logout):
|
||||
assert spawner.ready == (not shutdown_on_logout)
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_login_no_whitelist_adds_user(app):
|
||||
async def test_login_no_whitelist_adds_user(app):
|
||||
auth = app.authenticator
|
||||
mock_add_user = mock.Mock()
|
||||
with mock.patch.object(auth, 'add_user', mock_add_user):
|
||||
cookies = yield app.login_user('jubal')
|
||||
cookies = await app.login_user('jubal')
|
||||
|
||||
user = app.users['jubal']
|
||||
assert mock_add_user.mock_calls == [mock.call(user)]
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_static_files(app):
|
||||
async def test_static_files(app):
|
||||
base_url = ujoin(public_host(app), app.hub.base_url)
|
||||
r = yield async_requests.get(ujoin(base_url, 'logo'))
|
||||
r = await async_requests.get(ujoin(base_url, 'logo'))
|
||||
r.raise_for_status()
|
||||
assert r.headers['content-type'] == 'image/png'
|
||||
r = yield async_requests.get(ujoin(base_url, 'static', 'images', 'jupyter.png'))
|
||||
r = await async_requests.get(ujoin(base_url, 'static', 'images', 'jupyter.png'))
|
||||
r.raise_for_status()
|
||||
assert r.headers['content-type'] == 'image/png'
|
||||
r = yield async_requests.get(ujoin(base_url, 'static', 'css', 'style.min.css'))
|
||||
r = await async_requests.get(ujoin(base_url, 'static', 'css', 'style.min.css'))
|
||||
r.raise_for_status()
|
||||
assert r.headers['content-type'] == 'text/css'
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_token_auth(app):
|
||||
cookies = yield app.login_user('token')
|
||||
r = yield get_page('token', app, cookies=cookies)
|
||||
async def test_token_auth(app):
|
||||
cookies = await app.login_user('token')
|
||||
r = await get_page('token', app, cookies=cookies)
|
||||
r.raise_for_status()
|
||||
assert r.status_code == 200
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_oauth_token_page(app):
|
||||
async def test_oauth_token_page(app):
|
||||
name = 'token'
|
||||
cookies = yield app.login_user(name)
|
||||
cookies = await app.login_user(name)
|
||||
user = app.users[orm.User.find(app.db, name)]
|
||||
client = orm.OAuthClient(identifier='token')
|
||||
app.db.add(client)
|
||||
oauth_token = orm.OAuthAccessToken(client=client, user=user, grant_type=orm.GrantType.authorization_code)
|
||||
app.db.add(oauth_token)
|
||||
app.db.commit()
|
||||
r = yield get_page('token', app, cookies=cookies)
|
||||
r = await get_page('token', app, cookies=cookies)
|
||||
r.raise_for_status()
|
||||
assert r.status_code == 200
|
||||
|
||||
@@ -587,14 +557,11 @@ def test_oauth_token_page(app):
|
||||
503,
|
||||
404,
|
||||
])
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_proxy_error(app, error_status):
|
||||
r = yield get_page('/error/%i' % error_status, app)
|
||||
async def test_proxy_error(app, error_status):
|
||||
r = await get_page('/error/%i' % error_status, app)
|
||||
assert r.status_code == 200
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
@pytest.mark.parametrize(
|
||||
"announcements",
|
||||
[
|
||||
@@ -604,7 +571,7 @@ def test_proxy_error(app, error_status):
|
||||
"login,logout",
|
||||
]
|
||||
)
|
||||
def test_announcements(app, announcements):
|
||||
async def test_announcements(app, announcements):
|
||||
"""Test announcements on various pages"""
|
||||
# Default announcement - same on all pages
|
||||
ann01 = "ANNOUNCE01"
|
||||
@@ -620,26 +587,26 @@ def test_announcements(app, announcements):
|
||||
else:
|
||||
assert ann01 in text
|
||||
|
||||
cookies = yield app.login_user("jones")
|
||||
cookies = await app.login_user("jones")
|
||||
|
||||
with mock.patch.dict(
|
||||
app.tornado_settings,
|
||||
{"template_vars": template_vars, "spawner_class": FormSpawner},
|
||||
):
|
||||
r = yield get_page("login", app)
|
||||
r = await get_page("login", app)
|
||||
r.raise_for_status()
|
||||
assert_announcement("login", r.text)
|
||||
r = yield get_page("spawn", app, cookies=cookies)
|
||||
r = await get_page("spawn", app, cookies=cookies)
|
||||
r.raise_for_status()
|
||||
assert_announcement("spawn", r.text)
|
||||
r = yield get_page("home", app, cookies=cookies) # hub/home
|
||||
r = await get_page("home", app, cookies=cookies) # hub/home
|
||||
r.raise_for_status()
|
||||
assert_announcement("home", r.text)
|
||||
# need auto_login=True to get logout page
|
||||
auto_login = app.authenticator.auto_login
|
||||
app.authenticator.auto_login = True
|
||||
try:
|
||||
r = yield get_page("logout", app, cookies=cookies)
|
||||
r = await get_page("logout", app, cookies=cookies)
|
||||
finally:
|
||||
app.authenticator.auto_login = auto_login
|
||||
r.raise_for_status()
|
||||
@@ -654,18 +621,16 @@ def test_announcements(app, announcements):
|
||||
"redirect_uri=ok&client_id=nosuchthing",
|
||||
]
|
||||
)
|
||||
@pytest.mark.gen_test
|
||||
def test_bad_oauth_get(app, params):
|
||||
cookies = yield app.login_user("authorizer")
|
||||
r = yield get_page("hub/api/oauth2/authorize?" + params, app, hub=False, cookies=cookies)
|
||||
async def test_bad_oauth_get(app, params):
|
||||
cookies = await app.login_user("authorizer")
|
||||
r = await get_page("hub/api/oauth2/authorize?" + params, app, hub=False, cookies=cookies)
|
||||
assert r.status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_token_page(app):
|
||||
async def test_token_page(app):
|
||||
name = "cake"
|
||||
cookies = yield app.login_user(name)
|
||||
r = yield get_page("token", app, cookies=cookies)
|
||||
cookies = await app.login_user(name)
|
||||
r = await get_page("token", app, cookies=cookies)
|
||||
r.raise_for_status()
|
||||
assert urlparse(r.url).path.endswith('/hub/token')
|
||||
def extract_body(r):
|
||||
@@ -684,7 +649,7 @@ def test_token_page(app):
|
||||
token = user.new_api_token(expires_in=60, note="my-test-token")
|
||||
app.db.commit()
|
||||
|
||||
r = yield get_page("token", app, cookies=cookies)
|
||||
r = await get_page("token", app, cookies=cookies)
|
||||
r.raise_for_status()
|
||||
body = extract_body(r)
|
||||
assert "API Tokens" in body, body
|
||||
@@ -695,10 +660,10 @@ def test_token_page(app):
|
||||
# spawn the user to trigger oauth, etc.
|
||||
# request an oauth token
|
||||
user.spawner.cmd = [sys.executable, '-m', 'jupyterhub.singleuser']
|
||||
r = yield get_page("spawn", app, cookies=cookies)
|
||||
r = await get_page("spawn", app, cookies=cookies)
|
||||
r.raise_for_status()
|
||||
|
||||
r = yield get_page("token", app, cookies=cookies)
|
||||
r = await get_page("token", app, cookies=cookies)
|
||||
r.raise_for_status()
|
||||
body = extract_body(r)
|
||||
assert "API Tokens" in body, body
|
||||
@@ -706,31 +671,27 @@ def test_token_page(app):
|
||||
assert "Authorized Applications" in body, body
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_server_not_running_api_request(app):
|
||||
cookies = yield app.login_user("bees")
|
||||
r = yield get_page("user/bees/api/status", app, hub=False, cookies=cookies)
|
||||
async def test_server_not_running_api_request(app):
|
||||
cookies = await app.login_user("bees")
|
||||
r = await get_page("user/bees/api/status", app, hub=False, cookies=cookies)
|
||||
assert r.status_code == 404
|
||||
assert r.headers["content-type"] == "application/json"
|
||||
assert r.json() == {"message": "bees is not running"}
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_metrics_no_auth(app):
|
||||
r = yield get_page("metrics", app)
|
||||
async def test_metrics_no_auth(app):
|
||||
r = await get_page("metrics", app)
|
||||
assert r.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_metrics_auth(app):
|
||||
cookies = yield app.login_user('river')
|
||||
async def test_metrics_auth(app):
|
||||
cookies = await app.login_user('river')
|
||||
metrics_url = ujoin(public_host(app), app.hub.base_url, 'metrics')
|
||||
r = yield get_page("metrics", app, cookies=cookies)
|
||||
r = await get_page("metrics", app, cookies=cookies)
|
||||
assert r.status_code == 200
|
||||
assert r.url == metrics_url
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_health_check_request(app):
|
||||
r = yield get_page('health', app)
|
||||
async def test_health_check_request(app):
|
||||
r = await get_page('health', app)
|
||||
assert r.status_code == 200
|
||||
|
@@ -16,6 +16,7 @@ from .mocking import MockHub
|
||||
from .test_api import api_request, add_user
|
||||
from ..utils import wait_for_http_server, url_path_join as ujoin
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def disable_check_routes(app):
|
||||
# disable periodic check_routes while we are testing
|
||||
@@ -25,9 +26,8 @@ def disable_check_routes(app):
|
||||
finally:
|
||||
app.last_activity_callback.start()
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_external_proxy(request):
|
||||
|
||||
async def test_external_proxy(request):
|
||||
auth_token = 'secret!'
|
||||
proxy_ip = '127.0.0.1'
|
||||
proxy_port = 54321
|
||||
@@ -71,23 +71,23 @@ def test_external_proxy(request):
|
||||
|
||||
def wait_for_proxy():
|
||||
return wait_for_http_server('http://%s:%i' % (proxy_ip, proxy_port))
|
||||
yield wait_for_proxy()
|
||||
await wait_for_proxy()
|
||||
|
||||
yield app.initialize([])
|
||||
yield app.start()
|
||||
await app.initialize([])
|
||||
await app.start()
|
||||
assert app.proxy.proxy_process is None
|
||||
|
||||
# test if api service has a root route '/'
|
||||
routes = yield app.proxy.get_all_routes()
|
||||
routes = await app.proxy.get_all_routes()
|
||||
assert list(routes.keys()) == [app.hub.routespec]
|
||||
|
||||
# add user to the db and start a single user server
|
||||
name = 'river'
|
||||
add_user(app.db, app, name=name)
|
||||
r = yield api_request(app, 'users', name, 'server', method='post')
|
||||
r = await api_request(app, 'users', name, 'server', method='post')
|
||||
r.raise_for_status()
|
||||
|
||||
routes = yield app.proxy.get_all_routes()
|
||||
routes = await app.proxy.get_all_routes()
|
||||
# sets the desired path result
|
||||
user_path = ujoin(app.base_url, 'user/river') + '/'
|
||||
print(app.base_url, user_path)
|
||||
@@ -101,18 +101,18 @@ def test_external_proxy(request):
|
||||
proxy.terminate()
|
||||
proxy.wait(timeout=10)
|
||||
proxy = Popen(cmd, env=env)
|
||||
yield wait_for_proxy()
|
||||
await wait_for_proxy()
|
||||
|
||||
routes = yield app.proxy.get_all_routes()
|
||||
routes = await app.proxy.get_all_routes()
|
||||
|
||||
assert list(routes.keys()) == []
|
||||
|
||||
# poke the server to update the proxy
|
||||
r = yield api_request(app, 'proxy', method='post')
|
||||
r = await api_request(app, 'proxy', method='post')
|
||||
r.raise_for_status()
|
||||
|
||||
# check that the routes are correct
|
||||
routes = yield app.proxy.get_all_routes()
|
||||
routes = await app.proxy.get_all_routes()
|
||||
assert sorted(routes.keys()) == [app.hub.routespec, user_spec]
|
||||
|
||||
# teardown the proxy, and start a new one with different auth and port
|
||||
@@ -131,11 +131,11 @@ def test_external_proxy(request):
|
||||
if app.subdomain_host:
|
||||
cmd.append('--host-routing')
|
||||
proxy = Popen(cmd, env=env)
|
||||
yield wait_for_proxy()
|
||||
await wait_for_proxy()
|
||||
|
||||
# tell the hub where the new proxy is
|
||||
new_api_url = 'http://{}:{}'.format(proxy_ip, proxy_port)
|
||||
r = yield api_request(app, 'proxy', method='patch', data=json.dumps({
|
||||
r = await api_request(app, 'proxy', method='patch', data=json.dumps({
|
||||
'api_url': new_api_url,
|
||||
'auth_token': new_auth_token,
|
||||
}))
|
||||
@@ -145,11 +145,10 @@ def test_external_proxy(request):
|
||||
assert app.proxy.auth_token == new_auth_token
|
||||
|
||||
# check that the routes are correct
|
||||
routes = yield app.proxy.get_all_routes()
|
||||
routes = await app.proxy.get_all_routes()
|
||||
assert sorted(routes.keys()) == [app.hub.routespec, user_spec]
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
@pytest.mark.parametrize("username", [
|
||||
'zoe',
|
||||
'50fia',
|
||||
@@ -157,27 +156,27 @@ def test_external_proxy(request):
|
||||
'~TestJH',
|
||||
'has@',
|
||||
])
|
||||
def test_check_routes(app, username, disable_check_routes):
|
||||
async def test_check_routes(app, username, disable_check_routes):
|
||||
proxy = app.proxy
|
||||
test_user = add_user(app.db, app, name=username)
|
||||
r = yield api_request(app, 'users/%s/server' % username, method='post')
|
||||
r = await api_request(app, 'users/%s/server' % username, method='post')
|
||||
r.raise_for_status()
|
||||
|
||||
# check a valid route exists for user
|
||||
routes = yield app.proxy.get_all_routes()
|
||||
routes = await app.proxy.get_all_routes()
|
||||
before = sorted(routes)
|
||||
assert test_user.proxy_spec in before
|
||||
|
||||
# check if a route is removed when user deleted
|
||||
yield app.proxy.check_routes(app.users, app._service_map)
|
||||
yield proxy.delete_user(test_user)
|
||||
routes = yield app.proxy.get_all_routes()
|
||||
await app.proxy.check_routes(app.users, app._service_map)
|
||||
await proxy.delete_user(test_user)
|
||||
routes = await app.proxy.get_all_routes()
|
||||
during = sorted(routes)
|
||||
assert test_user.proxy_spec not in during
|
||||
|
||||
# check if a route exists for user
|
||||
yield app.proxy.check_routes(app.users, app._service_map)
|
||||
routes = yield app.proxy.get_all_routes()
|
||||
await app.proxy.check_routes(app.users, app._service_map)
|
||||
routes = await app.proxy.get_all_routes()
|
||||
after = sorted(routes)
|
||||
assert test_user.proxy_spec in after
|
||||
|
||||
@@ -185,7 +184,6 @@ def test_check_routes(app, username, disable_check_routes):
|
||||
assert before == after
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
@pytest.mark.parametrize("routespec", [
|
||||
'/has%20space/foo/',
|
||||
'/missing-trailing/slash',
|
||||
@@ -194,7 +192,7 @@ def test_check_routes(app, username, disable_check_routes):
|
||||
'host.name/path/',
|
||||
'other.host/path/no/slash',
|
||||
])
|
||||
def test_add_get_delete(app, routespec, disable_check_routes):
|
||||
async def test_add_get_delete(app, routespec, disable_check_routes):
|
||||
arg = routespec
|
||||
if not routespec.endswith('/'):
|
||||
routespec = routespec + '/'
|
||||
@@ -213,26 +211,25 @@ def test_add_get_delete(app, routespec, disable_check_routes):
|
||||
proxy = app.proxy
|
||||
target = 'https://localhost:1234'
|
||||
with context():
|
||||
yield proxy.add_route(arg, target, {})
|
||||
routes = yield proxy.get_all_routes()
|
||||
await proxy.add_route(arg, target, {})
|
||||
routes = await proxy.get_all_routes()
|
||||
if not expect_value_error:
|
||||
assert routespec in routes.keys()
|
||||
with context():
|
||||
route = yield proxy.get_route(arg)
|
||||
route = await proxy.get_route(arg)
|
||||
assert route == {
|
||||
'target': target,
|
||||
'routespec': routespec,
|
||||
'data': route.get('data'),
|
||||
}
|
||||
with context():
|
||||
yield proxy.delete_route(arg)
|
||||
await proxy.delete_route(arg)
|
||||
with context():
|
||||
route = yield proxy.get_route(arg)
|
||||
route = await proxy.get_route(arg)
|
||||
assert route is None
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
@pytest.mark.parametrize("test_data", [None, 'notjson', json.dumps([])])
|
||||
def test_proxy_patch_bad_request_data(app, test_data):
|
||||
r = yield api_request(app, 'proxy', method='patch', data=test_data)
|
||||
async def test_proxy_patch_bad_request_data(app, test_data):
|
||||
r = await api_request(app, 'proxy', method='patch', data=test_data)
|
||||
assert r.status_code == 400
|
||||
|
@@ -1,5 +1,6 @@
|
||||
"""Tests for services"""
|
||||
|
||||
import asyncio
|
||||
from binascii import hexlify
|
||||
from contextlib import contextmanager
|
||||
import os
|
||||
@@ -8,13 +9,14 @@ import sys
|
||||
from threading import Event
|
||||
import time
|
||||
|
||||
from async_generator import asynccontextmanager, async_generator, yield_
|
||||
import pytest
|
||||
import requests
|
||||
from tornado import gen
|
||||
from tornado.ioloop import IOLoop
|
||||
|
||||
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, maybe_future
|
||||
from .utils import async_requests
|
||||
|
||||
mockservice_path = os.path.dirname(os.path.abspath(__file__))
|
||||
@@ -22,8 +24,9 @@ mockservice_py = os.path.join(mockservice_path, 'mockservice.py')
|
||||
mockservice_cmd = [sys.executable, mockservice_py]
|
||||
|
||||
|
||||
@contextmanager
|
||||
def external_service(app, name='mockservice'):
|
||||
@asynccontextmanager
|
||||
@async_generator
|
||||
async def external_service(app, name='mockservice'):
|
||||
env = {
|
||||
'JUPYTERHUB_API_TOKEN': hexlify(os.urandom(5)),
|
||||
'JUPYTERHUB_SERVICE_NAME': name,
|
||||
@@ -31,17 +34,14 @@ def external_service(app, name='mockservice'):
|
||||
'JUPYTERHUB_SERVICE_URL': 'http://127.0.0.1:%i' % random_port(),
|
||||
}
|
||||
proc = Popen(mockservice_cmd, env=env)
|
||||
IOLoop().run_sync(
|
||||
lambda: wait_for_http_server(env['JUPYTERHUB_SERVICE_URL'])
|
||||
)
|
||||
try:
|
||||
yield env
|
||||
await wait_for_http_server(env['JUPYTERHUB_SERVICE_URL'])
|
||||
await yield_(env)
|
||||
finally:
|
||||
proc.terminate()
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_managed_service(mockservice):
|
||||
async def test_managed_service(mockservice):
|
||||
service = mockservice
|
||||
proc = service.proc
|
||||
assert isinstance(proc.pid, object)
|
||||
@@ -58,19 +58,18 @@ def test_managed_service(mockservice):
|
||||
if service.proc is not proc:
|
||||
break
|
||||
else:
|
||||
yield gen.sleep(0.2)
|
||||
await asyncio.sleep(0.2)
|
||||
|
||||
assert service.proc.pid != first_pid
|
||||
assert service.proc.poll() is None
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_proxy_service(app, mockservice_url):
|
||||
async def test_proxy_service(app, mockservice_url):
|
||||
service = mockservice_url
|
||||
name = service.name
|
||||
yield app.proxy.get_all_routes()
|
||||
await app.proxy.get_all_routes()
|
||||
url = public_url(app, service) + '/foo'
|
||||
r = yield async_requests.get(url, allow_redirects=False)
|
||||
r = await async_requests.get(url, allow_redirects=False)
|
||||
path = '/services/{}/foo'.format(name)
|
||||
r.raise_for_status()
|
||||
|
||||
@@ -78,23 +77,22 @@ def test_proxy_service(app, mockservice_url):
|
||||
assert r.text.endswith(path)
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_external_service(app):
|
||||
async def test_external_service(app):
|
||||
name = 'external'
|
||||
with external_service(app, name=name) as env:
|
||||
async with external_service(app, name=name) as env:
|
||||
app.services = [{
|
||||
'name': name,
|
||||
'admin': True,
|
||||
'url': env['JUPYTERHUB_SERVICE_URL'],
|
||||
'api_token': env['JUPYTERHUB_API_TOKEN'],
|
||||
}]
|
||||
yield app.init_services()
|
||||
yield app.init_api_tokens()
|
||||
yield app.proxy.add_all_services(app._service_map)
|
||||
await maybe_future(app.init_services())
|
||||
await app.init_api_tokens()
|
||||
await app.proxy.add_all_services(app._service_map)
|
||||
|
||||
service = app._service_map[name]
|
||||
url = public_url(app, service) + '/api/users'
|
||||
r = yield async_requests.get(url, allow_redirects=False)
|
||||
r = await async_requests.get(url, allow_redirects=False)
|
||||
r.raise_for_status()
|
||||
assert r.status_code == 200
|
||||
resp = r.json()
|
||||
|
@@ -227,11 +227,10 @@ def test_hub_authenticated(request):
|
||||
assert r.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_hubauth_cookie(app, mockservice_url):
|
||||
async def test_hubauth_cookie(app, mockservice_url):
|
||||
"""Test HubAuthenticated service with user cookies"""
|
||||
cookies = yield app.login_user('badger')
|
||||
r = yield async_requests.get(public_url(app, mockservice_url) + '/whoami/', cookies=cookies)
|
||||
cookies = await app.login_user('badger')
|
||||
r = await async_requests.get(public_url(app, mockservice_url) + '/whoami/', cookies=cookies)
|
||||
r.raise_for_status()
|
||||
print(r.text)
|
||||
reply = r.json()
|
||||
@@ -242,15 +241,14 @@ def test_hubauth_cookie(app, mockservice_url):
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_hubauth_token(app, mockservice_url):
|
||||
async def test_hubauth_token(app, mockservice_url):
|
||||
"""Test HubAuthenticated service with user API tokens"""
|
||||
u = add_user(app.db, name='river')
|
||||
token = u.new_api_token()
|
||||
app.db.commit()
|
||||
|
||||
# token in Authorization header
|
||||
r = yield async_requests.get(public_url(app, mockservice_url) + '/whoami/',
|
||||
r = await async_requests.get(public_url(app, mockservice_url) + '/whoami/',
|
||||
headers={
|
||||
'Authorization': 'token %s' % token,
|
||||
})
|
||||
@@ -262,7 +260,7 @@ def test_hubauth_token(app, mockservice_url):
|
||||
}
|
||||
|
||||
# token in ?token parameter
|
||||
r = yield async_requests.get(public_url(app, mockservice_url) + '/whoami/?token=%s' % token)
|
||||
r = await async_requests.get(public_url(app, mockservice_url) + '/whoami/?token=%s' % token)
|
||||
r.raise_for_status()
|
||||
reply = r.json()
|
||||
sub_reply = { key: reply.get(key, 'missing') for key in ['name', 'admin']}
|
||||
@@ -271,7 +269,7 @@ def test_hubauth_token(app, mockservice_url):
|
||||
'admin': False,
|
||||
}
|
||||
|
||||
r = yield async_requests.get(public_url(app, mockservice_url) + '/whoami/?token=no-such-token',
|
||||
r = await async_requests.get(public_url(app, mockservice_url) + '/whoami/?token=no-such-token',
|
||||
allow_redirects=False,
|
||||
)
|
||||
assert r.status_code == 302
|
||||
@@ -281,17 +279,16 @@ def test_hubauth_token(app, mockservice_url):
|
||||
assert path.endswith('/hub/login')
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_hubauth_service_token(app, mockservice_url):
|
||||
async def test_hubauth_service_token(app, mockservice_url):
|
||||
"""Test HubAuthenticated service with service API tokens"""
|
||||
|
||||
token = hexlify(os.urandom(5)).decode('utf8')
|
||||
name = 'test-api-service'
|
||||
app.service_tokens[token] = name
|
||||
yield app.init_api_tokens()
|
||||
await app.init_api_tokens()
|
||||
|
||||
# token in Authorization header
|
||||
r = yield async_requests.get(public_url(app, mockservice_url) + '/whoami/',
|
||||
r = await async_requests.get(public_url(app, mockservice_url) + '/whoami/',
|
||||
headers={
|
||||
'Authorization': 'token %s' % token,
|
||||
})
|
||||
@@ -305,7 +302,7 @@ def test_hubauth_service_token(app, mockservice_url):
|
||||
assert not r.cookies
|
||||
|
||||
# token in ?token parameter
|
||||
r = yield async_requests.get(public_url(app, mockservice_url) + '/whoami/?token=%s' % token)
|
||||
r = await async_requests.get(public_url(app, mockservice_url) + '/whoami/?token=%s' % token)
|
||||
r.raise_for_status()
|
||||
reply = r.json()
|
||||
assert reply == {
|
||||
@@ -314,7 +311,7 @@ def test_hubauth_service_token(app, mockservice_url):
|
||||
'admin': False,
|
||||
}
|
||||
|
||||
r = yield async_requests.get(public_url(app, mockservice_url) + '/whoami/?token=no-such-token',
|
||||
r = await async_requests.get(public_url(app, mockservice_url) + '/whoami/?token=no-such-token',
|
||||
allow_redirects=False,
|
||||
)
|
||||
assert r.status_code == 302
|
||||
@@ -324,16 +321,15 @@ def test_hubauth_service_token(app, mockservice_url):
|
||||
assert path.endswith('/hub/login')
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_oauth_service(app, mockservice_url):
|
||||
async def test_oauth_service(app, mockservice_url):
|
||||
service = mockservice_url
|
||||
url = url_path_join(public_url(app, mockservice_url) + 'owhoami/?arg=x')
|
||||
# first request is only going to login and get us to the oauth form page
|
||||
s = AsyncSession()
|
||||
name = 'link'
|
||||
s.cookies = yield app.login_user(name)
|
||||
s.cookies = await app.login_user(name)
|
||||
|
||||
r = yield s.get(url)
|
||||
r = await s.get(url)
|
||||
r.raise_for_status()
|
||||
# we should be looking at the oauth confirmation page
|
||||
assert urlparse(r.url).path == app.base_url + 'hub/api/oauth2/authorize'
|
||||
@@ -341,7 +337,7 @@ def test_oauth_service(app, mockservice_url):
|
||||
assert set(r.history[0].cookies.keys()) == {'service-%s-oauth-state' % service.name}
|
||||
|
||||
# submit the oauth form to complete authorization
|
||||
r = yield s.post(r.url, data={'scopes': ['identify']}, headers={'Referer': r.url})
|
||||
r = await s.post(r.url, data={'scopes': ['identify']}, headers={'Referer': r.url})
|
||||
r.raise_for_status()
|
||||
assert r.url == url
|
||||
# verify oauth cookie is set
|
||||
@@ -350,7 +346,7 @@ def test_oauth_service(app, mockservice_url):
|
||||
assert 'service-%s-oauth-state' % service.name not in set(s.cookies.keys())
|
||||
|
||||
# second request should be authenticated, which means no redirects
|
||||
r = yield s.get(url, allow_redirects=False)
|
||||
r = await s.get(url, allow_redirects=False)
|
||||
r.raise_for_status()
|
||||
assert r.status_code == 200
|
||||
reply = r.json()
|
||||
@@ -363,7 +359,7 @@ def test_oauth_service(app, mockservice_url):
|
||||
# token-authenticated request to HubOAuth
|
||||
token = app.users[name].new_api_token()
|
||||
# token in ?token parameter
|
||||
r = yield async_requests.get(url_concat(url, {'token': token}))
|
||||
r = await async_requests.get(url_concat(url, {'token': token}))
|
||||
r.raise_for_status()
|
||||
reply = r.json()
|
||||
assert reply['name'] == name
|
||||
@@ -371,7 +367,7 @@ def test_oauth_service(app, mockservice_url):
|
||||
# verify that ?token= requests set a cookie
|
||||
assert len(r.cookies) != 0
|
||||
# ensure cookie works in future requests
|
||||
r = yield async_requests.get(
|
||||
r = await async_requests.get(
|
||||
url,
|
||||
cookies=r.cookies,
|
||||
allow_redirects=False,
|
||||
@@ -382,17 +378,16 @@ def test_oauth_service(app, mockservice_url):
|
||||
assert reply['name'] == name
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_oauth_cookie_collision(app, mockservice_url):
|
||||
async def test_oauth_cookie_collision(app, mockservice_url):
|
||||
service = mockservice_url
|
||||
url = url_path_join(public_url(app, mockservice_url), 'owhoami/')
|
||||
print(url)
|
||||
s = AsyncSession()
|
||||
name = 'mypha'
|
||||
s.cookies = yield app.login_user(name)
|
||||
s.cookies = await app.login_user(name)
|
||||
state_cookie_name = 'service-%s-oauth-state' % service.name
|
||||
service_cookie_name = 'service-%s' % service.name
|
||||
oauth_1 = yield s.get(url)
|
||||
oauth_1 = await s.get(url)
|
||||
print(oauth_1.headers)
|
||||
print(oauth_1.cookies, oauth_1.url, url)
|
||||
assert state_cookie_name in s.cookies
|
||||
@@ -402,7 +397,7 @@ def test_oauth_cookie_collision(app, mockservice_url):
|
||||
state_1 = s.cookies[state_cookie_name]
|
||||
|
||||
# start second oauth login before finishing the first
|
||||
oauth_2 = yield s.get(url)
|
||||
oauth_2 = await s.get(url)
|
||||
state_cookies = [ c for c in s.cookies.keys() if c.startswith(state_cookie_name) ]
|
||||
assert len(state_cookies) == 2
|
||||
# get the random-suffix cookie name
|
||||
@@ -412,7 +407,7 @@ def test_oauth_cookie_collision(app, mockservice_url):
|
||||
|
||||
# finish oauth 2
|
||||
# submit the oauth form to complete authorization
|
||||
r = yield s.post(
|
||||
r = await s.post(
|
||||
oauth_2.url,
|
||||
data={'scopes': ['identify']},
|
||||
headers={'Referer': oauth_2.url},
|
||||
@@ -426,7 +421,7 @@ def test_oauth_cookie_collision(app, mockservice_url):
|
||||
service_cookie_2 = s.cookies[service_cookie_name]
|
||||
|
||||
# finish oauth 1
|
||||
r = yield s.post(
|
||||
r = await s.post(
|
||||
oauth_1.url,
|
||||
data={'scopes': ['identify']},
|
||||
headers={'Referer': oauth_1.url},
|
||||
@@ -445,8 +440,7 @@ def test_oauth_cookie_collision(app, mockservice_url):
|
||||
assert state_cookies == []
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_oauth_logout(app, mockservice_url):
|
||||
async def test_oauth_logout(app, mockservice_url):
|
||||
"""Verify that logout via the Hub triggers logout for oauth services
|
||||
|
||||
1. clears session id cookie
|
||||
@@ -471,18 +465,18 @@ def test_oauth_logout(app, mockservice_url):
|
||||
# ensure we start empty
|
||||
assert auth_tokens() == []
|
||||
|
||||
s.cookies = yield app.login_user(name)
|
||||
s.cookies = await app.login_user(name)
|
||||
assert 'jupyterhub-session-id' in s.cookies
|
||||
r = yield s.get(url)
|
||||
r = await s.get(url)
|
||||
r.raise_for_status()
|
||||
assert urlparse(r.url).path.endswith('oauth2/authorize')
|
||||
# submit the oauth form to complete authorization
|
||||
r = yield s.post(r.url, data={'scopes': ['identify']}, headers={'Referer': r.url})
|
||||
r = await s.post(r.url, data={'scopes': ['identify']}, headers={'Referer': r.url})
|
||||
r.raise_for_status()
|
||||
assert r.url == url
|
||||
|
||||
# second request should be authenticated
|
||||
r = yield s.get(url, allow_redirects=False)
|
||||
r = await s.get(url, allow_redirects=False)
|
||||
r.raise_for_status()
|
||||
assert r.status_code == 200
|
||||
reply = r.json()
|
||||
@@ -501,13 +495,13 @@ def test_oauth_logout(app, mockservice_url):
|
||||
assert len(auth_tokens()) == 1
|
||||
|
||||
# hit hub logout URL
|
||||
r = yield s.get(public_url(app, path='hub/logout'))
|
||||
r = await s.get(public_url(app, path='hub/logout'))
|
||||
r.raise_for_status()
|
||||
# verify that all cookies other than the service cookie are cleared
|
||||
assert list(s.cookies.keys()) == [service_cookie_name]
|
||||
# verify that clearing session id invalidates service cookie
|
||||
# i.e. redirect back to login page
|
||||
r = yield s.get(url)
|
||||
r = await s.get(url)
|
||||
r.raise_for_status()
|
||||
assert r.url.split('?')[0] == public_url(app, path='hub/login')
|
||||
|
||||
@@ -524,7 +518,7 @@ def test_oauth_logout(app, mockservice_url):
|
||||
# check that we got the old session id back
|
||||
assert session_id == s.cookies['jupyterhub-session-id']
|
||||
|
||||
r = yield s.get(url, allow_redirects=False)
|
||||
r = await s.get(url, allow_redirects=False)
|
||||
r.raise_for_status()
|
||||
assert r.status_code == 200
|
||||
reply = r.json()
|
||||
|
@@ -13,42 +13,41 @@ from ..utils import url_path_join
|
||||
from .utils import async_requests, AsyncSession
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_singleuser_auth(app):
|
||||
async def test_singleuser_auth(app):
|
||||
# use StubSingleUserSpawner to launch a single-user app in a thread
|
||||
app.spawner_class = StubSingleUserSpawner
|
||||
app.tornado_settings['spawner_class'] = StubSingleUserSpawner
|
||||
|
||||
# login, start the server
|
||||
cookies = yield app.login_user('nandy')
|
||||
cookies = await app.login_user('nandy')
|
||||
user = app.users['nandy']
|
||||
if not user.running:
|
||||
yield user.spawn()
|
||||
await user.spawn()
|
||||
url = public_url(app, user)
|
||||
|
||||
# no cookies, redirects to login page
|
||||
r = yield async_requests.get(url)
|
||||
r = await async_requests.get(url)
|
||||
r.raise_for_status()
|
||||
assert '/hub/login' in r.url
|
||||
|
||||
# with cookies, login successful
|
||||
r = yield async_requests.get(url, cookies=cookies)
|
||||
r = await async_requests.get(url, cookies=cookies)
|
||||
r.raise_for_status()
|
||||
assert urlparse(r.url).path.rstrip('/').endswith('/user/nandy/tree')
|
||||
assert r.status_code == 200
|
||||
|
||||
# logout
|
||||
r = yield async_requests.get(url_path_join(url, 'logout'), cookies=cookies)
|
||||
r = await async_requests.get(url_path_join(url, 'logout'), cookies=cookies)
|
||||
assert len(r.cookies) == 0
|
||||
|
||||
# accessing another user's server hits the oauth confirmation page
|
||||
cookies = yield app.login_user('burgess')
|
||||
cookies = await app.login_user('burgess')
|
||||
s = AsyncSession()
|
||||
s.cookies = cookies
|
||||
r = yield s.get(url)
|
||||
r = await s.get(url)
|
||||
assert urlparse(r.url).path.endswith('/oauth2/authorize')
|
||||
# submit the oauth form to complete authorization
|
||||
r = yield s.post(
|
||||
r = await s.post(
|
||||
r.url,
|
||||
data={'scopes': ['identify']},
|
||||
headers={'Referer': r.url},
|
||||
@@ -59,28 +58,27 @@ def test_singleuser_auth(app):
|
||||
assert 'burgess' in r.text
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_disable_user_config(app):
|
||||
async def test_disable_user_config(app):
|
||||
# use StubSingleUserSpawner to launch a single-user app in a thread
|
||||
app.spawner_class = StubSingleUserSpawner
|
||||
app.tornado_settings['spawner_class'] = StubSingleUserSpawner
|
||||
# login, start the server
|
||||
cookies = yield app.login_user('nandy')
|
||||
cookies = await app.login_user('nandy')
|
||||
user = app.users['nandy']
|
||||
# stop spawner, if running:
|
||||
if user.running:
|
||||
print("stopping")
|
||||
yield user.stop()
|
||||
await user.stop()
|
||||
# start with new config:
|
||||
user.spawner.debug = True
|
||||
user.spawner.disable_user_config = True
|
||||
yield user.spawn()
|
||||
yield app.proxy.add_user(user)
|
||||
await user.spawn()
|
||||
await app.proxy.add_user(user)
|
||||
|
||||
url = public_url(app, user)
|
||||
|
||||
# with cookies, login successful
|
||||
r = yield async_requests.get(url, cookies=cookies)
|
||||
r = await async_requests.get(url, cookies=cookies)
|
||||
r.raise_for_status()
|
||||
assert r.url.rstrip('/').endswith('/user/nandy/tree')
|
||||
assert r.status_code == 200
|
||||
@@ -90,6 +88,7 @@ def test_help_output():
|
||||
out = check_output([sys.executable, '-m', 'jupyterhub.singleuser', '--help-all']).decode('utf8', 'replace')
|
||||
assert 'JupyterHub' in out
|
||||
|
||||
|
||||
def test_version():
|
||||
out = check_output([sys.executable, '-m', 'jupyterhub.singleuser', '--version']).decode('utf8', 'replace')
|
||||
assert jupyterhub.__version__ in out
|
||||
|
@@ -60,10 +60,9 @@ def new_spawner(db, **kwargs):
|
||||
return user._new_spawner('', spawner_class=LocalProcessSpawner, **kwargs)
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_spawner(db, request):
|
||||
async def test_spawner(db, request):
|
||||
spawner = new_spawner(db)
|
||||
ip, port = yield spawner.start()
|
||||
ip, port = await spawner.start()
|
||||
assert ip == '127.0.0.1'
|
||||
assert isinstance(port, int)
|
||||
assert port > 0
|
||||
@@ -72,15 +71,14 @@ def test_spawner(db, request):
|
||||
# wait for the process to get to the while True: loop
|
||||
time.sleep(1)
|
||||
|
||||
status = yield spawner.poll()
|
||||
status = await spawner.poll()
|
||||
assert status is None
|
||||
yield spawner.stop()
|
||||
status = yield spawner.poll()
|
||||
await spawner.stop()
|
||||
status = await spawner.poll()
|
||||
assert status == 1
|
||||
|
||||
|
||||
@gen.coroutine
|
||||
def wait_for_spawner(spawner, timeout=10):
|
||||
async def wait_for_spawner(spawner, timeout=10):
|
||||
"""Wait for an http server to show up
|
||||
|
||||
polling at shorter intervals for early termination
|
||||
@@ -89,72 +87,68 @@ def wait_for_spawner(spawner, timeout=10):
|
||||
def wait():
|
||||
return spawner.server.wait_up(timeout=1, http=True)
|
||||
while time.monotonic() < deadline:
|
||||
status = yield spawner.poll()
|
||||
status = await spawner.poll()
|
||||
assert status is None
|
||||
try:
|
||||
yield wait()
|
||||
await wait()
|
||||
except TimeoutError:
|
||||
continue
|
||||
else:
|
||||
break
|
||||
yield wait()
|
||||
await wait()
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_single_user_spawner(app, request):
|
||||
async def test_single_user_spawner(app, request):
|
||||
user = next(iter(app.users.values()), None)
|
||||
spawner = user.spawner
|
||||
spawner.cmd = ['jupyterhub-singleuser']
|
||||
yield user.spawn()
|
||||
await user.spawn()
|
||||
assert spawner.server.ip == '127.0.0.1'
|
||||
assert spawner.server.port > 0
|
||||
yield wait_for_spawner(spawner)
|
||||
status = yield spawner.poll()
|
||||
await wait_for_spawner(spawner)
|
||||
status = await spawner.poll()
|
||||
assert status is None
|
||||
yield spawner.stop()
|
||||
status = yield spawner.poll()
|
||||
await spawner.stop()
|
||||
status = await spawner.poll()
|
||||
assert status == 0
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_stop_spawner_sigint_fails(db):
|
||||
async def test_stop_spawner_sigint_fails(db):
|
||||
spawner = new_spawner(db, cmd=[sys.executable, '-c', _uninterruptible])
|
||||
yield spawner.start()
|
||||
await spawner.start()
|
||||
|
||||
# wait for the process to get to the while True: loop
|
||||
yield gen.sleep(1)
|
||||
await gen.sleep(1)
|
||||
|
||||
status = yield spawner.poll()
|
||||
status = await spawner.poll()
|
||||
assert status is None
|
||||
|
||||
yield spawner.stop()
|
||||
status = yield spawner.poll()
|
||||
await spawner.stop()
|
||||
status = await spawner.poll()
|
||||
assert status == -signal.SIGTERM
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_stop_spawner_stop_now(db):
|
||||
async def test_stop_spawner_stop_now(db):
|
||||
spawner = new_spawner(db)
|
||||
yield spawner.start()
|
||||
await spawner.start()
|
||||
|
||||
# wait for the process to get to the while True: loop
|
||||
yield gen.sleep(1)
|
||||
await gen.sleep(1)
|
||||
|
||||
status = yield spawner.poll()
|
||||
status = await spawner.poll()
|
||||
assert status is None
|
||||
|
||||
yield spawner.stop(now=True)
|
||||
status = yield spawner.poll()
|
||||
await spawner.stop(now=True)
|
||||
status = await spawner.poll()
|
||||
assert status == -signal.SIGTERM
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_spawner_poll(db):
|
||||
async def test_spawner_poll(db):
|
||||
first_spawner = new_spawner(db)
|
||||
user = first_spawner.user
|
||||
yield first_spawner.start()
|
||||
await first_spawner.start()
|
||||
proc = first_spawner.proc
|
||||
status = yield first_spawner.poll()
|
||||
status = await first_spawner.poll()
|
||||
assert status is None
|
||||
if user.state is None:
|
||||
user.state = {}
|
||||
@@ -166,21 +160,21 @@ def test_spawner_poll(db):
|
||||
spawner.start_polling()
|
||||
|
||||
# wait for the process to get to the while True: loop
|
||||
yield gen.sleep(1)
|
||||
status = yield spawner.poll()
|
||||
await gen.sleep(1)
|
||||
status = await spawner.poll()
|
||||
assert status is None
|
||||
|
||||
# kill the process
|
||||
proc.terminate()
|
||||
for i in range(10):
|
||||
if proc.poll() is None:
|
||||
yield gen.sleep(1)
|
||||
await gen.sleep(1)
|
||||
else:
|
||||
break
|
||||
assert proc.poll() is not None
|
||||
|
||||
yield gen.sleep(2)
|
||||
status = yield spawner.poll()
|
||||
await gen.sleep(2)
|
||||
status = await spawner.poll()
|
||||
assert status is not None
|
||||
|
||||
|
||||
@@ -213,8 +207,7 @@ def test_string_formatting(db):
|
||||
assert s.format_string(s.default_url) == '/base/%s' % name
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_popen_kwargs(db):
|
||||
async def test_popen_kwargs(db):
|
||||
mock_proc = mock.Mock(spec=Popen)
|
||||
def mock_popen(*args, **kwargs):
|
||||
mock_proc.args = args
|
||||
@@ -224,14 +217,13 @@ def test_popen_kwargs(db):
|
||||
|
||||
s = new_spawner(db, popen_kwargs={'shell': True}, cmd='jupyterhub-singleuser')
|
||||
with mock.patch.object(spawnermod, 'Popen', mock_popen):
|
||||
yield s.start()
|
||||
await s.start()
|
||||
|
||||
assert mock_proc.kwargs['shell'] == True
|
||||
assert mock_proc.args[0][:1] == (['jupyterhub-singleuser'])
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_shell_cmd(db, tmpdir, request):
|
||||
async def test_shell_cmd(db, tmpdir, request):
|
||||
f = tmpdir.join('bashrc')
|
||||
f.write('export TESTVAR=foo\n')
|
||||
s = new_spawner(db,
|
||||
@@ -243,17 +235,17 @@ def test_shell_cmd(db, tmpdir, request):
|
||||
db.commit()
|
||||
s.server = Server.from_orm(server)
|
||||
db.commit()
|
||||
(ip, port) = yield s.start()
|
||||
(ip, port) = await s.start()
|
||||
request.addfinalizer(s.stop)
|
||||
s.server.ip = ip
|
||||
s.server.port = port
|
||||
db.commit()
|
||||
yield wait_for_spawner(s)
|
||||
r = yield async_requests.get('http://%s:%i/env' % (ip, port))
|
||||
await wait_for_spawner(s)
|
||||
r = await async_requests.get('http://%s:%i/env' % (ip, port))
|
||||
r.raise_for_status()
|
||||
env = r.json()
|
||||
assert env['TESTVAR'] == 'foo'
|
||||
yield s.stop()
|
||||
await s.stop()
|
||||
|
||||
|
||||
def test_inherit_overwrite():
|
||||
@@ -277,8 +269,7 @@ def test_inherit_ok():
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_spawner_reuse_api_token(db, app):
|
||||
async def test_spawner_reuse_api_token(db, app):
|
||||
# setup: user with no tokens, whose spawner has set the .will_resume flag
|
||||
user = add_user(app.db, app, name='snoopy')
|
||||
spawner = user.spawner
|
||||
@@ -286,26 +277,25 @@ def test_spawner_reuse_api_token(db, app):
|
||||
# will_resume triggers reuse of tokens
|
||||
spawner.will_resume = True
|
||||
# first start: gets a new API token
|
||||
yield user.spawn()
|
||||
await user.spawn()
|
||||
api_token = spawner.api_token
|
||||
found = orm.APIToken.find(app.db, api_token)
|
||||
assert found
|
||||
assert found.user.name == user.name
|
||||
assert user.api_tokens == [found]
|
||||
yield user.stop()
|
||||
await user.stop()
|
||||
# stop now deletes unused spawners.
|
||||
# put back the mock spawner!
|
||||
user.spawners[''] = spawner
|
||||
# second start: should reuse the token
|
||||
yield user.spawn()
|
||||
await user.spawn()
|
||||
# verify re-use of API token
|
||||
assert spawner.api_token == api_token
|
||||
# verify that a new token was not created
|
||||
assert user.api_tokens == [found]
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_spawner_insert_api_token(app):
|
||||
async def test_spawner_insert_api_token(app):
|
||||
"""Token provided by spawner is not in the db
|
||||
|
||||
Insert token into db as a user-provided token.
|
||||
@@ -322,17 +312,16 @@ def test_spawner_insert_api_token(app):
|
||||
# The spawner's provided API token would already be in the db
|
||||
# unless there is a bug somewhere else (in the Spawner),
|
||||
# but handle it anyway.
|
||||
yield user.spawn()
|
||||
await user.spawn()
|
||||
assert spawner.api_token == api_token
|
||||
found = orm.APIToken.find(app.db, api_token)
|
||||
assert found
|
||||
assert found.user.name == user.name
|
||||
assert user.api_tokens == [found]
|
||||
yield user.stop()
|
||||
await user.stop()
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_spawner_bad_api_token(app):
|
||||
async def test_spawner_bad_api_token(app):
|
||||
"""Tokens are revoked when a Spawner gets another user's token"""
|
||||
# we need two users for this one
|
||||
user = add_user(app.db, app, name='antimone')
|
||||
@@ -349,13 +338,12 @@ def test_spawner_bad_api_token(app):
|
||||
# starting a user's server with another user's token
|
||||
# should revoke it
|
||||
with pytest.raises(ValueError):
|
||||
yield user.spawn()
|
||||
await user.spawn()
|
||||
assert orm.APIToken.find(app.db, other_token) is None
|
||||
assert other_user.api_tokens == []
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_spawner_delete_server(app):
|
||||
async def test_spawner_delete_server(app):
|
||||
"""Test deleting spawner.server
|
||||
|
||||
This can occur during app startup if their server has been deleted.
|
||||
@@ -393,22 +381,21 @@ def test_spawner_delete_server(app):
|
||||
"has%40x",
|
||||
]
|
||||
)
|
||||
@pytest.mark.gen_test
|
||||
def test_spawner_routing(app, name):
|
||||
async def test_spawner_routing(app, name):
|
||||
"""Test routing of names with special characters"""
|
||||
db = app.db
|
||||
with mock.patch.dict(app.config.LocalProcessSpawner, {'cmd': [sys.executable, '-m', 'jupyterhub.tests.mocksu']}):
|
||||
user = add_user(app.db, app, name=name)
|
||||
yield user.spawn()
|
||||
yield wait_for_spawner(user.spawner)
|
||||
yield app.proxy.add_user(user)
|
||||
await user.spawn()
|
||||
await wait_for_spawner(user.spawner)
|
||||
await app.proxy.add_user(user)
|
||||
kwargs = {'allow_redirects': False}
|
||||
if app.internal_ssl:
|
||||
kwargs['cert'] = (app.internal_ssl_cert, app.internal_ssl_key)
|
||||
kwargs["verify"] = app.internal_ssl_ca
|
||||
url = url_path_join(public_url(app, user), "test/url")
|
||||
r = yield async_requests.get(url, **kwargs)
|
||||
r = await async_requests.get(url, **kwargs)
|
||||
r.raise_for_status()
|
||||
assert r.url == url
|
||||
assert r.text == urlparse(url).path
|
||||
yield user.stop()
|
||||
await user.stop()
|
||||
|
@@ -26,7 +26,6 @@ def schedule_future(io_loop, *, delay, result=None):
|
||||
return f
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
@pytest.mark.parametrize("deadline, n, delay, expected", [
|
||||
(0, 3, 1, []),
|
||||
(0, 3, 0, [0, 1, 2]),
|
||||
@@ -43,7 +42,6 @@ async def test_iterate_until(io_loop, deadline, n, delay, expected):
|
||||
assert yielded == expected
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
async def test_iterate_until_ready_after_deadline(io_loop):
|
||||
f = schedule_future(io_loop, delay=0)
|
||||
|
||||
|
@@ -13,11 +13,15 @@ class _AsyncRequests:
|
||||
|
||||
def __init__(self):
|
||||
self.executor = ThreadPoolExecutor(1)
|
||||
real_submit = self.executor.submit
|
||||
self.executor.submit = lambda *args, **kwargs: asyncio.wrap_future(
|
||||
real_submit(*args, **kwargs)
|
||||
)
|
||||
|
||||
def __getattr__(self, name):
|
||||
requests_method = getattr(requests, name)
|
||||
return lambda *args, **kwargs: asyncio.wrap_future(
|
||||
self.executor.submit(requests_method, *args, **kwargs)
|
||||
return lambda *args, **kwargs: self.executor.submit(
|
||||
requests_method, *args, **kwargs
|
||||
)
|
||||
|
||||
|
||||
|
@@ -531,7 +531,7 @@ class User:
|
||||
self.settings['statsd'].incr('spawner.failure.error')
|
||||
e.reason = 'error'
|
||||
try:
|
||||
await self.stop()
|
||||
await self.stop(spawner.name)
|
||||
except Exception:
|
||||
self.log.error("Failed to cleanup {user}'s server that failed to start".format(
|
||||
user=self.name,
|
||||
@@ -550,6 +550,15 @@ class User:
|
||||
spawner.orm_spawner.state = spawner.get_state()
|
||||
db.commit()
|
||||
spawner._waiting_for_response = True
|
||||
await self._wait_up(spawner)
|
||||
|
||||
async def _wait_up(self, spawner):
|
||||
"""Wait for a server to finish starting.
|
||||
|
||||
Shuts the server down if it doesn't respond within
|
||||
spawner.http_timeout.
|
||||
"""
|
||||
server = spawner.server
|
||||
key = self.settings.get('internal_ssl_key')
|
||||
cert = self.settings.get('internal_ssl_cert')
|
||||
ca = self.settings.get('internal_ssl_ca')
|
||||
@@ -578,7 +587,7 @@ class User:
|
||||
))
|
||||
self.settings['statsd'].incr('spawner.failure.http_error')
|
||||
try:
|
||||
await self.stop()
|
||||
await self.stop(spawner.name)
|
||||
except Exception:
|
||||
self.log.error("Failed to cleanup {user}'s server that failed to start".format(
|
||||
user=self.name,
|
||||
@@ -594,7 +603,7 @@ class User:
|
||||
finally:
|
||||
spawner._waiting_for_response = False
|
||||
spawner._start_pending = False
|
||||
return self
|
||||
return spawner
|
||||
|
||||
async def stop(self, server_name=''):
|
||||
"""Stop the user's spawner
|
||||
|
Reference in New Issue
Block a user