Merge pull request #96 from alphagov/api-keys-flow

Add pages for create/view/revoke API keys
This commit is contained in:
Adam Shimali
2016-01-21 17:27:08 +00:00
19 changed files with 506 additions and 62 deletions

View File

@@ -1,6 +1,7 @@
import os import os
import re import re
import dateutil
from flask import Flask, session, Markup, escape, render_template from flask import Flask, session, Markup, escape, render_template
from flask._compat import string_types from flask._compat import string_types
from flask.ext.sqlalchemy import SQLAlchemy from flask.ext.sqlalchemy import SQLAlchemy
@@ -8,6 +9,7 @@ from flask_login import LoginManager
from flask_wtf import CsrfProtect from flask_wtf import CsrfProtect
from werkzeug.exceptions import abort from werkzeug.exceptions import abort
from app.notify_client.api_client import NotificationsAdminAPIClient from app.notify_client.api_client import NotificationsAdminAPIClient
from app.notify_client.api_key_api_client import ApiKeyApiClient
from app.notify_client.user_api_client import UserApiClient from app.notify_client.user_api_client import UserApiClient
from app.its_dangerous_session import ItsdangerousSessionInterface from app.its_dangerous_session import ItsdangerousSessionInterface
import app.proxy_fix import app.proxy_fix
@@ -20,6 +22,7 @@ csrf = CsrfProtect()
notifications_api_client = NotificationsAdminAPIClient() notifications_api_client = NotificationsAdminAPIClient()
user_api_client = UserApiClient() user_api_client = UserApiClient()
api_key_api_client = ApiKeyApiClient()
def create_app(config_name, config_overrides=None): def create_app(config_name, config_overrides=None):
@@ -34,6 +37,7 @@ def create_app(config_name, config_overrides=None):
notifications_api_client.init_app(application) notifications_api_client.init_app(application)
user_api_client.init_app(application) user_api_client.init_app(application)
api_key_api_client.init_app(application)
login_manager.init_app(application) login_manager.init_app(application)
login_manager.login_view = 'main.sign_in' login_manager.login_view = 'main.sign_in'
@@ -51,6 +55,7 @@ def create_app(config_name, config_overrides=None):
application.add_template_filter(placeholders) application.add_template_filter(placeholders)
application.add_template_filter(replace_placeholders) application.add_template_filter(replace_placeholders)
application.add_template_filter(nl2br) application.add_template_filter(nl2br)
application.add_template_filter(format_datetime)
application.after_request(useful_headers_after_request) application.after_request(useful_headers_after_request)
register_errorhandlers(application) register_errorhandlers(application)
@@ -131,6 +136,12 @@ def replace_placeholders(template, values):
)) ))
def format_datetime(date):
date = dateutil.parser.parse(date)
native = date.replace(tzinfo=None)
return native.strftime('%A %d %B %Y at %H:%M')
# https://www.owasp.org/index.php/List_of_useful_HTTP_headers # https://www.owasp.org/index.php/List_of_useful_HTTP_headers
def useful_headers_after_request(response): def useful_headers_after_request(response):
response.headers.add('X-Frame-Options', 'deny') response.headers.add('X-Frame-Options', 'deny')

View File

