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

Reimplement checkrealtime as part of smokey #8

Merged
merged 2 commits into from Mar 1, 2016
Jump to file or symbol
Failed to load files and symbols.
+208 −5
Diff settings

Always

Just for now

@@ -5,15 +5,17 @@
from behave.model import ScenarioOutline
logging.basicConfig(format='%(asctime)s [%(levelname)s] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
level=logging.INFO)
log = logging.getLogger(__name__)
# Configuration default values
CONFIG_DEFAULTS = {
'api_root': 'https://hypothes.is/api',
'proxy_root': 'https://via.hypothes.is',
'unsafe_disable_ssl_verification': False,
'websocket_endpoint': 'wss://hypothes.is/ws',
}
# Configuration configurable from the environment
@@ -22,9 +24,20 @@
'proxy_root',
'sauce_access_key',
'sauce_username',
'websocket_endpoint',
]
# A list of test users that scenarios can use
TEST_USERS = [
'smokey'
]
class UnsafeHTTPAdapter(requests.adapters.HTTPAdapter):
def cert_verify(self, conn, url, verify, cert):
return
def before_all(context):
# Load config from system environment
for k in CONFIG_ENV:
@@ -36,10 +49,27 @@ def before_all(context):
for k, v in CONFIG_DEFAULTS.items():
context.config.userdata.setdefault(k, v)
# Load test user keys from environment
context.test_users = {}
for user in TEST_USERS:
token_key = 'TEST_USER_{user}_KEY'.format(user=user.upper())
userid_key = 'TEST_USER_{user}_USERID'.format(user=user.upper())

This comment has been minimized.

@robertknight

robertknight Mar 1, 2016

Contributor

Based on my own screwups in testing this, it might be a good idea to validate the format of the user ID here.

@robertknight

robertknight Mar 1, 2016

Contributor

Based on my own screwups in testing this, it might be a good idea to validate the format of the user ID here.

