Merge pull request #18 from alphagov/proxy-to-alpha

Proxy to alpha
This commit is contained in:
Adam Shimali
2016-01-19 16:02:42 +00:00
11 changed files with 389 additions and 5 deletions

2
.gitignore vendored
View File

@@ -2,6 +2,8 @@
__pycache__/ __pycache__/
*.py[cod] *.py[cod]
venv/
# C extensions # C extensions
*.so *.so

View File

@@ -8,10 +8,12 @@ from flask_marshmallow import Marshmallow
from werkzeug.local import LocalProxy from werkzeug.local import LocalProxy
from config import configs from config import configs
from utils import logging from utils import logging
from notify_client import NotifyAPIClient
db = SQLAlchemy() db = SQLAlchemy()
ma = Marshmallow() ma = Marshmallow()
notify_alpha_client = NotifyAPIClient()
api_user = LocalProxy(lambda: _request_ctx_stack.top.api_user) api_user = LocalProxy(lambda: _request_ctx_stack.top.api_user)
@@ -26,17 +28,20 @@ def create_app(config_name, config_overrides=None):
ma.init_app(application) ma.init_app(application)
init_app(application, config_overrides) init_app(application, config_overrides)
logging.init_app(application) logging.init_app(application)
notify_alpha_client.init_app(application)
from app.service.rest import service as service_blueprint from app.service.rest import service as service_blueprint
from app.user.rest import user as user_blueprint from app.user.rest import user as user_blueprint
from app.template.rest import template as template_blueprint from app.template.rest import template as template_blueprint
from app.status.healthcheck import status as status_blueprint from app.status.healthcheck import status as status_blueprint
from app.job.rest import job as job_blueprint from app.job.rest import job as job_blueprint
from app.notifications.rest import notifications as notifications_blueprint
application.register_blueprint(service_blueprint, url_prefix='/service') application.register_blueprint(service_blueprint, url_prefix='/service')
application.register_blueprint(user_blueprint, url_prefix='/user') application.register_blueprint(user_blueprint, url_prefix='/user')
application.register_blueprint(template_blueprint, url_prefix="/template") application.register_blueprint(template_blueprint, url_prefix="/template")
application.register_blueprint(status_blueprint, url_prefix='/status') application.register_blueprint(status_blueprint, url_prefix='/status')
application.register_blueprint(notifications_blueprint, url_prefix='/notifications')
application.register_blueprint(job_blueprint) application.register_blueprint(job_blueprint)
return application return application

View File

@@ -24,7 +24,6 @@ def requires_auth():
try: try:
auth_token = auth_header[7:] auth_token = auth_header[7:]
api_client = fetch_client(get_token_issuer(auth_token)) api_client = fetch_client(get_token_issuer(auth_token))
if api_client is None: if api_client is None:
authentication_response("Invalid credentials", 403) authentication_response("Invalid credentials", 403)

View File

73
app/notifications/rest.py Normal file
View File

@@ -0,0 +1,73 @@
from flask import (
Blueprint,
jsonify,
request
)
from app import notify_alpha_client
import re
mobile_regex = re.compile("^\\+44[\\d]{10}$")
notifications = Blueprint('notifications', __name__)
@notifications.route('/<notification_id>', methods=['GET'])
def get_notifications(notification_id):
return jsonify(notify_alpha_client.fetch_notification_by_id(notification_id)), 200
@notifications.route('/sms', methods=['POST'])
def create_sms_notification():
notification = request.get_json()['notification']
errors = {}
to_errors = validate_to(notification)
message_errors = validate_message(notification)
if to_errors:
errors.update(to_errors)
if message_errors:
errors.update(message_errors)
if errors:
return jsonify(result="error", message=errors), 400
return jsonify(notify_alpha_client.send_sms(mobile_number=notification['to'], message=notification['message'])), 200
@notifications.route('/email', methods=['POST'])
def create_email_notification():
return jsonify(id=123)
def validate_to(json_body):
errors = []
if 'to' not in json_body:
errors.append('required')
else:
if not mobile_regex.match(json_body['to']):
errors.append('invalid phone number, must be of format +441234123123')
if errors:
return {
"to": errors
}
return None
def validate_message(json_body):
errors = []
if 'message' not in json_body:
errors.append('required')
else:
message_length = len(json_body['message'])
if message_length < 1 or message_length > 160:
errors.append('Invalid length. [1 - 160]')
if errors:
return {
"message": errors
}
return None