@@ -1,15 +1,11 @@
(function(Modules) { (function(Modules) {
"use strict"; "use strict";
if (!document.queryCommandSupported('copy')) return;
Modules.ApiKey = function() { Modules.ApiKey = function() {
const states = { const states = {
'initial': `
<input type='button' class='api-key-button-show' value='Show API key' />
`,
'keyVisibleBasic': key => `
<span class="api-key-key">${key}</span>
`,
'keyVisible': key => ` 'keyVisible': key => `
<span class="api-key-key">${key}</span> <span class="api-key-key">${key}</span>
<input type='button' class='api-key-button-copy' value='Copy API key to clipboard' /> <input type='button' class='api-key-button-copy' value='Copy API key to clipboard' />
@@ -33,23 +29,22 @@
this.start = function(component) { this.start = function(component) {
const $component = $(component).html(states.initial).attr('aria-live', 'polite'), const $component = $(component),
key = $component.data('key'); key = $component.data('key');
$component $component
.on( .html(states.keyVisible(key))
'click', '.api-key-button-show', () => .attr('aria-live', 'polite')
$component.html(
document.queryCommandSupported('copy') ?
states.keyVisible(key) : states.keyVisibleBasic(key)
)
)
.on( .on(
'click', '.api-key-button-copy', () => 'click', '.api-key-button-copy', () =>
this.copyKey( this.copyKey(
$('.api-key-key', component)[0], () => $('.api-key-key', component)[0], () =>
$component.html(states.keyCopied) $component.html(states.keyCopied)
) )
)
.on(
'click', '.api-key-button-show', () =>
$component.html(states.keyVisible(key))
); );
}; };

View File

@@ -1,5 +1,10 @@
.api-key { .api-key {
&-name {
@include bold-19;
margin-bottom: 5px;
}
&-key { &-key {
font-family: monospace; font-family: monospace;
display: block; display: block;

View File

@@ -1,3 +1,7 @@
.table {
margin-bottom: $gutter;
}
.table-heading { .table-heading {
text-align: left; text-align: left;
} }
@@ -16,8 +20,15 @@
} }
&-error { &-error {
color: $error-colour; color: $error-colour;
font-weight: bold; font-weight: bold;
a:link,
a:visited {
color: $error-colour;
}
} }
} }

View File

@@ -235,3 +235,17 @@ class ChangeMobileNumberForm(Form):
class ConfirmMobileNumberForm(Form): class ConfirmMobileNumberForm(Form):
sms_code = sms_code() sms_code = sms_code()
class CreateKeyForm(Form):
def __init__(self, existing_key_names=[], *args, **kwargs):
self.existing_key_names = [x.lower() for x in existing_key_names]
super(CreateKeyForm, self).__init__(*args, **kwargs)
key_name = StringField(u'Description of key', validators=[
DataRequired(message='You need to give the key a name')
])
def validate_key_name(self, key_name):
if key_name.data.lower() in self.existing_key_names:
raise ValidationError('A key with this name already exists')

View File

@@ -1,9 +1,55 @@
from flask import render_template from flask import request, render_template, redirect, url_for, flash
from flask_login import login_required from flask_login import login_required
from app.main import main from app.main import main
from app.main.forms import CreateKeyForm
from app import api_key_api_client
@main.route("/services/<int:service_id>/documentation")
@login_required
def documentation(service_id):
return render_template('views/documentation.html', service_id=service_id)
@main.route("/services/<int:service_id>/api-keys") @main.route("/services/<int:service_id>/api-keys")
@login_required @login_required
def api_keys(service_id): def api_keys(service_id):
return render_template('views/api-keys.html', service_id=service_id) return render_template(
'views/api-keys.html',
service_id=service_id,
keys=api_key_api_client.get_api_keys(service_id=service_id)['apiKeys']
)
@main.route("/services/<int:service_id>/api-keys/create", methods=['GET', 'POST'])
@login_required
def create_api_key(service_id):
key_names = [
key['name'] for key in api_key_api_client.get_api_keys(service_id=service_id)['apiKeys']
]
form = CreateKeyForm(key_names)
if form.validate_on_submit():
secret = api_key_api_client.create_api_key(service_id=service_id, key_name=form.key_name.data)
return render_template('views/api-keys/show.html', service_id=service_id, secret=secret,
key_name=form.key_name.data)
return render_template(
'views/api-keys/create.html',
service_id=service_id,
key_name=form.key_name
)
@main.route("/services/<int:service_id>/api-keys/revoke/<int:key_id>", methods=['GET', 'POST'])
@login_required
def revoke_api_key(service_id, key_id):
key_name = api_key_api_client.get_api_keys(service_id=service_id, key_id=key_id)['apiKeys'][0]['name']
if request.method == 'GET':
return render_template(
'views/api-keys/revoke.html',
service_id=service_id,
key_name=key_name
)
elif request.method == 'POST':
api_key_api_client.revoke_api_key(service_id=service_id, key_id=key_id)
flash('{} was revoked'.format(key_name))
return redirect(url_for('.api_keys', service_id=service_id))

View File

@@ -0,0 +1,27 @@
from client.base import BaseAPIClient
class ApiKeyApiClient(BaseAPIClient):
def __init__(self, base_url=None, client_id=None, secret=None):
super(self.__class__, self).__init__(base_url=base_url or 'base_url',
client_id=client_id or 'client_id',
secret=secret or 'secret')
def init_app(self, app):
self.base_url = app.config['API_HOST_NAME']
self.client_id = app.config['ADMIN_CLIENT_USER_NAME']
self.secret = app.config['ADMIN_CLIENT_SECRET']
def get_api_keys(self, service_id, key_id=None, *params):
if key_id:
return self.get(url='/service/{}/api-keys/{}'.format(service_id, key_id))
else:
return self.get(url='/service/{}/api-keys'.format(service_id))
def create_api_key(self, service_id, key_name, *params):
data = {"name": key_name}
key = self.post(url='/service/{}/api-key'.format(service_id), data=data)
return key['data']
def revoke_api_key(self, service_id, key_id, *params):
return self.post(url='/service/{0}/api-key/revoke/{1}'.format(service_id, key_id), data=None)

View File

@@ -1,4 +1,7 @@
{% macro api_key(key) %} {% macro api_key(key, name) %}
<h2 class="api-key-name">
{{ name }}
</h2>
<div data-module="api-key" data-key="{{ key }}"> <div data-module="api-key" data-key="{{ key }}">
<span class="api-key-key">{{ key }}</span> <span class="api-key-key">{{ key }}</span>
</div> </div>

View File

@@ -55,3 +55,7 @@
{% macro right_aligned_field_heading(text) %} {% macro right_aligned_field_heading(text) %}
<span class="table-field-heading-right-aligned">{{ text }}</span> <span class="table-field-heading-right-aligned">{{ text }}</span>
{%- endmacro %} {%- endmacro %}
{% macro hidden_field_heading(text) %}
<span class="visuallyhidden">{{ text }}</span>
{%- endmacro %}

View File

@@ -9,7 +9,8 @@
<li><a href="{{ url_for('.manage_service_templates', service_id=service_id) }}">Templates</a></li> <li><a href="{{ url_for('.manage_service_templates', service_id=service_id) }}">Templates</a></li>
</ul> </ul>
<ul> <ul>
<li><a href="{{ url_for('.api_keys', service_id=service_id) }}">API keys and documentation</a></li> <li><a href="{{ url_for('.api_keys', service_id=123) }}">API keys</a></li>
<li><a href="{{ url_for('.documentation', service_id=123) }}">Developer documentation</a></li>
</ul> </ul>
<ul> <ul>
<li><a href="{{ url_for('.manage_users', service_id=service_id) }}">Manage users</a></li> <li><a href="{{ url_for('.manage_users', service_id=service_id) }}">Manage users</a></li>

View File

@@ -1,6 +1,5 @@
{% extends "withnav_template.html" %} {% extends "withnav_template.html" %}
{% from "components/page-footer.html" import page_footer %} {% from "components/table.html" import list_table, field, hidden_field_heading %}
{% from "components/api-key.html" import api_key %}
{% block page_title %} {% block page_title %}
GOV.UK Notify | API keys and documentation GOV.UK Notify | API keys and documentation
@@ -8,49 +7,51 @@
{% block maincolumn_content %} {% block maincolumn_content %}
<div class="grid-row"> <h1 class="heading-xlarge">
<div class="column-two-thirds"> API keys
</h1>
<h1 class="heading-xlarge"> <p>
API keys and documentation To connect to the API you will need to send your service ID, encrypted with
</h1> an API key. The API key stays secret.
</p>
<h2 class="heading-medium"> <p>
How to integrate GOV.UK Notify into your service There are client libraries available which can do this for you. See
</h2> <a href="{{ url_for(".documentation", service_id=service_id) }}">the
developer documentation</a> for more information.
</p>
<p> <h2 class="api-key-name">
blah blah blah this is where we tell you how the API works Service ID
</p> </h2>
<p class="api-key-key">
{{ service_id }}
</p>
<h2 class="heading-medium">Repositories</h2> {% call(item) list_table(
keys,
empty_message="You havent created any API keys yet",
caption="API keys",
caption_visible=False,
field_headings=['Key name', hidden_field_heading('Action')]
) %}
{% call field() %}
{{ item.name }}
{% endcall %}
{% if item.expiry_date %}
{% call field(align='right', status='default') %}
Revoked {{ item.expiry_date|format_datetime }}
{% endcall %}
{% else %}
{% call field(align='right', status='error') %}
<a href='{{ url_for('.revoke_api_key', service_id=service_id, key_id=item.id) }}'>Revoke</a>
{% endcall %}
{% endif %}
{% endcall %}
<p>
<a href="{{ url_for('.create_api_key', service_id=service_id) }}">Create a new API key</a>
<p> </p>
<a href="https://github.com/alphagov/notifications-api">GOV.UK Notify API</a>
</p>
<p>
<a href="https://github.com/alphagov/notify-api-client">GOV.UK Notify Python client</a>
</p>
<h2 class="heading-medium">API key for [service name]</h2>
{{ api_key('d30512af92e1386d63b90e5973b49a10') }}
<h2 class="heading-medium">API endpoint</h2>
<p>
https://www.notify.works/api/endpoint
</p>
</div>
</div>
{{ page_footer(
back_link=url_for('.service_dashboard', service_id=service_id),
back_link_text='Back to dashboard'
) }}
{% endblock %} {% endblock %}

View File

@@ -0,0 +1,24 @@
{% extends "withnav_template.html" %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/textbox.html" import textbox %}
{% block page_title %}
GOV.UK Notify | API keys and documentation
{% endblock %}
{% block maincolumn_content %}
<h1 class="heading-xlarge">
Add a new API key
</h1>
<form method="post">
{{ textbox(key_name, hint='eg CRM application') }}
{{ page_footer(
'Continue',
back_link=url_for('.api_keys', service_id=service_id),
back_link_text='Back to API keys'
) }}
</form>
{% endblock %}

View File

@@ -0,0 +1,37 @@
{% extends "withnav_template.html" %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/api-key.html" import api_key %}
{% block page_title %}
GOV.UK Notify | API keys and documentation
{% endblock %}
{% block maincolumn_content %}
<div class="grid-row">
<div class="column-two-thirds">
<h1 class="heading-xlarge">
Revoke API key
</h1>
<p>
{{ key_name }} will no longer let you connect to GOV.UK Notify.
</p>
<p>
You cant undo this.
</p>
</div>
</div>
<form method="post">
{{ page_footer(
'Revoke this API key',
back_link=url_for('.api_keys', service_id=service_id),
back_link_text='Back to API keys',
destructive=True
) }}
</form>
{% endblock %}

View File

@@ -0,0 +1,33 @@
{% extends "withnav_template.html" %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/api-key.html" import api_key %}
{% block page_title %}
GOV.UK Notify | API keys and documentation
{% endblock %}
{% block maincolumn_content %}
<div class="grid-row">
<div class="column-two-thirds">
<h1 class="heading-xlarge">
New API key
</h1>
<p>
Copy your key to somewhere safe. You wont be able to see it again
once you leave this page.
</p>
{{ api_key(secret, key_name) }}
</div>
</div>
{{ page_footer(
back_link=url_for('.api_keys', service_id=service_id),
back_link_text='Back to API keys'
) }}
{% endblock %}

View File

@@ -0,0 +1,49 @@
{% extends "withnav_template.html" %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/api-key.html" import api_key %}
{% block page_title %}
GOV.UK Notify | API keys and documentation
{% endblock %}
{% block maincolumn_content %}
<div class="grid-row">
<div class="column-two-thirds">
<h1 class="heading-xlarge">
Developer documentation
</h1>
<h2 class="heading-medium">
How to integrate GOV.UK Notify into your service
</h2>
<p>
blah blah blah this is where we tell you how the API works
</p>
<h2 class="heading-medium">Repositories</h2>
<p>
<a href="https://github.com/alphagov/notifications-api">GOV.UK Notify API</a>
</p>
<p>
<a href="https://github.com/alphagov/notify-api-client">GOV.UK Notify Python client</a>
</p>
<h2 class="heading-medium">API endpoint</h2>
<p>
https://www.notify.works/api/endpoint
</p>
<p>
<a href="{{ url_for('.api_keys', service_id=service_id) }}">API keys for your service</a>
</p>
</div>
</div>
{% endblock %}

View File

@@ -39,6 +39,12 @@ def template_json(id_, name, type_, content, service_id):
} }
def api_key_json(id_, name, expiry_date=None):
return {'id': id_,
'name': name,
'expiry_date': expiry_date
}
TEST_USER_EMAIL = 'test@user.gov.uk' TEST_USER_EMAIL = 'test@user.gov.uk'

View File

@@ -0,0 +1,16 @@
from werkzeug.datastructures import MultiDict
from app.main.forms import CreateKeyForm
def test_return_validation_error_when_key_name_exists(app_,
db_,
db_session):
def _get_names():
return ['some key', 'another key']
with app_.test_request_context():
form = CreateKeyForm(_get_names(),
formdata=MultiDict([('key_name', 'Some key')]))
form.validate()
assert {'key_name': ['A key with this name already exists']} == form.errors

View File

@@ -1,3 +1,4 @@
from datetime import date
from flask import url_for from flask import url_for
@@ -7,9 +8,116 @@ def test_should_show_api_keys_and_documentation_page(app_,
mock_api_user, mock_api_user,
mock_user_loader, mock_user_loader,
mock_user_dao_get_by_email): mock_user_dao_get_by_email):
with app_.test_request_context():
with app_.test_client() as client:
client.login(mock_api_user)
response = client.get(url_for('main.documentation', service_id=123))
assert response.status_code == 200
def test_should_show_empty_api_keys_page(app_,
db_,
db_session,
mock_api_user,
mock_user_loader,
mock_user_dao_get_by_email,
mock_get_no_api_keys):
with app_.test_request_context(): with app_.test_request_context():
with app_.test_client() as client: with app_.test_client() as client:
client.login(mock_api_user) client.login(mock_api_user)
response = client.get(url_for('main.api_keys', service_id=123)) response = client.get(url_for('main.api_keys', service_id=123))
assert response.status_code == 200 assert response.status_code == 200
assert 'You havent created any API keys yet' in response.get_data(as_text=True)
assert 'Create a new API key' in response.get_data(as_text=True)
mock_get_no_api_keys.assert_called_once_with(service_id=123)
def test_should_show_api_keys_page(app_,
db_,
db_session,
mock_api_user,
mock_user_loader,
mock_user_dao_get_by_email,
mock_get_api_keys):
with app_.test_request_context():
with app_.test_client() as client:
client.login(mock_api_user)
response = client.get(url_for('main.api_keys', service_id=123))
assert response.status_code == 200
assert 'some key name' in response.get_data(as_text=True)
assert 'another key name' in response.get_data(as_text=True)
assert 'Revoked Thursday 01 January 1970 at 00:00' in response.get_data(as_text=True)
mock_get_api_keys.assert_called_once_with(service_id=123)
def test_should_show_name_api_key_page(app_,
db_,
db_session,
mock_api_user,
mock_user_loader,
mock_user_dao_get_by_email,
mock_get_api_keys):
with app_.test_request_context():
with app_.test_client() as client:
client.login(mock_api_user)
response = client.get(url_for('main.create_api_key', service_id=123))
assert response.status_code == 200
def test_should_render_show_api_key(app_,
db_,
db_session,
mock_api_user,
mock_user_loader,
mock_user_dao_get_by_email,
mock_create_api_key,
mock_get_api_keys):
with app_.test_request_context():
with app_.test_client() as client:
client.login(mock_api_user)
response = client.post(url_for('main.create_api_key', service_id=123),
data={'key_name': 'some default key name'})
assert response.status_code == 200
assert 'some default key name' in response.get_data(as_text=True)
mock_create_api_key.assert_called_once_with(service_id=123, key_name='some default key name')
def test_should_show_confirm_revoke_api_key(app_,
db_,
db_session,
mock_api_user,
mock_user_loader,
mock_user_dao_get_by_email,
mock_get_api_keys):
with app_.test_request_context():
with app_.test_client() as client:
client.login(mock_api_user)
response = client.get(url_for('main.revoke_api_key', service_id=123, key_id=321))
assert response.status_code == 200
assert 'some key name' in response.get_data(as_text=True)
mock_get_api_keys.assert_called_once_with(service_id=123, key_id=321)
def test_should_redirect_after_revoking_api_key(app_,
db_,
db_session,
mock_api_user,
mock_user_loader,
mock_user_dao_get_by_email,
mock_revoke_api_key,
mock_get_api_keys):
with app_.test_request_context():
with app_.test_client() as client:
client.login(mock_api_user)
response = client.post(url_for('main.revoke_api_key', service_id=123, key_id=321))
assert response.status_code == 302
assert response.location == url_for('.api_keys', service_id=123, _external=True)
mock_revoke_api_key.assert_called_once_with(service_id=123, key_id=321)
mock_get_api_keys.assert_called_once_with(service_id=123, key_id=321)

View File

@@ -1,14 +1,17 @@
import os import os
from datetime import date
import pytest import pytest
from alembic.command import upgrade from alembic.command import upgrade
from alembic.config import Config from alembic.config import Config
from flask.ext.migrate import Migrate, MigrateCommand from flask.ext.migrate import Migrate, MigrateCommand
from flask.ext.script import Manager from flask.ext.script import Manager
from sqlalchemy.schema import MetaData from sqlalchemy.schema import MetaData
from . import (
create_test_user, create_another_test_user, service_json, TestClient,
get_test_user, template_json)
from app import create_app, db from app import create_app, db
from . import (
create_test_user, service_json, TestClient,
get_test_user, template_json, api_key_json)
@pytest.fixture(scope='session') @pytest.fixture(scope='session')
@@ -89,6 +92,7 @@ def mock_get_service(mocker, mock_api_user):
service_id, "Test Service", [mock_api_user.id], limit=1000, service_id, "Test Service", [mock_api_user.id], limit=1000,
active=False, restricted=True) active=False, restricted=True)
return {'data': service, 'token': 1} return {'data': service, 'token': 1}
return mocker.patch('app.notifications_api_client.get_service', side_effect=_create) return mocker.patch('app.notifications_api_client.get_service', side_effect=_create)
@@ -99,6 +103,7 @@ def mock_create_service(mocker):
101, service_name, [user_id], limit=limit, 101, service_name, [user_id], limit=limit,
active=active, restricted=restricted) active=active, restricted=restricted)
return {'data': service} return {'data': service}
mock_class = mocker.patch( mock_class = mocker.patch(
'app.notifications_api_client.create_service', side_effect=_create) 'app.notifications_api_client.create_service', side_effect=_create)
return mock_class return mock_class
@@ -116,6 +121,7 @@ def mock_update_service(mocker):
service_id, service_name, users, limit=limit, service_id, service_name, users, limit=limit,
active=active, restricted=restricted) active=active, restricted=restricted)
return {'data': service} return {'data': service}
mock_class = mocker.patch( mock_class = mocker.patch(
'app.notifications_api_client.update_service', side_effect=_update) 'app.notifications_api_client.update_service', side_effect=_update)
return mock_class return mock_class
@@ -129,6 +135,7 @@ def mock_get_services(mocker, mock_api_user):
service_two = service_json( service_two = service_json(
2, "service_two", [mock_api_user.id], 1000, True, False) 2, "service_two", [mock_api_user.id], 1000, True, False)
return {'data': [service_one, service_two]} return {'data': [service_one, service_two]}
mock_class = mocker.patch( mock_class = mocker.patch(
'app.notifications_api_client.get_services', side_effect=_create) 'app.notifications_api_client.get_services', side_effect=_create)
return mock_class return mock_class
@@ -138,6 +145,7 @@ def mock_get_services(mocker, mock_api_user):
def mock_delete_service(mocker, mock_get_service): def mock_delete_service(mocker, mock_get_service):
def _delete(service_id): def _delete(service_id):
return mock_get_service.side_effect(service_id) return mock_get_service.side_effect(service_id)
mock_class = mocker.patch( mock_class = mocker.patch(
'app.notifications_api_client.delete_service', side_effect=_delete) 'app.notifications_api_client.delete_service', side_effect=_delete)
return mock_class return mock_class
@@ -149,6 +157,7 @@ def mock_get_service_template(mocker):
template = template_json( template = template_json(
template_id, "Template Name", "sms", "template content", service_id) template_id, "Template Name", "sms", "template content", service_id)
return {'data': template} return {'data': template}
return mocker.patch( return mocker.patch(
'app.notifications_api_client.get_service_template', 'app.notifications_api_client.get_service_template',
side_effect=_create) side_effect=_create)
@@ -160,6 +169,7 @@ def mock_create_service_template(mocker):
template = template_json( template = template_json(
101, name, type_, content, service) 101, name, type_, content, service)
return {'data': template} return {'data': template}
mock_class = mocker.patch( mock_class = mocker.patch(
'app.notifications_api_client.create_service_template', 'app.notifications_api_client.create_service_template',
side_effect=_create) side_effect=_create)
@@ -172,6 +182,7 @@ def mock_update_service_template(mocker):
template = template_json( template = template_json(
id_, name, type_, content, service) id_, name, type_, content, service)
return {'data': template} return {'data': template}
mock_class = mocker.patch( mock_class = mocker.patch(
'app.notifications_api_client.update_service_template', 'app.notifications_api_client.update_service_template',
side_effect=_update) side_effect=_update)
@@ -186,6 +197,7 @@ def mock_get_service_templates(mocker):
template_two = template_json( template_two = template_json(
2, "template_two", "sms", "template two content", service_id) 2, "template_two", "sms", "template two content", service_id)
return {'data': [template_one, template_two]} return {'data': [template_one, template_two]}
mock_class = mocker.patch( mock_class = mocker.patch(
'app.notifications_api_client.get_service_templates', 'app.notifications_api_client.get_service_templates',
side_effect=_create) side_effect=_create)
@@ -199,6 +211,7 @@ def mock_delete_service_template(mocker):
template_id, "Template to delete", template_id, "Template to delete",
"sms", "content to be deleted", service_id) "sms", "content to be deleted", service_id)
return {'data': template} return {'data': template}
return mocker.patch( return mocker.patch(
'app.notifications_api_client.delete_service_template', side_effect=_delete) 'app.notifications_api_client.delete_service_template', side_effect=_delete)
@@ -314,3 +327,43 @@ def mock_user_dao_get_new_password(mocker, mock_api_user):
mock_class = mocker.patch('app.main.dao.users_dao.get_user_by_email') mock_class = mocker.patch('app.main.dao.users_dao.get_user_by_email')
mock_class.return_value = mock_api_user mock_class.return_value = mock_api_user
return mock_class return mock_class
@pytest.fixture(scope='function')
def mock_create_api_key(mocker):
def _create(service_id, key_name):
import uuid
return {'data': str(uuid.uuid4())}
mock_class = mocker.patch('app.api_key_api_client.create_api_key', side_effect=_create)
return mock_class
@pytest.fixture(scope='function')
def mock_revoke_api_key(mocker):
def _revoke(service_id, key_id):
return {}
mock_class = mocker.patch('app.api_key_api_client.revoke_api_key', side_effect=_revoke)
return mock_class
@pytest.fixture(scope='function')
def mock_get_api_keys(mocker):
def _get_keys(service_id, key_id=None):
keys = {'apiKeys': [api_key_json(1, 'some key name'),
api_key_json(2, 'another key name', expiry_date=str(date.fromtimestamp(0)))]}
return keys
mock_class = mocker.patch('app.api_key_api_client.get_api_keys', side_effect=_get_keys)
return mock_class
@pytest.fixture(scope='function')
def mock_get_no_api_keys(mocker):
def _get_keys(service_id):
keys = {'apiKeys': []}
return keys
mock_class = mocker.patch('app.api_key_api_client.get_api_keys', side_effect=_get_keys)
return mock_class