if token_key in os.environ and userid_key in os.environ:
token = os.environ[token_key]
userid = os.environ[userid_key]
if not userid.startswith('acct:') or '@' not in userid:
log.warn('{var} should contain a full userid of the form '
'acct:<username>@<domain>'.format(var=userid_key))
context.test_users[user] = {'token': token, 'userid': userid}
# Set up an HTTP client session with some reasonable defaults (including
# retrying requests that fail).
session = requests.Session()
adapter = requests.adapters.HTTPAdapter(max_retries=3)
if context.config.userdata['unsafe_disable_ssl_verification']:
adapter = UnsafeHTTPAdapter(max_retries=3)
session.mount('http://', adapter)
session.mount('https://', adapter)
@@ -91,11 +121,13 @@ def before_scenario(context, scenario):
if 'sauce' in scenario.tags and not _check_sauce_config(context):
scenario.skip("Sauce config not provided")
# Allow scenarios to register teardown tasks
context.teardown = []
def after_scenario(context, scenario):
# Shut down any webdriver instances that were started by the scenario.
if hasattr(context, 'browser'):
context.browser.close()
for teardown_task in context.teardown:
teardown_task()
def _check_sauce_config(context):
@@ -1,4 +1,4 @@
from behave import *
from behave import given, then, when
def search(context, params={}):
@@ -7,6 +7,63 @@ def search(context, params={}):
params=params)
def create_annotation(context, data=None):
try:
getattr(context, 'user')
except AttributeError:
raise RuntimeError("can't create annotations without active test user")
if data is None:
data = {}
data.update({
"smokey": True,
"permissions": {
"read": ["group:__world__"],
"delete": [context.user['userid']],
}
})
api_root = context.config.userdata['api_root']
url = '{root}/annotations'.format(root=api_root)
context.last_response = context.http.post(url, json=data)
def delete_annotation(context, id):
try:
getattr(context, 'user')
except AttributeError:
raise RuntimeError("can't delete annotations without active test user")
api_root = context.config.userdata['api_root']
url = '{root}/annotations/{id}'.format(root=api_root, id=id)
context.last_response = context.http.delete(url)
@given('I am acting as the test user "{user}"')
def act_as_test_user(context, user):
if user not in context.test_users:
context.scenario.skip('API key for test user "{user}" not '
'provided!'.format(user=user))
return
# Set the current user so that other step definitions can refer to it
context.user = context.test_users[user]
# Set the API token in use
context.http.headers.update({
'Authorization': 'Bearer {token}'.format(token=context.user['token'])
})
@when('I create a test annotation')
def create_test_annotation(context):
create_annotation(context)
ann = context.last_response.json()
context.last_test_annotation = ann
context.teardown.append(lambda: delete_annotation(context, ann['id']))
@when('I search with no query')
def search_with_no_query(context):
search(context)
@@ -98,3 +98,4 @@ def _start_sauce_browser(self, context):
def connect_browser(context, browser):
context.browser = Browser(browser)
context.browser.start(context)
context.teardown.append(context.browser.close)
@@ -0,0 +1,105 @@
import asyncio
import logging
import json
import ssl
import uuid
from behave import *
import websockets
log = logging.getLogger(__name__)
def wait_for(timeout, func, *args, **kwargs):
"""Block waiting for a a coroutine call to complete, with a timeout."""
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(func(*args, **kwargs))
try:
loop.run_until_complete(asyncio.wait_for(task, timeout=timeout))
except asyncio.TimeoutError as e:
task.cancel()
raise
async def connect(context):
"""Establish a websocket connection and send a client_id message."""
endpoint = context.config.userdata['websocket_endpoint']
verify = True
if context.config.userdata['unsafe_disable_ssl_verification']:
verify = False
ssl_context = _ssl_context(verify=verify)
context.websocket = await websockets.connect(endpoint, ssl=ssl_context)
context.teardown.append(lambda: wait_for(10.0, context.websocket.close))
async def send(websocket, message):
"""JSON-encode and send a message over the websocket."""
await websocket.send(json.dumps(message))
async def await_annotation(websocket, id):
"""Wait to see a notification about annotation with the given `id`"""
while True:
msg = await websocket.recv()
try:
data = json.loads(msg)
except ValueError:
log.warn('received non-JSON message: {!r}'.format(msg))
continue
if data.get('type') != 'annotation-notification':
continue
if data.get('options') != {'action': 'create'}:
continue
if 'payload' not in data:
log.warn('saw annotation-notification lacking payload: {!r}'.format(msg))
continue
if not isinstance(data['payload'], list):
log.warn('saw annotation-notification with bad payload format: {!r}'.format(msg))
continue
for annotation in data['payload']:
if annotation.get('id') == id:
return
@given('I am connected to the websocket')
def connect_websocket(context):
wait_for(10.0, connect, context)
wait_for(2.0, send, context.websocket, {
'messageType': 'client_id',
'value': str(uuid.uuid4()),
})
@given('I request to be notified of all annotation events')
def request_notification_all(context):
wait_for(2.0, send, context.websocket, {
'filter': {
'match_policy': 'include_all',
'clauses': [],
'actions': {'create': True, 'update': True, 'delete': True},
}
})
@then('I should receive notification of my test annotation on the websocket')
def wait_for_notification(context):
try:
getattr(context, 'last_test_annotation')
except AttributeError:
raise RuntimeError("you must create a test annotation first!")
id = context.last_test_annotation['id']
wait_for(5.0, await_annotation, context.websocket, id)
def _ssl_context(verify=True):
ssl_context = ssl.create_default_context()
if not verify:
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
return ssl_context
@@ -0,0 +1,7 @@
Feature: Real-time websocket streaming
Scenario: Receiving recently-created annotations over the websocket
Given I am acting as the test user "smokey"
And I am connected to the websocket
And I request to be notified of all annotation events
When I create a test annotation
Then I should receive notification of my test annotation on the websocket
View
@@ -1,3 +1,4 @@
behave
requests
selenium
websockets
ProTip! Use n and p to navigate between commits in a pull request.