View File

@@ -1,3 +1,6 @@
import os
class Config(object): class Config(object):
DEBUG = False DEBUG = False
NOTIFY_LOG_LEVEL = 'DEBUG' NOTIFY_LOG_LEVEL = 'DEBUG'
@@ -6,6 +9,8 @@ class Config(object):
SQLALCHEMY_COMMIT_ON_TEARDOWN = False SQLALCHEMY_COMMIT_ON_TEARDOWN = False
SQLALCHEMY_RECORD_QUERIES = True SQLALCHEMY_RECORD_QUERIES = True
SQLALCHEMY_DATABASE_URI = 'postgresql://localhost/notification_api' SQLALCHEMY_DATABASE_URI = 'postgresql://localhost/notification_api'
NOTIFY_DATA_API_URL = os.getenv('NOTIFY_API_URL', "http://localhost:6001")
NOTIFY_DATA_API_AUTH_TOKEN = os.getenv('NOTIFY_API_TOKEN', "dev-token")
ADMIN_CLIENT_USER_NAME = None ADMIN_CLIENT_USER_NAME = None
ADMIN_CLIENT_SECRET = None ADMIN_CLIENT_SECRET = None

View File

@@ -16,3 +16,5 @@ credstash==1.8.0
git+https://github.com/alphagov/notifications-python-client.git@0.1.5#egg=notifications-python-client==0.1.5 git+https://github.com/alphagov/notifications-python-client.git@0.1.5#egg=notifications-python-client==0.1.5
git+https://github.com/alphagov/notifications-utils.git@0.0.3#egg=notifications-utils==0.0.3 git+https://github.com/alphagov/notifications-utils.git@0.0.3#egg=notifications-utils==0.0.3
git+https://github.com/alphagov/notify-api-client.git@0.1.6#egg=notify-api-client==0.1.6

View File

@@ -30,5 +30,5 @@ display_result $? 1 "Code style check"
#display_result $? 2 "Code coverage" #display_result $? 2 "Code coverage"
py.test -v py.test -v tests/
display_result $? 3 "Unit tests" display_result $? 3 "Unit tests"

View File

View File

