Merge pull request #634 from alphagov/research-mode

Research mode
This commit is contained in:
minglis
2016-06-02 12:00:39 +01:00
7 changed files with 194 additions and 29 deletions

View File

@@ -285,6 +285,7 @@ def register_errorhandlers(application):
@application.errorhandler(HTTPError)
def render_http_error(error):
application.logger.error("API called failed with status {} message {}".format(error.status_code, error.message))
error_code = error.status_code
if error_code not in [401, 404, 403, 500]:
error_code = 500

View File

@@ -55,10 +55,10 @@ def service_name_change(service_id):
@login_required
@user_has_permissions('manage_settings', admin_override=True)
def service_name_change_confirm(service_id):
# Validate password for form
def _check_password(pwd):
return user_api_client.verify_password(current_user.id, pwd)
form = ConfirmPasswordForm(_check_password)
if form.validate_on_submit():
@@ -94,7 +94,6 @@ def service_name_change_confirm(service_id):
@login_required
@user_has_permissions('manage_settings', admin_override=True)
def service_request_to_go_live(service_id):
form = RequestToGoLiveForm()
if form.validate_on_submit():
@@ -126,7 +125,7 @@ def service_request_to_go_live(service_id):
"Deskpro create ticket request failed with {} '{}'".format(
resp.status_code,
resp.json())
)
)
abort(500, "Request to go live submission failed")
flash('Weve received your request to go live', 'default')
@@ -152,6 +151,17 @@ def service_switch_live(service_id):
return redirect(url_for('.service_settings', service_id=service_id))
@main.route("/services/<service_id>/service-settings/research-mode")
@login_required
@user_has_permissions(admin_override=True)
def service_switch_research_mode(service_id):
service_api_client.update_service_with_properties(
service_id,
{"research_mode": False if current_service['research_mode'] else True}
)
return redirect(url_for('.service_settings', service_id=service_id))
@main.route("/services/<service_id>/service-settings/status", methods=['GET', 'POST'])
@login_required
@user_has_permissions('manage_settings', admin_override=True)
@@ -171,6 +181,7 @@ def service_status_change_confirm(service_id):
# Validate password for form
def _check_password(pwd):
return user_api_client.verify_password(current_user.id, pwd)
form = ConfirmPasswordForm(_check_password)
if form.validate_on_submit():
@@ -195,7 +206,6 @@ def service_status_change_confirm(service_id):
@login_required
@user_has_permissions('manage_settings', admin_override=True)
def service_delete(service_id):
if request.method == 'GET':
return render_template(
'views/service-settings/delete.html'
@@ -208,10 +218,10 @@ def service_delete(service_id):
@login_required
@user_has_permissions('manage_settings', admin_override=True)
def service_delete_confirm(service_id):
# Validate password for form
def _check_password(pwd):
return user_api_client.verify_password(current_user.id, pwd)
form = ConfirmPasswordForm(_check_password)
if form.validate_on_submit():

View File

@@ -6,7 +6,6 @@ from app.notify_client import _attach_current_user
class ServiceAPIClient(NotificationsAPIClient):
# Fudge assert in the super __init__ so
# we can set those variables later.
def __init__(self):
@@ -81,6 +80,11 @@ class ServiceAPIClient(NotificationsAPIClient):
endpoint = "/service/{0}".format(service_id)
return self.post(endpoint, data)
def update_service_with_properties(self, service_id, properties):
_attach_current_user(properties)
endpoint = "/service/{0}".format(service_id)
return self.post(endpoint, properties)
def remove_user_from_service(self, service_id, user_id):
"""
Remove a user from a service
@@ -181,7 +185,6 @@ class ServiceAPIClient(NotificationsAPIClient):
class ServicesBrowsableItem(BrowsableItem):
@property
def title(self):
return self._item['name']

View File

@@ -111,6 +111,9 @@
<nav class="footer-nav">
Built by the <a href="https://www.gov.uk/government/organisations/government-digital-service">Government Digital Service</a>
<a href="{{ url_for("main.cookies") }}">Cookies</a>
{% if current_service.research_mode %}
<span id="research-mode" style="font-weight: bold; display: inline-block; padding: 5px 10px; background: #005EA5; color: #fff; border-radius: 2px;">research mode</span>
{% endif %}
</nav>
{% endblock %}

View File

@@ -40,6 +40,14 @@
'link': url_for('.service_switch_live', service_id=current_service.id)
} if not current_service.restricted and current_user.has_permissions([], admin_override=True) else {
},
{
'title': 'Put service into research mode',
'link': url_for('.service_switch_research_mode', service_id=current_service.id)
} if not current_service.research_mode and current_user.has_permissions([], admin_override=True) else {
'title': 'Take service out of research mode',
'link': url_for('.service_switch_research_mode', service_id=current_service.id)
} if current_service.research_mode and current_user.has_permissions([], admin_override=True) else {
},
]) }}
{% endblock %}

View File

@@ -39,7 +39,16 @@ def created_by_json(id_, name='', email_address=''):
return {'id': id_, 'name': name, 'email_address': email_address}
def service_json(id_, name, users, message_limit=1000, active=False, restricted=True, email_from=None, reply_to_email_address=None): # noqa
def service_json(
id_,
name,
users,
message_limit=1000,
active=False,
restricted=True,
email_from=None,
reply_to_email_address=None,
research_mode=False):
return {
'id': id_,
'name': name,
@@ -48,7 +57,8 @@ def service_json(id_, name, users, message_limit=1000, active=False, restricted=
'active': active,
'restricted': restricted,
'email_from': email_from,
'reply_to_email_address': reply_to_email_address
'reply_to_email_address': reply_to_email_address,
'research_mode': research_mode
}
@@ -223,6 +233,8 @@ def validate_route_permission(mocker,
mocker.patch(
'app.service_api_client.get_services',
return_value={'data': []})
mocker.patch('app.service_api_client.update_service', return_value=service)
mocker.patch('app.service_api_client.update_service_with_properties', return_value=service)
mocker.patch('app.user_api_client.get_user', return_value=usr)
mocker.patch('app.user_api_client.get_user_by_email', return_value=usr)
mocker.patch('app.service_api_client.get_service', return_value={'data': service})
@@ -238,5 +250,6 @@ def validate_route_permission(mocker,
else:
pytest.fail("Invalid method call {}".format(method))
if resp.status_code != response_code:
print(resp.status_code)
pytest.fail("Invalid permissions set for endpoint {}".format(route))
return resp

View File

@@ -3,7 +3,7 @@ from flask import url_for
import app
from app.utils import email_safe
from tests import validate_route_permission
from tests import validate_route_permission, service_json
from bs4 import BeautifulSoup
from unittest.mock import ANY, Mock
from werkzeug.exceptions import InternalServerError
@@ -215,12 +215,12 @@ def test_should_show_request_to_go_live(app_,
def test_should_redirect_after_request_to_go_live(
app_,
api_user_active,
mock_get_user,
mock_get_service,
mock_has_permissions,
mocker
app_,
api_user_active,
mock_get_user,
mock_get_service,
mock_has_permissions,
mocker
):
mock_post = mocker.patch(
'app.main.views.feedback.requests.post',
@@ -241,6 +241,8 @@ def test_should_redirect_after_request_to_go_live(
'department_id': ANY,
'agent_team_id': ANY,
'message': 'From Test User <test@user.gov.uk> on behalf of Test Service (http://localhost/services/6ce466d0-fd6a-11e5-82f5-e0accb9d11a6/dashboard)\n\nUsage estimate\n---\n\nOne million messages', # noqa
# noqa
# noqa
'person_email': ANY
},
headers=ANY
@@ -254,12 +256,12 @@ def test_should_redirect_after_request_to_go_live(
def test_log_error_on_request_to_go_live(
app_,
api_user_active,
mock_get_user,
mock_get_service,
mock_has_permissions,
mocker
app_,
api_user_active,
mock_get_user,
mock_get_service,
mock_has_permissions,
mocker
):
mock_post = mocker.patch(
'app.main.views.service_settings.requests.post',
@@ -489,6 +491,8 @@ def test_route_invalid_permissions(mocker, app_, api_user_active, service_one):
'main.service_name_change',
'main.service_name_change_confirm',
'main.service_request_to_go_live',
'main.service_switch_live',
'main.service_switch_research_mode',
'main.service_status_change',
'main.service_status_change_confirm',
'main.service_delete',
@@ -516,7 +520,7 @@ def test_route_for_platform_admin(mocker, app_, platform_admin_user, service_one
'main.service_status_change_confirm',
'main.service_delete',
'main.service_delete_confirm'
]
]
with app_.test_request_context():
for route in routes:
validate_route_permission(mocker,
@@ -529,11 +533,29 @@ def test_route_for_platform_admin(mocker, app_, platform_admin_user, service_one
service_one)
def test_set_reply_to_email_address(app_,
active_user_with_permissions,
mocker,
mock_update_service,
service_one):
def test_route_for_platform_admin_update_service(mocker, app_, platform_admin_user, service_one):
routes = [
'main.service_switch_live',
'main.service_switch_research_mode'
]
with app_.test_request_context():
for route in routes:
validate_route_permission(mocker,
app_,
"GET",
302,
url_for(route, service_id=service_one['id']),
[],
platform_admin_user,
service_one)
def test_set_reply_to_email_address(
app_,
active_user_with_permissions,
mocker,
mock_update_service,
service_one):
with app_.test_request_context():
with app_.test_client() as client:
client.login(active_user_with_permissions, mocker, service_one)
@@ -556,7 +578,6 @@ def test_if_reply_to_email_address_set_then_form_populated(app_,
active_user_with_permissions,
mocker,
service_one):
service_one['reply_to_email_address'] = 'test@service.gov.uk'
with app_.test_request_context():
with app_.test_client() as client:
@@ -566,3 +587,109 @@ def test_if_reply_to_email_address_set_then_form_populated(app_,
assert response.status_code == 200
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
assert page.find(id='email_address')['value'] == 'test@service.gov.uk'
def test_switch_service_to_research_mode(
app_,
service_one,
mock_login,
mock_get_user,
active_user_with_permissions,
mock_get_service,
mock_has_permissions,
mocker):
with app_.test_request_context():
with app_.test_client() as client:
mocker.patch('app.service_api_client.update_service_with_properties', return_value=service_one)
client.login(active_user_with_permissions)
response = client.get(url_for('main.service_switch_research_mode', service_id=service_one['id']))
assert response.status_code == 302
assert response.location == url_for('main.service_settings', service_id=service_one['id'], _external=True)
app.service_api_client.update_service_with_properties.assert_called_with(
service_one['id'], {"research_mode": True}
)
def test_switch_service_from_research_mode_to_normal(
app_,
service_one,
mock_login,
mock_get_user,
active_user_with_permissions,
mock_get_service,
mock_has_permissions,
mocker):
with app_.test_request_context():
with app_.test_client() as client:
service = service_json(
"1234",
"Test Service",
[active_user_with_permissions.id],
message_limit=1000,
active=False,
restricted=True,
research_mode=True
)
mocker.patch('app.service_api_client.get_service', return_value={"data": service})
mocker.patch('app.service_api_client.update_service_with_properties', return_value=service_one)
client.login(active_user_with_permissions)
response = client.get(url_for('main.service_switch_research_mode', service_id=service_one['id']))
assert response.status_code == 302
assert response.location == url_for('main.service_settings', service_id=service_one['id'], _external=True)
app.service_api_client.update_service_with_properties.assert_called_with(
service_one['id'], {"research_mode": False}
)
def test_shows_research_mode_indicator(
app_,
service_one,
mock_login,
mock_get_user,
active_user_with_permissions,
mock_get_service,
mock_has_permissions,
mocker):
with app_.test_request_context():
with app_.test_client() as client:
service = service_json(
"1234",
"Test Service",
[active_user_with_permissions.id],
message_limit=1000,
active=False,
restricted=True,
research_mode=True
)
mocker.patch('app.service_api_client.get_service', return_value={"data": service})
mocker.patch('app.service_api_client.update_service_with_properties', return_value=service_one)
client.login(active_user_with_permissions)
response = client.get(url_for('main.service_settings', service_id=service_one['id']))
assert response.status_code == 200
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
element = page.find('span', {"id": "research-mode"})
assert element.text == 'research mode'
def test_does_not_show_research_mode_indicator(
app_,
service_one,
mock_login,
mock_get_user,
active_user_with_permissions,
mock_get_service,
mock_has_permissions,
mocker):
with app_.test_request_context():
with app_.test_client() as client:
client.login(active_user_with_permissions)
response = client.get(url_for('main.service_settings', service_id=service_one['id']))
assert response.status_code == 200
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
element = page.find('span', {"id": "research-mode"})
assert not element