Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add test VersionHandler; Update Version Handler's API #130

Merged
merged 15 commits into from
Jul 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions bookstore/bookstore_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def validate_bookstore(settings: BookstoreSettings):
Returns
-------
validation_checks : dict
Existence of settings by category (general, archive, publish)
Statements about whether features are validly configured and available
"""
general_settings = [settings.s3_bucket != "", settings.s3_endpoint_url != ""]
archive_settings = [*general_settings, settings.workspace_prefix != ""]
Expand All @@ -85,6 +85,6 @@ def validate_bookstore(settings: BookstoreSettings):
"bookstore_valid": all(general_settings),
"archive_valid": all(archive_settings),
"publish_valid": all(published_settings),
"cloning_valid": all(cloning_settings),
"clone_valid": all(cloning_settings),
}
return validation_checks
38 changes: 24 additions & 14 deletions bookstore/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,34 @@ class BookstoreVersionHandler(APIHandler):
"""Handler responsible for Bookstore version information

Used to lay foundations for the bookstore package. Though, frontends can use this endpoint for feature detection.

Methods
-------
get(self)
Provides version info and feature availability based on serverside settings.
build_response_dict(self)
Helper to populate response.
"""

@web.authenticated
def get(self):
self.finish(
json.dumps(
{
"bookstore": True,
"version": self.settings['bookstore']["version"],
"validation": self.settings['bookstore']["validation"],
}
)
)
"""GET /api/bookstore/

Returns version info and validation info for various bookstore features.
"""
self.finish(json.dumps(self.build_response_dict()))

def build_response_dict(self):
"""Helper for building the version handler's response before serialization."""
return {
"release": self.settings['bookstore']["release"],
"features": self.settings['bookstore']["features"],
}


# TODO: Add a check. Note: We need to ensure that publishing is not configured if bookstore settings are not
# set. Because of how the APIHandlers cannot be configurable, all we can do is reach into settings
# For applications this will mean checking the config and then applying it in
def build_settings_dict(validation):
"""Helper for building the settings info that will be assigned to the web_app."""
return {"release": version, "features": validation}


def load_jupyter_server_extension(nb_app):
Expand All @@ -48,7 +58,7 @@ def load_jupyter_server_extension(nb_app):

bookstore_settings = BookstoreSettings(parent=nb_app)
validation = validate_bookstore(bookstore_settings)
web_app.settings['bookstore'] = {"version": version, "validation": validation}
web_app.settings['bookstore'] = build_settings_dict(validation)
handlers = collect_handlers(nb_app.log, base_url, validation)
web_app.add_handlers(host_pattern, handlers)

Expand Down Expand Up @@ -92,7 +102,7 @@ def collect_handlers(log, base_url, validation):
else:
log.info("[bookstore] Publishing disabled. s3_bucket or endpoint are not configured.")

if validation['cloning_valid']:
if validation['clone_valid']:
log.info(f"[bookstore] Enabling bookstore cloning, version: {version}")
handlers.append(
(url_path_join(base_bookstore_api_pattern, r"/clone(?:/?)*"), BookstoreCloneAPIHandler)
Expand Down
12 changes: 6 additions & 6 deletions bookstore/tests/test_bookstore_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def test_validate_bookstore_defaults():
"bookstore_valid": False,
"publish_valid": False,
"archive_valid": False,
"cloning_valid": True,
"clone_valid": True,
}
settings = BookstoreSettings()
assert validate_bookstore(settings) == expected
Expand All @@ -22,7 +22,7 @@ def test_validate_bookstore_published():
"bookstore_valid": True,
"publish_valid": False,
"archive_valid": True,
"cloning_valid": True,
"clone_valid": True,
}
settings = BookstoreSettings(s3_bucket="A_bucket", published_prefix="")
assert validate_bookstore(settings) == expected
Expand All @@ -34,7 +34,7 @@ def test_validate_bookstore_workspace():
"bookstore_valid": True,
"publish_valid": True,
"archive_valid": False,
"cloning_valid": True,
"clone_valid": True,
}
settings = BookstoreSettings(s3_bucket="A_bucket", workspace_prefix="")
assert validate_bookstore(settings) == expected
Expand All @@ -46,7 +46,7 @@ def test_validate_bookstore_endpoint():
"bookstore_valid": False,
"publish_valid": False,
"archive_valid": False,
"cloning_valid": True,
"clone_valid": True,
}
settings = BookstoreSettings(s3_endpoint_url="")
assert validate_bookstore(settings) == expected
Expand All @@ -58,7 +58,7 @@ def test_validate_bookstore_bucket():
"bookstore_valid": True,
"publish_valid": True,
"archive_valid": True,
"cloning_valid": True,
"clone_valid": True,
}
settings = BookstoreSettings(s3_bucket="A_bucket")
assert validate_bookstore(settings) == expected
Expand All @@ -70,7 +70,7 @@ def test_disable_cloning():
"bookstore_valid": True,
"publish_valid": True,
"archive_valid": True,
"cloning_valid": False,
"clone_valid": False,
}
settings = BookstoreSettings(s3_bucket="A_bucket", enable_cloning=False)
assert validate_bookstore(settings) == expected
105 changes: 100 additions & 5 deletions bookstore/tests/test_handlers.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
"""Tests for handlers"""
from unittest.mock import Mock

