Add tests

This commit is contained in:
Duc Trung Le
2023-03-09 22:58:13 +01:00
committed by Duc Trung LE
parent bf565ece3b
commit bdcf697fe9
7 changed files with 232 additions and 22 deletions

View File

@@ -21,7 +21,7 @@ fi
# Configure a set of databases in the database server for upgrade tests # Configure a set of databases in the database server for upgrade tests
# this list must be in sync with versions in test_db.py:test_upgrade # this list must be in sync with versions in test_db.py:test_upgrade
set -x set -x
for SUFFIX in '' _upgrade_110 _upgrade_122 _upgrade_130 _upgrade_150 _upgrade_211; do for SUFFIX in '' _upgrade_110 _upgrade_122 _upgrade_130 _upgrade_150 _upgrade_211 _upgrade_311; do
$SQL_CLIENT "DROP DATABASE jupyterhub${SUFFIX};" 2>/dev/null || true $SQL_CLIENT "DROP DATABASE jupyterhub${SUFFIX};" 2>/dev/null || true
$SQL_CLIENT "CREATE DATABASE jupyterhub${SUFFIX} ${EXTRA_CREATE_DATABASE_ARGS:-};" $SQL_CLIENT "CREATE DATABASE jupyterhub${SUFFIX} ${EXTRA_CREATE_DATABASE_ARGS:-};"
done done

View File