@@ -0,0 +1,298 @@
from tests import create_authorization_header
from flask import url_for, json
from app import notify_alpha_client
def test_get_notifications(
notify_api, notify_db, notify_db_session, sample_service, sample_admin_service_id, mocker):
"""
Tests GET endpoint '/' to retrieve entire service list.
"""
with notify_api.test_request_context():
with notify_api.test_client() as client:
mocker.patch(
'app.notify_alpha_client.fetch_notification_by_id',
return_value={
'notifications': [
{
'id': 'my_id',
'notification': 'some notify'
}
]
}
)
auth_header = create_authorization_header(
service_id=sample_admin_service_id,
path=url_for('notifications.get_notifications', notification_id=123),
method='GET')
response = client.get(
url_for('notifications.get_notifications', notification_id=123),
headers=[auth_header])
json_resp = json.loads(response.get_data(as_text=True))
assert response.status_code == 200
assert len(json_resp['notifications']) == 1
assert json_resp['notifications'][0]['id'] == 'my_id'
assert json_resp['notifications'][0]['notification'] == 'some notify'
notify_alpha_client.fetch_notification_by_id.assert_called_with("123")
def test_get_notifications_empty_result(
notify_api, notify_db, notify_db_session, sample_service, sample_admin_service_id, mocker):
"""
Tests GET endpoint '/' to retrieve entire service list.
"""
with notify_api.test_request_context():
with notify_api.test_client() as client:
mocker.patch(
'app.notify_alpha_client.fetch_notification_by_id',
return_value={
'notifications': [
]
}
)
auth_header = create_authorization_header(
service_id=sample_admin_service_id,
path=url_for('notifications.get_notifications', notification_id=123),
method='GET')
response = client.get(
url_for('notifications.get_notifications', notification_id=123),
headers=[auth_header])
json_resp = json.loads(response.get_data(as_text=True))
assert response.status_code == 200
assert len(json_resp['notifications']) == 0
notify_alpha_client.fetch_notification_by_id.assert_called_with("123")
def test_should_reject_if_no_phone_numbers(
notify_api, notify_db, notify_db_session, sample_service, sample_admin_service_id, mocker):
"""
Tests GET endpoint '/' to retrieve entire service list.
"""
with notify_api.test_request_context():
with notify_api.test_client() as client:
mocker.patch(
'app.notify_alpha_client.send_sms',
return_value='success'
)
data = {
'notification': {
'message': "my message"
}
}
auth_header = create_authorization_header(
service_id=sample_admin_service_id,
request_body=json.dumps(data),
path=url_for('notifications.create_sms_notification'),
method='POST')
response = client.post(
url_for('notifications.create_sms_notification'),
data=json.dumps(data),
headers=[('Content-Type', 'application/json'), auth_header])
json_resp = json.loads(response.get_data(as_text=True))
print(json_resp)
assert response.status_code == 400
assert json_resp['result'] == 'error'
assert len(json_resp['message']) == 1
assert len(json_resp['message']['to']) == 1
assert json_resp['message']['to'][0] == 'required'
assert not notify_alpha_client.send_sms.called
def test_should_reject_bad_phone_numbers(
notify_api, notify_db, notify_db_session, sample_service, sample_admin_service_id, mocker):
"""
Tests GET endpoint '/' to retrieve entire service list.
"""
with notify_api.test_request_context():
with notify_api.test_client() as client:
mocker.patch(
'app.notify_alpha_client.send_sms',
return_value='success'
)
data = {
'notification': {
'to': 'invalid',
'message': "my message"
}
}
auth_header = create_authorization_header(
service_id=sample_admin_service_id,
request_body=json.dumps(data),
path=url_for('notifications.create_sms_notification'),
method='POST')
response = client.post(
url_for('notifications.create_sms_notification'),
data=json.dumps(data),
headers=[('Content-Type', 'application/json'), auth_header])
json_resp = json.loads(response.get_data(as_text=True))
print(json_resp)
assert response.status_code == 400
assert json_resp['result'] == 'error'
assert len(json_resp['message']) == 1
assert len(json_resp['message']['to']) == 1
assert json_resp['message']['to'][0] == 'invalid phone number, must be of format +441234123123'
assert not notify_alpha_client.send_sms.called
def test_should_reject_missing_message(
notify_api, notify_db, notify_db_session, sample_service, sample_admin_service_id, mocker):
"""
Tests GET endpoint '/' to retrieve entire service list.
"""
with notify_api.test_request_context():
with notify_api.test_client() as client:
mocker.patch(
'app.notify_alpha_client.send_sms',
return_value='success'
)
data = {
'notification': {
'to': '+441234123123'
}
}
auth_header = create_authorization_header(
service_id=sample_admin_service_id,
request_body=json.dumps(data),
path=url_for('notifications.create_sms_notification'),
method='POST')
response = client.post(
url_for('notifications.create_sms_notification'),
data=json.dumps(data),
headers=[('Content-Type', 'application/json'), auth_header])
json_resp = json.loads(response.get_data(as_text=True))
assert response.status_code == 400
assert json_resp['result'] == 'error'
assert len(json_resp['message']) == 1
assert len(json_resp['message']['message']) == 1
assert json_resp['message']['message'][0] == 'required'
assert not notify_alpha_client.send_sms.called
def test_should_reject_too_short_message(
notify_api, notify_db, notify_db_session, sample_service, sample_admin_service_id, mocker):
"""
Tests GET endpoint '/' to retrieve entire service list.
"""
with notify_api.test_request_context():
with notify_api.test_client() as client:
mocker.patch(
'app.notify_alpha_client.send_sms',
return_value='success'
)
data = {
'notification': {
'to': '+441234123123',
'message': ''
}
}
auth_header = create_authorization_header(
service_id=sample_admin_service_id,
request_body=json.dumps(data),
path=url_for('notifications.create_sms_notification'),
method='POST')
response = client.post(
url_for('notifications.create_sms_notification'),
data=json.dumps(data),
headers=[('Content-Type', 'application/json'), auth_header])
json_resp = json.loads(response.get_data(as_text=True))
assert response.status_code == 400
assert json_resp['result'] == 'error'
assert len(json_resp['message']) == 1
assert len(json_resp['message']['message']) == 1
assert json_resp['message']['message'][0] == 'Invalid length. [1 - 160]'
assert not notify_alpha_client.send_sms.called
def test_should_reject_too_long_message(
notify_api, notify_db, notify_db_session, sample_service, sample_admin_service_id, mocker):
"""
Tests GET endpoint '/' to retrieve entire service list.
"""
with notify_api.test_request_context():
with notify_api.test_client() as client:
mocker.patch(
'app.notify_alpha_client.send_sms',
return_value='success'
)
data = {
'notification': {
'to': '+441234123123',
'message': '1' * 161
}
}
auth_header = create_authorization_header(
service_id=sample_admin_service_id,
request_body=json.dumps(data),
path=url_for('notifications.create_sms_notification'),
method='POST')
response = client.post(
url_for('notifications.create_sms_notification'),
data=json.dumps(data),
headers=[('Content-Type', 'application/json'), auth_header])
json_resp = json.loads(response.get_data(as_text=True))
assert response.status_code == 400
assert json_resp['result'] == 'error'
assert len(json_resp['message']) == 1
assert len(json_resp['message']['message']) == 1
assert json_resp['message']['message'][0] == 'Invalid length. [1 - 160]'
assert not notify_alpha_client.send_sms.called
def test_should_allow_valid_message(
notify_api, notify_db, notify_db_session, sample_service, sample_admin_service_id, mocker):
"""
Tests GET endpoint '/' to retrieve entire service list.
"""
with notify_api.test_request_context():
with notify_api.test_client() as client:
mocker.patch(
'app.notify_alpha_client.send_sms',
return_value={
"notification": {
"createdAt": "2015-11-03T09:37:27.414363Z",
"id": 100,
"jobId": 65,
"message": "This is the message",
"method": "sms",
"status": "created",
"to": "+449999999999"
}
}
)
data = {
'notification': {
'to': '+441234123123',
'message': 'valid'
}
}
auth_header = create_authorization_header(
service_id=sample_admin_service_id,
request_body=json.dumps(data),
path=url_for('notifications.create_sms_notification'),
method='POST')
response = client.post(
url_for('notifications.create_sms_notification'),
data=json.dumps(data),
headers=[('Content-Type', 'application/json'), auth_header])
json_resp = json.loads(response.get_data(as_text=True))
assert response.status_code == 200
assert json_resp['notification']['id'] == 100
notify_alpha_client.send_sms.assert_called_with(mobile_number='+441234123123', message='valid')

View File

@@ -351,10 +351,10 @@ def test_delete_user_not_exists(notify_api, notify_db, notify_db_session, sample
with notify_api.test_client() as client: with notify_api.test_client() as client:
assert User.query.count() == 2 assert User.query.count() == 2
auth_header = create_authorization_header(service_id=sample_admin_service_id, auth_header = create_authorization_header(service_id=sample_admin_service_id,
path=url_for('user.update_user', user_id='123'), path=url_for('user.update_user', user_id='99999'),
method='DELETE') method='DELETE')
resp = client.delete( resp = client.delete(
url_for('user.update_user', user_id="123"), url_for('user.update_user', user_id="99999"),
headers=[('Content-Type', 'application/json'), auth_header]) headers=[('Content-Type', 'application/json'), auth_header])
assert resp.status_code == 404 assert resp.status_code == 404
assert User.query.count() == 2 assert User.query.count() == 2