import pytest
import logging
from unittest.mock import Mock

from bookstore.handlers import collect_handlers, BookstoreVersionHandler
from bookstore._version import __version__
from bookstore.handlers import collect_handlers, build_settings_dict, BookstoreVersionHandler
from bookstore.bookstore_config import BookstoreSettings, validate_bookstore
from bookstore.clone import BookstoreCloneHandler, BookstoreCloneAPIHandler
from bookstore.publish import BookstorePublishAPIHandler
from notebook.base.handlers import path_regex
from tornado.web import Application
from tornado.testing import AsyncTestCase
from tornado.web import Application, HTTPError
from tornado.httpserver import HTTPRequest
from traitlets.config import Config

log = logging.getLogger('test_handlers')
version = __version__


def test_handlers():
pass
from traitlets.config import Config


def test_collect_handlers_all():
Expand Down Expand Up @@ -68,3 +72,94 @@ def test_collect_handlers_only_version():
validation = validate_bookstore(bookstore_settings)
handlers = collect_handlers(log, '/', validation)
assert expected == handlers


@pytest.fixture(scope="class")
def bookstore_settings(request):
mock_settings = {
"BookstoreSettings": {
"s3_access_key_id": "mock_id",
"s3_secret_access_key": "mock_access",
"s3_bucket": "my_bucket",
}
}
config = Config(mock_settings)
bookstore_settings = BookstoreSettings(config=config)
if request.cls is not None:
request.cls.bookstore_settings = bookstore_settings
return bookstore_settings


def test_build_settings_dict(bookstore_settings):
expected = {
'features': {
'archive_valid': True,
'bookstore_valid': True,
'publish_valid': True,
'clone_valid': True,
},
'release': version,
}
validation = validate_bookstore(bookstore_settings)
assert expected == build_settings_dict(validation)


@pytest.mark.usefixtures("bookstore_settings")
class TestCloneAPIHandler(AsyncTestCase):
def setUp(self):
super().setUp()

validation = validate_bookstore(self.bookstore_settings)
self.mock_application = Mock(
spec=Application,
ui_methods={},
ui_modules={},
settings={"bookstore": build_settings_dict(validation)},
transforms=[],
)

def get_handler(self, uri, app=None):
if app is None:
app = self.mock_application
connection = Mock(context=Mock(protocol="https"))
payload_request = HTTPRequest(
method='GET',
uri=uri,
headers={"Host": "localhost:8888"},
body=None,
connection=connection,
)
return BookstoreVersionHandler(app, payload_request)

def test_get(self):
"""This is a simple test of the get API at /api/bookstore

The most notable feature is the need to set _transforms on the handler.

The default value of handler()._transforms is `None`.
willingc marked this conversation as resolved.
Show resolved Hide resolved
This is iterated over when handler().flush() is called, raising a TypeError.

In normal usage, the application assigns this when it creates a handler delegate.

Because our mock application does not do this
As a result this raises an error when self.finish() (and therefore self.flush()) is called.

At runtime on a live Jupyter server, application.transforms == [].
"""
get_handler = self.get_handler('/api/bookstore/')
setattr(get_handler, '_transforms', [])
return_val = get_handler.get()
assert return_val is None

def test_build_response(self):
empty_handler = self.get_handler('/api/bookstore/')
expected = {
'features': {
'archive_valid': True,
'bookstore_valid': True,
'publish_valid': True,
'clone_valid': True,
},
'release': version,
}
assert empty_handler.build_response_dict() == expected
27 changes: 13 additions & 14 deletions docs/source/bookstore_api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -194,27 +194,26 @@ components:
format:
type: string
description: Format of content (one of null, 'text', 'base64', 'json')
ValidationInfo:
FeatureValidationInfo:
type: object
required:
- bookstore_validation
- archive_validation
- published_validation
- bookstore_valid
- archive_valid
- publish_valid
- clone_valid
properties:
bookstore_validation:
bookstore_valid:
type: boolean
archive_validation:
archive_valid:
type: boolean
published_validation:
publish_valid:
type: boolean
clone_valid:
type: boolean
VersionInfo:
type: object
required:
- s3path
properties:
bookstore:
type: boolean
version:
release:
type: string
validation:
$ref: '#/components/schemas/ValidationInfo'
features:
$ref: '#/components/schemas/FeatureValidationInfo'