@@ -49,10 +49,16 @@ class ServiceAPIHandler(APIHandler):
self._check_service_model(spec) self._check_service_model(spec)
service_name = spec["name"] service_name = spec["name"]
new_service = self.service_from_spec(spec) try:
new_service = self.service_from_spec(spec)
except Exception:
msg = f"Failed to create service {service_name}"
self.log.error(msg, exc_info=True)
raise web.HTTPError(400, msg)
if new_service is None: if new_service is None:
raise web.HTTPError(400, f"Failed to create service {service_name}") raise web.HTTPError(400, f"Failed to create service {service_name}")
if new_service.api_token: if new_service.api_token:
# Add api token to database # Add api token to database
await self.app._add_tokens( await self.app._add_tokens(
@@ -150,7 +156,7 @@ class ServiceAPIHandler(APIHandler):
if service.oauth_no_confirm: if service.oauth_no_confirm:
oauth_no_confirm_list = self.settings.get('oauth_no_confirm_list') oauth_no_confirm_list = self.settings.get('oauth_no_confirm_list')
oauth_no_confirm_list.remove(service.oauth_client_id) oauth_no_confirm_list.discard(service.oauth_client_id)
self.db.delete(orm_service) self.db.delete(orm_service)
self.db.commit() self.db.commit()

View File

@@ -458,39 +458,54 @@ class Service(Base):
""" """
return db.query(cls).filter(cls.name == name).first() return db.query(cls).filter(cls.name == name).first()
def update_column(self, column_name: str, value: any) -> bool: def _check_data_only_column(self, column_name: str) -> bool:
"""_summary_ """Check if a column is not a ForeignKey or in a
relationship.
Args: Args:
column_name (str): _description_ column_name (str): ame of the column
value (any): _description_
Returns: Returns:
bool: _description_ bool: returns `True` if the column is data-only,
returns `False` otherwise.
""" """
if ( if hasattr(self, column_name):
hasattr(self, column_name) column = getattr(Service, column_name)
and not getattr(Service, column_name).foreign_keys if hasattr(column, 'foreign_keys'):
): return len(column.foreign_keys) == 0
else:
return False
else:
return False
def update_column(self, column_name: str, value: any) -> bool:
"""Update the value of a non-ForeignKey column.
Args:
column_name (str): Name of the column
value (any): New value
Returns:
bool: returns `True` if the column is updated,
returns `False` otherwise.
"""
if self._check_data_only_column(column_name):
setattr(self, column_name, value) setattr(self, column_name, value)
return True return True
else: else:
return False return False
def get_column(self, column_name: str): def get_column(self, column_name: str):
"""_summary_ """Get non-ForeignKey, non-relationship column
Args: Args:
column_name (str): _description_ column_name (str): Name of the column
value (any): _description_
Returns: Returns:
bool: _description_ The value of requested column, None if it is
a foreign key or it does not exist.
""" """
if ( if self._check_data_only_column(column_name):
hasattr(self, column_name)
and not getattr(Service, column_name).foreign_keys
):
return getattr(self, column_name) return getattr(self, column_name)
else: else:
return None return None

View File

@@ -8,12 +8,13 @@ from datetime import datetime, timedelta
from unittest import mock from unittest import mock
from urllib.parse import quote, urlparse from urllib.parse import quote, urlparse
import pytest
from pytest import fixture, mark from pytest import fixture, mark
from tornado.httputil import url_concat from tornado.httputil import url_concat
import jupyterhub import jupyterhub
from .. import orm from .. import orm, roles
from ..apihandlers.base import PAGINATION_MEDIA_TYPE from ..apihandlers.base import PAGINATION_MEDIA_TYPE
from ..objects import Server from ..objects import Server
from ..utils import url_path_join as ujoin from ..utils import url_path_join as ujoin
@@ -2091,6 +2092,155 @@ async def test_get_service(app, mockservice_url):
assert r.status_code == 403 assert r.status_code == 403
@pytest.fixture
def service_data():
return {
"oauth_client_id": "service-oauth-client-from-api",
"api_token": "api_token-from-api",
"oauth_redirect_uri": "http://127.0.0.1:5555/oauth_callback-from-api",
"oauth_no_confirm": True,
"info": {'foo': 'bar'},
}
@pytest.fixture
def service_admin_user(app):
user_name = 'admin_services'
service_role = {
'name': 'admin-services-role',
'description': '',
'users': [user_name],
'scopes': ['admin:services'],
}
roles.create_role(app.db, service_role)
user = add_user(app.db, name=user_name)
roles.update_roles(app.db, user, roles=['admin-services-role'])
return user
@mark.services
async def test_create_service(app, service_admin_user, service_data):
db = app.db
service_name = 'service-from-api'
r = await api_request(
app,
f'services/{service_name}',
headers=auth_header(db, service_admin_user.name),
data=json.dumps(service_data),
method='post',
)
r.raise_for_status()
assert r.status_code == 201
assert r.json()['name'] == service_name
orm_service = orm.Service.find(db, service_name)
assert orm_service is not None
oath_client = (
db.query(orm.OAuthClient)
.filter_by(identifier=service_data['oauth_client_id'])
.first()
)
assert oath_client.redirect_uri == service_data['oauth_redirect_uri']
assert service_name in app._service_map
assert (
app._service_map[service_name].oauth_no_confirm
== service_data['oauth_no_confirm']
)
@mark.services
async def test_create_service_no_role(app, service_data):
db = app.db
service_name = 'service-from-api'
r = await api_request(
app,
f'services/{service_name}',
headers=auth_header(db, 'user'),
data=json.dumps(service_data),
method='post',
)
assert r.status_code == 403
@mark.services
async def test_create_service_conflict(app, service_admin_user, service_data):
db = app.db
service_name = 'service-from-config'
app.services = [{'name': service_name}]
app.init_services()
r = await api_request(
app,
f'services/{service_name}',
headers=auth_header(db, service_admin_user.name),
data=json.dumps(service_data),
method='post',
)
assert r.status_code == 409
@mark.services
async def test_create_service_duplication(app, service_admin_user, service_data):
db = app.db
service_name = 'service-from-api'
r = await api_request(
app,
f'services/{service_name}',
headers=auth_header(db, service_admin_user.name),
data=json.dumps(service_data),
method='post',
)
assert r.status_code == 409
@mark.services
async def test_remove_service(app, service_admin_user, service_data):
db = app.db
service_name = 'service-from-api'
r = await api_request(
app,
f'services/{service_name}',
headers=auth_header(db, service_admin_user.name),
method='delete',
)
assert r.status_code == 200
orm_service = orm.Service.find(db, service_name)
assert orm_service is None
oath_client = (
db.query(orm.OAuthClient)
.filter_by(identifier=service_data['oauth_client_id'])
.first()
)
assert oath_client is None
assert service_name not in app._service_map
@mark.services
async def test_remove_service_from_config(app, service_admin_user):
db = app.db
service_name = 'service-from-config'
r = await api_request(
app,
f'services/{service_name}',
headers=auth_header(db, service_admin_user.name),
method='delete',
)
assert r.status_code == 405
assert (
r.json()['message']
== 'Service service-from-config is not modifiable at runtime'
)
async def test_root_api(app): async def test_root_api(app):
base_url = app.hub.url base_url = app.hub.url
url = ujoin(base_url, 'api') url = ujoin(base_url, 'api')

View File

@@ -44,7 +44,9 @@ def generate_old_db(env_dir, hub_version, db_url):
# changes to this version list must also be reflected # changes to this version list must also be reflected
# in ci/init-db.sh # in ci/init-db.sh
@pytest.mark.parametrize('hub_version', ["1.1.0", "1.2.2", "1.3.0", "1.5.0", "2.1.1"]) @pytest.mark.parametrize(
'hub_version', ['1.1.0', '1.2.2', '1.3.0', '1.5.0', '2.1.1', '3.1.1']
)
async def test_upgrade(tmpdir, hub_version): async def test_upgrade(tmpdir, hub_version):
db_url = os.getenv('JUPYTERHUB_TEST_DB_URL') db_url = os.getenv('JUPYTERHUB_TEST_DB_URL')
if db_url: if db_url:

View File

@@ -145,6 +145,33 @@ def test_token_expiry(db):
assert orm_token not in user.api_tokens assert orm_token not in user.api_tokens
def test_service_check_data_only_column(db):
orm_service = orm.Service(name='check_data_only_column', from_config=True)
db.add(orm_service)
db.commit()
assert orm_service._check_data_only_column('from_config')
assert not orm_service._check_data_only_column('server')
assert not orm_service._check_data_only_column('oauth_client_id')
def test_service_get_column(db):
orm_service = orm.Service(name='test_service_get_column', from_config=True)
db.add(orm_service)
db.commit()
orm_service.server = orm.Server()
assert orm_service.get_column('from_config')
assert orm_service.get_column('server') is None
def test_service_update_column(db):
orm_service = orm.Service(name='test_service_set_column', from_config=True)
db.add(orm_service)
db.commit()
assert orm_service.update_column('from_config', False)
assert not orm_service.update_column('server', orm.Server())
def test_service_tokens(db): def test_service_tokens(db):
service = orm.Service(name='secret') service = orm.Service(name='secret')
db.add(service) db.add(service)

View File

@@ -236,6 +236,16 @@ def test_orm_roles_delete_cascade(db):
['tokens!group=hobbits'], ['tokens!group=hobbits'],
{'tokens!group=hobbits', 'read:tokens!group=hobbits'}, {'tokens!group=hobbits', 'read:tokens!group=hobbits'},
), ),
(
['admin:services'],
{
'read:roles:services',
'read:services:name',
'admin:services',
'list:services',
'read:services',
},
),
], ],
) )
def test_get_expanded_scopes(db, scopes, expected_scopes): def test_get_expanded_scopes(db, scopes, expected_scopes):